mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36118b700f | ||
|
|
2fb947b47a | ||
|
|
0f1156e951 | ||
|
|
e9f8be92b7 | ||
|
|
6360ea9f8e | ||
|
|
ad2d8e80f0 | ||
|
|
bc0a5fdaa6 | ||
|
|
d6cfc887c3 | ||
|
|
ce41c436aa | ||
|
|
ec096eaaa2 | ||
|
|
9c32dd5308 | ||
|
|
69d7ee9547 | ||
|
|
e2e4cb67ca | ||
|
|
4a08ad4997 | ||
|
|
107a1c12bd | ||
|
|
8fe5268a73 | ||
|
|
faba1ad8e3 | ||
|
|
ea4885c710 | ||
|
|
4d85002d2b | ||
|
|
535b17f592 | ||
|
|
e556b45d4d | ||
|
|
48130dcbdc | ||
|
|
1419ca6325 | ||
|
|
8513dc0645 | ||
|
|
70e8f8370b | ||
|
|
135c90a8b8 | ||
|
|
fa588c2d24 | ||
|
|
b4ce7a91c2 | ||
|
|
8ca9d66c32 | ||
|
|
a86fabde59 | ||
|
|
40a896675f |
No files matched your search
@@ -28,8 +28,8 @@ jobs:
|
||||
echo "Invalid /changelog-entry command"
|
||||
exit 1
|
||||
fi
|
||||
CATEGORY=$( cut -d ' ' -f 1 <<< "$PARAMS" | xargs )
|
||||
MESSAGE=$( cut -d ' ' -f 2- <<< "$PARAMS" | xargs )
|
||||
CATEGORY=$( cut -d ' ' -f 1 <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
|
||||
MESSAGE=$( cut -d ' ' -f 2- <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
|
||||
yarn changelog-entry "$CATEGORY" "$MESSAGE"
|
||||
env:
|
||||
GITHUB_PR_AUTHOR: ${{ github.event.issue.user.login }}
|
||||
|
||||
@@ -6,6 +6,61 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- `[EAS BUILD API]` Use new build job format. ([#701](https://github.com/expo/eas-cli/pull/701) and [#711](https://github.com/expo/eas-cli/pull/711) by [@dsokal](https://github.com/dsokal))
|
||||
- Don't print logs to `stderr`. ([#708](https://github.com/expo/eas-cli/pull/708) by [@dsokal](https://github.com/dsokal))
|
||||
- Remove automatic migration for legacy `eas.json` format introduced in [v0.22.2](https://github.com/expo/eas-cli/releases/tag/v0.22.2). ([#695](https://github.com/expo/eas-cli/pull/695) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Add required `cli` field in root of `eas.json` ([#695](https://github.com/expo/eas-cli/pull/695) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Better error message when eas.json is invalid. ([#707](https://github.com/expo/eas-cli/pull/707) by [@dsokal](https://github.com/dsokal))
|
||||
- Fix credentials workflow for new users: add additionalTypenames for credentials. ([#703](https://github.com/expo/eas-cli/pull/703) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add additionalTypenames for other queries. ([#704](https://github.com/expo/eas-cli/pull/704) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Fix errors after pulling down most recent graphql schema changes. ([#713](https://github.com/expo/eas-cli/pull/713) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Refactor credentials manager. ([#712](https://github.com/expo/eas-cli/pull/712) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Remove unneeded parameters. ([#710](https://github.com/expo/eas-cli/pull/710) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Enforce additionalTypenames for graphql queries. ([#709](https://github.com/expo/eas-cli/pull/709) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.33.1](https://github.com/expo/eas-cli/releases/tag/0.33.1) - 2021-10-22
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Compute runtime version using config-plugins. ([#697](https://github.com/expo/eas-cli/pull/697) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- [eas-cli] Skip validating updates scripts integration, which no longer exist in SDK 43+ ([#706](https://github.com/expo/eas-cli/pull/706) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.33.0](https://github.com/expo/eas-cli/releases/tag/0.33.0) - 2021-10-20
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Make "production" the default profile for building and submitting. ([#677](https://github.com/expo/eas-cli/pull/677)) by [@jonsamp](https://github.com/jonsamp)
|
||||
- Previously, the default profile when running `eas build` or `eas submit` was "release". We're changing it to a more recognizable name that is consistent with our docs, which is now "production". You can always specify a profile manually with the `--profile` flag. For this major version, if you do not have a profile named "production", and still have a profile named "release", we will fall back to the "release" profile as the default, however you'll see a warning as we're going to remove that behavior in the next major release of EAS CLI.
|
||||
- To upgrade, update **eas.json** to have a "production" profile in both the `build` and `submit` objects. If you already have a project set up, this will replace the existing "release" profile. After the change, eas.json should have the following profiles:
|
||||
```json
|
||||
{
|
||||
"build": {
|
||||
"production": { ... }
|
||||
},
|
||||
"submit": {
|
||||
"production": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Skip the second prompt for Apple ID if the user is already signed in with Apple. ([#691](https://github.com/expo/eas-cli/pull/691) by [@dsokal](https://github.com/dsokal))
|
||||
- Unify reading `app.json`. ([#692](https://github.com/expo/eas-cli/pull/692) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.32.0](https://github.com/expo/eas-cli/releases/tag/0.32.0) - 2021-10-15
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add App Store Connect Api Key support to iOS submissions. ([#678](https://github.com/expo/eas-cli/pull/678) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.32.0"
|
||||
"version": "0.34.0"
|
||||
}
|
||||
@@ -33,264 +33,7 @@ eas --help COMMAND
|
||||
# Commands
|
||||
|
||||
<!-- commands -->
|
||||
* [`eas account:login`](#eas-accountlogin)
|
||||
* [`eas account:logout`](#eas-accountlogout)
|
||||
* [`eas account:view`](#eas-accountview)
|
||||
* [`eas analytics [STATUS]`](#eas-analytics-status)
|
||||
* [`eas build`](#eas-build)
|
||||
* [`eas build:cancel [BUILD_ID]`](#eas-buildcancel-build_id)
|
||||
* [`eas build:configure`](#eas-buildconfigure)
|
||||
* [`eas build:list`](#eas-buildlist)
|
||||
* [`eas build:view [BUILD_ID]`](#eas-buildview-build_id)
|
||||
* [`eas config`](#eas-config)
|
||||
* [`eas credentials`](#eas-credentials)
|
||||
* [`eas device:create`](#eas-devicecreate)
|
||||
* [`eas device:list`](#eas-devicelist)
|
||||
* [`eas device:view [UDID]`](#eas-deviceview-udid)
|
||||
* [`eas diagnostics`](#eas-diagnostics)
|
||||
* [`eas help [COMMAND]`](#eas-help-command)
|
||||
* [`eas project:info`](#eas-projectinfo)
|
||||
* [`eas project:init`](#eas-projectinit)
|
||||
* [`eas secret:create`](#eas-secretcreate)
|
||||
* [`eas secret:delete`](#eas-secretdelete)
|
||||
* [`eas secret:list`](#eas-secretlist)
|
||||
* [`eas submit`](#eas-submit)
|
||||
* [`eas webhook:create`](#eas-webhookcreate)
|
||||
* [`eas webhook:delete [ID]`](#eas-webhookdelete-id)
|
||||
* [`eas webhook:list`](#eas-webhooklist)
|
||||
* [`eas webhook:update`](#eas-webhookupdate)
|
||||
* [`eas webhook:view ID`](#eas-webhookview-id)
|
||||
|
||||
## `eas account:login`
|
||||
|
||||
log in with your Expo account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas account:login
|
||||
|
||||
ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/login.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/account/login.js)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
log out
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas account:logout
|
||||
|
||||
ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/logout.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/account/logout.js)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
show the username you are logged in as
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas account:view
|
||||
|
||||
ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/view.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/account/view.js)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
view or change analytics settings
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas analytics [STATUS]
|
||||
```
|
||||
|
||||
_See code: [src/commands/analytics.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/analytics.js)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
start a build
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build
|
||||
|
||||
OPTIONS
|
||||
-p, --platform=(android|ios|all)
|
||||
|
||||
--auto-submit Submit on build complete using the submit profile with the same name as the
|
||||
build profile
|
||||
|
||||
--auto-submit-with-profile=PROFILE_NAME Submit on build complete using the submit profile with provided name
|
||||
|
||||
--clear-cache Clear cache before the build
|
||||
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
|
||||
--local Run build locally [experimental]
|
||||
|
||||
--non-interactive Run command in non-interactive mode
|
||||
|
||||
--profile=PROFILE_NAME [default: release] Name of the build profile from eas.json
|
||||
|
||||
--skip-project-configuration Skip project configuration
|
||||
|
||||
--[no-]wait Wait for build(s) to complete
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/index.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/build/index.js)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
Cancel a build.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:cancel [BUILD_ID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/cancel.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/build/cancel.js)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
Configure the project to support EAS Build.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:configure
|
||||
|
||||
OPTIONS
|
||||
-p, --platform=(android|ios|all) Platform to configure
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/configure.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/build/configure.js)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
list all builds for your project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:list
|
||||
|
||||
OPTIONS
|
||||
--appBuildVersion=appBuildVersion
|
||||
--appIdentifier=appIdentifier
|
||||
--appVersion=appVersion
|
||||
--buildProfile=buildProfile
|
||||
--channel=channel
|
||||
--distribution=(store|internal|simulator)
|
||||
--gitCommitHash=gitCommitHash
|
||||
|
||||
--json Enable JSON output, non-JSON messages will be printed
|
||||
to stderr
|
||||
|
||||
--limit=limit
|
||||
|
||||
--platform=(all|android|ios)
|
||||
|
||||
--runtimeVersion=runtimeVersion
|
||||
|
||||
--sdkVersion=sdkVersion
|
||||
|
||||
--status=(new|in-queue|in-progress|errored|finished|canceled)
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/list.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/build/list.js)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
view a build for your project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:view [BUILD_ID]
|
||||
|
||||
OPTIONS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/view.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/build/view.js)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
show the eas.json config
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas config
|
||||
|
||||
OPTIONS
|
||||
-p, --platform=(android|ios)
|
||||
--profile=profile
|
||||
```
|
||||
|
||||
_See code: [src/commands/config.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/config.js)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
manage your credentials
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas credentials
|
||||
```
|
||||
|
||||
_See code: [src/commands/credentials.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/credentials.js)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
register new Apple Devices to use for internal distribution
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas device:create
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/create.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/device/create.js)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
list all registered devices for your account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas device:list
|
||||
|
||||
OPTIONS
|
||||
--apple-team-id=apple-team-id
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/list.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/device/list.js)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
view a device for your project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas device:view [UDID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/view.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/device/view.js)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
log environment info to the console
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas diagnostics
|
||||
```
|
||||
|
||||
_See code: [src/commands/diagnostics.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/diagnostics.js)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -308,188 +51,4 @@ OPTIONS
|
||||
```
|
||||
|
||||
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.3/src/commands/help.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
information about the current project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas project:info
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/info.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/project/info.js)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
create or link an EAS project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas project:init
|
||||
|
||||
ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/init.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/project/init.js)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
Create an environment secret on the current project or owner account.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas secret:create
|
||||
|
||||
OPTIONS
|
||||
--force Delete and recreate existing secrets
|
||||
--name=name Name of the secret
|
||||
--scope=(account|project) [default: project] Scope for the secret
|
||||
--value=value Value of the secret
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/create.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/secret/create.js)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
Delete an environment secret by ID.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas secret:delete
|
||||
|
||||
OPTIONS
|
||||
--id=id ID of the secret to delete
|
||||
|
||||
DESCRIPTION
|
||||
Unsure where to find the secret's ID? Run eas secrets:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/delete.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/secret/delete.js)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
Lists environment secrets available for your current app
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas secret:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/list.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/secret/list.js)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
submit build archive to app store
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas submit
|
||||
|
||||
OPTIONS
|
||||
-p, --platform=(android|ios|all)
|
||||
--id=id ID of the build to submit
|
||||
--latest Submit the latest build for specified platform
|
||||
--non-interactive Run command in non-interactive mode
|
||||
--path=path Path to the .apk/.aab/.ipa file
|
||||
|
||||
--profile=profile Name of the submit profile from eas.json. Defaults to "release" if defined in
|
||||
eas.json.
|
||||
|
||||
--url=url App archive url
|
||||
|
||||
--verbose Always print logs from Submission Service
|
||||
|
||||
--[no-]wait Wait for submission to complete
|
||||
|
||||
DESCRIPTION
|
||||
See how to configure submits with eas.json: https://docs.expo.dev/submit/eas-json/
|
||||
|
||||
ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [src/commands/submit.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/submit.js)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
Create a webhook on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:create
|
||||
|
||||
OPTIONS
|
||||
--event=(BUILD) [default: BUILD] Event type that triggers the webhook
|
||||
|
||||
--secret=secret Secret used to create a hash signature of the request payload, provided in the 'Expo-Signature'
|
||||
header.
|
||||
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/create.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/webhook/create.js)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
Delete a webhook on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:delete [ID]
|
||||
|
||||
ARGUMENTS
|
||||
ID ID of the webhook to delete
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/delete.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/webhook/delete.js)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
List webhooks on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:list
|
||||
|
||||
OPTIONS
|
||||
--event=(BUILD) Event type that triggers the webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/list.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/webhook/list.js)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
Create a webhook on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:update
|
||||
|
||||
OPTIONS
|
||||
--event=(BUILD) [default: BUILD] Event type that triggers the webhook
|
||||
--id=id (required) Webhook ID
|
||||
|
||||
--secret=secret Secret used to create a hash signature of the request payload, provided in the 'Expo-Signature'
|
||||
header.
|
||||
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/update.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/webhook/update.js)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
View a webhook on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:view ID
|
||||
|
||||
ARGUMENTS
|
||||
ID ID of the webhook to view
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/view.js](https://github.com/expo/eas-cli/blob/v0.32.0/packages/eas-cli/src/commands/webhook/view.js)_
|
||||
<!-- commandsstop -->
|
||||
Generated
+842
-1025
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.32.0",
|
||||
"version": "0.34.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
@@ -9,15 +9,15 @@
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/apple-utils": "0.0.0-alpha.25",
|
||||
"@expo/config": "6.0.2",
|
||||
"@expo/config-plugins": "4.0.2",
|
||||
"@expo/eas-build-job": "0.2.52",
|
||||
"@expo/eas-json": "^0.32.0",
|
||||
"@expo/config": "6.0.3",
|
||||
"@expo/config-plugins": "4.0.3",
|
||||
"@expo/eas-build-job": "0.2.57",
|
||||
"@expo/eas-json": "^0.34.0",
|
||||
"@expo/json-file": "8.2.33",
|
||||
"@expo/pkcs12": "0.0.4",
|
||||
"@expo/plist": "0.0.14",
|
||||
"@expo/plugin-warn-if-update-available": "1.7.0",
|
||||
"@expo/prebuild-config": "3.0.2",
|
||||
"@expo/prebuild-config": "3.0.3",
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.0",
|
||||
"@expo/sdk-runtime-versions": "1.0.0",
|
||||
|
||||
@@ -4,13 +4,7 @@ import { URL } from 'url';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import UserSettings from './user/UserSettings';
|
||||
|
||||
/**
|
||||
* We use require() to exclude package.json from TypeScript's analysis since it lives outside
|
||||
* the src directory and would change the directory structure of the emitted files
|
||||
* under the build directory
|
||||
*/
|
||||
const packageJSON = require('../package.json');
|
||||
import { easCliVersion } from './utils/easCli';
|
||||
|
||||
const PLATFORM_TO_ANALYTICS_PLATFORM: { [platform: string]: string } = {
|
||||
darwin: 'Mac',
|
||||
@@ -97,7 +91,7 @@ export function logEvent(name: string, properties: Record<string, any> = {}): vo
|
||||
ensureUserIdentified();
|
||||
|
||||
const { userId, deviceId } = identifyData ?? {};
|
||||
const commonEventProperties = { source_version: packageJSON?.version, source: 'eas cli' };
|
||||
const commonEventProperties = { source_version: easCliVersion, source: 'eas cli' };
|
||||
|
||||
const identity = { userId: userId ?? undefined, anonymousId: deviceId ?? uuidv4() };
|
||||
rudderstackClient.track({
|
||||
@@ -126,7 +120,7 @@ function getRudderStackContext(): Record<string, any> {
|
||||
return {
|
||||
os: { name: platform, version: os.release() },
|
||||
device: { type: platform, model: platform },
|
||||
app: { name: 'eas cli', version: packageJSON?.version ?? undefined },
|
||||
app: { name: 'eas cli', version: easCliVersion ?? undefined },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { AndroidConfig, AndroidManifest } from '@expo/config-plugins';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import Log from '../../log';
|
||||
import { getProjectAccountName } from '../../project/projectUtils';
|
||||
@@ -10,17 +9,6 @@ import { ensureValidVersions } from '../utils/updates';
|
||||
export async function configureUpdatesAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
|
||||
ensureValidVersions(exp);
|
||||
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
|
||||
const buildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(projectDir);
|
||||
const buildGradleContents = await fs.readFile(buildGradlePath, 'utf8');
|
||||
|
||||
if (!AndroidConfig.Updates.isBuildGradleConfigured(projectDir, buildGradleContents)) {
|
||||
const updatedBuildGradleContents =
|
||||
AndroidConfig.Updates.ensureBuildGradleContainsConfigurationScript(
|
||||
projectDir,
|
||||
buildGradleContents
|
||||
);
|
||||
await fs.writeFile(buildGradlePath, updatedBuildGradleContents);
|
||||
}
|
||||
|
||||
const androidManifestPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectDir);
|
||||
const androidManifest = await getAndroidManifestAsync(projectDir);
|
||||
@@ -39,7 +27,7 @@ export async function syncUpdatesConfigurationAsync(
|
||||
ensureValidVersions(exp);
|
||||
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
|
||||
try {
|
||||
await ensureUpdatesConfiguredAsync(projectDir, exp);
|
||||
await ensureUpdatesConfiguredAsync(projectDir);
|
||||
} catch (error) {
|
||||
Log.error(
|
||||
'expo-updates module is not configured. Please run "eas build:configure" first to configure the project'
|
||||
@@ -62,15 +50,9 @@ export async function syncUpdatesConfigurationAsync(
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureUpdatesConfiguredAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
|
||||
const buildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(projectDir);
|
||||
const buildGradleContents = await fs.readFile(buildGradlePath, 'utf8');
|
||||
|
||||
if (!AndroidConfig.Updates.isBuildGradleConfigured(projectDir, buildGradleContents)) {
|
||||
const gradleScriptApply = AndroidConfig.Updates.formatApplyLineForBuildGradle(projectDir);
|
||||
throw new Error(`Missing ${gradleScriptApply} in ${buildGradlePath}`);
|
||||
}
|
||||
|
||||
// Note: we assume here that Expo modules are properly configured in the project. Aside from that,
|
||||
// all that is needed on Expo SDK 43+ to configure expo-updates configuration in AndroidManifest.xml
|
||||
async function ensureUpdatesConfiguredAsync(projectDir: string): Promise<void> {
|
||||
const androidManifest = await getAndroidManifestAsync(projectDir);
|
||||
|
||||
if (!AndroidConfig.Updates.isMainApplicationMetaDataSet(androidManifest)) {
|
||||
|
||||
@@ -31,7 +31,7 @@ beforeEach(async () => {
|
||||
await fs.mkdirp(os.tmpdir());
|
||||
});
|
||||
|
||||
// generic workflow
|
||||
// bare workflow
|
||||
describe(bumpVersionAsync, () => {
|
||||
it('throws an informative error when multiple flavors are detected in the android project', async () => {
|
||||
await initProjectWithGradleFileAsync(
|
||||
@@ -69,7 +69,7 @@ describe(bumpVersionAsync, () => {
|
||||
});
|
||||
it('bumps expo.android.versionCode and buildGradle versionCode when strategy = BumpStrategy.VERSION_CODE', async () => {
|
||||
const nativeVersionCode = 100; // this should be overwritten by bumpVersionAsync
|
||||
const fakeExp = initGenericProject({ versionCode: nativeVersionCode });
|
||||
const fakeExp = initBareWorkflowProject({ versionCode: nativeVersionCode });
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.VERSION_CODE,
|
||||
@@ -91,7 +91,7 @@ describe(bumpVersionAsync, () => {
|
||||
|
||||
it('bumps expo.version and gradle versionCode when strategy = BumpStrategy.SHORT_VERSION', async () => {
|
||||
const nativeVersionName = '1.0.0'; // this should be overwritten by bumpVersionAsync
|
||||
const fakeExp = initGenericProject({ versionName: nativeVersionName });
|
||||
const fakeExp = initBareWorkflowProject({ versionName: nativeVersionName });
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.APP_VERSION,
|
||||
@@ -112,7 +112,7 @@ describe(bumpVersionAsync, () => {
|
||||
});
|
||||
|
||||
it('does not bump any version when strategy = BumpStrategy.NOOP', async () => {
|
||||
const fakeExp = initGenericProject();
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.NOOP,
|
||||
@@ -185,9 +185,9 @@ describe(bumpVersionInAppJsonAsync, () => {
|
||||
});
|
||||
|
||||
describe(maybeResolveVersionsAsync, () => {
|
||||
describe('generic project', () => {
|
||||
describe('bare project', () => {
|
||||
it('reads the versions from native code', async () => {
|
||||
const exp = initGenericProject();
|
||||
const exp = initBareWorkflowProject();
|
||||
const { appVersion, appBuildVersion } = await maybeResolveVersionsAsync(
|
||||
'/repo',
|
||||
exp,
|
||||
@@ -211,7 +211,7 @@ describe(maybeResolveVersionsAsync, () => {
|
||||
});
|
||||
});
|
||||
|
||||
function initGenericProject({
|
||||
function initBareWorkflowProject({
|
||||
versionCode = 123,
|
||||
versionName = '3.0.0',
|
||||
}: {
|
||||
|
||||
@@ -6,10 +6,10 @@ import { AndroidBuildProfile, AndroidVersionAutoIncrement } from '@expo/eas-json
|
||||
import Log from '../../log';
|
||||
import {
|
||||
ensureApplicationIdIsDefinedForManagedProjectAsync,
|
||||
warnIfAndroidPackageDefinedInAppConfigForGenericProject,
|
||||
warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject,
|
||||
} from '../../project/android/applicationId';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import vcs from '../../vcs';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { ConfigureContext } from '../context';
|
||||
import { isExpoUpdatesInstalled } from '../utils/updates';
|
||||
import { configureUpdatesAsync, syncUpdatesConfigurationAsync } from './UpdatesModule';
|
||||
@@ -21,12 +21,12 @@ export async function configureAndroidAsync(ctx: ConfigureContext): Promise<void
|
||||
return;
|
||||
}
|
||||
|
||||
warnIfAndroidPackageDefinedInAppConfigForGenericProject(ctx.projectDir, ctx.exp);
|
||||
warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject(ctx.projectDir, ctx.exp);
|
||||
|
||||
await AndroidConfig.EasBuild.configureEasBuildAsync(ctx.projectDir);
|
||||
|
||||
const easGradlePath = AndroidConfig.EasBuild.getEasBuildGradlePath(ctx.projectDir);
|
||||
await vcs.trackFileAsync(easGradlePath);
|
||||
await getVcsClient().trackFileAsync(easGradlePath);
|
||||
|
||||
if (isExpoUpdatesInstalled(ctx.projectDir)) {
|
||||
await configureUpdatesAsync(ctx.projectDir, ctx.exp);
|
||||
|
||||
@@ -17,15 +17,14 @@ export function transformJob(job: Android.Job): AndroidJobInput {
|
||||
artifactPath: job.artifactPath,
|
||||
username: job.username,
|
||||
buildType: job.buildType && transformBuildType(job.buildType),
|
||||
developmentClient: job.developmentClient,
|
||||
};
|
||||
}
|
||||
|
||||
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 AndroidBuildType.DevelopmentClient;
|
||||
return AndroidBuildType.AppBundle;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import slash from 'slash';
|
||||
import { AndroidCredentials } from '../../credentials/android/AndroidCredentialsProvider';
|
||||
import { getUsername } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import vcs from '../../vcs';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { BuildContext } from '../context';
|
||||
|
||||
interface JobData {
|
||||
@@ -27,7 +27,7 @@ export async function prepareJobAsync(
|
||||
const username = getUsername(ctx.exp, await ensureLoggedInAsync());
|
||||
const buildProfile: AndroidBuildProfile = ctx.buildProfile;
|
||||
const projectRootDirectory =
|
||||
slash(path.relative(await vcs.getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
const { credentials } = jobData;
|
||||
const buildCredentials = credentials
|
||||
? {
|
||||
@@ -70,12 +70,11 @@ export async function prepareJobAsync(
|
||||
},
|
||||
releaseChannel: ctx.buildProfile.releaseChannel,
|
||||
updates: { channel: ctx.buildProfile.channel },
|
||||
|
||||
developmentClient: buildProfile.developmentClient,
|
||||
gradleCommand: buildProfile.gradleCommand,
|
||||
artifactPath: buildProfile.artifactPath,
|
||||
|
||||
buildType,
|
||||
username,
|
||||
buildType: buildProfile.developmentClient ? Android.BuildType.DEVELOPMENT_CLIENT : buildType,
|
||||
};
|
||||
|
||||
return sanitizeJob(job);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { uploadAsync } from '../uploads';
|
||||
import { formatBytes } from '../utils/files';
|
||||
import { createProgressTracker } from '../utils/progress';
|
||||
import { sleepAsync } from '../utils/promise';
|
||||
import vcs from '../vcs';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import { BuildContext } from './context';
|
||||
import { runLocalBuildAsync } from './local';
|
||||
import { MetadataContext, collectMetadataAsync } from './metadata';
|
||||
@@ -68,7 +68,7 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
});
|
||||
}
|
||||
|
||||
if (await vcs.hasUncommittedChangesAsync()) {
|
||||
if (await getVcsClient().isCommitRequiredAsync()) {
|
||||
Log.addNewLineIfNone();
|
||||
await reviewAndCommitChangesAsync(
|
||||
`[EAS Build] Run EAS Build for ${requestedPlatformDisplayNames[ctx.platform as Platform]}`,
|
||||
|
||||
@@ -10,7 +10,8 @@ import { RequestedPlatform } from '../platform';
|
||||
import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { confirmAsync, promptAsync } from '../prompts';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import vcs from '../vcs';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import { configureAndroidAsync } from './android/configure';
|
||||
import { ConfigureContext } from './context';
|
||||
import { configureIosAsync } from './ios/configure';
|
||||
@@ -37,7 +38,7 @@ export async function ensureProjectConfiguredAsync(
|
||||
projectDir,
|
||||
platform: requestedPlatform,
|
||||
});
|
||||
if (await vcs.hasUncommittedChangesAsync()) {
|
||||
if (await getVcsClient().isCommitRequiredAsync()) {
|
||||
error(
|
||||
'Build process requires clean working tree, please commit all your changes and run `eas build` again',
|
||||
{ exit: 1 }
|
||||
@@ -57,7 +58,7 @@ export async function configureAsync(options: {
|
||||
platform: RequestedPlatform;
|
||||
projectDir: string;
|
||||
}): Promise<void> {
|
||||
await vcs.ensureRepoExistsAsync();
|
||||
await getVcsClient().ensureRepoExistsAsync();
|
||||
await maybeBailOnRepoStatusAsync();
|
||||
|
||||
const { exp } = getConfig(options.projectDir, { skipSDKVersionRequirement: true });
|
||||
@@ -86,31 +87,39 @@ export async function configureAsync(options: {
|
||||
await configureIosAsync(ctx);
|
||||
}
|
||||
|
||||
if (await vcs.hasUncommittedChangesAsync()) {
|
||||
if (await getVcsClient().isCommitRequiredAsync()) {
|
||||
Log.newLine();
|
||||
await reviewAndCommitChangesAsync(configureCommitMessage[options.platform]);
|
||||
} else {
|
||||
} else if (!(await getVcsClient().hasUncommittedChangesAsync())) {
|
||||
Log.newLine();
|
||||
Log.withTick('No changes were necessary, the project is already configured correctly.');
|
||||
}
|
||||
}
|
||||
|
||||
const EAS_JSON_MANAGED_DEFAULT: EasJson = {
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
},
|
||||
build: {
|
||||
release: {},
|
||||
development: {
|
||||
developmentClient: true,
|
||||
distribution: 'internal',
|
||||
},
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
},
|
||||
production: {},
|
||||
},
|
||||
submit: {
|
||||
release: {},
|
||||
production: {},
|
||||
},
|
||||
};
|
||||
|
||||
const EAS_JSON_GENERIC_DEFAULT: EasJson = {
|
||||
const EAS_JSON_BARE_DEFAULT: EasJson = {
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
},
|
||||
build: {
|
||||
release: {},
|
||||
development: {
|
||||
distribution: 'internal',
|
||||
android: {
|
||||
@@ -120,9 +129,13 @@ const EAS_JSON_GENERIC_DEFAULT: EasJson = {
|
||||
buildConfiguration: 'Debug',
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
},
|
||||
production: {},
|
||||
},
|
||||
submit: {
|
||||
release: {},
|
||||
production: {},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -139,11 +152,11 @@ export async function ensureEasJsonExistsAsync(ctx: ConfigureContext): Promise<v
|
||||
|
||||
const easJson =
|
||||
ctx.hasAndroidNativeProject && ctx.hasIosNativeProject
|
||||
? EAS_JSON_GENERIC_DEFAULT
|
||||
? EAS_JSON_BARE_DEFAULT
|
||||
: EAS_JSON_MANAGED_DEFAULT;
|
||||
|
||||
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
|
||||
await vcs.trackFileAsync(easJsonPath);
|
||||
await getVcsClient().trackFileAsync(easJsonPath);
|
||||
Log.withTick('Generated eas.json');
|
||||
}
|
||||
|
||||
@@ -177,7 +190,7 @@ async function reviewAndCommitChangesAsync(
|
||||
await commitPromptAsync({ initialCommitMessage });
|
||||
Log.withTick('Committed changes');
|
||||
} else if (selected === ShouldCommitChanges.ShowDiffFirst) {
|
||||
await vcs.showDiffAsync();
|
||||
await getVcsClient().showDiffAsync();
|
||||
await reviewAndCommitChangesAsync(initialCommitMessage, false);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,16 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { IOSConfig } from '@expo/config-plugins';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import Log from '../../log';
|
||||
import { getProjectAccountName } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import vcs from '../../vcs';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { ensureValidVersions } from '../utils/updates';
|
||||
import { readPlistAsync, writePlistAsync } from './plist';
|
||||
|
||||
export async function configureUpdatesAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
|
||||
ensureValidVersions(exp);
|
||||
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
|
||||
let xcodeProject = IOSConfig.XcodeUtils.getPbxproj(projectDir);
|
||||
|
||||
if (!IOSConfig.Updates.isShellScriptBuildPhaseConfigured(projectDir, xcodeProject)) {
|
||||
xcodeProject = IOSConfig.Updates.ensureBundleReactNativePhaseContainsConfigurationScript(
|
||||
projectDir,
|
||||
xcodeProject
|
||||
);
|
||||
await fs.writeFile(
|
||||
IOSConfig.Paths.getPBXProjectPath(projectDir),
|
||||
// eslint-disable-next-line node/no-sync
|
||||
xcodeProject.writeSync()
|
||||
);
|
||||
}
|
||||
|
||||
let expoPlist = await readExpoPlistAsync(projectDir);
|
||||
if (!IOSConfig.Updates.isPlistConfigurationSynced(exp, expoPlist, accountName)) {
|
||||
@@ -41,7 +27,7 @@ export async function syncUpdatesConfigurationAsync(
|
||||
ensureValidVersions(exp);
|
||||
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
|
||||
try {
|
||||
await ensureUpdatesConfiguredAsync(projectDir, exp);
|
||||
await ensureUpdatesConfiguredAsync(projectDir);
|
||||
} catch (error) {
|
||||
Log.error(
|
||||
'expo-updates module is not configured. Please run "eas build:configure" first to configure the project'
|
||||
@@ -62,15 +48,9 @@ export async function syncUpdatesConfigurationAsync(
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureUpdatesConfiguredAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
|
||||
const xcodeProject = IOSConfig.XcodeUtils.getPbxproj(projectDir);
|
||||
|
||||
if (!IOSConfig.Updates.isShellScriptBuildPhaseConfigured(projectDir, xcodeProject)) {
|
||||
const script = 'expo-updates/scripts/create-manifest-ios.sh';
|
||||
const buildPhase = '"Bundle React Native code and images"';
|
||||
throw new Error(`Path to ${script} is missing in a ${buildPhase} build phase.`);
|
||||
}
|
||||
|
||||
// Note: we assume here that Expo modules are properly configured in the project. Aside from that,
|
||||
// all that is needed on Expo SDK 43+ to configure expo-updates configuration in Expo.plist
|
||||
async function ensureUpdatesConfiguredAsync(projectDir: string): Promise<void> {
|
||||
const expoPlist = await readExpoPlistAsync(projectDir);
|
||||
if (!IOSConfig.Updates.isPlistConfigurationSet(expoPlist)) {
|
||||
throw new Error('Missing values in Expo.plist');
|
||||
@@ -88,7 +68,7 @@ async function writeExpoPlistAsync(
|
||||
): Promise<void> {
|
||||
const expoPlistPath = IOSConfig.Paths.getExpoPlistPath(projectDir);
|
||||
await writePlistAsync(expoPlistPath, expoPlist);
|
||||
await vcs.trackFileAsync(expoPlistPath);
|
||||
await getVcsClient().trackFileAsync(expoPlistPath);
|
||||
}
|
||||
|
||||
export async function readReleaseChannelSafelyAsync(projectDir: string): Promise<string | null> {
|
||||
|
||||
@@ -49,10 +49,10 @@ describe(evaluateTemplateString, () => {
|
||||
});
|
||||
});
|
||||
|
||||
// generic workflow
|
||||
// bare workflow
|
||||
describe(bumpVersionAsync, () => {
|
||||
it('bumps expo.ios.buildNumber and CFBundleVersion when strategy = BumpStrategy.BUILD_NUMBER', async () => {
|
||||
const fakeExp = initGenericProject();
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.BUILD_NUMBER,
|
||||
@@ -74,7 +74,7 @@ describe(bumpVersionAsync, () => {
|
||||
});
|
||||
|
||||
it('bumps expo.ios.buildNumber and CFBundleVersion for non default Info.plist location', async () => {
|
||||
const fakeExp = initGenericProject({ infoPlistName: 'Info2.plist' });
|
||||
const fakeExp = initBareWorkflowProject({ infoPlistName: 'Info2.plist' });
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.BUILD_NUMBER,
|
||||
@@ -98,7 +98,7 @@ describe(bumpVersionAsync, () => {
|
||||
});
|
||||
|
||||
it('bumps expo.version and CFBundleShortVersionString when strategy = BumpStrategy.SHORT_VERSION', async () => {
|
||||
const fakeExp = initGenericProject();
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.APP_VERSION,
|
||||
@@ -120,7 +120,7 @@ describe(bumpVersionAsync, () => {
|
||||
});
|
||||
|
||||
it('does not bump any version when strategy = BumpStrategy.NOOP', async () => {
|
||||
const fakeExp = initGenericProject();
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.NOOP,
|
||||
@@ -194,9 +194,9 @@ describe(bumpVersionInAppJsonAsync, () => {
|
||||
});
|
||||
|
||||
describe(readBuildNumberAsync, () => {
|
||||
describe('generic project', () => {
|
||||
describe('bare project', () => {
|
||||
it('reads the build number from native code', async () => {
|
||||
const exp = initGenericProject();
|
||||
const exp = initBareWorkflowProject();
|
||||
const buildNumber = await readBuildNumberAsync('/repo', exp, {});
|
||||
expect(buildNumber).toBe('1');
|
||||
});
|
||||
@@ -212,14 +212,14 @@ describe(readBuildNumberAsync, () => {
|
||||
});
|
||||
|
||||
describe(readShortVersionAsync, () => {
|
||||
describe('generic project', () => {
|
||||
describe('bare project', () => {
|
||||
it('reads the short version from native code', async () => {
|
||||
const exp = initGenericProject();
|
||||
const exp = initBareWorkflowProject();
|
||||
const appVersion = await readShortVersionAsync('/repo', exp, {});
|
||||
expect(appVersion).toBe('1.0.0');
|
||||
});
|
||||
it('evaluates interpolated build number', async () => {
|
||||
const exp = initGenericProject({
|
||||
const exp = initBareWorkflowProject({
|
||||
appVersion: '$(CURRENT_PROJECT_VERSION)',
|
||||
});
|
||||
const buildNumber = await readShortVersionAsync('/repo', exp, {
|
||||
@@ -231,7 +231,7 @@ describe(readShortVersionAsync, () => {
|
||||
|
||||
describe('managed project', () => {
|
||||
it('reads the version from app config', async () => {
|
||||
const exp = initGenericProject();
|
||||
const exp = initBareWorkflowProject();
|
||||
const appVersion = await readShortVersionAsync('/repo', exp, {});
|
||||
expect(appVersion).toBe('1.0.0');
|
||||
});
|
||||
@@ -286,7 +286,7 @@ describe(getInfoPlistPath, () => {
|
||||
});
|
||||
});
|
||||
|
||||
function initGenericProject({
|
||||
function initBareWorkflowProject({
|
||||
appVersion = '1.0.0',
|
||||
version = '1',
|
||||
infoPlistName = 'Info.plist',
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { XCBuildConfiguration } from 'xcode';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
ensureBundleIdentifierIsDefinedForManagedProjectAsync,
|
||||
warnIfBundleIdentifierDefinedInAppConfigForGenericProject,
|
||||
warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject,
|
||||
} from '../../project/ios/bundleIdentifier';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { ConfigureContext } from '../context';
|
||||
@@ -20,7 +20,7 @@ export async function configureIosAsync(ctx: ConfigureContext): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
warnIfBundleIdentifierDefinedInAppConfigForGenericProject(ctx.projectDir, ctx.exp);
|
||||
warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject(ctx.projectDir, ctx.exp);
|
||||
|
||||
if (isExpoUpdatesInstalled(ctx.projectDir)) {
|
||||
await configureUpdatesAsync(ctx.projectDir, ctx.exp);
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { Env, Ios } from '@expo/eas-build-job';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import {
|
||||
DistributionType,
|
||||
IosBuildType,
|
||||
IosJobInput,
|
||||
IosJobSecretsInput,
|
||||
} from '../../graphql/generated';
|
||||
import { IosJobInput, IosJobSecretsInput } from '../../graphql/generated';
|
||||
import { transformProjectArchive, transformWorkflow } from '../graphql';
|
||||
|
||||
export function transformJob(job: Ios.Job): IosJobInput {
|
||||
@@ -16,30 +11,18 @@ export function transformJob(job: Ios.Job): IosJobInput {
|
||||
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,
|
||||
|
||||
scheme: job.scheme,
|
||||
buildConfiguration: job.buildConfiguration,
|
||||
artifactPath: job.artifactPath,
|
||||
|
||||
buildType: job.buildType && transformBuildType(job.buildType),
|
||||
username: job.username,
|
||||
developmentClient: job.developmentClient,
|
||||
simulator: job.simulator,
|
||||
};
|
||||
}
|
||||
|
||||
function transformDistributionType(distributionType: Ios.DistributionType): DistributionType {
|
||||
if (distributionType === 'store') {
|
||||
return DistributionType.Store;
|
||||
} else if (distributionType === 'internal') {
|
||||
return DistributionType.Internal;
|
||||
} else {
|
||||
return DistributionType.Simulator;
|
||||
}
|
||||
}
|
||||
|
||||
function transformIosSecrets(secrets: {
|
||||
buildCredentials?: Ios.BuildCredentials;
|
||||
env?: Env;
|
||||
@@ -61,11 +44,3 @@ function transformIosSecrets(secrets: {
|
||||
environmentSecrets: secrets.env,
|
||||
};
|
||||
}
|
||||
|
||||
function transformBuildType(buildType: Ios.BuildType): IosBuildType {
|
||||
if (buildType === Ios.BuildType.DEVELOPMENT_CLIENT) {
|
||||
return IosBuildType.DevelopmentClient;
|
||||
} else {
|
||||
return IosBuildType.Release;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import slash from 'slash';
|
||||
import { IosCredentials, TargetCredentials } from '../../credentials/ios/types';
|
||||
import { getUsername } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import vcs from '../../vcs';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { BuildContext } from '../context';
|
||||
|
||||
interface JobData {
|
||||
@@ -25,7 +25,7 @@ export async function prepareJobAsync(
|
||||
jobData: JobData
|
||||
): Promise<Job> {
|
||||
const projectRootDirectory =
|
||||
slash(path.relative(await vcs.getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
const username = getUsername(ctx.exp, await ensureLoggedInAsync());
|
||||
const buildCredentials: Ios.Job['secrets']['buildCredentials'] = {};
|
||||
if (jobData.credentials) {
|
||||
@@ -40,7 +40,6 @@ export async function prepareJobAsync(
|
||||
platform: Platform.IOS,
|
||||
projectArchive: jobData.projectArchive,
|
||||
projectRootDirectory,
|
||||
distribution: ctx.buildProfile.simulator ? 'simulator' : ctx.buildProfile.distribution,
|
||||
builderEnvironment: {
|
||||
image: ctx.buildProfile.image,
|
||||
node: ctx.buildProfile.node,
|
||||
@@ -61,14 +60,11 @@ export async function prepareJobAsync(
|
||||
},
|
||||
releaseChannel: ctx.buildProfile.releaseChannel,
|
||||
updates: { channel: ctx.buildProfile.channel },
|
||||
|
||||
developmentClient: ctx.buildProfile.developmentClient,
|
||||
simulator: ctx.buildProfile.simulator,
|
||||
scheme: jobData.buildScheme,
|
||||
buildConfiguration: ctx.buildProfile.buildConfiguration,
|
||||
artifactPath: ctx.buildProfile.artifactPath,
|
||||
|
||||
buildType: ctx.buildProfile.developmentClient
|
||||
? Ios.BuildType.DEVELOPMENT_CLIENT
|
||||
: Ios.BuildType.RELEASE,
|
||||
username,
|
||||
};
|
||||
return sanitizeJob(job);
|
||||
|
||||
@@ -7,7 +7,8 @@ import { GradleBuildContext } from '../project/android/gradle';
|
||||
import { getBundleIdentifierAsync } from '../project/ios/bundleIdentifier';
|
||||
import { getUsername } from '../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import vcs from '../vcs';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import {
|
||||
readChannelSafelyAsync as readAndroidChannelSafelyAsync,
|
||||
readReleaseChannelSafelyAsync as readAndroidReleaseChannelSafelyAsync,
|
||||
@@ -21,13 +22,6 @@ import {
|
||||
import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios/version';
|
||||
import { isExpoUpdatesInstalled } from './utils/updates';
|
||||
|
||||
/**
|
||||
* We use require() to exclude package.json from TypeScript's analysis since it lives outside
|
||||
* the src directory and would change the directory structure of the emitted files
|
||||
* under the build directory
|
||||
*/
|
||||
const packageJSON = require('../../package.json');
|
||||
|
||||
export type MetadataContext<T extends Platform> = T extends Platform.ANDROID
|
||||
? AndroidMetadataContext
|
||||
: IosMetadataContext;
|
||||
@@ -53,7 +47,7 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
const metadata = {
|
||||
trackingContext: ctx.trackingCtx,
|
||||
...(await maybeResolveVersionsAsync(ctx, platformContext)),
|
||||
cliVersion: packageJSON.version,
|
||||
cliVersion: easCliVersion,
|
||||
workflow: ctx.workflow,
|
||||
credentialsSource: ctx.buildProfile.credentialsSource,
|
||||
sdkVersion: ctx.exp.sdkVersion,
|
||||
@@ -63,7 +57,8 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
appName: ctx.exp.name,
|
||||
appIdentifier: await resolveAppIdentifierAsync(ctx, platformContext),
|
||||
buildProfile: ctx.buildProfileName,
|
||||
gitCommitHash: await vcs.getCommitHashAsync(),
|
||||
gitCommitHash: await getVcsClient().getCommitHashAsync(),
|
||||
isGitWorkingTreeDirty: await getVcsClient().hasUncommittedChangesAsync(),
|
||||
username: getUsername(ctx.exp, await ensureLoggedInAsync()),
|
||||
...(ctx.platform === Platform.IOS && {
|
||||
iosEnterpriseProvisioning: resolveIosEnterpriseProvisioning(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as ExpoConfig from '@expo/config';
|
||||
import JsonFile from '@expo/json-file';
|
||||
import assert from 'assert';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
export async function updateAppJsonConfigAsync(
|
||||
{
|
||||
@@ -15,10 +15,19 @@ export async function updateAppJsonConfigAsync(
|
||||
const paths = ExpoConfig.getConfigFilePaths(projectDir);
|
||||
assert(paths.staticConfigPath, "can't update dynamic config");
|
||||
|
||||
const rawStaticConfig = await fs.readJSON(paths.staticConfigPath);
|
||||
const rawStaticConfig = readAppJson(paths.staticConfigPath);
|
||||
rawStaticConfig.expo = rawStaticConfig.expo ?? {};
|
||||
modifyConfig(rawStaticConfig.expo);
|
||||
await fs.writeJson(paths.staticConfigPath, rawStaticConfig, { spaces: 2 });
|
||||
await JsonFile.writeAsync(paths.staticConfigPath, rawStaticConfig, { json5: false });
|
||||
|
||||
modifyConfig(exp);
|
||||
}
|
||||
|
||||
// TODO: remove this once @expo/config exports getStaticConfig
|
||||
export function readAppJson(appJsonPath: string): any {
|
||||
const config = JsonFile.read(appJsonPath, { json5: true });
|
||||
if (config) {
|
||||
return config as any;
|
||||
}
|
||||
throw new Error(`Failed to read app.json`);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import { Workflow } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
import { error } from '@oclif/errors';
|
||||
import chalk from 'chalk';
|
||||
import resolveFrom from 'resolve-from';
|
||||
@@ -9,40 +9,37 @@ import Log, { learnMore } from '../../log';
|
||||
import { appPlatformDisplayNames } from '../../platform';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { expoCommandAsync } from '../../utils/expoCommand';
|
||||
import zipObject from '../../utils/expodash/zipObject';
|
||||
import { expoCommandAsync } from '../../utils/expoCli';
|
||||
import { ProfileData } from '../../utils/profiles';
|
||||
import { reviewAndCommitChangesAsync } from './repository';
|
||||
|
||||
export async function ensureExpoDevClientInstalledForDevClientBuildsAsync({
|
||||
projectDir,
|
||||
platforms,
|
||||
profile,
|
||||
nonInteractive = false,
|
||||
buildProfiles = [],
|
||||
}: {
|
||||
projectDir: string;
|
||||
platforms: Platform[];
|
||||
profile: string;
|
||||
nonInteractive?: boolean;
|
||||
buildProfiles?: ProfileData<BuildProfile>[];
|
||||
}): Promise<void> {
|
||||
if (await isExpoDevClientInstalledAsync(projectDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const devClientPerPlatformList = await Promise.all(
|
||||
platforms.map(async platform => {
|
||||
const buildProfile = await easJsonReader.readBuildProfileAsync(platform, profile);
|
||||
return buildProfile.developmentClient ?? false;
|
||||
})
|
||||
const buildProfilesWithDevelopmentClientRequired = buildProfiles.filter(
|
||||
buildProfile => buildProfile.profile.developmentClient
|
||||
);
|
||||
|
||||
const isDevClientRequired = devClientPerPlatformList.some(i => i);
|
||||
if (!isDevClientRequired) {
|
||||
const isDevelopmentClientRequired = buildProfilesWithDevelopmentClientRequired.some(Boolean);
|
||||
|
||||
if (!isDevelopmentClientRequired) {
|
||||
return;
|
||||
}
|
||||
|
||||
const devClientPerPlatform = zipObject(platforms, devClientPerPlatformList);
|
||||
const platformsToCheck = platforms.filter(platform => devClientPerPlatform[platform]);
|
||||
const platformsToCheck = buildProfilesWithDevelopmentClientRequired.map(
|
||||
({ platform }) => platform
|
||||
);
|
||||
|
||||
const workflowPerPlatformList = await Promise.all(
|
||||
platformsToCheck.map(platform => resolveWorkflowAsync(projectDir, platform))
|
||||
);
|
||||
@@ -67,15 +64,15 @@ export async function ensureExpoDevClientInstalledForDevClientBuildsAsync({
|
||||
if (install) {
|
||||
await installExpoDevClientAsync(projectDir, { nonInteractive });
|
||||
} else {
|
||||
error(`Install ${chalk.bold('expo-dev-client')} on your own and come back later.`, {
|
||||
error(`Install ${chalk.bold('expo-dev-client')} manually and come back later.`, {
|
||||
exit: 1,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Log.warn(`Unfortunately, you need to install ${chalk.bold('expo-dev-client')} on your own.`);
|
||||
Log.warn(`You'll need to install ${chalk.bold('expo-dev-client')} manually.`);
|
||||
Log.warn(
|
||||
learnMore('https://docs.expo.dev/clients/installation/', {
|
||||
learnMoreMessage: 'See installation instructions on how to do it',
|
||||
learnMoreMessage: 'See installation instructions on how to do it.',
|
||||
dim: false,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -10,10 +10,10 @@ import { confirmAsync, promptAsync } from '../../prompts';
|
||||
import { formatBytes } from '../../utils/files';
|
||||
import { getTmpDirectory } from '../../utils/paths';
|
||||
import { endTimer, formatMilliseconds, startTimer } from '../../utils/timer';
|
||||
import vcs from '../../vcs';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
|
||||
export async function maybeBailOnRepoStatusAsync(): Promise<void> {
|
||||
if (!(await vcs.hasUncommittedChangesAsync())) {
|
||||
if (!(await getVcsClient().isCommitRequiredAsync())) {
|
||||
return;
|
||||
}
|
||||
Log.addNewLineIfNone();
|
||||
@@ -33,7 +33,7 @@ export async function maybeBailOnRepoStatusAsync(): Promise<void> {
|
||||
}
|
||||
|
||||
export async function ensureRepoIsCleanAsync(nonInteractive = false): Promise<void> {
|
||||
if (!(await vcs.hasUncommittedChangesAsync())) {
|
||||
if (!(await getVcsClient().isCommitRequiredAsync())) {
|
||||
return;
|
||||
}
|
||||
Log.addNewLineIfNone();
|
||||
@@ -70,7 +70,7 @@ export async function commitPromptAsync({
|
||||
initial: initialCommitMessage,
|
||||
validate: (input: string) => input !== '',
|
||||
});
|
||||
await vcs.commitAsync({ commitAllFiles, commitMessage: message });
|
||||
await getVcsClient().commitAsync({ commitAllFiles, commitMessage: message });
|
||||
}
|
||||
|
||||
export async function makeProjectTarballAsync(): Promise<{ path: string; size: number }> {
|
||||
@@ -95,7 +95,7 @@ export async function makeProjectTarballAsync(): Promise<{ path: string; size: n
|
||||
startTimer(compressTimerLabel);
|
||||
|
||||
try {
|
||||
await vcs.makeShallowCopyAsync(shallowClonePath);
|
||||
await getVcsClient().makeShallowCopyAsync(shallowClonePath);
|
||||
await tar.create({ cwd: shallowClonePath, file: tarPath, prefix: 'project', gzip: true }, [
|
||||
'.',
|
||||
]);
|
||||
@@ -133,7 +133,10 @@ export async function reviewAndCommitChangesAsync(
|
||||
{ nonInteractive, askedFirstTime = true }: { nonInteractive: boolean; askedFirstTime?: boolean }
|
||||
): Promise<void> {
|
||||
if (process.env.EAS_BUILD_AUTOCOMMIT) {
|
||||
await vcs.commitAsync({ commitMessage: initialCommitMessage, commitAllFiles: false });
|
||||
await getVcsClient().commitAsync({
|
||||
commitMessage: initialCommitMessage,
|
||||
commitAllFiles: false,
|
||||
});
|
||||
Log.withTick('Committed changes.');
|
||||
return;
|
||||
}
|
||||
@@ -166,7 +169,7 @@ export async function reviewAndCommitChangesAsync(
|
||||
await commitPromptAsync({ initialCommitMessage });
|
||||
Log.withTick('Committed changes.');
|
||||
} else if (selected === ShouldCommitChanges.ShowDiffFirst) {
|
||||
await vcs.showDiffAsync();
|
||||
await getVcsClient().showDiffAsync();
|
||||
await reviewAndCommitChangesAsync(initialCommitMessage, {
|
||||
nonInteractive,
|
||||
askedFirstTime: false,
|
||||
|
||||
@@ -3,7 +3,7 @@ import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import Log, { learnMore } from '../log';
|
||||
import vcs from '../vcs';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import { BuildContext } from './context';
|
||||
|
||||
export function checkNodeEnvVariable(ctx: BuildContext<Platform>): void {
|
||||
@@ -25,12 +25,12 @@ export async function checkGoogleServicesFileAsync<T extends Platform>(
|
||||
if (!googleServicesFilePath) {
|
||||
return;
|
||||
}
|
||||
const rootDir = path.normalize(await vcs.getRootPathAsync());
|
||||
const rootDir = path.normalize(await getVcsClient().getRootPathAsync());
|
||||
const absGoogleServicesFilePath = path.resolve(ctx.projectDir, googleServicesFilePath);
|
||||
if (
|
||||
(await fs.pathExists(absGoogleServicesFilePath)) &&
|
||||
(!isInsideDirectory(absGoogleServicesFilePath, rootDir) ||
|
||||
(await vcs.isFileIgnoredAsync(path.relative(rootDir, absGoogleServicesFilePath))))
|
||||
(await getVcsClient().isFileIgnoredAsync(path.relative(rootDir, absGoogleServicesFilePath))))
|
||||
) {
|
||||
Log.warn(
|
||||
`File specified via "${ctx.platform}.googleServicesFile" field in your app.json is not checked in to your repository and won't be uploaded to the builder.`
|
||||
@@ -41,7 +41,7 @@ export async function checkGoogleServicesFileAsync<T extends Platform>(
|
||||
)}`
|
||||
);
|
||||
Log.warn(
|
||||
'If you are using that file for compatibility with the classic build service (expo build) you can silence this warning by setting GOOGLE_SERVICES_FILE in your build profile in eas.json to any non-falsy value.'
|
||||
`If you are using that file for compatibility with the classic build service (expo build) you can silence this warning by setting your build profile's env.GOOGLE_SERVICES_FILE in eas.json to any non-empty string.`
|
||||
);
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import { Command } from '@oclif/command';
|
||||
import semver from 'semver';
|
||||
|
||||
import {
|
||||
AnalyticsEvent,
|
||||
@@ -6,8 +8,13 @@ import {
|
||||
initAsync as initAnalyticsAsync,
|
||||
logEvent,
|
||||
} from '../analytics';
|
||||
import Log from '../log';
|
||||
import { findProjectRootAsync } from '../project/projectUtils';
|
||||
import { getUserAsync } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { setVcsClient } from '../vcs';
|
||||
import GitClient from '../vcs/clients/git';
|
||||
|
||||
export default abstract class EasCommand extends Command {
|
||||
/**
|
||||
@@ -20,7 +27,10 @@ export default abstract class EasCommand extends Command {
|
||||
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
async run(): Promise<any> {
|
||||
EasJsonReader.setLog(Log);
|
||||
|
||||
await initAnalyticsAsync();
|
||||
await this.applyCliConfigAsync();
|
||||
|
||||
if (this.requiresAuthentication) {
|
||||
const { flags } = this.parse();
|
||||
@@ -42,4 +52,18 @@ export default abstract class EasCommand extends Command {
|
||||
await flushAnalyticsAsync();
|
||||
return super.finally(err);
|
||||
}
|
||||
|
||||
private async applyCliConfigAsync(): Promise<void> {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const config = await easJsonReader.getCliConfigAsync();
|
||||
if (config?.version && !semver.satisfies(easCliVersion, config.version)) {
|
||||
throw new Error(
|
||||
`You are on eas-cli@${easCliVersion} which does not satisfy the CLI version constraint in eas.json (${config.version})`
|
||||
);
|
||||
}
|
||||
if (config?.requireCommit) {
|
||||
setVcsClient(new GitClient());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import vcs from '../../vcs';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
|
||||
export async function createUpdateBranchOnAppAsync({
|
||||
appId,
|
||||
@@ -90,7 +90,8 @@ export default class BranchCreate extends EasCommand {
|
||||
name: 'name',
|
||||
message: 'Please name the branch:',
|
||||
initial:
|
||||
(await vcs.getBranchNameAsync()) || `branch-${Math.random().toString(36).substr(2, 4)}`,
|
||||
(await getVcsClient().getBranchNameAsync()) ||
|
||||
`branch-${Math.random().toString(36).substr(2, 4)}`,
|
||||
validate: value => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ async function getBranchInfoAsync({
|
||||
{
|
||||
appId,
|
||||
name,
|
||||
}
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -44,7 +44,8 @@ export async function listBranchesAsync({
|
||||
{
|
||||
appId: projectId,
|
||||
limit: BRANCHES_LIMIT,
|
||||
}
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ExpoConfig, getConfig, getDefaultTarget } from '@expo/config';
|
||||
import { getRuntimeVersionForSDKVersion } from '@expo/sdk-runtime-versions';
|
||||
import { ExpoConfig, getConfig } from '@expo/config';
|
||||
import { Updates } from '@expo/config-plugins';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { flags } from '@oclif/command';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
@@ -28,11 +29,12 @@ import {
|
||||
collectAssetsAsync,
|
||||
uploadAssetsAsync,
|
||||
} from '../../project/publish';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { promptAsync, selectAsync } from '../../prompts';
|
||||
import { formatUpdate } from '../../update/utils';
|
||||
import uniqBy from '../../utils/expodash/uniqBy';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import vcs from '../../vcs';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { createUpdateBranchOnAppAsync } from './create';
|
||||
import { listBranchesAsync } from './list';
|
||||
import { viewUpdateBranchAsync } from './view';
|
||||
@@ -60,7 +62,8 @@ async function getUpdateGroupAsync({
|
||||
`,
|
||||
{
|
||||
group,
|
||||
}
|
||||
},
|
||||
{ additionalTypenames: ['Update'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
@@ -168,13 +171,14 @@ export default class BranchPublish extends EasCommand {
|
||||
isPublicConfig: true,
|
||||
});
|
||||
|
||||
const runtimeVersions = getRuntimeVersionObject(exp, platformFlag, projectDir);
|
||||
const runtimeVersions = await getRuntimeVersionObjectAsync(exp, platformFlag, projectDir);
|
||||
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
if (!branchName && autoFlag) {
|
||||
branchName =
|
||||
(await vcs.getBranchNameAsync()) || `branch-${Math.random().toString(36).substr(2, 4)}`;
|
||||
(await getVcsClient().getBranchNameAsync()) ||
|
||||
`branch-${Math.random().toString(36).substr(2, 4)}`;
|
||||
}
|
||||
|
||||
if (!branchName) {
|
||||
@@ -297,7 +301,7 @@ export default class BranchPublish extends EasCommand {
|
||||
}
|
||||
|
||||
if (!message && autoFlag) {
|
||||
message = (await vcs.getLastCommitMessageAsync())?.trim();
|
||||
message = (await getVcsClient().getLastCommitMessageAsync())?.trim();
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
@@ -311,7 +315,7 @@ export default class BranchPublish extends EasCommand {
|
||||
message: `Please enter a publication message.`,
|
||||
initial: republish
|
||||
? `Republish "${oldMessage!}" - group: ${group}`
|
||||
: (await vcs.getLastCommitMessageAsync())?.trim(),
|
||||
: (await getVcsClient().getLastCommitMessageAsync())?.trim(),
|
||||
validate: value => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
@@ -388,59 +392,26 @@ export default class BranchPublish extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
function getRuntimeVersionObject(
|
||||
async function getRuntimeVersionObjectAsync(
|
||||
exp: ExpoConfig,
|
||||
platformFlag: string,
|
||||
platformFlag: PlatformFlag,
|
||||
projectDir: string
|
||||
): Record<string, string> {
|
||||
let { runtimeVersion: defaultRuntimeVersion, sdkVersion } = exp;
|
||||
): Promise<Record<string, string>> {
|
||||
const platforms = (platformFlag === 'all' ? ['android', 'ios'] : [platformFlag]) as Platform[];
|
||||
|
||||
// When a SDK version is supplied instead of a runtime version and we're in the managed workflow
|
||||
// construct the runtimeVersion with special meaning indicating that the runtime is an
|
||||
// Expo SDK preset runtime that can be launched in Expo Go.
|
||||
const isManagedProject = getDefaultTarget(projectDir) === 'managed';
|
||||
if (!defaultRuntimeVersion && sdkVersion && isManagedProject) {
|
||||
Log.withTick('Generating runtime version from sdk version');
|
||||
defaultRuntimeVersion = getRuntimeVersionForSDKVersion(sdkVersion);
|
||||
}
|
||||
const iOSRuntimeVersion = (exp as any).ios?.runtimeVersion; // TODO-JJ remove cast to any
|
||||
const androidRuntimeVersion = (exp as any).android?.runtimeVersion; // TODO-JJ remove cast to any
|
||||
let runtimeVersions: Record<string, string>;
|
||||
switch (platformFlag) {
|
||||
case 'ios': {
|
||||
runtimeVersions = {
|
||||
ios: iOSRuntimeVersion ?? defaultRuntimeVersion,
|
||||
};
|
||||
break;
|
||||
for (const platform of platforms) {
|
||||
const isPolicy = typeof (exp[platform]?.runtimeVersion ?? exp.runtimeVersion) === 'object';
|
||||
if (isPolicy) {
|
||||
const isManaged = (await resolveWorkflowAsync(projectDir, platform)) === Workflow.MANAGED;
|
||||
if (!isManaged) {
|
||||
throw new Error('Runtime version policies are only supported in the managed workflow.');
|
||||
}
|
||||
}
|
||||
case 'android': {
|
||||
runtimeVersions = {
|
||||
android: androidRuntimeVersion ?? defaultRuntimeVersion,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'all': {
|
||||
runtimeVersions = {
|
||||
ios: iOSRuntimeVersion ?? defaultRuntimeVersion,
|
||||
android: androidRuntimeVersion ?? defaultRuntimeVersion,
|
||||
};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error('Platform flag must be "ios", "android", or "all"');
|
||||
}
|
||||
if (Object.values(runtimeVersions).some(runtime => !runtime)) {
|
||||
throw new Error(
|
||||
"Couldn't find a 'runtimeVersion' for every platform. Please specify it under the 'expo' key in 'app.json'"
|
||||
);
|
||||
}
|
||||
if (Object.values(runtimeVersions).some(runtime => typeof runtime !== 'string')) {
|
||||
throw new Error(
|
||||
`Please ensure that all of the runtime versions defined in the app.json are strings.`
|
||||
);
|
||||
}
|
||||
|
||||
return runtimeVersions;
|
||||
return Object.fromEntries(
|
||||
platforms.map(platform => [platform, Updates.getRuntimeVersion(exp, platform)])
|
||||
);
|
||||
}
|
||||
|
||||
function formatUpdateTitle(
|
||||
|
||||
@@ -58,7 +58,8 @@ export async function viewUpdateBranchAsync({
|
||||
appId,
|
||||
name,
|
||||
limit: PAGE_LIMIT,
|
||||
}
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch', 'Update'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import {
|
||||
EasJsonReader,
|
||||
hasMismatchedExtendsAsync,
|
||||
isUsingDeprecatedFormatAsync,
|
||||
migrateAsync,
|
||||
} from '@expo/eas-json';
|
||||
import { BuildProfile, EasJsonReader } from '@expo/eas-json';
|
||||
import { flags } from '@oclif/command';
|
||||
import { error } from '@oclif/errors';
|
||||
import chalk from 'chalk';
|
||||
import figures from 'figures';
|
||||
import fs from 'fs-extra';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { prepareAndroidBuildAsync } from '../../build/android/build';
|
||||
@@ -27,7 +24,7 @@ import {
|
||||
SubmissionFragment,
|
||||
} from '../../graphql/generated';
|
||||
import { toAppPlatform, toPlatform } from '../../graphql/types/AppPlatform';
|
||||
import Log from '../../log';
|
||||
import Log, { learnMore, link } from '../../log';
|
||||
import {
|
||||
RequestedPlatform,
|
||||
appPlatformDisplayNames,
|
||||
@@ -41,21 +38,24 @@ import {
|
||||
} from '../../project/isEasEnabledForProject';
|
||||
import { validateMetroConfigForManagedWorkflowAsync } from '../../project/metroConfig';
|
||||
import { findProjectRootAsync } from '../../project/projectUtils';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { createSubmissionContextAsync } from '../../submit/context';
|
||||
import {
|
||||
submitAsync,
|
||||
waitToCompleteAsync as waitForSubmissionsToCompleteAsync,
|
||||
} from '../../submit/submit';
|
||||
import { printSubmissionDetailsUrls } from '../../submit/utils/urls';
|
||||
import { easCliVersion } from '../../utils/easCli';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
import vcs from '../../vcs';
|
||||
import { ProfileData, getProfilesAsync } from '../../utils/profiles';
|
||||
import { getVcsClient, setVcsClient } from '../../vcs';
|
||||
import GitClient from '../../vcs/clients/git';
|
||||
|
||||
interface RawBuildFlags {
|
||||
platform?: string;
|
||||
'skip-credentials-check': boolean;
|
||||
'skip-project-configuration': boolean;
|
||||
profile: string;
|
||||
profile?: string;
|
||||
'non-interactive': boolean;
|
||||
local: boolean;
|
||||
wait: boolean;
|
||||
@@ -68,7 +68,7 @@ interface RawBuildFlags {
|
||||
interface BuildFlags {
|
||||
requestedPlatform: RequestedPlatform;
|
||||
skipProjectConfiguration: boolean;
|
||||
profile: string;
|
||||
profile: string | null;
|
||||
nonInteractive: boolean;
|
||||
local: boolean;
|
||||
wait: boolean;
|
||||
@@ -99,8 +99,8 @@ export default class Build extends EasCommand {
|
||||
description: 'Skip project configuration',
|
||||
}),
|
||||
profile: flags.string({
|
||||
default: 'release',
|
||||
description: 'Name of the build profile from eas.json',
|
||||
description:
|
||||
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
helpValue: 'PROFILE_NAME',
|
||||
}),
|
||||
'non-interactive': flags.boolean({
|
||||
@@ -146,50 +146,66 @@ export default class Build extends EasCommand {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||
|
||||
await vcs.ensureRepoExistsAsync();
|
||||
await getVcsClient().ensureRepoExistsAsync();
|
||||
await ensureRepoIsCleanAsync(flags.nonInteractive);
|
||||
|
||||
await ensureProjectConfiguredAsync(projectDir, requestedPlatform);
|
||||
|
||||
const platforms = toPlatforms(requestedPlatform);
|
||||
await ensureExpoDevClientInstalledForDevClientBuildsAsync({
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const buildProfiles = await getProfilesAsync({
|
||||
platforms,
|
||||
projectDir,
|
||||
profile: flags.profile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
profileName: flags.profile,
|
||||
async readProfileAsync(platform, profileName) {
|
||||
return await easJsonReader.readBuildProfileAsync(platform, profileName);
|
||||
},
|
||||
});
|
||||
|
||||
const startedBuilds: BuildFragment[] = [];
|
||||
await ensureExpoDevClientInstalledForDevClientBuildsAsync({
|
||||
projectDir,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
buildProfiles,
|
||||
});
|
||||
|
||||
const startedBuilds: {
|
||||
build: BuildFragment;
|
||||
buildProfile: ProfileData<BuildProfile>;
|
||||
}[] = [];
|
||||
const buildCtxByPlatform: { [p in AppPlatform]?: BuildContext<Platform> } = {};
|
||||
for (const platform of platforms) {
|
||||
|
||||
for (const buildProfile of buildProfiles) {
|
||||
const { build: maybeBuild, buildCtx } = await this.prepareAndStartBuildAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
flags,
|
||||
moreBuilds: platforms.length > 1,
|
||||
buildProfile,
|
||||
});
|
||||
if (maybeBuild) {
|
||||
startedBuilds.push(maybeBuild);
|
||||
startedBuilds.push({ build: maybeBuild, buildProfile });
|
||||
}
|
||||
buildCtxByPlatform[toAppPlatform(platform)] = buildCtx;
|
||||
buildCtxByPlatform[toAppPlatform(buildProfile.platform)] = buildCtx;
|
||||
}
|
||||
|
||||
if (flags.local) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.newLine();
|
||||
printLogsUrls(startedBuilds);
|
||||
printLogsUrls(startedBuilds.map(startedBuild => startedBuild.build));
|
||||
Log.newLine();
|
||||
|
||||
const submissions: SubmissionFragment[] = [];
|
||||
if (flags.autoSubmit) {
|
||||
for (const build of startedBuilds) {
|
||||
for (const startedBuild of startedBuilds) {
|
||||
const submission = await this.prepareAndStartSubmissionAsync({
|
||||
build,
|
||||
credentialsCtx: nullthrows(buildCtxByPlatform[build.platform]?.credentialsCtx),
|
||||
build: startedBuild.build,
|
||||
credentialsCtx: nullthrows(
|
||||
buildCtxByPlatform[startedBuild.build.platform]?.credentialsCtx
|
||||
),
|
||||
flags,
|
||||
moreBuilds: startedBuilds.length > 1,
|
||||
projectDir,
|
||||
buildProfile: startedBuild.buildProfile,
|
||||
});
|
||||
submissions.push(submission);
|
||||
}
|
||||
@@ -203,7 +219,7 @@ export default class Build extends EasCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
const builds = await waitForBuildEndAsync(startedBuilds.map(build => build.id));
|
||||
const builds = await waitForBuildEndAsync(startedBuilds.map(({ build }) => build.id));
|
||||
printBuildResults(builds, flags.json);
|
||||
|
||||
const haveAllBuildsFailedOrCanceled = builds.every(
|
||||
@@ -252,7 +268,7 @@ export default class Build extends EasCommand {
|
||||
return {
|
||||
requestedPlatform,
|
||||
skipProjectConfiguration: flags['skip-project-configuration'],
|
||||
profile,
|
||||
profile: profile ?? null,
|
||||
nonInteractive,
|
||||
local: flags['local'],
|
||||
wait: flags['wait'],
|
||||
@@ -263,34 +279,31 @@ export default class Build extends EasCommand {
|
||||
};
|
||||
}
|
||||
|
||||
private async prepareAndStartBuildAsync<T extends Platform>({
|
||||
private async prepareAndStartBuildAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
flags,
|
||||
moreBuilds,
|
||||
buildProfile,
|
||||
}: {
|
||||
projectDir: string;
|
||||
platform: T;
|
||||
flags: BuildFlags;
|
||||
moreBuilds: boolean;
|
||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<T> }> {
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const buildProfile = await easJsonReader.readBuildProfileAsync(platform, flags.profile);
|
||||
|
||||
buildProfile: ProfileData<BuildProfile>;
|
||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
||||
const buildCtx = await createBuildContextAsync({
|
||||
buildProfileName: flags.profile,
|
||||
buildProfileName: buildProfile.profileName,
|
||||
clearCache: flags.clearCache,
|
||||
buildProfile,
|
||||
buildProfile: buildProfile.profile,
|
||||
local: flags.local,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
platform,
|
||||
platform: buildProfile.platform,
|
||||
projectDir,
|
||||
skipProjectConfiguration: flags.skipProjectConfiguration,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
Log.newLine();
|
||||
const appPlatform = toAppPlatform(platform);
|
||||
const appPlatform = toAppPlatform(buildProfile.platform);
|
||||
Log.log(
|
||||
`${appPlatformEmojis[appPlatform]} ${chalk.bold(
|
||||
`${appPlatformDisplayNames[appPlatform]} build`
|
||||
@@ -330,16 +343,17 @@ export default class Build extends EasCommand {
|
||||
flags,
|
||||
moreBuilds,
|
||||
projectDir,
|
||||
buildProfile,
|
||||
}: {
|
||||
build: BuildFragment;
|
||||
credentialsCtx: CredentialsContext;
|
||||
flags: BuildFlags;
|
||||
moreBuilds: boolean;
|
||||
projectDir: string;
|
||||
buildProfile: ProfileData<BuildProfile>;
|
||||
}): Promise<SubmissionFragment> {
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const platform = toPlatform(build.platform);
|
||||
const buildProfile = await easJsonReader.readBuildProfileAsync(platform, flags.profile);
|
||||
const submitProfile = await easJsonReader.readSubmitProfileAsync(platform, flags.submitProfile);
|
||||
const submissionCtx = await createSubmissionContextAsync({
|
||||
platform,
|
||||
@@ -348,7 +362,7 @@ export default class Build extends EasCommand {
|
||||
profile: submitProfile,
|
||||
archiveFlags: { id: build.id },
|
||||
nonInteractive: flags.nonInteractive,
|
||||
env: buildProfile.env,
|
||||
env: buildProfile.profile.env,
|
||||
credentialsCtx,
|
||||
});
|
||||
|
||||
@@ -378,36 +392,52 @@ export async function handleDeprecatedEasJsonAsync(
|
||||
projectDir: string,
|
||||
nonInteractive: boolean
|
||||
): Promise<void> {
|
||||
if (await isUsingDeprecatedFormatAsync(projectDir)) {
|
||||
const hasMismatchedExtendsKeys = await hasMismatchedExtendsAsync(projectDir);
|
||||
if (nonInteractive) {
|
||||
Log.error('We detected that your eas.json is using a deprecated format.');
|
||||
Log.error(
|
||||
'We will convert it automatically if run this command without --non-interactive flag. Alternatively, you can update eas.json manually according to https://docs.expo.dev/build/eas-json'
|
||||
);
|
||||
error('Unsupported eas.json format', { exit: 1 });
|
||||
}
|
||||
if (!(await fs.pathExists(EasJsonReader.formatEasJsonPath(projectDir)))) {
|
||||
return;
|
||||
}
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const rawEasJson = await easJsonReader.readRawAsync();
|
||||
if (rawEasJson?.cli) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = await confirmAsync({
|
||||
message:
|
||||
'We detected that your eas.json is using a deprecated format, do you want to migrate to the new format?',
|
||||
});
|
||||
if (confirm) {
|
||||
await migrateAsync(projectDir);
|
||||
if (hasMismatchedExtendsKeys) {
|
||||
Log.warn(
|
||||
'"extends" keyword can only be migrated automatically to the new format if both Android and iOS profiles extend base profiles with the same names for both platforms'
|
||||
);
|
||||
Log.warn(
|
||||
'Migration was successful, but you need to manually adjust the extend rules for your profiles'
|
||||
);
|
||||
error('Fix eas.json manually', { exit: 1 });
|
||||
}
|
||||
} else {
|
||||
Log.error(
|
||||
"Aborting, update your eas.json according to https://docs.expo.dev/build/eas-json and run 'eas build' again"
|
||||
);
|
||||
error('Unsupported eas.json format', { exit: 1 });
|
||||
}
|
||||
if (nonInteractive) {
|
||||
Log.warn(
|
||||
`${
|
||||
figures.warning
|
||||
} Action required: the default behavior of EAS CLI has changed and your eas.json must be updated to remove ambiguity around which Git integration workflow to use. Refer to ${link(
|
||||
'https://expo.fyi/eas-vcs-workflow'
|
||||
)} for more information.`
|
||||
);
|
||||
Log.warn(
|
||||
'This warning will become an error in an upcoming EAS CLI release. For now, we will proceed with the old default behavior to avoid disruption of your builds.'
|
||||
);
|
||||
setVcsClient(new GitClient());
|
||||
return;
|
||||
}
|
||||
Log.log(
|
||||
`${chalk.bold(
|
||||
'eas-cli@>=0.34.0 no longer requires that you commit changes to Git before starting a build.'
|
||||
)} ${learnMore('https://expo.fyi/eas-vcs-workflow')}`
|
||||
);
|
||||
Log.log(
|
||||
`If you want to continue using the Git integration, you can opt in with ${chalk.bold(
|
||||
'cli.requireCommit'
|
||||
)} in ${chalk.bold('eas.json')} or with the following prompt`
|
||||
);
|
||||
Log.newLine();
|
||||
|
||||
const mode = await selectAsync('Select your preferred Git integration', [
|
||||
{ title: 'Require changes to be committed in Git (old default)', value: 'requireCommit' },
|
||||
{ title: 'Allow builds with dirty Git working tree (new default)', value: 'noCommit' },
|
||||
]);
|
||||
|
||||
rawEasJson.cli =
|
||||
mode === 'requireCommit'
|
||||
? { version: `>= ${easCliVersion}`, requireCommit: true }
|
||||
: { version: `>= ${easCliVersion}` };
|
||||
await fs.writeJSON(EasJsonReader.formatEasJsonPath(projectDir), rawEasJson, { spaces: 2 });
|
||||
if (mode === 'requireCommit') {
|
||||
setVcsClient(new GitClient());
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,8 @@ async function getChannelByNameForAppAsync({
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ appId, channelName }
|
||||
{ appId, channelName },
|
||||
{ additionalTypenames: ['UpdateChannel', 'UpdateBranch'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -58,7 +58,8 @@ async function getAllUpdateChannelForAppAsync({
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ appId, offset: 0, limit: CHANNEL_LIMIT }
|
||||
{ appId, offset: 0, limit: CHANNEL_LIMIT },
|
||||
{ additionalTypenames: ['UpdateChannel', 'UpdateBranch', 'Update'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -133,7 +133,8 @@ export async function getUpdateChannelByNameForAppAsync({
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ appId, channelName }
|
||||
{ appId, channelName },
|
||||
{ additionalTypenames: ['UpdateChannel', 'UpdateBranch', 'Update'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -2,8 +2,7 @@ import envinfo from 'envinfo';
|
||||
|
||||
import EasCommand from '../commandUtils/EasCommand';
|
||||
import Log from '../log';
|
||||
|
||||
const packageJSON = require('../../package.json');
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
|
||||
export default class Diagnostics extends EasCommand {
|
||||
static description = 'log environment info to the console';
|
||||
@@ -30,7 +29,7 @@ export default class Diagnostics extends EasCommand {
|
||||
npmGlobalPackages: ['eas-cli', 'expo-cli'],
|
||||
},
|
||||
{
|
||||
title: `EAS CLI ${packageJSON.version} environment info`,
|
||||
title: `EAS CLI ${easCliVersion} environment info`,
|
||||
}
|
||||
);
|
||||
Log.log(info);
|
||||
|
||||
@@ -22,7 +22,8 @@ async function projectInfoByIdAsync(appId: string): Promise<AppInfoQuery> {
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ appId }
|
||||
{ appId },
|
||||
{ additionalTypenames: ['App'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ import { findProjectRootAsync, getProjectIdAsync } from '../project/projectUtils
|
||||
import { SubmitArchiveFlags, createSubmissionContextAsync } from '../submit/context';
|
||||
import { submitAsync, waitToCompleteAsync } from '../submit/submit';
|
||||
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
|
||||
import { getProfilesAsync } from '../utils/profiles';
|
||||
|
||||
interface RawFlags {
|
||||
platform?: string;
|
||||
@@ -54,7 +55,7 @@ See how to configure submits with eas.json: ${link('https://docs.expo.dev/submit
|
||||
}),
|
||||
profile: flags.string({
|
||||
description:
|
||||
'Name of the submit profile from eas.json. Defaults to "release" if defined in eas.json.',
|
||||
'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
}),
|
||||
latest: flags.boolean({
|
||||
description: 'Submit the latest build for specified platform',
|
||||
@@ -101,23 +102,30 @@ See how to configure submits with eas.json: ${link('https://docs.expo.dev/submit
|
||||
return;
|
||||
}
|
||||
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const platforms = toPlatforms(flags.requestedPlatform);
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const submissionProfiles = await getProfilesAsync({
|
||||
platforms,
|
||||
profileName: flags.profile,
|
||||
async readProfileAsync(platform, profileName) {
|
||||
return await easJsonReader.readSubmitProfileAsync(platform, profileName);
|
||||
},
|
||||
});
|
||||
|
||||
const submissions: SubmissionFragment[] = [];
|
||||
for (const platform of platforms) {
|
||||
const profile = await easJsonReader.readSubmitProfileAsync(platform, flags.profile);
|
||||
for (const submissionProfile of submissionProfiles) {
|
||||
const ctx = await createSubmissionContextAsync({
|
||||
platform,
|
||||
platform: submissionProfile.platform,
|
||||
projectDir,
|
||||
projectId,
|
||||
profile,
|
||||
profile: submissionProfile.profile,
|
||||
archiveFlags: flags.archiveFlags,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
});
|
||||
|
||||
if (platforms.length > 1) {
|
||||
if (submissionProfiles.length > 1) {
|
||||
Log.newLine();
|
||||
const appPlatform = toAppPlatform(platform);
|
||||
const appPlatform = toAppPlatform(submissionProfile.platform);
|
||||
Log.log(
|
||||
`${appPlatformEmojis[appPlatform]} ${chalk.bold(
|
||||
`${appPlatformDisplayNames[appPlatform]} submission`
|
||||
|
||||
@@ -39,7 +39,8 @@ export async function viewUpdateAsync({
|
||||
`,
|
||||
{
|
||||
groupId,
|
||||
}
|
||||
},
|
||||
{ additionalTypenames: ['Update'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
+3
@@ -29,6 +29,9 @@ export const GoogleServiceAccountKeyQuery = {
|
||||
`,
|
||||
{
|
||||
accountName,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['GoogleServiceAccountKey'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
|
||||
@@ -6,8 +6,8 @@ import { AndroidAppBuildCredentialsFragment, IosDistributionType } from '../../g
|
||||
import Log from '../../log';
|
||||
import { findApplicationTarget, findTargetByName } from '../../project/ios/target';
|
||||
import zipObject from '../../utils/expodash/zipObject';
|
||||
import vcs from '../../vcs';
|
||||
import GitClient from '../../vcs/git';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import GitClient from '../../vcs/clients/git';
|
||||
import { CredentialsContext } from '../context';
|
||||
import { App, Target, TargetCredentials } from '../ios/types';
|
||||
import { readRawAsync } from './read';
|
||||
@@ -294,8 +294,9 @@ async function updateFileAsync(
|
||||
}
|
||||
|
||||
async function isFileUntrackedAsync(path: string): Promise<boolean> {
|
||||
if (vcs instanceof GitClient) {
|
||||
return await vcs.isFileUntrackedAsync(path);
|
||||
const vcsClient = getVcsClient();
|
||||
if (vcsClient instanceof GitClient) {
|
||||
return await vcsClient.isFileUntrackedAsync(path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export default class IosCredentialsProvider {
|
||||
}
|
||||
|
||||
const confirmSetup = await confirmAsync({
|
||||
message: `Would you like to setup Push Notifications for your project?`,
|
||||
message: `Would you like to set up Push Notifications for your project?`,
|
||||
});
|
||||
if (!confirmSetup) {
|
||||
return null;
|
||||
|
||||
@@ -10,7 +10,7 @@ export class CreateDistributionCertificate {
|
||||
public async runAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<AppleDistributionCertificateMutationResult> {
|
||||
const distCert = await provideOrGenerateDistributionCertificateAsync(ctx, this.account.name);
|
||||
const distCert = await provideOrGenerateDistributionCertificateAsync(ctx);
|
||||
const result = await ctx.ios.createDistributionCertificateAsync(this.account, distCert);
|
||||
Log.succeed('Created distribution certificate');
|
||||
return result;
|
||||
|
||||
@@ -12,7 +12,7 @@ export class CreatePushKey {
|
||||
throw new Error(`A new push key cannot be created in non-interactive mode.`);
|
||||
}
|
||||
|
||||
const pushKey = await provideOrGeneratePushKeyAsync(ctx, this.account.name);
|
||||
const pushKey = await provideOrGeneratePushKeyAsync(ctx);
|
||||
const result = await ctx.ios.createPushKeyAsync(this.account, pushKey);
|
||||
Log.succeed('Created push key');
|
||||
return result;
|
||||
|
||||
@@ -152,8 +152,7 @@ Please remember that Apple Distribution Certificates are not application specifi
|
||||
`;
|
||||
|
||||
export async function provideOrGenerateDistributionCertificateAsync(
|
||||
ctx: CredentialsContext,
|
||||
accountName: string
|
||||
ctx: CredentialsContext
|
||||
): Promise<DistributionCertificate> {
|
||||
if (!ctx.nonInteractive) {
|
||||
const userProvided = await promptForDistCertAsync(ctx);
|
||||
@@ -168,13 +167,11 @@ export async function provideOrGenerateDistributionCertificateAsync(
|
||||
if (!isValid) {
|
||||
Log.warn("Provided Distribution Certificate is no longer valid on Apple's server");
|
||||
}
|
||||
return isValid
|
||||
? userProvided
|
||||
: await provideOrGenerateDistributionCertificateAsync(ctx, accountName);
|
||||
return isValid ? userProvided : await provideOrGenerateDistributionCertificateAsync(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
return await generateDistributionCertificateAsync(ctx, accountName);
|
||||
return await generateDistributionCertificateAsync(ctx);
|
||||
}
|
||||
|
||||
async function promptForDistCertAsync(
|
||||
@@ -200,8 +197,7 @@ async function promptForDistCertAsync(
|
||||
}
|
||||
|
||||
async function generateDistributionCertificateAsync(
|
||||
ctx: CredentialsContext,
|
||||
accountName: string
|
||||
ctx: CredentialsContext
|
||||
): Promise<DistributionCertificate> {
|
||||
await ctx.appStore.ensureAuthenticatedAsync();
|
||||
try {
|
||||
@@ -246,7 +242,7 @@ async function generateDistributionCertificateAsync(
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return await generateDistributionCertificateAsync(ctx, accountName);
|
||||
return await generateDistributionCertificateAsync(ctx);
|
||||
}
|
||||
|
||||
function formatDistributionCertificateFromApple(
|
||||
|
||||
@@ -14,10 +14,7 @@ import { pushKeySchema } from '../credentials';
|
||||
import { isPushKeyValidAndTrackedAsync } from '../validators/validatePushKey';
|
||||
import { formatAppleTeam } from './AppleTeamUtils';
|
||||
|
||||
export async function provideOrGeneratePushKeyAsync(
|
||||
ctx: CredentialsContext,
|
||||
accountName: string
|
||||
): Promise<PushKey> {
|
||||
export async function provideOrGeneratePushKeyAsync(ctx: CredentialsContext): Promise<PushKey> {
|
||||
if (!ctx.nonInteractive) {
|
||||
const userProvided = await promptForPushKeyAsync(ctx);
|
||||
if (userProvided) {
|
||||
@@ -36,11 +33,11 @@ export async function provideOrGeneratePushKeyAsync(
|
||||
if (useUserProvided) {
|
||||
return userProvided;
|
||||
}
|
||||
return await provideOrGeneratePushKeyAsync(ctx, accountName);
|
||||
return await provideOrGeneratePushKeyAsync(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
return await generatePushKeyAsync(ctx, accountName);
|
||||
return await generatePushKeyAsync(ctx);
|
||||
}
|
||||
|
||||
async function promptForPushKeyAsync(ctx: CredentialsContext): Promise<PushKey | null> {
|
||||
@@ -63,10 +60,7 @@ async function promptForPushKeyAsync(ctx: CredentialsContext): Promise<PushKey |
|
||||
return userProvided;
|
||||
}
|
||||
|
||||
async function generatePushKeyAsync(
|
||||
ctx: CredentialsContext,
|
||||
accountName: string
|
||||
): Promise<PushKey> {
|
||||
async function generatePushKeyAsync(ctx: CredentialsContext): Promise<PushKey> {
|
||||
await ctx.appStore.ensureAuthenticatedAsync();
|
||||
try {
|
||||
return await ctx.appStore.createPushKeyAsync();
|
||||
@@ -106,7 +100,7 @@ async function generatePushKeyAsync(
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return await generatePushKeyAsync(ctx, accountName);
|
||||
return await generatePushKeyAsync(ctx);
|
||||
}
|
||||
|
||||
function formatPushKeyFromApple(pushKey: PushKeyStoreInfo): string {
|
||||
|
||||
@@ -39,7 +39,7 @@ export class SetupTargetBuildCredentials {
|
||||
try {
|
||||
return await this.setupBuildCredentialsAsync(ctx);
|
||||
} catch (error) {
|
||||
Log.error('Failed to setup credentials.');
|
||||
Log.error('Failed to set up credentials.');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ export const AppQuery = {
|
||||
}
|
||||
${print(AppFragmentNode)}
|
||||
`,
|
||||
{ fullName }
|
||||
{ fullName },
|
||||
{
|
||||
additionalTypenames: ['App'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -111,7 +111,10 @@ export const AppleDeviceQuery = {
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ accountName, appleTeamIdentifier }
|
||||
{ accountName, appleTeamIdentifier },
|
||||
{
|
||||
additionalTypenames: ['AppleDevice', 'AppleTeam'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
@@ -147,7 +150,10 @@ export const AppleDeviceQuery = {
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ accountName, identifier }
|
||||
{ accountName, identifier },
|
||||
{
|
||||
additionalTypenames: ['AppleDevice', 'AppleTeam'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
+8
-1
@@ -60,7 +60,11 @@ export const AppleDistributionCertificateQuery = {
|
||||
iosDistributionType,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['IosAppCredentials', 'IosAppBuildCredentials'],
|
||||
additionalTypenames: [
|
||||
'AppleDistributionCertificate',
|
||||
'IosAppCredentials',
|
||||
'IosAppBuildCredentials',
|
||||
],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
@@ -93,6 +97,9 @@ export const AppleDistributionCertificateQuery = {
|
||||
`,
|
||||
{
|
||||
accountName,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['AppleDistributionCertificate'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
|
||||
+5
-1
@@ -77,7 +77,11 @@ export const AppleProvisioningProfileQuery = {
|
||||
iosDistributionType,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['IosAppCredentials', 'IosAppBuildCredentials'],
|
||||
additionalTypenames: [
|
||||
'AppleProvisioningProfile',
|
||||
'IosAppCredentials',
|
||||
'IosAppBuildCredentials',
|
||||
],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
|
||||
@@ -26,6 +26,9 @@ export const ApplePushKeyQuery = {
|
||||
`,
|
||||
{
|
||||
accountName,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['ApplePushKey'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
|
||||
@@ -28,7 +28,10 @@ export const AppleTeamQuery = {
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ accountName }
|
||||
{ accountName },
|
||||
{
|
||||
additionalTypenames: ['AppleTeam'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
@@ -57,6 +60,9 @@ export const AppleTeamQuery = {
|
||||
{
|
||||
accountId,
|
||||
appleTeamIdentifier,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['AppleTeam'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
export type ActionInfo = {
|
||||
value: AndroidActionType | IosActionType;
|
||||
title: string;
|
||||
scope: Scope;
|
||||
};
|
||||
|
||||
export enum Scope {
|
||||
Project,
|
||||
Manager,
|
||||
Account,
|
||||
}
|
||||
|
||||
export enum AndroidActionType {
|
||||
ManageBuildCredentials,
|
||||
ManageFcm,
|
||||
ManageGoogleServiceAccountKey,
|
||||
ManageCredentialsJson,
|
||||
GoBackToCaller,
|
||||
GoBackToHighLevelActions,
|
||||
CreateKeystore,
|
||||
DownloadKeystore,
|
||||
RemoveKeystore,
|
||||
CreateFcm,
|
||||
RemoveFcm,
|
||||
CreateGsaKey,
|
||||
UseExistingGsaKey,
|
||||
RemoveGsaKey,
|
||||
SetupGsaKey,
|
||||
UpdateCredentialsJson,
|
||||
SetupBuildCredentialsFromCredentialsJson,
|
||||
}
|
||||
|
||||
export enum IosActionType {
|
||||
ManageCredentialsJson,
|
||||
ManageBuildCredentials,
|
||||
ManagePushKey,
|
||||
GoBackToCaller,
|
||||
GoBackToHighLevelActions,
|
||||
SetupBuildCredentials,
|
||||
SetupBuildCredentialsFromCredentialsJson,
|
||||
UpdateCredentialsJson,
|
||||
UseExistingDistributionCertificate,
|
||||
RemoveProvisioningProfile,
|
||||
CreateDistributionCertificate,
|
||||
RemoveDistributionCertificate,
|
||||
SetupPushKey,
|
||||
CreatePushKey,
|
||||
UseExistingPushKey,
|
||||
RemovePushKey,
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import { ActionInfo, AndroidActionType, Scope } from './Actions';
|
||||
|
||||
export const highLevelActions: ActionInfo[] = [
|
||||
{
|
||||
value: AndroidActionType.ManageBuildCredentials,
|
||||
title: 'Keystore: Manage everything needed to build your project',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.ManageFcm,
|
||||
title: 'Push Notifications: Manage your FCM Api Key',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.ManageGoogleServiceAccountKey,
|
||||
title: 'Google Service Account: Manage your Service Account Key',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.ManageCredentialsJson,
|
||||
title: 'credentials.json: Upload/Download credentials between EAS servers and your local json ',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.GoBackToCaller,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
export const credentialsJsonActions: ActionInfo[] = [
|
||||
{
|
||||
value: AndroidActionType.UpdateCredentialsJson,
|
||||
title: 'Download credentials from EAS to credentials.json',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.SetupBuildCredentialsFromCredentialsJson,
|
||||
title: 'Upload credentials from credentials.json to EAS',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
export const buildCredentialsActions: ActionInfo[] = [
|
||||
{
|
||||
value: AndroidActionType.CreateKeystore,
|
||||
title: 'Set up a new keystore',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.DownloadKeystore,
|
||||
title: 'Download existing keystore',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.RemoveKeystore,
|
||||
title: 'Delete your keystore',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
export const fcmActions: ActionInfo[] = [
|
||||
{
|
||||
value: AndroidActionType.CreateFcm,
|
||||
title: 'Upload an FCM Api Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.RemoveFcm,
|
||||
title: 'Delete your FCM Api Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
export const gsaKeyActions: ActionInfo[] = [
|
||||
{
|
||||
value: AndroidActionType.SetupGsaKey,
|
||||
title: 'Setup a Google Service Account Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.CreateGsaKey,
|
||||
title: 'Upload a Google Service Account Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.UseExistingGsaKey,
|
||||
title: 'Use an existing Google Service Account Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.RemoveGsaKey,
|
||||
title: 'Delete a Google Service Account Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,110 @@
|
||||
import { CredentialsContext } from '../context';
|
||||
import { ActionInfo, IosActionType, Scope } from './Actions';
|
||||
|
||||
export const highLevelActions: ActionInfo[] = [
|
||||
{
|
||||
value: IosActionType.ManageBuildCredentials,
|
||||
title: 'Build Credentials: Manage everything needed to build your project',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: IosActionType.ManagePushKey,
|
||||
title: 'Push Notifications: Manage your Apple Push Notifications Key',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: IosActionType.ManageCredentialsJson,
|
||||
title: 'credentials.json: Upload/Download credentials between EAS servers and your local json ',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: IosActionType.GoBackToCaller,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
export const credentialsJsonActions: ActionInfo[] = [
|
||||
{
|
||||
value: IosActionType.UpdateCredentialsJson,
|
||||
title: 'Download credentials from EAS to credentials.json',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.SetupBuildCredentialsFromCredentialsJson,
|
||||
title: 'Upload credentials from credentials.json to EAS',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
export function getPushKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
||||
return [
|
||||
{
|
||||
value: IosActionType.SetupPushKey,
|
||||
title: 'Setup your project to use Push Notifications',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.CreatePushKey,
|
||||
title: 'Add a new push key',
|
||||
scope: ctx.hasProjectContext ? Scope.Project : Scope.Account,
|
||||
},
|
||||
{
|
||||
value: IosActionType.UseExistingPushKey,
|
||||
title: 'Use an existing push key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.RemovePushKey,
|
||||
title: 'Remove a push key from your account',
|
||||
scope: Scope.Account,
|
||||
},
|
||||
{
|
||||
value: IosActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function getBuildCredentialsActions(ctx: CredentialsContext): ActionInfo[] {
|
||||
return [
|
||||
{
|
||||
// This command will be triggered during build to ensure all credentials are ready
|
||||
// I'm leaving it here for now to simplify testing
|
||||
value: IosActionType.SetupBuildCredentials,
|
||||
title: 'All: Set up all the required credentials to build your project',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.UseExistingDistributionCertificate,
|
||||
title: 'Distribution Certificate: Use an existing one for your project',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.CreateDistributionCertificate,
|
||||
title: `Distribution Certificate: Add a new one to your account`,
|
||||
scope: ctx.hasProjectContext ? Scope.Project : Scope.Account,
|
||||
},
|
||||
{
|
||||
value: IosActionType.RemoveDistributionCertificate,
|
||||
title: 'Distribution Certificate: Delete one from your account',
|
||||
scope: Scope.Account,
|
||||
},
|
||||
{
|
||||
value: IosActionType.RemoveProvisioningProfile,
|
||||
title: 'Provisioning Profile: Delete one from your project',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -34,6 +34,14 @@ import {
|
||||
displayEmptyAndroidCredentials,
|
||||
} from '../android/utils/printCredentials';
|
||||
import { CredentialsContext } from '../context';
|
||||
import { ActionInfo, AndroidActionType, Scope } from './Actions';
|
||||
import {
|
||||
buildCredentialsActions,
|
||||
credentialsJsonActions,
|
||||
fcmActions,
|
||||
gsaKeyActions,
|
||||
highLevelActions,
|
||||
} from './AndroidActions';
|
||||
import { Action, PressAnyKeyToContinue } from './HelperActions';
|
||||
import {
|
||||
SelectAndroidBuildCredentials,
|
||||
@@ -42,148 +50,6 @@ import {
|
||||
} from './SelectAndroidBuildCredentials';
|
||||
import { SelectBuildProfileFromEasJson } from './SelectBuildProfileFromEasJson';
|
||||
|
||||
enum ActionType {
|
||||
ManageBuildCredentials,
|
||||
ManageFcm,
|
||||
ManageGoogleServiceAccountKey,
|
||||
ManageCredentialsJson,
|
||||
GoBackToCaller,
|
||||
GoBackToHighLevelActions,
|
||||
CreateKeystore,
|
||||
DownloadKeystore,
|
||||
RemoveKeystore,
|
||||
CreateFcm,
|
||||
RemoveFcm,
|
||||
CreateGsaKey,
|
||||
UseExistingGsaKey,
|
||||
RemoveGsaKey,
|
||||
SetupGsaKey,
|
||||
UpdateCredentialsJson,
|
||||
SetupBuildCredentialsFromCredentialsJson,
|
||||
}
|
||||
|
||||
enum Scope {
|
||||
Project,
|
||||
Manager,
|
||||
}
|
||||
|
||||
type ActionInfo = { value: ActionType; title: string; scope: Scope };
|
||||
|
||||
const highLevelActions: ActionInfo[] = [
|
||||
{
|
||||
value: ActionType.ManageBuildCredentials,
|
||||
title: 'Keystore: Manage everything needed to build your project',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: ActionType.ManageFcm,
|
||||
title: 'Push Notifications: Manage your FCM Api Key',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: ActionType.ManageGoogleServiceAccountKey,
|
||||
title: 'Google Service Account: Manage your Service Account Key',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: ActionType.ManageCredentialsJson,
|
||||
title: 'Credentials.json: Upload/Download credentials between EAS servers and your local json ',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: ActionType.GoBackToCaller,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
const credentialsJsonActions: ActionInfo[] = [
|
||||
{
|
||||
value: ActionType.UpdateCredentialsJson,
|
||||
title: 'Download credentials from EAS to credentials.json',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.SetupBuildCredentialsFromCredentialsJson,
|
||||
title: 'Upload credentials from credentials.json to EAS',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
const buildCredentialsActions: ActionInfo[] = [
|
||||
{
|
||||
value: ActionType.CreateKeystore,
|
||||
title: 'Set up a new keystore',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.DownloadKeystore,
|
||||
title: 'Download existing keystore',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.RemoveKeystore,
|
||||
title: 'Delete your keystore',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
const fcmActions: ActionInfo[] = [
|
||||
{
|
||||
value: ActionType.CreateFcm,
|
||||
title: 'Upload an FCM Api Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.RemoveFcm,
|
||||
title: 'Delete your FCM Api Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
const gsaKeyActions: ActionInfo[] = [
|
||||
{
|
||||
value: ActionType.SetupGsaKey,
|
||||
title: 'Setup a Google Service Account Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.CreateGsaKey,
|
||||
title: 'Upload a Google Service Account Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.UseExistingGsaKey,
|
||||
title: 'Use an existing Google Service Account Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.RemoveGsaKey,
|
||||
title: 'Delete a Google Service Account Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
export class ManageAndroid {
|
||||
constructor(private callingAction: Action) {}
|
||||
|
||||
@@ -242,22 +108,22 @@ export class ManageAndroid {
|
||||
throw new Error('Action not supported yet');
|
||||
}
|
||||
if (actionInfo.scope === Scope.Manager) {
|
||||
if (chosenAction === ActionType.ManageBuildCredentials) {
|
||||
if (chosenAction === AndroidActionType.ManageBuildCredentials) {
|
||||
currentActions = buildCredentialsActions;
|
||||
continue;
|
||||
} else if (chosenAction === ActionType.ManageFcm) {
|
||||
} else if (chosenAction === AndroidActionType.ManageFcm) {
|
||||
currentActions = fcmActions;
|
||||
continue;
|
||||
} else if (chosenAction === ActionType.ManageGoogleServiceAccountKey) {
|
||||
} else if (chosenAction === AndroidActionType.ManageGoogleServiceAccountKey) {
|
||||
currentActions = gsaKeyActions;
|
||||
continue;
|
||||
} else if (chosenAction === ActionType.ManageCredentialsJson) {
|
||||
} else if (chosenAction === AndroidActionType.ManageCredentialsJson) {
|
||||
currentActions = credentialsJsonActions;
|
||||
continue;
|
||||
} else if (chosenAction === ActionType.GoBackToHighLevelActions) {
|
||||
} else if (chosenAction === AndroidActionType.GoBackToHighLevelActions) {
|
||||
currentActions = highLevelActions;
|
||||
continue;
|
||||
} else if (chosenAction === ActionType.GoBackToCaller) {
|
||||
} else if (chosenAction === AndroidActionType.GoBackToCaller) {
|
||||
return await this.callingAction.runAsync(ctx);
|
||||
}
|
||||
}
|
||||
@@ -302,7 +168,7 @@ export class ManageAndroid {
|
||||
|
||||
private async runProjectSpecificActionAsync(
|
||||
ctx: CredentialsContext,
|
||||
action: ActionType,
|
||||
action: AndroidActionType,
|
||||
gradleContext?: GradleBuildContext
|
||||
): Promise<void> {
|
||||
assert(
|
||||
@@ -310,7 +176,7 @@ export class ManageAndroid {
|
||||
'You must be in your project directory in order to perform this action'
|
||||
);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx, gradleContext);
|
||||
if (action === ActionType.CreateKeystore) {
|
||||
if (action === AndroidActionType.CreateKeystore) {
|
||||
const selectBuildCredentialsResult = await new SelectAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
).runAsync(ctx);
|
||||
@@ -331,47 +197,47 @@ export class ManageAndroid {
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (action === ActionType.DownloadKeystore) {
|
||||
} else if (action === AndroidActionType.DownloadKeystore) {
|
||||
const buildCredentials = await new SelectExistingAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
).runAsync(ctx);
|
||||
if (buildCredentials) {
|
||||
await new DownloadKeystore({ app: appLookupParams }).runAsync(ctx, buildCredentials);
|
||||
}
|
||||
} else if (action === ActionType.RemoveKeystore) {
|
||||
} else if (action === AndroidActionType.RemoveKeystore) {
|
||||
const buildCredentials = await new SelectExistingAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
).runAsync(ctx);
|
||||
if (buildCredentials) {
|
||||
await new RemoveKeystore(appLookupParams).runAsync(ctx, buildCredentials);
|
||||
}
|
||||
} else if (action === ActionType.CreateFcm) {
|
||||
} else if (action === AndroidActionType.CreateFcm) {
|
||||
const fcm = await new CreateFcm(appLookupParams.account).runAsync(ctx);
|
||||
await new AssignFcm(appLookupParams).runAsync(ctx, fcm);
|
||||
} else if (action === ActionType.RemoveFcm) {
|
||||
} else if (action === AndroidActionType.RemoveFcm) {
|
||||
await new RemoveFcm(appLookupParams).runAsync(ctx);
|
||||
} else if (action === ActionType.CreateGsaKey) {
|
||||
} else if (action === AndroidActionType.CreateGsaKey) {
|
||||
const gsaKey = await new CreateGoogleServiceAccountKey(appLookupParams.account).runAsync(ctx);
|
||||
await new AssignGoogleServiceAccountKey(appLookupParams).runAsync(ctx, gsaKey);
|
||||
} else if (action === ActionType.UseExistingGsaKey) {
|
||||
} else if (action === AndroidActionType.UseExistingGsaKey) {
|
||||
const gsaKey = await new UseExistingGoogleServiceAccountKey(appLookupParams.account).runAsync(
|
||||
ctx
|
||||
);
|
||||
if (gsaKey) {
|
||||
await new AssignGoogleServiceAccountKey(appLookupParams).runAsync(ctx, gsaKey);
|
||||
}
|
||||
} else if (action === ActionType.RemoveGsaKey) {
|
||||
} else if (action === AndroidActionType.RemoveGsaKey) {
|
||||
await new SelectAndRemoveGoogleServiceAccountKey(appLookupParams.account).runAsync(ctx);
|
||||
} else if (action === ActionType.SetupGsaKey) {
|
||||
} else if (action === AndroidActionType.SetupGsaKey) {
|
||||
await new SetupGoogleServiceAccountKey(appLookupParams).runAsync(ctx);
|
||||
} else if (action === ActionType.UpdateCredentialsJson) {
|
||||
} else if (action === AndroidActionType.UpdateCredentialsJson) {
|
||||
const buildCredentials = await new SelectExistingAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
).runAsync(ctx);
|
||||
if (buildCredentials) {
|
||||
await new UpdateCredentialsJson().runAsync(ctx, buildCredentials);
|
||||
}
|
||||
} else if (action === ActionType.SetupBuildCredentialsFromCredentialsJson) {
|
||||
} else if (action === AndroidActionType.SetupBuildCredentialsFromCredentialsJson) {
|
||||
await new SetupBuildCredentialsFromCredentialsJson(appLookupParams).runAsync(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,145 +38,17 @@ import { AppLookupParams } from '../ios/api/GraphqlClient';
|
||||
import { getManagedEntitlementsJsonAsync } from '../ios/appstore/entitlements';
|
||||
import { App, IosAppCredentialsMap, Target } from '../ios/types';
|
||||
import { displayIosCredentials } from '../ios/utils/printCredentials';
|
||||
import { ActionInfo, IosActionType, Scope } from './Actions';
|
||||
import { Action, PressAnyKeyToContinue } from './HelperActions';
|
||||
import {
|
||||
credentialsJsonActions,
|
||||
getBuildCredentialsActions,
|
||||
getPushKeyActions,
|
||||
highLevelActions,
|
||||
} from './IosActions';
|
||||
import { SelectBuildProfileFromEasJson } from './SelectBuildProfileFromEasJson';
|
||||
import { SelectIosDistributionTypeGraphqlFromBuildProfile } from './SelectIosDistributionTypeGraphqlFromBuildProfile';
|
||||
|
||||
enum ActionType {
|
||||
ManageCredentialsJson,
|
||||
ManageBuildCredentials,
|
||||
ManagePushKey,
|
||||
GoBackToCaller,
|
||||
GoBackToHighLevelActions,
|
||||
SetupBuildCredentials,
|
||||
SetupBuildCredentialsFromCredentialsJson,
|
||||
UpdateCredentialsJson,
|
||||
UseExistingDistributionCertificate,
|
||||
RemoveProvisioningProfile,
|
||||
CreateDistributionCertificate,
|
||||
RemoveDistributionCertificate,
|
||||
SetupPushKey,
|
||||
CreatePushKey,
|
||||
UseExistingPushKey,
|
||||
RemovePushKey,
|
||||
}
|
||||
|
||||
enum Scope {
|
||||
Project,
|
||||
Account,
|
||||
Manager,
|
||||
}
|
||||
|
||||
type ActionInfo = { value: ActionType; title: string; scope: Scope };
|
||||
|
||||
const highLevelActions: ActionInfo[] = [
|
||||
{
|
||||
value: ActionType.ManageBuildCredentials,
|
||||
title: 'Build Credentials: Manage everything needed to build your project',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: ActionType.ManagePushKey,
|
||||
title: 'Push Notifications: Manage your Apple Push Notifications Key',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: ActionType.ManageCredentialsJson,
|
||||
title: 'Credentials.json: Upload/Download credentials between EAS servers and your local json ',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
value: ActionType.GoBackToCaller,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
const credentialsJsonActions: ActionInfo[] = [
|
||||
{
|
||||
value: ActionType.UpdateCredentialsJson,
|
||||
title: 'Download credentials from EAS to credentials.json',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.SetupBuildCredentialsFromCredentialsJson,
|
||||
title: 'Upload credentials from credentials.json to EAS',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
|
||||
function getPushKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
||||
return [
|
||||
{
|
||||
value: ActionType.SetupPushKey,
|
||||
title: 'Setup your project to use Push Notifications',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.CreatePushKey,
|
||||
title: 'Add a new push key',
|
||||
scope: ctx.hasProjectContext ? Scope.Project : Scope.Account,
|
||||
},
|
||||
{
|
||||
value: ActionType.UseExistingPushKey,
|
||||
title: 'Use an existing push key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.RemovePushKey,
|
||||
title: 'Remove a push key from your account',
|
||||
scope: Scope.Account,
|
||||
},
|
||||
{
|
||||
value: ActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getBuildCredentialsActions(ctx: CredentialsContext): ActionInfo[] {
|
||||
return [
|
||||
{
|
||||
// This command will be triggered during build to ensure all credentials are ready
|
||||
// I'm leaving it here for now to simplify testing
|
||||
value: ActionType.SetupBuildCredentials,
|
||||
title: 'All: Set up all the required credentials to build your project',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.UseExistingDistributionCertificate,
|
||||
title: 'Distribution Certificate: Use an existing one for your project',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.CreateDistributionCertificate,
|
||||
title: `Distribution Certificate: Add a new one to your account`,
|
||||
scope: ctx.hasProjectContext ? Scope.Project : Scope.Account,
|
||||
},
|
||||
{
|
||||
value: ActionType.RemoveDistributionCertificate,
|
||||
title: 'Distribution Certificate: Delete one from your account',
|
||||
scope: Scope.Account,
|
||||
},
|
||||
{
|
||||
value: ActionType.RemoveProvisioningProfile,
|
||||
title: 'Provisioning Profile: Delete one from your project',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: ActionType.GoBackToHighLevelActions,
|
||||
title: 'Go back',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export class ManageIos {
|
||||
constructor(private callingAction: Action) {}
|
||||
|
||||
@@ -226,19 +98,19 @@ export class ManageIos {
|
||||
throw new Error('Action not supported yet');
|
||||
}
|
||||
if (actionInfo.scope === Scope.Manager) {
|
||||
if (chosenAction === ActionType.ManageBuildCredentials) {
|
||||
if (chosenAction === IosActionType.ManageBuildCredentials) {
|
||||
currentActions = buildCredentialsActions;
|
||||
continue;
|
||||
} else if (chosenAction === ActionType.ManageCredentialsJson) {
|
||||
} else if (chosenAction === IosActionType.ManageCredentialsJson) {
|
||||
currentActions = credentialsJsonActions;
|
||||
continue;
|
||||
} else if (chosenAction === ActionType.ManagePushKey) {
|
||||
} else if (chosenAction === IosActionType.ManagePushKey) {
|
||||
currentActions = pushKeyActions;
|
||||
continue;
|
||||
} else if (chosenAction === ActionType.GoBackToHighLevelActions) {
|
||||
} else if (chosenAction === IosActionType.GoBackToHighLevelActions) {
|
||||
currentActions = highLevelActions;
|
||||
continue;
|
||||
} else if (chosenAction === ActionType.GoBackToCaller) {
|
||||
} else if (chosenAction === IosActionType.GoBackToCaller) {
|
||||
return await this.callingAction.runAsync(ctx);
|
||||
}
|
||||
} else if (actionInfo.scope === Scope.Project) {
|
||||
@@ -315,15 +187,15 @@ export class ManageIos {
|
||||
private async runAccountSpecificActionAsync(
|
||||
ctx: CredentialsContext,
|
||||
account: Account,
|
||||
action: ActionType
|
||||
action: IosActionType
|
||||
): Promise<void> {
|
||||
if (action === ActionType.RemoveDistributionCertificate) {
|
||||
if (action === IosActionType.RemoveDistributionCertificate) {
|
||||
await new SelectAndRemoveDistributionCertificate(account).runAsync(ctx);
|
||||
} else if (action === ActionType.CreateDistributionCertificate) {
|
||||
} else if (action === IosActionType.CreateDistributionCertificate) {
|
||||
await new CreateDistributionCertificate(account).runAsync(ctx);
|
||||
} else if (action === ActionType.CreatePushKey) {
|
||||
} else if (action === IosActionType.CreatePushKey) {
|
||||
await new CreatePushKey(account).runAsync(ctx);
|
||||
} else if (action === ActionType.RemovePushKey) {
|
||||
} else if (action === IosActionType.RemovePushKey) {
|
||||
await new SelectAndRemovePushKey(account).runAsync(ctx);
|
||||
}
|
||||
}
|
||||
@@ -333,9 +205,9 @@ export class ManageIos {
|
||||
app: App,
|
||||
targets: Target[],
|
||||
buildProfile: IosBuildProfile,
|
||||
action: ActionType
|
||||
action: IosActionType
|
||||
): Promise<void> {
|
||||
if (action === ActionType.SetupBuildCredentials) {
|
||||
if (action === IosActionType.SetupBuildCredentials) {
|
||||
await new SetupBuildCredentials({
|
||||
app,
|
||||
targets,
|
||||
@@ -352,12 +224,12 @@ export class ManageIos {
|
||||
buildProfile
|
||||
).runAsync(ctx);
|
||||
|
||||
if (action === ActionType.SetupBuildCredentialsFromCredentialsJson) {
|
||||
if (action === IosActionType.SetupBuildCredentialsFromCredentialsJson) {
|
||||
await new SetupBuildCredentialsFromCredentialsJson(app, targets, distributionType).runAsync(
|
||||
ctx
|
||||
);
|
||||
return;
|
||||
} else if (action === ActionType.UpdateCredentialsJson) {
|
||||
} else if (action === IosActionType.UpdateCredentialsJson) {
|
||||
await new UpdateCredentialsJson(app, targets, distributionType).runAsync(ctx);
|
||||
return;
|
||||
}
|
||||
@@ -365,7 +237,7 @@ export class ManageIos {
|
||||
const target = await this.selectTargetAsync(targets);
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, target);
|
||||
switch (action) {
|
||||
case ActionType.UseExistingDistributionCertificate: {
|
||||
case IosActionType.UseExistingDistributionCertificate: {
|
||||
const distCert = await selectValidDistributionCertificateAsync(ctx, appLookupParams);
|
||||
if (!distCert) {
|
||||
return;
|
||||
@@ -378,7 +250,7 @@ export class ManageIos {
|
||||
);
|
||||
return;
|
||||
}
|
||||
case ActionType.CreateDistributionCertificate: {
|
||||
case IosActionType.CreateDistributionCertificate: {
|
||||
const distCert = await new CreateDistributionCertificate(appLookupParams.account).runAsync(
|
||||
ctx
|
||||
);
|
||||
@@ -395,7 +267,7 @@ export class ManageIos {
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ActionType.RemoveProvisioningProfile: {
|
||||
case IosActionType.RemoveProvisioningProfile: {
|
||||
const iosAppCredentials = await ctx.ios.getIosAppCredentialsWithCommonFieldsAsync(
|
||||
appLookupParams
|
||||
);
|
||||
@@ -418,7 +290,7 @@ export class ManageIos {
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ActionType.SetupPushKey: {
|
||||
case IosActionType.SetupPushKey: {
|
||||
const setupPushKeyAction = await new SetupPushKey(appLookupParams);
|
||||
const isPushKeySetup = await setupPushKeyAction.isPushKeySetupAsync(ctx);
|
||||
if (isPushKeySetup) {
|
||||
@@ -430,7 +302,7 @@ export class ManageIos {
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ActionType.CreatePushKey: {
|
||||
case IosActionType.CreatePushKey: {
|
||||
const pushKey = await new CreatePushKey(appLookupParams.account).runAsync(ctx);
|
||||
const confirm = await confirmAsync({
|
||||
message: `Do you want ${appLookupParams.projectName} to use the new Push Key?`,
|
||||
@@ -440,7 +312,7 @@ export class ManageIos {
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ActionType.UseExistingPushKey: {
|
||||
case IosActionType.UseExistingPushKey: {
|
||||
const selectedPushKey = await selectPushKeyAsync(ctx, appLookupParams.account);
|
||||
if (selectedPushKey) {
|
||||
await new AssignPushKey(appLookupParams).runAsync(ctx, selectedPushKey);
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import {
|
||||
Client,
|
||||
CombinedError as GraphqlError,
|
||||
OperationContext,
|
||||
OperationResult,
|
||||
PromisifiedSource,
|
||||
TypedDocumentNode,
|
||||
cacheExchange,
|
||||
createClient as createUrqlClient,
|
||||
dedupExchange,
|
||||
fetchExchange,
|
||||
} from '@urql/core';
|
||||
import { retryExchange } from '@urql/exchange-retry';
|
||||
import { DocumentNode } from 'graphql';
|
||||
// node-fetch is used here because @urql/core uses the fetch API under the hood
|
||||
// don't use node-fetch elsewhere
|
||||
// eslint-disable-next-line
|
||||
@@ -57,7 +62,17 @@ export const graphqlClient = createUrqlClient({
|
||||
}
|
||||
return {};
|
||||
},
|
||||
});
|
||||
}) as StricterClient;
|
||||
|
||||
/* Please specify additionalTypenames in your Graphql queries */
|
||||
export interface StricterClient extends Client {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
query<Data = any, Variables extends object = {}>(
|
||||
query: DocumentNode | TypedDocumentNode<Data, Variables> | string,
|
||||
variables: Variables | undefined,
|
||||
context: Partial<OperationContext> & { additionalTypenames: string[] }
|
||||
): PromisifiedSource<OperationResult<Data, Variables>>;
|
||||
}
|
||||
|
||||
export async function withErrorHandlingAsync<T>(promise: Promise<OperationResult<T>>): Promise<T> {
|
||||
const { data, error } = await promise;
|
||||
|
||||
+125
-137
@@ -213,6 +213,7 @@ export type Account = {
|
||||
appleProvisioningProfiles: Array<AppleProvisioningProfile>;
|
||||
appleDevices: Array<AppleDevice>;
|
||||
appleAppSpecificPasswords: Array<AppleAppSpecificPassword>;
|
||||
appStoreConnectApiKeys: Array<AppStoreConnectApiKey>;
|
||||
/** Android credentials for account */
|
||||
googleServiceAccountKeys: Array<GoogleServiceAccountKey>;
|
||||
/** Environment secrets for an account */
|
||||
@@ -705,6 +706,7 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
|
||||
iosEnterpriseProvisioning?: Maybe<BuildIosEnterpriseProvisioning>;
|
||||
buildProfile?: Maybe<Scalars['String']>;
|
||||
gitCommitHash?: Maybe<Scalars['String']>;
|
||||
isGitWorkingTreeDirty?: Maybe<Scalars['Boolean']>;
|
||||
error?: Maybe<BuildError>;
|
||||
submissions: Array<Submission>;
|
||||
};
|
||||
@@ -965,7 +967,7 @@ export type IosSubmissionConfig = {
|
||||
__typename?: 'IosSubmissionConfig';
|
||||
ascAppIdentifier: Scalars['String'];
|
||||
appleIdUsername: Scalars['String'];
|
||||
appleAppSpecificPasswordId?: Maybe<Scalars['String']>;
|
||||
ascApiKeyId?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type SubmissionError = {
|
||||
@@ -1112,6 +1114,7 @@ export type IosAppCredentials = {
|
||||
appleAppIdentifier: AppleAppIdentifier;
|
||||
iosAppBuildCredentialsList: Array<IosAppBuildCredentials>;
|
||||
pushKey?: Maybe<ApplePushKey>;
|
||||
appStoreConnectApiKeyForSubmissions?: Maybe<AppStoreConnectApiKey>;
|
||||
appSpecificPassword?: Maybe<AppleAppSpecificPassword>;
|
||||
/** @deprecated use iosAppBuildCredentialsList instead */
|
||||
iosAppBuildCredentialsArray: Array<IosAppBuildCredentials>;
|
||||
@@ -1244,6 +1247,38 @@ export type IosAppBuildCredentialsFilter = {
|
||||
iosDistributionType?: Maybe<IosDistributionType>;
|
||||
};
|
||||
|
||||
export type AppStoreConnectApiKey = {
|
||||
__typename?: 'AppStoreConnectApiKey';
|
||||
id: Scalars['ID'];
|
||||
account: Account;
|
||||
appleTeam?: Maybe<AppleTeam>;
|
||||
issuerIdentifier: Scalars['String'];
|
||||
keyIdentifier: Scalars['String'];
|
||||
name?: Maybe<Scalars['String']>;
|
||||
roles?: Maybe<Array<AppStoreConnectUserRole>>;
|
||||
createdAt: Scalars['DateTime'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export enum AppStoreConnectUserRole {
|
||||
Admin = 'ADMIN',
|
||||
Finance = 'FINANCE',
|
||||
Technical = 'TECHNICAL',
|
||||
AccountHolder = 'ACCOUNT_HOLDER',
|
||||
ReadOnly = 'READ_ONLY',
|
||||
Sales = 'SALES',
|
||||
Marketing = 'MARKETING',
|
||||
AppManager = 'APP_MANAGER',
|
||||
Developer = 'DEVELOPER',
|
||||
AccessToReports = 'ACCESS_TO_REPORTS',
|
||||
CustomerSupport = 'CUSTOMER_SUPPORT',
|
||||
CreateApps = 'CREATE_APPS',
|
||||
CloudManagedDeveloperId = 'CLOUD_MANAGED_DEVELOPER_ID',
|
||||
CloudManagedAppDistribution = 'CLOUD_MANAGED_APP_DISTRIBUTION',
|
||||
ImageManager = 'IMAGE_MANAGER',
|
||||
Unknown = 'UNKNOWN'
|
||||
}
|
||||
|
||||
export type AppleAppSpecificPassword = {
|
||||
__typename?: 'AppleAppSpecificPassword';
|
||||
id: Scalars['ID'];
|
||||
@@ -1426,6 +1461,7 @@ export type SubscriptionDetails = {
|
||||
planId?: Maybe<Scalars['String']>;
|
||||
addons: Array<AddonDetails>;
|
||||
name?: Maybe<Scalars['String']>;
|
||||
price: Scalars['Int'];
|
||||
nextInvoice?: Maybe<Scalars['DateTime']>;
|
||||
cancelledAt?: Maybe<Scalars['DateTime']>;
|
||||
willCancel?: Maybe<Scalars['Boolean']>;
|
||||
@@ -1855,6 +1891,8 @@ export type RootMutation = {
|
||||
applePushKey: ApplePushKeyMutation;
|
||||
/** Mutations that modify an Apple Team */
|
||||
appleTeam: AppleTeamMutation;
|
||||
/** Mutations that modify an App Store Connect Api Key */
|
||||
appStoreConnectApiKey: AppStoreConnectApiKeyMutation;
|
||||
/** Mutations that modify an App */
|
||||
app?: Maybe<AppMutation>;
|
||||
asset: AssetMutation;
|
||||
@@ -1960,6 +1998,8 @@ export type AccountMutation = {
|
||||
subscribeToProduct?: Maybe<Account>;
|
||||
/** Cancels the active subscription */
|
||||
cancelSubscription?: Maybe<Account>;
|
||||
/** Requests a refund for the specified charge. Returns true if auto-refund was possible, otherwise requests a manual refund from support and returns false. */
|
||||
requestRefund?: Maybe<Scalars['Boolean']>;
|
||||
/**
|
||||
* Makes a one time purchase
|
||||
* @deprecated Build packs are no longer supported
|
||||
@@ -2005,6 +2045,12 @@ export type AccountMutationCancelSubscriptionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type AccountMutationRequestRefundArgs = {
|
||||
accountID: Scalars['ID'];
|
||||
chargeIdentifier: Scalars['ID'];
|
||||
};
|
||||
|
||||
|
||||
export type AccountMutationBuyProductArgs = {
|
||||
accountName: Scalars['ID'];
|
||||
productId: Scalars['ID'];
|
||||
@@ -2420,6 +2466,39 @@ export type AppleTeamInput = {
|
||||
appleTeamName?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type AppStoreConnectApiKeyMutation = {
|
||||
__typename?: 'AppStoreConnectApiKeyMutation';
|
||||
/** Create an App Store Connect Api Key for an Apple Team */
|
||||
createAppStoreConnectApiKey: AppStoreConnectApiKey;
|
||||
/** Delete an App Store Connect Api Key */
|
||||
deleteAppStoreConnectApiKey: DeleteAppStoreConnectApiKeyResult;
|
||||
};
|
||||
|
||||
|
||||
export type AppStoreConnectApiKeyMutationCreateAppStoreConnectApiKeyArgs = {
|
||||
appStoreConnectApiKeyInput: AppStoreConnectApiKeyInput;
|
||||
accountId: Scalars['ID'];
|
||||
};
|
||||
|
||||
|
||||
export type AppStoreConnectApiKeyMutationDeleteAppStoreConnectApiKeyArgs = {
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type AppStoreConnectApiKeyInput = {
|
||||
issuerIdentifier: Scalars['String'];
|
||||
keyIdentifier: Scalars['String'];
|
||||
keyP8: Scalars['String'];
|
||||
name?: Maybe<Scalars['String']>;
|
||||
roles?: Maybe<Array<AppStoreConnectUserRole>>;
|
||||
appleTeamId?: Maybe<Scalars['ID']>;
|
||||
};
|
||||
|
||||
export type DeleteAppStoreConnectApiKeyResult = {
|
||||
__typename?: 'deleteAppStoreConnectApiKeyResult';
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type AppMutation = {
|
||||
__typename?: 'AppMutation';
|
||||
/** Create an unpublished app */
|
||||
@@ -2490,22 +2569,8 @@ export type BuildMutation = {
|
||||
* @deprecated Use cancelBuild instead
|
||||
*/
|
||||
cancel: Build;
|
||||
/**
|
||||
* Create an Android generic build
|
||||
* @deprecated Use createAndroidBuild instead
|
||||
*/
|
||||
createAndroidGenericBuild: CreateBuildResult;
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
@@ -2521,20 +2586,6 @@ export type BuildMutationDeleteBuildArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateAndroidGenericBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: AndroidGenericJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateAndroidManagedBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: AndroidManagedJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateAndroidBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: AndroidJobInput;
|
||||
@@ -2542,38 +2593,33 @@ export type BuildMutationCreateAndroidBuildArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateIosGenericBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: IosGenericJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateIosManagedBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: IosManagedJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateIosBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: IosJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
export type AndroidGenericJobInput = {
|
||||
export type AndroidJobInput = {
|
||||
type: BuildWorkflow;
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
updates?: Maybe<BuildUpdatesInput>;
|
||||
developmentClient?: Maybe<Scalars['Boolean']>;
|
||||
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 BuildWorkflow {
|
||||
Generic = 'GENERIC',
|
||||
Managed = 'MANAGED'
|
||||
}
|
||||
|
||||
export type ProjectArchiveSourceInput = {
|
||||
type: ProjectArchiveSourceType;
|
||||
bucketKey?: Maybe<Scalars['String']>;
|
||||
@@ -2622,6 +2668,13 @@ export type BuildCacheInput = {
|
||||
customPaths?: Maybe<Array<Maybe<Scalars['String']>>>;
|
||||
};
|
||||
|
||||
export enum AndroidBuildType {
|
||||
Apk = 'APK',
|
||||
AppBundle = 'APP_BUNDLE',
|
||||
/** @deprecated Use developmentClient option instead. */
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
}
|
||||
|
||||
export type BuildMetadataInput = {
|
||||
trackingContext?: Maybe<Scalars['JSONObject']>;
|
||||
appVersion?: Maybe<Scalars['String']>;
|
||||
@@ -2639,14 +2692,10 @@ export type BuildMetadataInput = {
|
||||
appIdentifier?: Maybe<Scalars['String']>;
|
||||
buildProfile?: Maybe<Scalars['String']>;
|
||||
gitCommitHash?: Maybe<Scalars['String']>;
|
||||
isGitWorkingTreeDirty?: Maybe<Scalars['Boolean']>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum BuildWorkflow {
|
||||
Generic = 'GENERIC',
|
||||
Managed = 'MANAGED'
|
||||
}
|
||||
|
||||
export enum BuildCredentialsSource {
|
||||
Local = 'LOCAL',
|
||||
Remote = 'REMOTE'
|
||||
@@ -2669,58 +2718,25 @@ export enum EasBuildDeprecationInfoType {
|
||||
Internal = 'INTERNAL'
|
||||
}
|
||||
|
||||
export type AndroidManagedJobInput = {
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
updates?: Maybe<BuildUpdatesInput>;
|
||||
secrets?: Maybe<AndroidJobSecretsInput>;
|
||||
builderEnvironment?: Maybe<AndroidBuilderEnvironmentInput>;
|
||||
cache?: Maybe<BuildCacheInput>;
|
||||
buildType?: Maybe<AndroidManagedBuildType>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum AndroidManagedBuildType {
|
||||
Apk = 'APK',
|
||||
AppBundle = 'APP_BUNDLE',
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
}
|
||||
|
||||
export type AndroidJobInput = {
|
||||
export type IosJobInput = {
|
||||
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'];
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
updates?: Maybe<BuildUpdatesInput>;
|
||||
/** @deprecated */
|
||||
distribution?: Maybe<DistributionType>;
|
||||
simulator?: Maybe<Scalars['Boolean']>;
|
||||
developmentClient?: Maybe<Scalars['Boolean']>;
|
||||
secrets?: Maybe<IosJobSecretsInput>;
|
||||
builderEnvironment?: Maybe<IosBuilderEnvironmentInput>;
|
||||
cache?: Maybe<BuildCacheInput>;
|
||||
scheme: Scalars['String'];
|
||||
schemeBuildConfiguration?: Maybe<IosSchemeBuildConfiguration>;
|
||||
scheme?: Maybe<Scalars['String']>;
|
||||
buildConfiguration?: Maybe<Scalars['String']>;
|
||||
artifactPath?: Maybe<Scalars['String']>;
|
||||
/** @deprecated */
|
||||
buildType?: Maybe<IosBuildType>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type IosJobSecretsInput = {
|
||||
@@ -2750,46 +2766,7 @@ export type IosBuilderEnvironmentInput = {
|
||||
env?: Maybe<Scalars['JSONObject']>;
|
||||
};
|
||||
|
||||
export enum IosSchemeBuildConfiguration {
|
||||
Release = 'RELEASE',
|
||||
Debug = 'DEBUG'
|
||||
}
|
||||
|
||||
export type IosManagedJobInput = {
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
updates?: Maybe<BuildUpdatesInput>;
|
||||
distribution?: Maybe<DistributionType>;
|
||||
secrets?: Maybe<IosJobSecretsInput>;
|
||||
builderEnvironment?: Maybe<IosBuilderEnvironmentInput>;
|
||||
cache?: Maybe<BuildCacheInput>;
|
||||
buildType?: Maybe<IosManagedBuildType>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum IosManagedBuildType {
|
||||
Release = 'RELEASE',
|
||||
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']>;
|
||||
};
|
||||
|
||||
/** @deprecated Use developmentClient option instead. */
|
||||
export enum IosBuildType {
|
||||
Release = 'RELEASE',
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
@@ -2835,8 +2812,8 @@ export type IosAppCredentialsMutation = {
|
||||
createIosAppCredentials: IosAppCredentials;
|
||||
/** Set the push key to be used in an iOS app */
|
||||
setPushKey: IosAppCredentials;
|
||||
/** Set the app-specific password to be used for an iOS app */
|
||||
setAppSpecificPassword: IosAppCredentials;
|
||||
/** Set the App Store Connect Api Key to be used for submitting an iOS app */
|
||||
setAppStoreConnectApiKeyForSubmissions: IosAppCredentials;
|
||||
};
|
||||
|
||||
|
||||
@@ -2853,15 +2830,15 @@ export type IosAppCredentialsMutationSetPushKeyArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type IosAppCredentialsMutationSetAppSpecificPasswordArgs = {
|
||||
export type IosAppCredentialsMutationSetAppStoreConnectApiKeyForSubmissionsArgs = {
|
||||
id: Scalars['ID'];
|
||||
appSpecificPasswordId: Scalars['ID'];
|
||||
ascApiKeyId: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type IosAppCredentialsInput = {
|
||||
appleTeamId?: Maybe<Scalars['ID']>;
|
||||
pushKeyId?: Maybe<Scalars['ID']>;
|
||||
appSpecificPasswordId?: Maybe<Scalars['ID']>;
|
||||
appStoreConnectApiKeyForSubmissionsId?: Maybe<Scalars['ID']>;
|
||||
};
|
||||
|
||||
export type RobotMutation = {
|
||||
@@ -2973,11 +2950,11 @@ export type CreateIosSubmissionInput = {
|
||||
};
|
||||
|
||||
export type IosSubmissionConfigInput = {
|
||||
appleAppSpecificPasswordId?: Maybe<Scalars['String']>;
|
||||
appleAppSpecificPassword?: Maybe<Scalars['String']>;
|
||||
ascApiKey?: Maybe<AscApiKeyInput>;
|
||||
ascApiKeyId?: Maybe<Scalars['String']>;
|
||||
archiveUrl?: Maybe<Scalars['String']>;
|
||||
appleIdUsername: Scalars['String'];
|
||||
appleIdUsername?: Maybe<Scalars['String']>;
|
||||
ascAppIdentifier: Scalars['String'];
|
||||
};
|
||||
|
||||
@@ -3538,6 +3515,17 @@ export enum StandardOffer {
|
||||
Support = 'SUPPORT'
|
||||
}
|
||||
|
||||
export enum IosSchemeBuildConfiguration {
|
||||
Release = 'RELEASE',
|
||||
Debug = 'DEBUG'
|
||||
}
|
||||
|
||||
/** @deprecated Use developmentClient option instead. */
|
||||
export enum IosManagedBuildType {
|
||||
Release = 'RELEASE',
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
}
|
||||
|
||||
export enum CacheControlScope {
|
||||
Public = 'PUBLIC',
|
||||
Private = 'PRIVATE'
|
||||
|
||||
@@ -54,6 +54,7 @@ export const BuildQuery = {
|
||||
{ buildId },
|
||||
{
|
||||
requestPolicy: useCache ? 'cache-first' : 'network-only',
|
||||
additionalTypenames: ['Build'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
@@ -89,7 +90,10 @@ export const BuildQuery = {
|
||||
}
|
||||
${print(BuildFragmentNode)}
|
||||
`,
|
||||
{ appId, offset, limit, filter }
|
||||
{ appId, offset, limit, filter },
|
||||
{
|
||||
additionalTypenames: ['Build'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -37,7 +37,10 @@ export const EnvironmentSecretsQuery = {
|
||||
}
|
||||
${print(EnvironmentSecretFragmentNode)}
|
||||
`,
|
||||
{ accountName }
|
||||
{ accountName },
|
||||
{
|
||||
additionalTypenames: ['EnvironmentSecret'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
@@ -62,7 +65,8 @@ export const EnvironmentSecretsQuery = {
|
||||
}
|
||||
${print(EnvironmentSecretFragmentNode)}
|
||||
`,
|
||||
{ appId }
|
||||
{ appId },
|
||||
{ additionalTypenames: ['EnvironmentSecret'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -19,7 +19,10 @@ export const ProjectQuery = {
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ username, slug }
|
||||
{ username, slug },
|
||||
{
|
||||
additionalTypenames: ['App', 'Snack'] /* typenames that use the Project type*/,
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -21,7 +21,10 @@ export const PublishQuery = {
|
||||
{
|
||||
storageKeys,
|
||||
},
|
||||
{ requestPolicy: 'network-only' } // Since we reptitively query this to monitor the asset upload, we need to ensure it is not cached.
|
||||
{
|
||||
requestPolicy: 'network-only',
|
||||
additionalTypenames: ['AssetMetadataResult'],
|
||||
} // Since we reptitively query this to monitor the asset upload, we need to ensure it is not cached.
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -40,7 +40,10 @@ export const SubmissionQuery = {
|
||||
${print(SubmissionFragmentNode)}
|
||||
`,
|
||||
{ submissionId },
|
||||
{ requestPolicy: useCache ? 'cache-first' : 'network-only' }
|
||||
{
|
||||
requestPolicy: useCache ? 'cache-first' : 'network-only',
|
||||
additionalTypenames: ['Submission'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
@@ -78,7 +81,8 @@ export const SubmissionQuery = {
|
||||
}
|
||||
${print(SubmissionFragmentNode)}
|
||||
`,
|
||||
{ appId, offset, limit, status, platform }
|
||||
{ appId, offset, limit, status, platform },
|
||||
{ additionalTypenames: ['Submission'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -26,7 +26,11 @@ export const UserQuery = {
|
||||
isExpoAdmin
|
||||
}
|
||||
}
|
||||
`
|
||||
`,
|
||||
/* variables */ undefined,
|
||||
{
|
||||
additionalTypenames: ['User'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -31,7 +31,10 @@ export const WebhookQuery = {
|
||||
}
|
||||
${print(WebhookFragmentNode)}
|
||||
`,
|
||||
{ appId, webhookFilter }
|
||||
{ appId, webhookFilter },
|
||||
{
|
||||
additionalTypenames: ['Webhook'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
@@ -52,7 +55,10 @@ export const WebhookQuery = {
|
||||
}
|
||||
${print(WebhookFragmentNode)}
|
||||
`,
|
||||
{ webhookId }
|
||||
{ webhookId },
|
||||
{
|
||||
additionalTypenames: ['Webhook'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -24,11 +24,11 @@ export default class Log {
|
||||
}
|
||||
|
||||
public static error(...args: any[]): void {
|
||||
Log.consoleError(...Log.withTextColor(args, chalk.red));
|
||||
Log.consoleLog(...Log.withTextColor(args, chalk.red));
|
||||
}
|
||||
|
||||
public static warn(...args: any[]): void {
|
||||
Log.consoleWarn(...Log.withTextColor(args, chalk.yellow));
|
||||
Log.consoleLog(...Log.withTextColor(args, chalk.yellow));
|
||||
}
|
||||
|
||||
public static debug(...args: any[]): void {
|
||||
@@ -59,18 +59,6 @@ export default class Log {
|
||||
console.log(...args);
|
||||
}
|
||||
|
||||
private static consoleWarn(...args: any[]): void {
|
||||
Log.updateIsLastLineNewLine(args);
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(...args);
|
||||
}
|
||||
|
||||
private static consoleError(...args: any[]): void {
|
||||
Log.updateIsLastLineNewLine(args);
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(...args);
|
||||
}
|
||||
|
||||
private static withTextColor(args: any[], chalkColor: Color): string[] {
|
||||
return args.map(arg => chalkColor(arg));
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ describe(resolveWorkflowAsync, () => {
|
||||
|
||||
const projectDir = '/app';
|
||||
|
||||
test('generic workflow for both platforms', async () => {
|
||||
test('bare workflow for both platforms', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/helloworld.xcodeproj/project.pbxproj': 'fake',
|
||||
@@ -27,7 +27,7 @@ describe(resolveWorkflowAsync, () => {
|
||||
await expect(resolveWorkflowAsync(projectDir, Platform.IOS)).resolves.toBe(Workflow.GENERIC);
|
||||
});
|
||||
|
||||
test('generic workflow for single platform', async () => {
|
||||
test('bare workflow for single platform', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/helloworld.xcodeproj/project.pbxproj': 'fake',
|
||||
|
||||
@@ -25,7 +25,7 @@ beforeEach(async () => {
|
||||
});
|
||||
|
||||
describe(getApplicationIdAsync, () => {
|
||||
describe('generic projects', () => {
|
||||
describe('bare projects', () => {
|
||||
it('reads applicationId from build.gradle', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ beforeEach(async () => {
|
||||
});
|
||||
|
||||
describe(resolveGradleBuildContextAsync, () => {
|
||||
describe('generic projects', () => {
|
||||
describe('bare projects', () => {
|
||||
it('resolves to default config (no flavor)', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { readAppJson } from '../../build/utils/appJson';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { getProjectConfigDescription, getUsername } from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
@@ -39,7 +40,7 @@ export async function getApplicationIdAsync(
|
||||
): Promise<string> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
warnIfAndroidPackageDefinedInAppConfigForGenericProject(projectDir, exp);
|
||||
warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject(projectDir, exp);
|
||||
|
||||
const errorMessage = 'Could not read application id from Android project.';
|
||||
if (gradleContext) {
|
||||
@@ -112,7 +113,7 @@ async function configureApplicationIdAsync(projectDir: string, exp: ExpoConfig):
|
||||
validate: value => (isApplicationIdValid(value) ? true : INVALID_APPLICATION_ID_MESSAGE),
|
||||
});
|
||||
|
||||
const rawStaticConfig = await fs.readJSON(paths.staticConfigPath);
|
||||
const rawStaticConfig = readAppJson(paths.staticConfigPath);
|
||||
rawStaticConfig.expo = {
|
||||
...rawStaticConfig.expo,
|
||||
android: { ...rawStaticConfig.expo?.android, package: packageName },
|
||||
@@ -129,7 +130,7 @@ function isApplicationIdValid(applicationId: string): boolean {
|
||||
}
|
||||
|
||||
let warnPrinted = false;
|
||||
export function warnIfAndroidPackageDefinedInAppConfigForGenericProject(
|
||||
export function warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
): void {
|
||||
@@ -137,7 +138,7 @@ export function warnIfAndroidPackageDefinedInAppConfigForGenericProject(
|
||||
Log.warn(
|
||||
`Specifying "android.package" in ${getProjectConfigDescription(
|
||||
projectDir
|
||||
)} is deprecated for generic projects.\n` +
|
||||
)} is deprecated for bare workflow projects.\n` +
|
||||
'EAS Build depends only on the value in the native code. Please remove the deprecated configuration.'
|
||||
);
|
||||
warnPrinted = true;
|
||||
|
||||
@@ -29,7 +29,7 @@ beforeEach(async () => {
|
||||
const originalFs = jest.requireActual('fs') as typeof fs;
|
||||
|
||||
describe(getBundleIdentifierAsync, () => {
|
||||
describe('generic projects', () => {
|
||||
describe('bare projects', () => {
|
||||
it('reads bundle identifier from project.', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { readAppJson } from '../../build/utils/appJson';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
@@ -34,7 +35,7 @@ export async function getBundleIdentifierAsync(
|
||||
): Promise<string> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
warnIfBundleIdentifierDefinedInAppConfigForGenericProject(projectDir, exp);
|
||||
warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject(projectDir, exp);
|
||||
|
||||
const bundleIdentifier = IOSConfig.BundleIdentifier.getBundleIdentifierFromPbxproj(projectDir, {
|
||||
targetName,
|
||||
@@ -107,7 +108,7 @@ async function configureBundleIdentifierAsync(
|
||||
validate: value => (isBundleIdentifierValid(value) ? true : INVALID_BUNDLE_IDENTIFIER_MESSAGE),
|
||||
});
|
||||
|
||||
const rawStaticConfig = await fs.readJSON(paths.staticConfigPath);
|
||||
const rawStaticConfig = readAppJson(paths.staticConfigPath);
|
||||
rawStaticConfig.expo = {
|
||||
...rawStaticConfig.expo,
|
||||
ios: { ...rawStaticConfig.expo?.ios, bundleIdentifier },
|
||||
@@ -124,7 +125,7 @@ function isBundleIdentifierValid(bundleIdentifier: string): boolean {
|
||||
}
|
||||
|
||||
let warnPrinted = false;
|
||||
export function warnIfBundleIdentifierDefinedInAppConfigForGenericProject(
|
||||
export function warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
): void {
|
||||
@@ -132,7 +133,7 @@ export function warnIfBundleIdentifierDefinedInAppConfigForGenericProject(
|
||||
Log.warn(
|
||||
`Specifying "ios.bundleIdentifier" in ${getProjectConfigDescription(
|
||||
projectDir
|
||||
)} is deprecated for generic projects.\n` +
|
||||
)} is deprecated for bare workflow projects.\n` +
|
||||
'EAS Build depends only on the value in the native code. Please remove the deprecated configuration.'
|
||||
);
|
||||
warnPrinted = true;
|
||||
|
||||
@@ -231,7 +231,8 @@ export async function getBranchByNameAsync({
|
||||
{
|
||||
appId,
|
||||
name,
|
||||
}
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ import { PresignedPost } from '../graphql/mutations/UploadSessionMutation';
|
||||
import { PublishQuery } from '../graphql/queries/PublishQuery';
|
||||
import Log from '../log';
|
||||
import { uploadWithPresignedPostAsync } from '../uploads';
|
||||
import { expoCommandAsync } from '../utils/expoCommand';
|
||||
import { expoCommandAsync } from '../utils/expoCli';
|
||||
import uniqBy from '../utils/expodash/uniqBy';
|
||||
|
||||
export const TIMEOUT_LIMIT = 60_000; // 1 minute
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import vcs from '../vcs';
|
||||
import { getVcsClient } from '../vcs';
|
||||
|
||||
export async function resolveWorkflowAsync(
|
||||
projectDir: string,
|
||||
@@ -20,7 +20,9 @@ export async function resolveWorkflowAsync(
|
||||
}
|
||||
|
||||
if (await fs.pathExists(platformWorkflowMarker)) {
|
||||
return (await vcs.isFileIgnoredAsync(path.relative(projectDir, platformWorkflowMarker)))
|
||||
return (await getVcsClient().isFileIgnoredAsync(
|
||||
path.relative(projectDir, platformWorkflowMarker)
|
||||
))
|
||||
? Workflow.MANAGED
|
||||
: Workflow.GENERIC;
|
||||
} else {
|
||||
|
||||
@@ -326,16 +326,16 @@ async function handlePromptSourceAsync(source: ArchivePromptSource): Promise<Arc
|
||||
message: 'What would you like to submit?',
|
||||
choices: [
|
||||
{
|
||||
title: 'Selected build from EAS',
|
||||
title: 'Select a build from EAS',
|
||||
value: ArchiveSourceType.buildList,
|
||||
},
|
||||
{ title: 'I have a url to the app archive', value: ArchiveSourceType.url },
|
||||
{ title: 'Provide a URL to the app archive', value: ArchiveSourceType.url },
|
||||
{
|
||||
title: 'Local app binary file',
|
||||
title: 'Provide a path to a local app binary file',
|
||||
value: ArchiveSourceType.path,
|
||||
},
|
||||
{
|
||||
title: 'A build identified by a build ID',
|
||||
title: 'Provide a build ID to identify a build on EAS',
|
||||
value: ArchiveSourceType.buildId,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -177,6 +177,10 @@ export default class IosSubmitCommand {
|
||||
return result(envAppleId);
|
||||
}
|
||||
|
||||
if (this.ctx.credentialsCtx.appStore.authCtx?.appleId) {
|
||||
return result(this.ctx.credentialsCtx.appStore.authCtx.appleId);
|
||||
}
|
||||
|
||||
// Get the email address that was last used and set it as
|
||||
// the default value for quicker authentication.
|
||||
const lastAppleId = await UserSettings.getAsync('appleId', null);
|
||||
|
||||
@@ -109,7 +109,7 @@ export default class IosSubmitter extends BaseSubmitter<Platform.IOS, IosSubmiss
|
||||
// structuring order affects table rows order
|
||||
return {
|
||||
ascAppIdentifier,
|
||||
appleIdUsername,
|
||||
appleIdUsername: appleIdUsername ?? undefined,
|
||||
projectId,
|
||||
...(ascApiKeyResult
|
||||
? { formattedAscApiKey: formatServiceAccountSummary(ascApiKeyResult) }
|
||||
@@ -121,7 +121,7 @@ export default class IosSubmitter extends BaseSubmitter<Platform.IOS, IosSubmiss
|
||||
|
||||
type SummaryData = {
|
||||
ascAppIdentifier: string;
|
||||
appleIdUsername: string;
|
||||
appleIdUsername?: string;
|
||||
projectId: string;
|
||||
formattedAscApiKey?: string;
|
||||
} & ArchiveSourceSummaryFields;
|
||||
|
||||
@@ -75,6 +75,7 @@ export async function loginAsync({
|
||||
'expo-session': sessionSecret,
|
||||
},
|
||||
},
|
||||
additionalTypenames: [] /* UserQuery has immutable fields */,
|
||||
}
|
||||
)
|
||||
.toPromise();
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { errors } from '@expo/eas-json';
|
||||
|
||||
import { getProfilesAsync } from '../profiles';
|
||||
|
||||
describe(getProfilesAsync, () => {
|
||||
it('defaults to production profile', async () => {
|
||||
const readProfileAsync = jest.fn();
|
||||
const result = await getProfilesAsync({
|
||||
platforms: [Platform.ANDROID, Platform.IOS],
|
||||
profileName: undefined,
|
||||
readProfileAsync,
|
||||
});
|
||||
|
||||
expect(result[0].profileName).toBe('production');
|
||||
expect(result[1].profileName).toBe('production');
|
||||
expect(readProfileAsync).toBeCalledWith(Platform.ANDROID, 'production');
|
||||
expect(readProfileAsync).toBeCalledWith(Platform.IOS, 'production');
|
||||
});
|
||||
|
||||
it('defaults to release profile when production profile is non-existent', async () => {
|
||||
const readProfileAsync = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(() => {
|
||||
throw new Error();
|
||||
})
|
||||
.mockRejectedValueOnce(() => {
|
||||
throw new Error();
|
||||
});
|
||||
|
||||
const result = await getProfilesAsync({
|
||||
platforms: [Platform.ANDROID, Platform.IOS],
|
||||
profileName: undefined,
|
||||
readProfileAsync,
|
||||
});
|
||||
|
||||
expect(result[0].profileName).toBe('release');
|
||||
expect(result[1].profileName).toBe('release');
|
||||
expect(readProfileAsync).toBeCalledWith(Platform.ANDROID, 'production');
|
||||
expect(readProfileAsync).toBeCalledWith(Platform.IOS, 'production');
|
||||
expect(readProfileAsync).toBeCalledWith(Platform.ANDROID, 'release');
|
||||
expect(readProfileAsync).toBeCalledWith(Platform.IOS, 'release');
|
||||
});
|
||||
|
||||
it('fails when neither production or release profiles are present', async () => {
|
||||
const readProfileAsync = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(() => {
|
||||
throw new Error();
|
||||
})
|
||||
.mockRejectedValueOnce(() => {
|
||||
throw new Error();
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProfilesAsync({
|
||||
platforms: [Platform.ANDROID],
|
||||
profileName: undefined,
|
||||
readProfileAsync,
|
||||
})
|
||||
).rejects.toThrowError(/There is no profile named "production" in eas.json/);
|
||||
});
|
||||
|
||||
it('gets a specific profile', async () => {
|
||||
const readProfileAsync = jest.fn();
|
||||
const result = await getProfilesAsync({
|
||||
platforms: [Platform.ANDROID, Platform.IOS],
|
||||
profileName: 'custom-profile',
|
||||
readProfileAsync,
|
||||
});
|
||||
|
||||
expect(result[0].profileName).toBe('custom-profile');
|
||||
expect(result[1].profileName).toBe('custom-profile');
|
||||
expect(readProfileAsync).toBeCalledWith(Platform.ANDROID, 'custom-profile');
|
||||
expect(readProfileAsync).toBeCalledWith(Platform.IOS, 'custom-profile');
|
||||
});
|
||||
|
||||
it('throws validation error if eas.json is invalid', async () => {
|
||||
const readProfileAsync = jest.fn().mockImplementation(() => {
|
||||
throw new errors.InvalidEasJsonError('eas.json is not valid');
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProfilesAsync({
|
||||
platforms: [Platform.ANDROID, Platform.IOS],
|
||||
profileName: undefined,
|
||||
readProfileAsync,
|
||||
})
|
||||
).rejects.toThrowError(/eas.json is not valid/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
const packageJSON = require('../../package.json');
|
||||
|
||||
export const easCliVersion: string = packageJSON.version;
|
||||
File renamed without changes.
@@ -0,0 +1,59 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { errors } from '@expo/eas-json';
|
||||
|
||||
import Log from '../log';
|
||||
|
||||
export type ProfileData<T> = {
|
||||
profile: T;
|
||||
platform: Platform;
|
||||
profileName: string;
|
||||
};
|
||||
|
||||
export async function getProfilesAsync<T>({
|
||||
platforms,
|
||||
profileName: profileNameArg,
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
readProfileAsync,
|
||||
}: {
|
||||
platforms: Platform[];
|
||||
profileName?: string | null;
|
||||
readProfileAsync: (platform: Platform, profileName: string) => Promise<T>;
|
||||
}): Promise<ProfileData<T>[]> {
|
||||
const results = platforms.map(async function (platform) {
|
||||
let profile;
|
||||
let profileName = profileNameArg;
|
||||
|
||||
if (!profileName) {
|
||||
try {
|
||||
profile = await readProfileAsync(platform, 'production');
|
||||
profileName = 'production';
|
||||
} catch (errorOuter) {
|
||||
if (errorOuter instanceof errors.InvalidEasJsonError) {
|
||||
throw errorOuter;
|
||||
}
|
||||
try {
|
||||
profile = await readProfileAsync(platform, 'release');
|
||||
profileName = 'release';
|
||||
Log.warn(
|
||||
'The default profile changed from "release" to "production". We detected that you still have a "release" build profile, so we are using it. Update eas.json to have a profile named "production" under the `build` key, or specify which profile you\'d like to use with the --profile flag. This fallback behavior will be removed in the next major version of EAS CLI.'
|
||||
);
|
||||
} catch (errorInner) {
|
||||
if (errorInner instanceof errors.InvalidEasJsonError) {
|
||||
throw errorInner;
|
||||
}
|
||||
throw new Error('There is no profile named "production" in eas.json');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
profile = await readProfileAsync(platform, profileName);
|
||||
}
|
||||
|
||||
return {
|
||||
profile,
|
||||
profileName,
|
||||
platform,
|
||||
};
|
||||
});
|
||||
|
||||
return await Promise.all(results);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { vol } from 'memfs';
|
||||
|
||||
import { Ignore } from '../local';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
beforeEach(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
describe(Ignore, () => {
|
||||
it('reads .gitignore from subdirectory', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'.gitignore': 'aaa',
|
||||
'dir/.gitignore': 'bbb',
|
||||
},
|
||||
'/root'
|
||||
);
|
||||
|
||||
const ignore = new Ignore('/root');
|
||||
await ignore.initIgnoreAsync();
|
||||
expect(ignore.ignores('aaa')).toBe(true);
|
||||
expect(ignore.ignores('bbb')).toBe(false);
|
||||
expect(ignore.ignores('dir/aaa')).toBe(true);
|
||||
expect(ignore.ignores('dir/bbb')).toBe(true);
|
||||
});
|
||||
|
||||
it('ignores .gitignore files if .easignore is present', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'.gitignore': 'aaa',
|
||||
'.easignore': 'ccc',
|
||||
'dir/.gitignore': 'bbb',
|
||||
},
|
||||
'/root'
|
||||
);
|
||||
|
||||
const ignore = new Ignore('/root');
|
||||
await ignore.initIgnoreAsync();
|
||||
expect(ignore.ignores('aaa')).toBe(false);
|
||||
expect(ignore.ignores('bbb')).toBe(false);
|
||||
expect(ignore.ignores('ccc')).toBe(true);
|
||||
expect((ignore as any).ignoreMapping.map((i: any) => i[0])).toEqual(['', '']);
|
||||
});
|
||||
|
||||
it('ignores .gitignore files in node_modules content', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'.gitignore': 'aaa',
|
||||
'dir/.gitignore': 'bbb',
|
||||
'node_modules/.gitignore': 'bbb',
|
||||
},
|
||||
'/root'
|
||||
);
|
||||
|
||||
const ignore = new Ignore('/root');
|
||||
await ignore.initIgnoreAsync();
|
||||
expect(ignore.ignores('aaa')).toBe(true);
|
||||
expect(ignore.ignores('bbb')).toBe(false);
|
||||
expect(ignore.ignores('node_modules/aaa')).toBe(true);
|
||||
expect(ignore.ignores('node_modules/bbb')).toBe(true);
|
||||
expect((ignore as any).ignoreMapping.map((i: any) => i[0])).toEqual(['', '', 'dir/']);
|
||||
});
|
||||
|
||||
it('applies all gitignore files in parent directories', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'.gitignore': 'dir/ccc',
|
||||
'dir/.gitignore': 'bbb',
|
||||
},
|
||||
'/root'
|
||||
);
|
||||
|
||||
const ignore = new Ignore('/root');
|
||||
await ignore.initIgnoreAsync();
|
||||
expect(ignore.ignores('dir/ccc')).toBe(true);
|
||||
});
|
||||
|
||||
it('ignores .git', async () => {
|
||||
vol.fromJSON({}, '/root');
|
||||
|
||||
const ignore = new Ignore('/root');
|
||||
await ignore.initIgnoreAsync();
|
||||
expect(ignore.ignores('.git')).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,211 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { confirmAsync, promptAsync } from '../../prompts';
|
||||
import {
|
||||
doesGitRepoExistAsync,
|
||||
getGitDiffOutputAsync,
|
||||
gitDiffAsync,
|
||||
gitStatusAsync,
|
||||
isGitInstalledAsync,
|
||||
} from '../git';
|
||||
import { Client } from '../vcs';
|
||||
|
||||
export default class GitClient extends Client {
|
||||
public async ensureRepoExistsAsync(): Promise<void> {
|
||||
if (!(await isGitInstalledAsync())) {
|
||||
throw new Error('git command not found, install it before proceeding');
|
||||
}
|
||||
|
||||
if (await doesGitRepoExistAsync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.warn("It looks like you haven't initialized the git repository yet.");
|
||||
Log.warn('EAS Build requires you to use a git repository for your project.');
|
||||
|
||||
const confirmInit = await confirmAsync({
|
||||
message: `Would you like us to run 'git init' in the current directory for you?`,
|
||||
});
|
||||
if (!confirmInit) {
|
||||
throw new Error(
|
||||
'A git repository is required for building your project. Initialize it and run this command again.'
|
||||
);
|
||||
}
|
||||
await spawnAsync('git', ['init']);
|
||||
|
||||
Log.log("We're going to make an initial commit for your repository.");
|
||||
|
||||
const { message } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'message',
|
||||
message: 'Commit message:',
|
||||
initial: 'Initial commit',
|
||||
validate: (input: string) => input !== '',
|
||||
});
|
||||
await this.commitAsync({ commitAllFiles: true, commitMessage: message });
|
||||
}
|
||||
|
||||
public async commitAsync({
|
||||
commitMessage,
|
||||
commitAllFiles,
|
||||
}: {
|
||||
commitMessage: string;
|
||||
commitAllFiles?: boolean;
|
||||
}): Promise<void> {
|
||||
if (commitAllFiles) {
|
||||
await spawnAsync('git', ['add', '-A']);
|
||||
}
|
||||
|
||||
await spawnAsync('git', ['add', '-u']);
|
||||
await spawnAsync('git', ['commit', '-m', commitMessage]);
|
||||
}
|
||||
|
||||
public async isCommitRequiredAsync(): Promise<boolean> {
|
||||
return await this.hasUncommittedChangesAsync();
|
||||
}
|
||||
|
||||
public async hasUncommittedChangesAsync(): Promise<boolean> {
|
||||
const changes = await gitStatusAsync({ showUntracked: true });
|
||||
return changes.length > 0;
|
||||
}
|
||||
|
||||
public async getRootPathAsync(): Promise<string> {
|
||||
return (await spawnAsync('git', ['rev-parse', '--show-toplevel'])).stdout.trim();
|
||||
}
|
||||
|
||||
public async makeShallowCopyAsync(destinationPath: string): Promise<void> {
|
||||
if (await this.hasUncommittedChangesAsync()) {
|
||||
// it should already be checked before this function is called, but in case it wasn't
|
||||
// we want to ensure that any changes were introduced by call to `setGitCaseSensitivityAsync`
|
||||
throw new Error('You have some uncommitted changes in your repository.');
|
||||
}
|
||||
let gitRepoUri;
|
||||
if (process.platform === 'win32') {
|
||||
// getRootDirectoryAsync() will return C:/path/to/repo on Windows and path
|
||||
// prefix should be file:///
|
||||
gitRepoUri = `file:///${await this.getRootPathAsync()}`;
|
||||
} else {
|
||||
// getRootDirectoryAsync() will /path/to/repo, and path prefix should be
|
||||
// file:/// so only file:// needs to be prepended
|
||||
gitRepoUri = `file://${await this.getRootPathAsync()}`;
|
||||
}
|
||||
const isCaseSensitive = await isGitCaseSensitiveAsync();
|
||||
await setGitCaseSensitivityAsync(true);
|
||||
try {
|
||||
if (await this.hasUncommittedChangesAsync()) {
|
||||
Log.error('Detected inconsistent filename casing between your local filesystem and git.');
|
||||
Log.error('This will likely cause your build to fail. Impacted files:');
|
||||
await spawnAsync('git', ['status', '--short'], { stdio: 'inherit' });
|
||||
Log.newLine();
|
||||
Log.error(
|
||||
`Error: Resolve filename casing inconsistencies before proceeding. ${learnMore(
|
||||
'https://expo.fyi/macos-ignorecase'
|
||||
)}`
|
||||
);
|
||||
throw new Error('You have some uncommitted changes in your repository.');
|
||||
}
|
||||
await spawnAsync('git', [
|
||||
'clone',
|
||||
'--no-hardlinks',
|
||||
'--depth',
|
||||
'1',
|
||||
gitRepoUri,
|
||||
destinationPath,
|
||||
]);
|
||||
} finally {
|
||||
await setGitCaseSensitivityAsync(isCaseSensitive);
|
||||
}
|
||||
}
|
||||
|
||||
public async getCommitHashAsync(): Promise<string | undefined> {
|
||||
try {
|
||||
return (await spawnAsync('git', ['rev-parse', 'HEAD'])).stdout.trim();
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async trackFileAsync(file: string): Promise<void> {
|
||||
await spawnAsync('git', ['add', '--intent-to-add', file]);
|
||||
}
|
||||
|
||||
public async getBranchNameAsync(): Promise<string | null> {
|
||||
try {
|
||||
return (await spawnAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'])).stdout.trim();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async getLastCommitMessageAsync(): Promise<string | null> {
|
||||
try {
|
||||
return (await spawnAsync('git', ['--no-pager', 'log', '-1', '--pretty=%B'])).stdout.trim();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async showDiffAsync(): Promise<void> {
|
||||
const outputTooLarge = (await getGitDiffOutputAsync()).split(/\r\n|\r|\n/).length > 100;
|
||||
await gitDiffAsync({ withPager: outputTooLarge });
|
||||
}
|
||||
|
||||
public async isFileUntrackedAsync(path: string): Promise<boolean> {
|
||||
const withUntrackedFiles = await gitStatusAsync({ showUntracked: true });
|
||||
const trackedFiles = await gitStatusAsync({ showUntracked: false });
|
||||
const pathWithoutLeadingDot = path.replace(/^\.\//, ''); // remove leading './' from path
|
||||
return (
|
||||
withUntrackedFiles.includes(pathWithoutLeadingDot) &&
|
||||
!trackedFiles.includes(pathWithoutLeadingDot)
|
||||
);
|
||||
}
|
||||
|
||||
public async isFileIgnoredAsync(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
await spawnAsync('git', ['check-ignore', '-q', filePath]);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if git is configured to be case sensitive
|
||||
* @returns {boolean | undefined}
|
||||
* - boolean - is git case sensitive
|
||||
* - undefined - case sensitivity is not configured and git is using default behavior
|
||||
*/
|
||||
export async function isGitCaseSensitiveAsync(): Promise<boolean | undefined> {
|
||||
if (process.platform !== 'darwin') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await spawnAsync('git', ['config', '--get', 'core.ignorecase']);
|
||||
const isIgnoreCaseEnabled = result.stdout.trim();
|
||||
if (isIgnoreCaseEnabled === '') {
|
||||
return undefined;
|
||||
} else if (isIgnoreCaseEnabled === 'true') {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function setGitCaseSensitivityAsync(enable: boolean | undefined): Promise<void> {
|
||||
// we are assuming that if someone sets that on non-macos device then
|
||||
// they know what they are doing
|
||||
if (process.platform !== 'darwin') {
|
||||
return;
|
||||
}
|
||||
if (enable === undefined) {
|
||||
await spawnAsync('git', ['config', '--unset', 'core.ignorecase']);
|
||||
} else {
|
||||
await spawnAsync('git', ['config', 'core.ignorecase', String(!enable)]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import path from 'path';
|
||||
|
||||
import { Ignore, makeShallowCopyAsync } from '../local';
|
||||
import GitClient from './git';
|
||||
|
||||
export default class GitNoCommitClient extends GitClient {
|
||||
public async isCommitRequiredAsync(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
public async getRootPathAsync(): Promise<string> {
|
||||
return (await spawnAsync('git', ['rev-parse', '--show-toplevel'])).stdout.trim();
|
||||
}
|
||||
|
||||
public async makeShallowCopyAsync(destinationPath: string): Promise<void> {
|
||||
// normalize converts C:/some/path to C:\some\path on windows
|
||||
const srcPath = path.normalize(await this.getRootPathAsync());
|
||||
await makeShallowCopyAsync(srcPath, destinationPath);
|
||||
}
|
||||
|
||||
public async isFileIgnoredAsync(filePath: string): Promise<boolean> {
|
||||
// normalize converts C:/some/path to C:\some\path on windows
|
||||
const ignore = new Ignore(await this.getRootPathAsync());
|
||||
await ignore.initIgnoreAsync();
|
||||
return ignore.ignores(filePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Ignore, getRootPath, makeShallowCopyAsync } from '../local';
|
||||
import { Client } from '../vcs';
|
||||
|
||||
export default class NoVcsClient extends Client {
|
||||
public async getRootPathAsync(): Promise<string> {
|
||||
return getRootPath();
|
||||
}
|
||||
|
||||
public async makeShallowCopyAsync(destinationPath: string): Promise<void> {
|
||||
const srcPath = getRootPath();
|
||||
await makeShallowCopyAsync(srcPath, destinationPath);
|
||||
}
|
||||
|
||||
public async isFileIgnoredAsync(filePath: string): Promise<boolean> {
|
||||
const ignore = new Ignore(getRootPath());
|
||||
await ignore.initIgnoreAsync();
|
||||
return ignore.ignores(filePath);
|
||||
}
|
||||
}
|
||||
@@ -1,166 +1,6 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
|
||||
import Log, { learnMore } from '../log';
|
||||
import { confirmAsync, promptAsync } from '../prompts';
|
||||
import { Client } from './vcs';
|
||||
|
||||
export default class GitClient extends Client {
|
||||
public async ensureRepoExistsAsync(): Promise<void> {
|
||||
if (!(await isGitInstalledAsync())) {
|
||||
throw new Error('git command not found, install it before proceeding');
|
||||
}
|
||||
|
||||
if (await doesGitRepoExistAsync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.warn("It looks like you haven't initialized the git repository yet.");
|
||||
Log.warn('EAS Build requires you to use a git repository for your project.');
|
||||
|
||||
const confirmInit = await confirmAsync({
|
||||
message: `Would you like to run 'git init' in the current directory?`,
|
||||
});
|
||||
if (!confirmInit) {
|
||||
throw new Error(
|
||||
'A git repository is required for building your project. Initialize it and run this command again.'
|
||||
);
|
||||
}
|
||||
await spawnAsync('git', ['init']);
|
||||
|
||||
Log.log("We're going to make an initial commit for you repository.");
|
||||
|
||||
const { message } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'message',
|
||||
message: 'Commit message:',
|
||||
initial: 'Initial commit',
|
||||
validate: (input: string) => input !== '',
|
||||
});
|
||||
await this.commitAsync({ commitAllFiles: true, commitMessage: message });
|
||||
}
|
||||
|
||||
public async commitAsync({
|
||||
commitMessage,
|
||||
commitAllFiles,
|
||||
}: {
|
||||
commitMessage: string;
|
||||
commitAllFiles?: boolean;
|
||||
}): Promise<void> {
|
||||
if (commitAllFiles) {
|
||||
await spawnAsync('git', ['add', '-A']);
|
||||
}
|
||||
|
||||
await spawnAsync('git', ['add', '-u']);
|
||||
await spawnAsync('git', ['commit', '-m', commitMessage]);
|
||||
}
|
||||
|
||||
public async hasUncommittedChangesAsync(): Promise<boolean> {
|
||||
const changes = await gitStatusAsync({ showUntracked: true });
|
||||
return changes.length > 0;
|
||||
}
|
||||
|
||||
public async getRootPathAsync(): Promise<string> {
|
||||
return (await spawnAsync('git', ['rev-parse', '--show-toplevel'])).stdout.trim();
|
||||
}
|
||||
|
||||
public async makeShallowCopyAsync(destinationPath: string): Promise<void> {
|
||||
if (await this.hasUncommittedChangesAsync()) {
|
||||
// it should not happen, we need that to make sure that check before clone
|
||||
// is always caused by case sensitivity
|
||||
throw new Error('You have some uncommitted changes in your repository.');
|
||||
}
|
||||
let gitRepoUri;
|
||||
if (process.platform === 'win32') {
|
||||
// getRootDirectoryAsync() will return C:/path/to/repo on Windows and path
|
||||
// prefix should be file:///
|
||||
gitRepoUri = `file:///${await this.getRootPathAsync()}`;
|
||||
} else {
|
||||
// getRootDirectoryAsync() will /path/to/repo, and path prefix should be
|
||||
// file:/// so only file:// needs to be prepended
|
||||
gitRepoUri = `file://${await this.getRootPathAsync()}`;
|
||||
}
|
||||
const isCaseSensitive = await isGitCaseSensitiveAsync();
|
||||
await setGitCaseSensitivityAsync(true);
|
||||
try {
|
||||
if (await this.hasUncommittedChangesAsync()) {
|
||||
Log.error('Detected inconsistent filename casing between your local filesystem and git.');
|
||||
Log.error('This will likely cause your build to fail. Impacted files:');
|
||||
await spawnAsync('git', ['status', '--short'], { stdio: 'inherit' });
|
||||
Log.newLine();
|
||||
Log.error(
|
||||
`Error: Resolve filename casing inconsistencies before proceeding. ${learnMore(
|
||||
'https://expo.fyi/macos-ignorecase'
|
||||
)}`
|
||||
);
|
||||
throw new Error('You have some uncommitted changes in your repository.');
|
||||
}
|
||||
await spawnAsync('git', [
|
||||
'clone',
|
||||
'--no-hardlinks',
|
||||
'--depth',
|
||||
'1',
|
||||
gitRepoUri,
|
||||
destinationPath,
|
||||
]);
|
||||
} finally {
|
||||
await setGitCaseSensitivityAsync(isCaseSensitive);
|
||||
}
|
||||
}
|
||||
|
||||
public async getCommitHashAsync(): Promise<string | undefined> {
|
||||
try {
|
||||
return (await spawnAsync('git', ['rev-parse', 'HEAD'])).stdout.trim();
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async trackFileAsync(file: string): Promise<void> {
|
||||
await spawnAsync('git', ['add', '--intent-to-add', file]);
|
||||
}
|
||||
|
||||
public async getBranchNameAsync(): Promise<string | null> {
|
||||
try {
|
||||
return (await spawnAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'])).stdout.trim();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async getLastCommitMessageAsync(): Promise<string | null> {
|
||||
try {
|
||||
return (await spawnAsync('git', ['--no-pager', 'log', '-1', '--pretty=%B'])).stdout.trim();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async showDiffAsync(): Promise<void> {
|
||||
const outputTooLarge = (await getGitDiffOutputAsync()).split(/\r\n|\r|\n/).length > 100;
|
||||
await gitDiffAsync({ withPager: outputTooLarge });
|
||||
}
|
||||
|
||||
public async isFileUntrackedAsync(path: string): Promise<boolean> {
|
||||
const withUntrackedFiles = await gitStatusAsync({ showUntracked: true });
|
||||
const trackedFiles = await gitStatusAsync({ showUntracked: false });
|
||||
const pathWithoutLeadingDot = path.replace(/^\.\//, ''); // remove leading './' from path
|
||||
return (
|
||||
withUntrackedFiles.includes(pathWithoutLeadingDot) &&
|
||||
!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> {
|
||||
export async function isGitInstalledAsync(): Promise<boolean> {
|
||||
try {
|
||||
await spawnAsync('git', ['--help']);
|
||||
} catch (error: any) {
|
||||
@@ -172,7 +12,7 @@ async function isGitInstalledAsync(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function doesGitRepoExistAsync(): Promise<boolean> {
|
||||
export async function doesGitRepoExistAsync(): Promise<boolean> {
|
||||
try {
|
||||
await spawnAsync('git', ['rev-parse', '--git-dir']);
|
||||
return true;
|
||||
@@ -184,54 +24,18 @@ async function doesGitRepoExistAsync(): Promise<boolean> {
|
||||
interface GitStatusOptions {
|
||||
showUntracked?: boolean;
|
||||
}
|
||||
async function gitStatusAsync({ showUntracked }: GitStatusOptions = {}): Promise<string> {
|
||||
|
||||
export async function gitStatusAsync({ showUntracked }: GitStatusOptions = {}): Promise<string> {
|
||||
return (await spawnAsync('git', ['status', '-s', showUntracked ? '-uall' : '-uno'])).stdout;
|
||||
}
|
||||
|
||||
async function getGitDiffOutputAsync(): Promise<string> {
|
||||
export async function getGitDiffOutputAsync(): Promise<string> {
|
||||
return (await spawnAsync('git', ['--no-pager', 'diff'])).stdout;
|
||||
}
|
||||
|
||||
async function gitDiffAsync({ withPager = false }: { withPager?: boolean } = {}): Promise<void> {
|
||||
export async function gitDiffAsync({
|
||||
withPager = false,
|
||||
}: { withPager?: boolean } = {}): Promise<void> {
|
||||
const options = withPager ? [] : ['--no-pager'];
|
||||
await spawnAsync('git', [...options, 'diff'], { stdio: ['ignore', 'inherit', 'inherit'] });
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if git is configured to be case sensitive
|
||||
* @returns {boolean | undefined}
|
||||
* - boolean - is git case senstive
|
||||
* - undefined - case sensitivity is not configured and git is using default behavior
|
||||
*/
|
||||
async function isGitCaseSensitiveAsync(): Promise<boolean | undefined> {
|
||||
if (process.platform !== 'darwin') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await spawnAsync('git', ['config', '--get', 'core.ignorecase']);
|
||||
const isIgnoreCaseEnabled = result.stdout.trim();
|
||||
if (isIgnoreCaseEnabled === '') {
|
||||
return undefined;
|
||||
} else if (isIgnoreCaseEnabled === 'true') {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function setGitCaseSensitivityAsync(enable: boolean | undefined): Promise<void> {
|
||||
// we are assuming that if someone sets that on non-macos device then
|
||||
// they know what they are doing
|
||||
if (process.platform !== 'darwin') {
|
||||
return;
|
||||
}
|
||||
if (enable === undefined) {
|
||||
await spawnAsync('git', ['config', '--unset', 'core.ignorecase']);
|
||||
} else {
|
||||
await spawnAsync('git', ['config', 'core.ignorecase', String(!enable)]);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,26 @@
|
||||
import log from '../log';
|
||||
import GitClient from './git';
|
||||
import LocalClient from './local';
|
||||
import GitNoCommitClient from './clients/gitNoCommit';
|
||||
import NoVcsClient from './clients/noVcs';
|
||||
import { Client } from './vcs';
|
||||
|
||||
const NO_VCS_WARNING = `Using EAS CLI without version control system is not recommended, use this mode only if you know what you are doing.`;
|
||||
|
||||
function resolveVcsClient(): Client {
|
||||
let vcsClient = resolveDefaultVcsClient();
|
||||
|
||||
function resolveDefaultVcsClient(): Client {
|
||||
if (process.env.EAS_NO_VCS) {
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
log.warn(NO_VCS_WARNING);
|
||||
}
|
||||
return new LocalClient();
|
||||
return new NoVcsClient();
|
||||
}
|
||||
return new GitClient();
|
||||
return new GitNoCommitClient();
|
||||
}
|
||||
|
||||
export default resolveVcsClient();
|
||||
export function setVcsClient(client: Client): void {
|
||||
vcsClient = client;
|
||||
}
|
||||
|
||||
export function getVcsClient(): Client {
|
||||
return vcsClient;
|
||||
}
|
||||
@@ -1,37 +1,17 @@
|
||||
import fg from 'fast-glob';
|
||||
import fs from 'fs-extra';
|
||||
import ignore, { Ignore } from 'ignore';
|
||||
import createIgnore, { Ignore as SingleFileIgnore } from 'ignore';
|
||||
import path from 'path';
|
||||
|
||||
import { Client } from './vcs';
|
||||
const EASIGNORE_FILENAME = '.easignore';
|
||||
const GITIGNORE_FILENAME = '.gitignore';
|
||||
|
||||
const DEFAULT_IGNORE = `
|
||||
.git
|
||||
node_modules
|
||||
`;
|
||||
|
||||
export default class LocalClient extends Client {
|
||||
public async getRootPathAsync(): Promise<string> {
|
||||
return getRootPath();
|
||||
}
|
||||
|
||||
public async makeShallowCopyAsync(destinationPath: string): Promise<void> {
|
||||
const srcPath = getRootPath();
|
||||
const ignore = await initIgnoreAsync();
|
||||
await fs.copy(srcPath, destinationPath, {
|
||||
filter: (srcFilePath: string) => {
|
||||
if (srcFilePath === srcPath) {
|
||||
return true;
|
||||
}
|
||||
return !ignore.ignores(path.relative(srcPath, srcFilePath));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async isFileIgnoredAsync(filePath: string): Promise<boolean> {
|
||||
return (await initIgnoreAsync()).ignores(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
function getRootPath(): string {
|
||||
export function getRootPath(): string {
|
||||
const rootPath = process.env.EAS_PROJECT_ROOT ?? process.cwd();
|
||||
if (!path.isAbsolute(rootPath)) {
|
||||
return path.resolve(process.cwd(), rootPath);
|
||||
@@ -39,17 +19,66 @@ function getRootPath(): string {
|
||||
return rootPath;
|
||||
}
|
||||
|
||||
async function initIgnoreAsync(): Promise<Ignore> {
|
||||
const srcPath = getRootPath();
|
||||
const easIgnorePath = path.join(srcPath, '.easignore');
|
||||
const gitIgnorePath = path.join(srcPath, '.gitignore');
|
||||
/**
|
||||
* Ignore wraps the 'ignore' package to support multiple .gitignore files
|
||||
* in subdirectories.
|
||||
*
|
||||
* Inconsistencies with git behavior:
|
||||
* - if parent .gitignore has ignore rule and child has exception to that rule,
|
||||
* file will still be ignored,
|
||||
* - node_modules is always ignored,
|
||||
* - if .easignore exists, .gitignore files are not used.
|
||||
*/
|
||||
export class Ignore {
|
||||
private ignoreMapping: (readonly [string, SingleFileIgnore])[] = [];
|
||||
|
||||
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');
|
||||
constructor(private rootDir: string) {}
|
||||
|
||||
public async initIgnoreAsync(): Promise<void> {
|
||||
const easIgnorePath = path.join(this.rootDir, EASIGNORE_FILENAME);
|
||||
if (await fs.pathExists(easIgnorePath)) {
|
||||
this.ignoreMapping = [
|
||||
['', createIgnore().add(DEFAULT_IGNORE)],
|
||||
['', createIgnore().add(await fs.readFile(easIgnorePath, 'utf8'))],
|
||||
];
|
||||
return;
|
||||
}
|
||||
const ignoreFilePaths = (
|
||||
await fg(`**/${GITIGNORE_FILENAME}`, { cwd: this.rootDir, ignore: ['node_modules'] })
|
||||
)
|
||||
// ensure that parent dir is before child directories
|
||||
.sort((a, b) => a.length - b.length && a.localeCompare(b));
|
||||
|
||||
const ignoreMapping = await Promise.all(
|
||||
ignoreFilePaths.map(async filePath => {
|
||||
return [
|
||||
filePath.slice(0, filePath.length - GITIGNORE_FILENAME.length),
|
||||
createIgnore().add(await fs.readFile(path.join(this.rootDir, filePath), 'utf8')),
|
||||
] as const;
|
||||
})
|
||||
);
|
||||
this.ignoreMapping = [['', createIgnore().add(DEFAULT_IGNORE)], ...ignoreMapping];
|
||||
}
|
||||
|
||||
return ignore().add(ignoreFile);
|
||||
public ignores(relativePath: string): boolean {
|
||||
for (const [prefix, ignore] of this.ignoreMapping) {
|
||||
if (relativePath.startsWith(prefix) && ignore.ignores(relativePath.slice(prefix.length))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function makeShallowCopyAsync(src: string, dst: string): Promise<void> {
|
||||
const ignore = new Ignore(src);
|
||||
await ignore.initIgnoreAsync();
|
||||
await fs.copy(src, dst, {
|
||||
filter: (srcFilePath: string) => {
|
||||
if (srcFilePath === src) {
|
||||
return true;
|
||||
}
|
||||
return !ignore.ignores(path.relative(src, srcFilePath));
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,29 +1,38 @@
|
||||
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
|
||||
// anything that is not committed to the repository. Most optimal solution is to create shallow clone
|
||||
// using tooling provided by specific VCS, that respects all ignore rules
|
||||
public abstract makeShallowCopyAsync(destinationPath: string): Promise<void>;
|
||||
|
||||
// Find root of the repository.
|
||||
//
|
||||
// On windows path might look different depending on implementation
|
||||
// - git based clients will return "C:/path/to/repo"
|
||||
// - non-git clients will return "C:\path\to\repo"
|
||||
public abstract getRootPathAsync(): Promise<string>;
|
||||
|
||||
// (optional) ensureRepoExistsAsync should verify whether repository exists and tooling is installed
|
||||
// it's not required for minimal support, but lack of validation might cause the failure at a later stage.
|
||||
public async ensureRepoExistsAsync(): Promise<void> {}
|
||||
|
||||
// (optional) hasUncommittedChangesAsync should check whether there are changes in local repository
|
||||
// (optional) checks whether commit is necessary before calling makeShallowCopyAsync
|
||||
//
|
||||
// If it's not implemented method `makeShallowCopyAsync` needs to be able to include uncommited changes
|
||||
// If it's not implemented method `makeShallowCopyAsync` needs to be able to include uncommitted changes
|
||||
// when creating copy
|
||||
public async hasUncommittedChangesAsync(): Promise<boolean> {
|
||||
public async isCommitRequiredAsync(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
// (optional) hasUncommittedChangesAsync should check whether there are changes in local repository
|
||||
public async hasUncommittedChangesAsync(): Promise<boolean | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// (optional) commitAsync commits changes
|
||||
//
|
||||
// - Should be implemented if hasUncommittedChangesAsync is implemented
|
||||
// - If it's not implemented method `makeShallowCopyAsync` needs to be able to include uncommited changes
|
||||
// - If it's not implemented method `makeShallowCopyAsync` needs to be able to include uncommitted changes
|
||||
// in project copy
|
||||
public async commitAsync(arg: {
|
||||
commitMessage: string;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"description": "A library for interacting with the eas.json",
|
||||
"version": "0.32.0",
|
||||
"version": "0.34.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/eas-build-job": "0.2.52",
|
||||
"@expo/eas-build-job": "0.2.57",
|
||||
"@expo/json-file": "8.2.33",
|
||||
"chalk": "4.1.2",
|
||||
"env-string": "1.0.1",
|
||||
"fs-extra": "10.0.0",
|
||||
"joi": "17.4.2",
|
||||
"log-symbols": "4.1.0",
|
||||
"tslib": "2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { Android, Cache, Ios } from '@expo/eas-build-job';
|
||||
|
||||
export enum CredentialsSource {
|
||||
LOCAL = 'local',
|
||||
REMOTE = 'remote',
|
||||
}
|
||||
|
||||
export type AndroidDistributionType = 'store' | 'internal';
|
||||
export type IosDistributionType = 'store' | 'internal' | 'simulator';
|
||||
export type DistributionType = AndroidDistributionType | IosDistributionType;
|
||||
|
||||
export type IosEnterpriseProvisioning = 'adhoc' | 'universal';
|
||||
|
||||
export type VersionAutoIncrement = boolean | 'version' | 'buildNumber';
|
||||
|
||||
interface IosBuilderEnvironment extends Omit<Ios.BuilderEnvironment, 'image'> {
|
||||
image?: Ios.BuilderEnvironment['image'];
|
||||
}
|
||||
|
||||
export interface AndroidBuildProfile extends Android.BuilderEnvironment {
|
||||
credentialsSource: CredentialsSource;
|
||||
releaseChannel?: string;
|
||||
channel?: string;
|
||||
distribution: AndroidDistributionType;
|
||||
cache: Cache;
|
||||
withoutCredentials?: boolean;
|
||||
|
||||
buildType?: Android.BuildType;
|
||||
|
||||
gradleCommand?: string;
|
||||
artifactPath?: string;
|
||||
}
|
||||
|
||||
export interface IosBuildProfile extends IosBuilderEnvironment {
|
||||
credentialsSource: CredentialsSource;
|
||||
releaseChannel?: string;
|
||||
channel?: string;
|
||||
distribution: IosDistributionType;
|
||||
enterpriseProvisioning?: IosEnterpriseProvisioning;
|
||||
autoIncrement: VersionAutoIncrement;
|
||||
cache: Cache;
|
||||
|
||||
artifactPath?: string;
|
||||
scheme?: string;
|
||||
schemeBuildConfiguration?: string;
|
||||
|
||||
buildType?: Ios.BuildType;
|
||||
}
|
||||
|
||||
export type BuildProfile = AndroidBuildProfile | IosBuildProfile;
|
||||
|
||||
// EasConfig represents eas.json with one specific profile
|
||||
export interface EasConfig {
|
||||
builds: {
|
||||
android?: AndroidBuildProfile;
|
||||
ios?: IosBuildProfile;
|
||||
};
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import {
|
||||
AndroidBuildProfile,
|
||||
BuildProfile,
|
||||
EasConfig,
|
||||
IosBuildProfile,
|
||||
} from './DeprecatedConfig.types';
|
||||
import { EasJsonSchema, schemaBuildProfileMap } from './DeprecatedEasJsonSchema';
|
||||
|
||||
export interface EasJson {
|
||||
builds: {
|
||||
android?: { [key: string]: BuildProfilePreValidation };
|
||||
ios?: { [key: string]: BuildProfilePreValidation };
|
||||
};
|
||||
}
|
||||
|
||||
interface BuildProfilePreValidation {
|
||||
workflow?: Workflow;
|
||||
extends?: string;
|
||||
}
|
||||
|
||||
function intersect<T>(setA: Set<T>, setB: Set<T>): Set<T> {
|
||||
return new Set([...setA].filter(i => setB.has(i)));
|
||||
}
|
||||
|
||||
export class EasJsonReader {
|
||||
constructor(private projectDir: string, private platform: 'android' | 'ios' | 'all') {}
|
||||
|
||||
/**
|
||||
* Return build profile names for a particular platform.
|
||||
* If platform is 'all', return common build profiles for all platforms
|
||||
*/
|
||||
public async getBuildProfileNamesAsync(): Promise<string[]> {
|
||||
const easJson = await this.readRawAsync();
|
||||
if (this.platform === 'android') {
|
||||
return Object.keys(easJson?.builds?.android ?? {});
|
||||
} else if (this.platform === 'ios') {
|
||||
return Object.keys(easJson?.builds?.ios ?? {});
|
||||
} else {
|
||||
const intersectingProfileNames = intersect(
|
||||
new Set(Object.keys(easJson?.builds?.ios ?? {})),
|
||||
new Set(Object.keys(easJson?.builds?.android ?? {}))
|
||||
);
|
||||
return Array.from(intersectingProfileNames);
|
||||
}
|
||||
}
|
||||
|
||||
public async readAsync(buildProfileName: string): Promise<EasConfig> {
|
||||
const easJson = await this.readRawAsync();
|
||||
|
||||
let androidConfig;
|
||||
if (['android', 'all'].includes(this.platform)) {
|
||||
androidConfig = this.validateBuildProfile<AndroidBuildProfile>(
|
||||
Platform.ANDROID,
|
||||
buildProfileName,
|
||||
easJson.builds?.android || {}
|
||||
);
|
||||
}
|
||||
let iosConfig;
|
||||
if (['ios', 'all'].includes(this.platform)) {
|
||||
iosConfig = this.validateBuildProfile<IosBuildProfile>(
|
||||
Platform.IOS,
|
||||
buildProfileName,
|
||||
easJson.builds?.ios || {}
|
||||
);
|
||||
}
|
||||
return {
|
||||
builds: {
|
||||
...(androidConfig ? { android: androidConfig } : {}),
|
||||
...(iosConfig ? { ios: iosConfig } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async validateAsync(): Promise<void> {
|
||||
const easJson = await this.readRawAsync();
|
||||
|
||||
const androidProfiles = easJson.builds?.android ?? {};
|
||||
for (const name of Object.keys(androidProfiles)) {
|
||||
try {
|
||||
this.validateBuildProfile(Platform.ANDROID, name, androidProfiles);
|
||||
} catch (err: any) {
|
||||
err.message = `Failed to validate Android build profile "${name}"\n${err.message}`;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
const iosProfiles = easJson.builds?.ios ?? {};
|
||||
for (const name of Object.keys(iosProfiles)) {
|
||||
try {
|
||||
this.validateBuildProfile(Platform.IOS, name, iosProfiles);
|
||||
} catch (err: any) {
|
||||
err.message = `Failed to validate iOS build profile "${name}"\n${err.message}`;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async readRawAsync(): Promise<EasJson> {
|
||||
const rawFile = await fs.readFile(path.join(this.projectDir, 'eas.json'), 'utf8');
|
||||
const json = JSON.parse(rawFile);
|
||||
|
||||
const { value, error } = EasJsonSchema.validate(json, {
|
||||
abortEarly: false,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(`eas.json is not valid [${error.toString()}]`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private validateBuildProfile<T extends BuildProfile>(
|
||||
platform: Platform,
|
||||
buildProfileName: string,
|
||||
buildProfiles: Record<string, BuildProfilePreValidation>
|
||||
): T {
|
||||
const buildProfile = this.resolveBuildProfile(platform, buildProfileName, buildProfiles);
|
||||
const schema = schemaBuildProfileMap[platform];
|
||||
const { value, error } = schema.validate(buildProfile, {
|
||||
stripUnknown: true,
|
||||
convert: true,
|
||||
abortEarly: false,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(
|
||||
`Object "${platform}.${buildProfileName}" in eas.json is not valid [${error.toString()}]`
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private resolveBuildProfile(
|
||||
platform: Platform,
|
||||
buildProfileName: string,
|
||||
buildProfiles: Record<string, BuildProfilePreValidation>,
|
||||
depth: number = 0
|
||||
): Record<string, any> {
|
||||
if (depth >= 2) {
|
||||
throw new Error(
|
||||
'Too long chain of build profile extensions, make sure "extends" keys do not make a cycle'
|
||||
);
|
||||
}
|
||||
const buildProfile = buildProfiles[buildProfileName];
|
||||
if (!buildProfile) {
|
||||
throw new Error(`There is no profile named ${buildProfileName} for platform ${platform}`);
|
||||
}
|
||||
const { extends: baseProfileName, ...buildProfileRest } = buildProfile;
|
||||
if (baseProfileName) {
|
||||
return deepMerge(
|
||||
this.resolveBuildProfile(platform, baseProfileName, buildProfiles, depth + 1),
|
||||
buildProfileRest
|
||||
);
|
||||
} else {
|
||||
return buildProfileRest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isObject(value: any): boolean {
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
|
||||
export function deepMerge(
|
||||
base: Record<string, any>,
|
||||
update: Record<string, any>
|
||||
): Record<string, any> {
|
||||
const result: Record<string, any> = {};
|
||||
Object.keys(base).forEach(key => {
|
||||
const oldValue = base[key];
|
||||
const newValue = update[key];
|
||||
if (isObject(newValue) && isObject(oldValue)) {
|
||||
result[key] = deepMerge(oldValue, newValue);
|
||||
} else if (newValue !== undefined) {
|
||||
result[key] = isObject(newValue) ? deepMerge({}, newValue) : newValue;
|
||||
} else {
|
||||
result[key] = isObject(oldValue) ? deepMerge({}, oldValue) : oldValue;
|
||||
}
|
||||
});
|
||||
Object.keys(update).forEach(key => {
|
||||
const newValue = update[key];
|
||||
if (result[key] === undefined) {
|
||||
result[key] = isObject(newValue) ? deepMerge({}, newValue) : newValue;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import { Android, Ios } from '@expo/eas-build-job';
|
||||
import Joi, { CustomHelpers } from 'joi';
|
||||
|
||||
const semverSchemaCheck = (value: any, helpers: CustomHelpers): any => {
|
||||
if (/^[0-9]+\.[0-9]+\.[0-9]+$/.test(value)) {
|
||||
return value;
|
||||
} else {
|
||||
throw new Error(`${value} is not a valid version`);
|
||||
}
|
||||
};
|
||||
|
||||
const AndroidBuilderEnvironmentSchema = Joi.object({
|
||||
image: Joi.string()
|
||||
.valid(...Android.builderBaseImages)
|
||||
.default('default'),
|
||||
node: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
yarn: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
ndk: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
expoCli: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
env: Joi.object().pattern(Joi.string(), Joi.string().empty(null)).default({}),
|
||||
});
|
||||
|
||||
const IosBuilderEnvironmentSchema = Joi.object({
|
||||
image: Joi.string().valid(...Ios.builderBaseImages),
|
||||
node: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
yarn: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
bundler: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
fastlane: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
cocoapods: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
expoCli: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
env: Joi.object().pattern(Joi.string(), Joi.string().empty(null)).default({}),
|
||||
});
|
||||
|
||||
const CacheSchema = Joi.object({
|
||||
disabled: Joi.boolean().default(false),
|
||||
key: Joi.string().max(128),
|
||||
cacheDefaultPaths: Joi.boolean().default(true),
|
||||
customPaths: Joi.array().items(Joi.string()).default([]),
|
||||
});
|
||||
|
||||
const AndroidSchema = Joi.object({
|
||||
workflow: Joi.string(),
|
||||
credentialsSource: Joi.string().valid('local', 'remote').default('remote'),
|
||||
releaseChannel: Joi.string(),
|
||||
channel: Joi.string(),
|
||||
distribution: Joi.string().valid('store', 'internal').default('store'),
|
||||
cache: CacheSchema.default(),
|
||||
withoutCredentials: Joi.boolean().default(false),
|
||||
|
||||
artifactPath: Joi.string(),
|
||||
gradleCommand: Joi.string(),
|
||||
|
||||
buildType: Joi.alternatives().conditional('distribution', {
|
||||
is: 'internal',
|
||||
then: Joi.string().valid('apk', 'development-client'),
|
||||
otherwise: Joi.string().valid('apk', 'app-bundle', 'development-client'),
|
||||
}),
|
||||
}).concat(AndroidBuilderEnvironmentSchema);
|
||||
|
||||
const IosSchema = Joi.object({
|
||||
workflow: Joi.string(),
|
||||
credentialsSource: Joi.string().valid('local', 'remote').default('remote'),
|
||||
releaseChannel: Joi.string(),
|
||||
channel: Joi.string(),
|
||||
distribution: Joi.string().valid('store', 'internal', 'simulator').default('store'),
|
||||
enterpriseProvisioning: Joi.string().valid('adhoc', 'universal'),
|
||||
autoIncrement: Joi.alternatives()
|
||||
.try(Joi.boolean(), Joi.string().valid('version', 'buildNumber'))
|
||||
.default(false),
|
||||
cache: CacheSchema.default(),
|
||||
|
||||
artifactPath: Joi.string(),
|
||||
scheme: Joi.string(),
|
||||
schemeBuildConfiguration: Joi.string(),
|
||||
|
||||
buildType: Joi.string().valid('release', 'development-client'),
|
||||
}).concat(IosBuilderEnvironmentSchema);
|
||||
|
||||
export const schemaBuildProfileMap: Record<string, Joi.Schema> = {
|
||||
android: AndroidSchema,
|
||||
ios: IosSchema,
|
||||
};
|
||||
|
||||
export const EasJsonSchema = Joi.object({
|
||||
builds: Joi.object({
|
||||
android: Joi.object().pattern(
|
||||
Joi.string(),
|
||||
Joi.object({}).unknown(true) // profile is validated further only if build is for that platform
|
||||
),
|
||||
ios: Joi.object().pattern(
|
||||
Joi.string(),
|
||||
Joi.object({}).unknown(true) // profile is validated further only if build is for that platform
|
||||
),
|
||||
}),
|
||||
});
|
||||
@@ -19,7 +19,13 @@ export interface EasSubmitConfiguration {
|
||||
[Platform.IOS]?: IosSubmitProfile;
|
||||
}
|
||||
|
||||
export interface CliConfig {
|
||||
version?: string;
|
||||
requireCommit?: boolean;
|
||||
}
|
||||
|
||||
export interface EasJson {
|
||||
cli?: CliConfig;
|
||||
build: { [profile: string]: RawBuildProfile };
|
||||
submit?: { [profile: string]: EasSubmitConfiguration };
|
||||
}
|
||||
@@ -4,9 +4,10 @@ import envString from 'env-string';
|
||||
import path from 'path';
|
||||
|
||||
import { BuildProfile } from './EasBuild.types';
|
||||
import { CredentialsSource, EasJson, RawBuildProfile } from './EasJson.types';
|
||||
import { CliConfig, CredentialsSource, EasJson, RawBuildProfile } from './EasJson.types';
|
||||
import {
|
||||
AndroidSubmitProfileSchema,
|
||||
CliConfigSchema,
|
||||
EasJsonSchema,
|
||||
IosSubmitProfileSchema,
|
||||
MinimalEasJsonSchema,
|
||||
@@ -16,8 +17,10 @@ import {
|
||||
IosSubmitProfileFieldsToEvaluate,
|
||||
SubmitProfile,
|
||||
} from './EasSubmit.types';
|
||||
import { InvalidEasJsonError } from './errors';
|
||||
|
||||
interface EasJsonPreValidation {
|
||||
cli?: object;
|
||||
build: { [profile: string]: object };
|
||||
submit?: { [profile: string]: object };
|
||||
}
|
||||
@@ -27,11 +30,24 @@ const defaults = {
|
||||
credentialsSource: CredentialsSource.REMOTE,
|
||||
} as const;
|
||||
|
||||
type LoggerFn = (...args: any[]) => void;
|
||||
|
||||
interface Logger {
|
||||
log: LoggerFn;
|
||||
warn: LoggerFn;
|
||||
}
|
||||
|
||||
export class EasJsonReader {
|
||||
private static log?: Logger;
|
||||
|
||||
public static formatEasJsonPath(projectDir: string): string {
|
||||
return path.join(projectDir, 'eas.json');
|
||||
}
|
||||
|
||||
public static setLog(log: Logger): void {
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
constructor(private projectDir: string) {}
|
||||
|
||||
public async getBuildProfileNamesAsync(): Promise<string[]> {
|
||||
@@ -39,6 +55,29 @@ export class EasJsonReader {
|
||||
return Object.keys(easJson?.build ?? {});
|
||||
}
|
||||
|
||||
public async getCliConfigAsync(): Promise<CliConfig | null> {
|
||||
try {
|
||||
const easJson = await this.readRawAsync();
|
||||
if (!easJson.cli) {
|
||||
return null;
|
||||
}
|
||||
const { value, error } = CliConfigSchema.validate(easJson.cli, {
|
||||
allowUnknown: false,
|
||||
convert: true,
|
||||
abortEarly: false,
|
||||
});
|
||||
if (error) {
|
||||
throw new Error(`"cli" field in eas.json is not valid [${error.toString()}]`);
|
||||
}
|
||||
return value as CliConfig;
|
||||
} catch (err: any) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
public async getSubmitProfileNamesAsync({ throwIfEasJsonDoesNotExist = true } = {}): Promise<
|
||||
string[]
|
||||
> {
|
||||
@@ -81,18 +120,24 @@ export class EasJsonReader {
|
||||
|
||||
public async readSubmitProfileAsync<T extends Platform>(
|
||||
platform: T,
|
||||
profileName?: string
|
||||
profileNameArg?: string
|
||||
): Promise<SubmitProfile<T>> {
|
||||
let profileName = profileNameArg;
|
||||
|
||||
if (!profileName) {
|
||||
const profileNames = await this.getSubmitProfileNamesAsync({
|
||||
throwIfEasJsonDoesNotExist: false,
|
||||
});
|
||||
if (profileNames.includes('release')) {
|
||||
return await this.readSubmitProfileAsync(platform, 'release');
|
||||
|
||||
if (profileNames.includes('production')) {
|
||||
profileName = 'production';
|
||||
} else if (profileNames.includes('release')) {
|
||||
profileName = 'release';
|
||||
} else {
|
||||
return getDefaultSubmitProfile(platform);
|
||||
}
|
||||
}
|
||||
|
||||
const easJson = await this.readAndValidateAsync();
|
||||
const profile = easJson?.submit?.[profileName];
|
||||
if (!profile) {
|
||||
@@ -115,7 +160,7 @@ export class EasJsonReader {
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(`eas.json is not valid [${error.toString()}]`);
|
||||
throw new InvalidEasJsonError(`eas.json is not valid [${error.toString()}]`);
|
||||
}
|
||||
return value as EasJson;
|
||||
}
|
||||
@@ -126,7 +171,7 @@ export class EasJsonReader {
|
||||
const rawEasJson = JsonFile.read(easJsonPath);
|
||||
const { value, error } = MinimalEasJsonSchema.validate(rawEasJson, { abortEarly: false });
|
||||
if (error) {
|
||||
throw new Error(`eas.json is not valid [${error.toString()}]`);
|
||||
throw new InvalidEasJsonError(`eas.json is not valid [${error.toString()}]`);
|
||||
}
|
||||
return value;
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -107,12 +107,19 @@ const EasJsonSubmitConfigurationSchema = Joi.object({
|
||||
ios: IosSubmitProfileSchema,
|
||||
});
|
||||
|
||||
export const CliConfigSchema = Joi.object({
|
||||
version: Joi.string(),
|
||||
requireCommit: Joi.boolean(),
|
||||
});
|
||||
|
||||
export const MinimalEasJsonSchema = Joi.object({
|
||||
cli: Joi.object(),
|
||||
build: Joi.object().pattern(Joi.string(), Joi.object()),
|
||||
submit: Joi.object().pattern(Joi.string(), Joi.object()),
|
||||
});
|
||||
|
||||
export const EasJsonSchema = Joi.object({
|
||||
cli: CliConfigSchema,
|
||||
build: Joi.object().pattern(Joi.string(), EasJsonBuildProfileSchema),
|
||||
submit: Joi.object().pattern(Joi.string(), EasJsonSubmitConfigurationSchema),
|
||||
});
|
||||
@@ -14,13 +14,13 @@ beforeEach(async () => {
|
||||
test('minimal valid eas.json for both platforms', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
release: {},
|
||||
production: {},
|
||||
},
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const iosProfile = await reader.readBuildProfileAsync(Platform.IOS, 'release');
|
||||
const androidProfile = await reader.readBuildProfileAsync(Platform.ANDROID, 'release');
|
||||
const iosProfile = await reader.readBuildProfileAsync(Platform.IOS, 'production');
|
||||
const androidProfile = await reader.readBuildProfileAsync(Platform.ANDROID, 'production');
|
||||
|
||||
expect({
|
||||
distribution: 'store',
|
||||
@@ -36,7 +36,7 @@ test('minimal valid eas.json for both platforms', async () => {
|
||||
test('valid eas.json for development client builds', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
release: {},
|
||||
production: {},
|
||||
debug: {
|
||||
developmentClient: true,
|
||||
android: {
|
||||
@@ -219,7 +219,7 @@ test('valid profile extending other profile with platform specific caching', asy
|
||||
test('valid eas.json with missing profile', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
release: {},
|
||||
production: {},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -231,14 +231,14 @@ test('valid eas.json with missing profile', async () => {
|
||||
test('invalid eas.json when using wrong buildType', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
release: { android: { buildType: 'archive' } },
|
||||
production: { android: { buildType: 'archive' } },
|
||||
},
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'release');
|
||||
const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'production');
|
||||
await expect(promise).rejects.toThrowError(
|
||||
'eas.json is not valid [ValidationError: "build.release.android.buildType" must be one of [apk, app-bundle]]'
|
||||
'eas.json is not valid [ValidationError: "build.production.android.buildType" must be one of [apk, app-bundle]]'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -246,33 +246,33 @@ test('empty json', async () => {
|
||||
await fs.writeJson('/project/eas.json', {});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'release');
|
||||
await expect(promise).rejects.toThrowError('There is no profile named release in eas.json');
|
||||
const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'production');
|
||||
await expect(promise).rejects.toThrowError('There is no profile named production in eas.json');
|
||||
});
|
||||
|
||||
test('invalid semver value', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
release: { node: '12.0.0-alpha' },
|
||||
production: { node: '12.0.0-alpha' },
|
||||
},
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'release');
|
||||
const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'production');
|
||||
await expect(promise).rejects.toThrowError(
|
||||
'eas.json is not valid [ValidationError: "build.release.node" failed custom validation because 12.0.0-alpha is not a valid version]'
|
||||
'eas.json is not valid [ValidationError: "build.production.node" failed custom validation because 12.0.0-alpha is not a valid version]'
|
||||
);
|
||||
});
|
||||
|
||||
test('get profile names', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
release: { node: '12.0.0-alpha' },
|
||||
production: { node: '12.0.0-alpha' },
|
||||
blah: { node: '12.0.0-alpha' },
|
||||
},
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const allProfileNames = await reader.getBuildProfileNamesAsync();
|
||||
expect(allProfileNames.sort()).toEqual(['blah', 'release'].sort());
|
||||
expect(allProfileNames.sort()).toEqual(['blah', 'production'].sort());
|
||||
});
|
||||
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user