Compare commits

..
Author SHA1 Message Date
Quinlan Jung 7a669bccb0 Temporary Commit at 4/23/2024, 12:39:50 PM 2024-04-23 12:39:50 -07:00
Quinlan Jung d33cfaba67 update CHANGELOG.md 2024-04-23 19:35:45 +00:00
Quinlan Jung 7363164889 Temporary Commit at 4/23/2024, 11:21:16 AM 2024-04-23 11:21:16 -07:00
111 changed files with 2186 additions and 4652 deletions

No files matched your search

+7 -4
View File
@@ -69,9 +69,6 @@ jobs:
- uses: actions/setup-node@v2
with:
node-version: 20
- run: brew install python-setuptools
env:
HOMEBREW_NO_AUTO_UPDATE: 1
- run: yarn install --frozen-lockfile --check-files
- run: yarn build
- name: Build tarballs
@@ -231,8 +228,14 @@ jobs:
needs: publish-to-npm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
eas-json:
- 'packages/eas-json/**'
- name: Open a PR with `@expo/eas-json` version bump in universe
if: steps.filter.outputs.eas-json == 'true'
uses: benc-uk/workflow-dispatch@v1
with:
repo: expo/universe
+1 -110
View File
@@ -8,119 +8,10 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features
- Add `target-profile` and `source-profile` flags to the `eas build:resign` command. ([#2410](https://github.com/expo/eas-cli/pull/2410) by [@szdziedzic](https://github.com/szdziedzic))
- Display build profile in the output of `eas build:list`. ([#2408](https://github.com/expo/eas-cli/pull/2408) by [@szdziedzic](https://github.com/szdziedzic))
- Add --no-channel flag for update and rollback to embedded. ([#2339](https://github.com/expo/eas-cli/pull/2339) by [@quinlanj](https://github.com/quinlanj))
### 🐛 Bug fixes
- Use the correct app config for no GitHub flow in `init:onboarding`. ([#2397](https://github.com/expo/eas-cli/pull/2397) by [@szdziedzic](https://github.com/szdziedzic))
- Disallow picking expired builds as submit archive source. ([#2406](https://github.com/expo/eas-cli/pull/2406) by [@sjchmiela](https://github.com/sjchmiela))
### 🧹 Chores
- Print network error message if present. ([#2407](https://github.com/expo/eas-cli/pull/2407) by [@szdziedzic](https://github.com/szdziedzic))
- Make flags for `eas build:list` command more aligned with flags for rest of the commands. ([#2409](https://github.com/expo/eas-cli/pull/2409) by [@szdziedzic](https://github.com/szdziedzic))
## [9.1.0](https://github.com/expo/eas-cli/releases/tag/v9.1.0) - 2024-05-23
### 🎉 New features
- Make `eas init:onboarding` command public. ([#2399](https://github.com/expo/eas-cli/pull/2399) by [@szdziedzic](https://github.com/szdziedzic))
## [9.0.10](https://github.com/expo/eas-cli/releases/tag/v9.0.10) - 2024-05-22
## [9.0.9](https://github.com/expo/eas-cli/releases/tag/v9.0.9) - 2024-05-22
## [9.0.8](https://github.com/expo/eas-cli/releases/tag/v9.0.8) - 2024-05-21
### 🧹 Chores
- Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies. ([#2387](https://github.com/expo/eas-cli/pull/2387) by [@expo-bot](https://github.com/expo-bot))
- Improve displaying of device registration QR code. ([#2391](https://github.com/expo/eas-cli/pull/2391) by [@szdziedzic](https://github.com/szdziedzic))
## [9.0.7](https://github.com/expo/eas-cli/releases/tag/v9.0.7) - 2024-05-17
## [9.0.6](https://github.com/expo/eas-cli/releases/tag/v9.0.6) - 2024-05-15
## [9.0.5](https://github.com/expo/eas-cli/releases/tag/v9.0.5) - 2024-05-13
## [9.0.4](https://github.com/expo/eas-cli/releases/tag/v9.0.4) - 2024-05-13
### 🧹 Chores
- Add loader and progress information to the `diagnostics` command. ([#2378](https://github.com/expo/eas-cli/pull/2378) by [@simek](https://github.com/simek))
## [9.0.3](https://github.com/expo/eas-cli/releases/tag/v9.0.3) - 2024-05-10
### 🧹 Chores
- Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies. ([#2372](https://github.com/expo/eas-cli/pull/2372) by [@expo-bot](https://github.com/expo-bot))
## [9.0.2](https://github.com/expo/eas-cli/releases/tag/v9.0.2) - 2024-05-09
## [9.0.1](https://github.com/expo/eas-cli/releases/tag/v9.0.1) - 2024-05-09
### 🧹 Chores
- Update image tags in `eas.schema.json`. ([#2363](https://github.com/expo/eas-cli/pull/2363) by [@szdziedzic](https://github.com/szdziedzic))
## [9.0.0](https://github.com/expo/eas-cli/releases/tag/v9.0.0) - 2024-05-08
### 🛠 Breaking changes
- Allow modification of provisioning profile in CI, add --freeze-credentials flag. ([#2347](https://github.com/expo/eas-cli/pull/2347) by [@quinlanj](https://github.com/quinlanj))
### 🐛 Bug fixes
- Pass env from process.env and build profile to expo-updates CLI calls where applicable. ([#2359](https://github.com/expo/eas-cli/pull/2359) by [@wschurman](https://github.com/wschurman))
### 🧹 Chores
- Remove more classic updates code. ([#2357](https://github.com/expo/eas-cli/pull/2357) by [@wschurman](https://github.com/wschurman))
- Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies. ([#2360](https://github.com/expo/eas-cli/pull/2360) by [@expo-bot](https://github.com/expo-bot))
- Don't pass custom global `"expoCli"` version set in `eas.json` to EAS Build process and warn users when setting it. ([#2361](https://github.com/expo/eas-cli/pull/2361) by [@szdziedzic](https://github.com/szdziedzic))
## [8.0.0](https://github.com/expo/eas-cli/releases/tag/v8.0.0) - 2024-05-01
### 🛠 Breaking changes
- Stop creating a channel on `eas update` and `eas update:roll-back-to-embedded` unless the `--channel` flag is specified. ([#2346](https://github.com/expo/eas-cli/pull/2346) by [@quinlanj](https://github.com/quinlanj))
### 🐛 Bug fixes
- Improve login info message for other login options. ([#2352](https://github.com/expo/eas-cli/pull/2352) by [@wschurman](https://github.com/wschurman))
- Show the -s, --sso option in the login command help. ([#2353](https://github.com/expo/eas-cli/pull/2353) by [@lzkb](https://github.com/lzkb))
### 🧹 Chores
- Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies. ([#2351](https://github.com/expo/eas-cli/pull/2351) by [@expo-bot](https://github.com/expo-bot))
- Don't prompt users to set push notifications by default if they don't have the `expo-notifications` installed. ([#2343](https://github.com/expo/eas-cli/pull/2343) by [@szdziedzic](https://github.com/szdziedzic))
## [7.8.5](https://github.com/expo/eas-cli/releases/tag/v7.8.5) - 2024-04-26
### 🐛 Bug fixes
- Add explicit workflow arg to expo-update CLI calls. ([#2340](https://github.com/expo/eas-cli/pull/2340) by [@wschurman](https://github.com/wschurman))
### 🧹 Chores
- Improve error message if the server returns `UNAUTHORIZED_ERROR`. ([#2345](https://github.com/expo/eas-cli/pull/2345) by [@szdziedzic](https://github.com/szdziedzic))
- Fill in min expo-updates version for expo-updates CLI. ([#2344](https://github.com/expo/eas-cli/pull/2344) by [@wschurman](https://github.com/wschurman))
## [7.8.4](https://github.com/expo/eas-cli/releases/tag/v7.8.4) - 2024-04-24
### 🧹 Chores
- Update the list of available Android images. ([#2337](https://github.com/expo/eas-cli/pull/2337) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
- Make multi-select for revoking distribution certificates more readable. ([#2342](https://github.com/expo/eas-cli/pull/2342) by [@szdziedzic](https://github.com/szdziedzic))
- Improve error message displayed when EAS CLI version doesn't satisfy minimal version required specified in eas.json. ([#2341](https://github.com/expo/eas-cli/pull/2341) by [@szdziedzic](https://github.com/szdziedzic))
## [7.8.3](https://github.com/expo/eas-cli/releases/tag/v7.8.3) - 2024-04-23
### 🐛 Bug fixes
- Don't prompt to set `android.package` and `ios.bundleIdentifier` values when running in non-interactive mode. ([#2336](https://github.com/expo/eas-cli/pull/2336) by [@szdziedzic](https://github.com/szdziedzic))
### 🧹 Chores
- Amend credential removal wording. ([#2334](https://github.com/expo/eas-cli/pull/2334) by [@quinlanj](https://github.com/quinlanj))
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"version": "9.2.0",
"version": "7.8.2",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
+86 -159
View File
@@ -94,17 +94,14 @@ eas --help COMMAND
* [`eas diagnostics`](#eas-diagnostics)
* [`eas help [COMMAND]`](#eas-help-command)
* [`eas init`](#eas-init)
* [`eas init:onboarding [TARGET_PROJECT_DIRECTORY]`](#eas-initonboarding-target_project_directory)
* [`eas login`](#eas-login)
* [`eas logout`](#eas-logout)
* [`eas metadata:lint`](#eas-metadatalint)
* [`eas metadata:pull`](#eas-metadatapull)
* [`eas metadata:push`](#eas-metadatapush)
* [`eas onboarding [TARGET_PROJECT_DIRECTORY]`](#eas-onboarding-target_project_directory)
* [`eas open`](#eas-open)
* [`eas project:info`](#eas-projectinfo)
* [`eas project:init`](#eas-projectinit)
* [`eas project:onboarding [TARGET_PROJECT_DIRECTORY]`](#eas-projectonboarding-target_project_directory)
* [`eas secret:create`](#eas-secretcreate)
* [`eas secret:delete`](#eas-secretdelete)
* [`eas secret:list`](#eas-secretlist)
@@ -131,10 +128,7 @@ log in with your Expo account
```
USAGE
$ eas account:login [-s]
FLAGS
-s, --sso Login with SSO
$ eas account:login
DESCRIPTION
log in with your Expo account
@@ -143,7 +137,7 @@ ALIASES
$ eas login
```
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/account/login.ts)_
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -160,7 +154,7 @@ ALIASES
$ eas logout
```
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/account/logout.ts)_
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view`
@@ -177,7 +171,7 @@ ALIASES
$ eas whoami
```
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/account/view.ts)_
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -191,7 +185,7 @@ DESCRIPTION
display or change analytics settings
```
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/analytics.ts)_
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]`
@@ -243,7 +237,7 @@ DESCRIPTION
create a branch
```
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/branch/create.ts)_
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -264,7 +258,7 @@ DESCRIPTION
delete a branch
```
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/branch/delete.ts)_
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -284,7 +278,7 @@ DESCRIPTION
list all branches
```
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/branch/list.ts)_
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -304,7 +298,7 @@ DESCRIPTION
rename a branch
```
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/branch/rename.ts)_
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -327,7 +321,7 @@ DESCRIPTION
view a branch
```
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/branch/view.ts)_
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build`
@@ -337,7 +331,7 @@ start a build
USAGE
$ eas build [-p android|ios|all] [-e <value>] [--local] [--output <value>] [--wait] [--clear-cache]
[--auto-submit | --auto-submit-with-profile <value>] [-m <value>] [--build-logger-level
trace|debug|info|warn|error|fatal] [--freeze-credentials] [--json --non-interactive]
trace|debug|info|warn|error|fatal] [--json --non-interactive]
FLAGS
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to
@@ -351,8 +345,6 @@ FLAGS
--build-logger-level=(trace|debug|info|warn|error|fatal) The level of logs to output during the build process.
Defaults to "info".
--clear-cache Clear cache before the build
--freeze-credentials Prevent the build from updating credentials in
non-interactive mode
--json Enable JSON output, non-JSON messages will be printed to
stderr.
--local Run build locally [experimental]
@@ -364,7 +356,7 @@ DESCRIPTION
start a build
```
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/build/index.ts)_
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -383,7 +375,7 @@ DESCRIPTION
cancel a build
```
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/build/cancel.ts)_
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -400,7 +392,7 @@ DESCRIPTION
configure the project to support EAS Build
```
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/build/configure.ts)_
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:delete [BUILD_ID]`
@@ -419,7 +411,7 @@ DESCRIPTION
delete a build
```
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/build/delete.ts)_
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/delete.ts)_
## `eas build:inspect`
@@ -454,7 +446,7 @@ DESCRIPTION
inspect the state of the project at specific build stages, useful for troubleshooting
```
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/build/inspect.ts)_
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -462,27 +454,20 @@ list all builds for your project
```
USAGE
$ eas build:list [-p android|ios|all] [--status
$ eas build:list [--platform all|android|ios] [--status
new|in-queue|in-progress|pending-cancel|errored|finished|canceled] [--distribution store|internal|simulator]
[--channel <value>] [--app-version <value>] [--app-build-version <value>] [--sdk-version <value>] [--runtime-version
<value>] [--app-identifier <value>] [-e <value>] [--git-commit-hash <value>] [--offset <value>] [--limit <value>]
[--json --non-interactive] [--simulator]
[--channel <value>] [--appVersion <value>] [--appBuildVersion <value>] [--sdkVersion <value>] [--runtimeVersion
<value>] [--appIdentifier <value>] [--buildProfile <value>] [--gitCommitHash <value>] [--offset <value>] [--limit
<value>] [--json --non-interactive] [--simulator]
FLAGS
-e, --build-profile=<value> Filter only builds created with the
specified build profile
-p, --platform=(android|ios|all)
--app-build-version=<value> Filter only builds created with the
specified app build version
--app-identifier=<value> Filter only builds created with the
specified app identifier
--app-version=<value> Filter only builds created with the
specified main app version
--appBuildVersion=<value>
--appIdentifier=<value>
--appVersion=<value>
--buildProfile=<value>
--channel=<value>
--distribution=(store|internal|simulator) Filter only builds with the specified
distribution type
--git-commit-hash=<value> Filter only builds created with the
specified git commit hash
--distribution=(store|internal|simulator)
--gitCommitHash=<value>
--json Enable JSON output, non-JSON messages
will be printed to stderr.
--limit=<value> The number of items to fetch each query.
@@ -490,21 +475,19 @@ FLAGS
--non-interactive Run the command in non-interactive mode.
--offset=<value> Start queries from specified index. Use
for paginating results. Defaults to 0.
--runtime-version=<value> Filter only builds created with the
specified runtime version
--sdk-version=<value> Filter only builds created with the
specified Expo SDK version
--platform=(all|android|ios)
--runtimeVersion=<value>
--sdkVersion=<value>
--simulator Filter only iOS simulator builds. Can
only be used with --platform flag set to
"ios"
--status=(new|in-queue|in-progress|pending-cancel|errored|finished|canceled) Filter only builds with the specified
status
--status=(new|in-queue|in-progress|pending-cancel|errored|finished|canceled)
DESCRIPTION
list all builds for your project
```
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/build/list.ts)_
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:resign`
@@ -512,28 +495,25 @@ re-sign a build archive
```
USAGE
$ eas build:resign [-p android|ios] [-e <value>] [--source-profile <value>] [--wait] [--id <value>] [--offset
<value>] [--limit <value>] [--json --non-interactive]
$ eas build:resign [-p android|ios] [-e <value>] [--wait] [--id <value>] [--offset <value>] [--limit <value>]
[--json --non-interactive]
FLAGS
-e, --target-profile=PROFILE_NAME Name of the target build profile from eas.json. Credentials and environment
variables from this profile will be used when re-signing. Defaults to "production"
if defined in eas.json.
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined in
eas.json.
-p, --platform=(android|ios)
--id=<value> ID of the build to re-sign.
--json Enable JSON output, non-JSON messages will be printed to stderr.
--limit=<value> The number of items to fetch each query. Defaults to 50 and is capped at 100.
--non-interactive Run the command in non-interactive mode.
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
--source-profile=PROFILE_NAME Name of the source build profile from eas.json. Used to filter builds eligible for
re-signing.
--[no-]wait Wait for build(s) to complete.
--id=<value> ID of the build to re-sign.
--json Enable JSON output, non-JSON messages will be printed to stderr.
--limit=<value> The number of items to fetch each query. Defaults to 50 and is capped at 100.
--non-interactive Run the command in non-interactive mode.
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
--[no-]wait Wait for build(s) to complete.
DESCRIPTION
re-sign a build archive
```
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/build/resign.ts)_
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/resign.ts)_
## `eas build:run`
@@ -559,7 +539,7 @@ DESCRIPTION
run simulator/emulator builds from eas-cli
```
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/build/run.ts)_
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/run.ts)_
## `eas build:submit`
@@ -609,7 +589,7 @@ DESCRIPTION
get the latest version from EAS servers
```
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/build/version/get.ts)_
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/version/get.ts)_
## `eas build:version:set`
@@ -628,7 +608,7 @@ DESCRIPTION
update version of an app
```
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/build/version/set.ts)_
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/version/set.ts)_
## `eas build:version:sync`
@@ -647,7 +627,7 @@ DESCRIPTION
update a version in native code with a value stored on EAS servers
```
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/build/version/sync.ts)_
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]`
@@ -664,7 +644,7 @@ DESCRIPTION
view a build for your project
```
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/build/view.ts)_
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -685,7 +665,7 @@ DESCRIPTION
create a channel
```
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/channel/create.ts)_
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:edit [NAME]`
@@ -707,7 +687,7 @@ DESCRIPTION
point a channel at a new branch
```
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/channel/edit.ts)_
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list`
@@ -727,7 +707,7 @@ DESCRIPTION
list all channels
```
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/channel/list.ts)_
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:rollout [CHANNEL]`
@@ -759,7 +739,7 @@ DESCRIPTION
Roll a new branch out on a channel incrementally.
```
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/channel/rollout.ts)_
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/channel/rollout.ts)_
## `eas channel:view [NAME]`
@@ -782,7 +762,7 @@ DESCRIPTION
view a channel
```
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/channel/view.ts)_
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -803,7 +783,7 @@ DESCRIPTION
display project configuration (app.json + eas.json)
```
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/config.ts)_
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/config.ts)_
## `eas credentials`
@@ -820,7 +800,7 @@ DESCRIPTION
manage credentials
```
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/credentials/index.ts)_
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/credentials/index.ts)_
## `eas credentials:configure-build`
@@ -838,7 +818,7 @@ DESCRIPTION
Set up credentials for building your project.
```
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/credentials/configure-build.ts)_
## `eas device:create`
@@ -852,7 +832,7 @@ DESCRIPTION
register new Apple Devices to use for internal distribution
```
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/device/create.ts)_
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -872,7 +852,7 @@ DESCRIPTION
remove a registered device from your account
```
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/device/delete.ts)_
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -893,7 +873,7 @@ DESCRIPTION
list all registered devices for your account
```
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/device/list.ts)_
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:rename`
@@ -914,7 +894,7 @@ DESCRIPTION
rename a registered device
```
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/device/rename.ts)_
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/device/rename.ts)_
## `eas device:view [UDID]`
@@ -928,7 +908,7 @@ DESCRIPTION
view a device for your project
```
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/device/view.ts)_
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -942,7 +922,7 @@ DESCRIPTION
display environment info
```
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/diagnostics.ts)_
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas help [COMMAND]`
@@ -984,32 +964,13 @@ ALIASES
$ eas init
```
## `eas init:onboarding [TARGET_PROJECT_DIRECTORY]`
continue onboarding process started on the https://expo.new website.
```
USAGE
$ eas init:onboarding [TARGET_PROJECT_DIRECTORY]
DESCRIPTION
continue onboarding process started on the https://expo.new website.
ALIASES
$ eas init:onboarding
$ eas onboarding
```
## `eas login`
log in with your Expo account
```
USAGE
$ eas login [-s]
FLAGS
-s, --sso Login with SSO
$ eas login
DESCRIPTION
log in with your Expo account
@@ -1049,7 +1010,7 @@ DESCRIPTION
validate the local store configuration
```
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/metadata/lint.ts)_
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/metadata/lint.ts)_
## `eas metadata:pull`
@@ -1066,7 +1027,7 @@ DESCRIPTION
generate the local store configuration from the app stores
```
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/metadata/pull.ts)_
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push`
@@ -1083,23 +1044,7 @@ DESCRIPTION
sync the local store configuration to the app stores
```
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas onboarding [TARGET_PROJECT_DIRECTORY]`
continue onboarding process started on the https://expo.new website.
```
USAGE
$ eas onboarding [TARGET_PROJECT_DIRECTORY]
DESCRIPTION
continue onboarding process started on the https://expo.new website.
ALIASES
$ eas init:onboarding
$ eas onboarding
```
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas open`
@@ -1113,7 +1058,7 @@ DESCRIPTION
open the project page in a web browser
```
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/open.ts)_
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/open.ts)_
## `eas project:info`
@@ -1127,7 +1072,7 @@ DESCRIPTION
information about the current project
```
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/project/info.ts)_
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -1149,25 +1094,7 @@ ALIASES
$ eas init
```
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/project/init.ts)_
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
continue onboarding process started on the https://expo.new website.
```
USAGE
$ eas project:onboarding [TARGET_PROJECT_DIRECTORY]
DESCRIPTION
continue onboarding process started on the https://expo.new website.
ALIASES
$ eas init:onboarding
$ eas onboarding
```
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/project/onboarding.ts)_
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/project/init.ts)_
## `eas secret:create`
@@ -1190,7 +1117,7 @@ DESCRIPTION
create an environment secret on the current project or owner account
```
_See code: [packages/eas-cli/src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/secret/create.ts)_
_See code: [packages/eas-cli/src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/secret/create.ts)_
## `eas secret:delete`
@@ -1208,7 +1135,7 @@ DESCRIPTION
delete an environment secret by ID
```
_See code: [packages/eas-cli/src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/secret/delete.ts)_
_See code: [packages/eas-cli/src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/secret/delete.ts)_
## `eas secret:list`
@@ -1222,7 +1149,7 @@ DESCRIPTION
list environment secrets available for your current app
```
_See code: [packages/eas-cli/src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/secret/list.ts)_
_See code: [packages/eas-cli/src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/secret/list.ts)_
## `eas secret:push`
@@ -1242,7 +1169,7 @@ DESCRIPTION
read environment secrets from env file and store on the server
```
_See code: [packages/eas-cli/src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/secret/push.ts)_
_See code: [packages/eas-cli/src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/secret/push.ts)_
## `eas submit`
@@ -1273,7 +1200,7 @@ ALIASES
$ eas build:submit
```
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/submit.ts)_
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/submit.ts)_
## `eas update`
@@ -1306,7 +1233,7 @@ DESCRIPTION
publish an update group
```
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/update/index.ts)_
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -1324,7 +1251,7 @@ DESCRIPTION
configure the project to support EAS Update
```
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/update/configure.ts)_
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -1345,7 +1272,7 @@ DESCRIPTION
delete all the updates in an update group
```
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/update/delete.ts)_
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:list`
@@ -1367,7 +1294,7 @@ DESCRIPTION
view the recent updates
```
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/update/list.ts)_
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:republish`
@@ -1394,7 +1321,7 @@ DESCRIPTION
roll back to an existing update
```
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/update/republish.ts)_
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/republish.ts)_
## `eas update:roll-back-to-embedded`
@@ -1421,7 +1348,7 @@ DESCRIPTION
roll back to the embedded update
```
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
## `eas update:rollback`
@@ -1440,7 +1367,7 @@ DESCRIPTION
roll back to an embedded update or an existing update
```
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/update/rollback.ts)_
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/rollback.ts)_
## `eas update:view GROUPID`
@@ -1460,7 +1387,7 @@ DESCRIPTION
update group details
```
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/update/view.ts)_
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/view.ts)_
## `eas webhook:create`
@@ -1481,7 +1408,7 @@ DESCRIPTION
create a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/webhook/create.ts)_
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -1501,7 +1428,7 @@ DESCRIPTION
delete a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/webhook/delete.ts)_
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -1519,7 +1446,7 @@ DESCRIPTION
list webhooks
```
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/webhook/list.ts)_
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -1541,7 +1468,7 @@ DESCRIPTION
update a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/webhook/update.ts)_
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -1558,7 +1485,7 @@ DESCRIPTION
view a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v9.2.0/packages/eas-cli/src/commands/webhook/view.ts)_
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
+1125 -2086
View File
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "9.2.0",
"version": "7.8.2",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
@@ -13,8 +13,8 @@
"@expo/config": "8.5.4",
"@expo/config-plugins": "7.8.4",
"@expo/config-types": "50.0.0",
"@expo/eas-build-job": "1.0.114",
"@expo/eas-json": "9.0.8",
"@expo/eas-build-job": "1.0.95",
"@expo/eas-json": "7.8.0",
"@expo/json-file": "8.2.37",
"@expo/logger": "1.0.57",
"@expo/multipart-body-parser": "1.1.0",
@@ -28,7 +28,7 @@
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.0",
"@expo/steps": "1.0.114",
"@expo/steps": "1.0.95",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "^1.26.2",
"@oclif/plugin-autocomplete": "^2.3.10",
@@ -24,7 +24,6 @@ export function getMockAppFragment(): AppFragment {
return {
id: mockProjectId,
slug: 'testapp',
name: 'testapp',
fullName: '@testuser/testpp',
ownerAccount: {
id: 'test-account-id',
@@ -107,7 +107,6 @@ export async function prepareAndroidBuildAsync(
? false
: ctx.buildProfile.autoIncrement,
vcsClient: ctx.vcsClient,
env: ctx.buildProfile.env,
});
},
prepareJobAsync: async (
@@ -15,6 +15,7 @@ export function transformJob(job: Android.Job): AndroidJobInput {
triggeredBy: transformBuildTrigger(job.triggeredBy),
projectArchive: transformProjectArchive(job.projectArchive),
projectRootDirectory: job.projectRootDirectory,
releaseChannel: job.releaseChannel,
updates: job.updates,
secrets: job.secrets,
builderEnvironment: job.builderEnvironment,
@@ -56,15 +56,6 @@ export async function prepareJobAsync(
? getCustomBuildConfigPathForJob(buildProfile.config)
: undefined;
let buildMode;
if (ctx.repack) {
buildMode = BuildMode.REPACK;
} else if (buildProfile.config) {
buildMode = BuildMode.CUSTOM;
} else {
buildMode = BuildMode.BUILD;
}
const job: Android.Job = {
type: ctx.workflow,
platform: Platform.ANDROID,
@@ -77,6 +68,7 @@ export async function prepareJobAsync(
bun: buildProfile.bun,
yarn: buildProfile.yarn,
ndk: buildProfile.ndk,
expoCli: buildProfile.expoCli,
env: buildProfile.env,
},
cache: {
@@ -87,6 +79,7 @@ export async function prepareJobAsync(
secrets: {
...buildCredentials,
},
releaseChannel: buildProfile.releaseChannel,
updates: { channel: buildProfile.channel },
developmentClient: buildProfile.developmentClient,
gradleCommand: buildProfile.gradleCommand,
@@ -102,18 +95,13 @@ export async function prepareJobAsync(
experimental: {
prebuildCommand: buildProfile.prebuildCommand,
},
mode: buildMode,
mode: buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
triggeredBy: BuildTrigger.EAS_CLI,
...(maybeCustomBuildConfigPath && {
customBuildConfig: {
path: maybeCustomBuildConfigPath,
},
}),
...(ctx.repack && {
customBuildConfig: {
path: '__eas/repack.yml',
},
}),
loggerLevel: ctx.loggerLevel,
};
@@ -1,6 +1,6 @@
import { ExpoConfig } from '@expo/config';
import { AndroidConfig } from '@expo/config-plugins';
import { Env, Platform, Workflow } from '@expo/eas-build-job';
import { Platform, Workflow } from '@expo/eas-build-job';
import { AndroidVersionAutoIncrement } from '@expo/eas-json';
import chalk from 'chalk';
import fs from 'fs-extra';
@@ -18,13 +18,11 @@ export async function syncProjectConfigurationAsync({
exp,
localAutoIncrement,
vcsClient,
env,
}: {
projectDir: string;
exp: ExpoConfig;
localAutoIncrement?: AndroidVersionAutoIncrement;
vcsClient: Client;
env: Env | undefined;
}): Promise<void> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
@@ -32,7 +30,7 @@ export async function syncProjectConfigurationAsync({
if (workflow === Workflow.GENERIC) {
await cleanUpOldEasBuildGradleScriptAsync(projectDir);
if (isExpoUpdatesInstalled(projectDir)) {
await syncUpdatesConfigurationAsync({ projectDir, exp, workflow, env });
await syncUpdatesConfigurationAsync(projectDir, exp);
}
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
} else {
+23 -18
View File
@@ -275,23 +275,20 @@ async function uploadProjectAsync<TPlatform extends Platform>(
}
projectTarballPath = projectTarball.path;
const [bucketKey, { metadataLocation }] = await Promise.all([
uploadFileAtPathToGCSAsync(
ctx.graphqlClient,
UploadSessionType.EasBuildGcsProjectSources,
projectTarball.path,
createProgressTracker({
total: projectTarball.size,
message: ratio =>
`Uploading to EAS Build (${formatBytes(
projectTarball.size * ratio
)} / ${formatBytes(projectTarball.size)})`,
completedMessage: (duration: string) => `Uploaded to EAS ${chalk.dim(duration)}`,
})
),
uploadMetadataFileAsync<TPlatform>(projectTarball, ctx),
]);
const bucketKey = await uploadFileAtPathToGCSAsync(
ctx.graphqlClient,
UploadSessionType.EasBuildGcsProjectSources,
projectTarball.path,
createProgressTracker({
total: projectTarball.size,
message: ratio =>
`Uploading to EAS Build (${formatBytes(projectTarball.size * ratio)} / ${formatBytes(
projectTarball.size
)})`,
completedMessage: (duration: string) => `Uploaded to EAS ${chalk.dim(duration)}`,
})
);
const { metadataLocation } = await uploadMetadataFileAsync<TPlatform>(projectTarball, ctx);
if (metadataLocation) {
return { bucketKey, metadataLocation };
}
@@ -330,7 +327,15 @@ async function uploadMetadataFileAsync<TPlatform extends Platform>(
const metadataLocation = await uploadFileAtPathToGCSAsync(
ctx.graphqlClient,
UploadSessionType.EasBuildGcsProjectMetadata,
projectMetadataFile.path
projectMetadataFile.path,
createProgressTracker({
total: projectMetadataFile.size,
message: ratio =>
`Uploading metadata to EAS Build (${formatBytes(
projectMetadataFile!.size * ratio
)} / ${formatBytes(projectMetadataFile!.size)})`,
completedMessage: (duration: string) => `Uploaded to EAS ${chalk.dim(duration)}`,
})
);
return { metadataLocation };
} catch (err: any) {
-1
View File
@@ -62,5 +62,4 @@ export interface BuildContext<T extends Platform> {
requiredPackageManager: NodePackageManager['name'] | null;
vcsClient: Client;
loggerLevel?: LoggerLevel;
repack: boolean;
}
@@ -41,8 +41,6 @@ export async function createBuildContextAsync<T extends Platform>({
getDynamicPrivateProjectConfigAsync,
customBuildConfigMetadata,
buildLoggerLevel,
freezeCredentials,
repack,
}: {
buildProfileName: string;
buildProfile: BuildProfile<T>;
@@ -62,8 +60,6 @@ export async function createBuildContextAsync<T extends Platform>({
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
customBuildConfigMetadata?: CustomBuildConfigMetadata;
buildLoggerLevel?: LoggerLevel;
freezeCredentials: boolean;
repack: boolean;
}): Promise<BuildContext<T>> {
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env });
const projectName = exp.slug;
@@ -90,7 +86,6 @@ export async function createBuildContextAsync<T extends Platform>({
env: buildProfile.env,
easJsonCliConfig,
vcsClient,
freezeCredentials,
});
const devClientProperties = getDevClientEventProperties({
@@ -145,7 +140,6 @@ export async function createBuildContextAsync<T extends Platform>({
developmentClient,
requiredPackageManager,
loggerLevel: buildLoggerLevel,
repack,
};
if (platform === Platform.ANDROID) {
const common = commonContext as CommonContext<Platform.ANDROID>;
-1
View File
@@ -94,7 +94,6 @@ const buildModeToGraphQLBuildMode: Record<BuildMode, GraphQLBuildMode> = {
[BuildMode.BUILD]: GraphQLBuildMode.Build,
[BuildMode.CUSTOM]: GraphQLBuildMode.Custom,
[BuildMode.RESIGN]: GraphQLBuildMode.Resign,
[BuildMode.REPACK]: GraphQLBuildMode.Repack,
};
export function transformBuildMode(buildMode: BuildMode): GraphQLBuildMode {
-1
View File
@@ -89,7 +89,6 @@ export async function prepareIosBuildAsync(
? false
: ctx.buildProfile.autoIncrement,
vcsClient: ctx.vcsClient,
env: ctx.buildProfile.env,
});
},
prepareJobAsync: async (
@@ -16,6 +16,7 @@ export function transformJob(job: Ios.Job): IosJobInput {
triggeredBy: transformBuildTrigger(job.triggeredBy),
projectArchive: transformProjectArchive(job.projectArchive),
projectRootDirectory: nullthrows(job.projectRootDirectory),
releaseChannel: job.releaseChannel,
updates: job.updates,
secrets: job.secrets ? transformIosSecrets(job.secrets) : undefined,
builderEnvironment: job.builderEnvironment,
+3 -15
View File
@@ -48,15 +48,6 @@ export async function prepareJobAsync(
? getCustomBuildConfigPathForJob(buildProfile.config)
: undefined;
let buildMode;
if (ctx.repack) {
buildMode = BuildMode.REPACK;
} else if (buildProfile.config) {
buildMode = BuildMode.CUSTOM;
} else {
buildMode = BuildMode.BUILD;
}
const job: Ios.Job = {
type: ctx.workflow,
platform: Platform.IOS,
@@ -71,6 +62,7 @@ export async function prepareJobAsync(
bundler: buildProfile.bundler,
cocoapods: buildProfile.cocoapods,
fastlane: buildProfile.fastlane,
expoCli: buildProfile.expoCli,
env: buildProfile.env,
},
cache: {
@@ -81,6 +73,7 @@ export async function prepareJobAsync(
secrets: {
buildCredentials,
},
releaseChannel: buildProfile.releaseChannel,
updates: { channel: buildProfile.channel },
developmentClient: buildProfile.developmentClient,
simulator: buildProfile.simulator,
@@ -97,18 +90,13 @@ export async function prepareJobAsync(
experimental: {
prebuildCommand: buildProfile.prebuildCommand,
},
mode: buildMode,
mode: buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
triggeredBy: BuildTrigger.EAS_CLI,
...(maybeCustomBuildConfigPath && {
customBuildConfig: {
path: maybeCustomBuildConfigPath,
},
}),
...(ctx.repack && {
customBuildConfig: {
path: '__eas/repack.yml',
},
}),
loggerLevel: ctx.loggerLevel,
};
return sanitizeBuildJob(job) as Ios.Job;
@@ -1,5 +1,5 @@
import { ExpoConfig } from '@expo/config';
import { Env, Platform, Workflow } from '@expo/eas-build-job';
import { Platform, Workflow } from '@expo/eas-build-job';
import { IosVersionAutoIncrement } from '@expo/eas-json';
import { BumpStrategy, bumpVersionAsync, bumpVersionInAppJsonAsync } from './version';
@@ -15,21 +15,19 @@ export async function syncProjectConfigurationAsync({
targets,
localAutoIncrement,
vcsClient,
env,
}: {
projectDir: string;
exp: ExpoConfig;
targets: Target[];
localAutoIncrement?: IosVersionAutoIncrement;
vcsClient: Client;
env: Env | undefined;
}): Promise<void> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS, vcsClient);
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
if (workflow === Workflow.GENERIC) {
if (isExpoUpdatesInstalled(projectDir)) {
await syncUpdatesConfigurationAsync({ vcsClient, projectDir, exp, workflow, env });
await syncUpdatesConfigurationAsync(vcsClient, projectDir, exp);
}
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, targets });
} else {
+1 -1
View File
@@ -6,7 +6,7 @@ import semver from 'semver';
import { ora } from '../ora';
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
const PLUGIN_PACKAGE_VERSION = '1.0.116';
const PLUGIN_PACKAGE_VERSION = '1.0.100';
export enum LocalBuildMode {
/**
+43 -17
View File
@@ -9,16 +9,26 @@ import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios
import { LocalBuildMode } from './local';
import { BuildDistributionType } from './types';
import Log from '../log';
import { getUsername, isExpoUpdatesInstalled } from '../project/projectUtils';
import {
getUsername,
isClassicUpdatesSupportedAsync,
isExpoUpdatesInstalled,
} from '../project/projectUtils';
import { resolveRuntimeVersionAsync } from '../project/resolveRuntimeVersionAsync';
import { readChannelSafelyAsync as readAndroidChannelSafelyAsync } from '../update/android/UpdatesModule';
import { readChannelSafelyAsync as readIosChannelSafelyAsync } from '../update/ios/UpdatesModule';
import {
readChannelSafelyAsync as readAndroidChannelSafelyAsync,
readReleaseChannelSafelyAsync as readAndroidReleaseChannelSafelyAsync,
} from '../update/android/UpdatesModule';
import {
readChannelSafelyAsync as readIosChannelSafelyAsync,
readReleaseChannelSafelyAsync as readIosReleaseChannelSafelyAsync,
} from '../update/ios/UpdatesModule';
import { easCliVersion } from '../utils/easCli';
export async function collectMetadataAsync<T extends Platform>(
ctx: BuildContext<T>
): Promise<Metadata> {
const channelObject = await resolveChannelAsync(ctx);
const channelOrReleaseChannel = await resolveChannelOrReleaseChannelAsync(ctx);
const distribution = ctx.buildProfile.distribution ?? BuildDistributionType.STORE;
const metadata: Metadata = {
trackingContext: ctx.analyticsEventProperties,
@@ -27,17 +37,9 @@ export async function collectMetadataAsync<T extends Platform>(
workflow: ctx.workflow,
credentialsSource: ctx.buildProfile.credentialsSource,
sdkVersion: ctx.exp.sdkVersion,
runtimeVersion:
(await resolveRuntimeVersionAsync({
exp: ctx.exp,
platform: ctx.platform,
workflow: ctx.workflow,
projectDir: ctx.projectDir,
env: ctx.buildProfile.env,
cwd: ctx.projectDir,
})) ?? undefined,
runtimeVersion: (await resolveRuntimeVersionAsync(ctx)) ?? undefined,
reactNativeVersion: await getReactNativeVersionAsync(ctx.projectDir),
...channelObject,
...channelOrReleaseChannel,
distribution,
appName: ctx.exp.name,
appIdentifier: resolveAppIdentifier(ctx),
@@ -115,9 +117,9 @@ function resolveAppIdentifier<T extends Platform>(ctx: BuildContext<T>): string
}
}
async function resolveChannelAsync<T extends Platform>(
async function resolveChannelOrReleaseChannelAsync<T extends Platform>(
ctx: BuildContext<T>
): Promise<{ channel: string } | null> {
): Promise<{ channel: string } | { releaseChannel: string } | null> {
if (!isExpoUpdatesInstalled(ctx.projectDir)) {
return null;
}
@@ -129,7 +131,31 @@ async function resolveChannelAsync<T extends Platform>(
return { channel };
}
return null;
if (!(await isClassicUpdatesSupportedAsync(ctx.projectDir))) {
return null;
}
if (ctx.buildProfile.releaseChannel) {
return { releaseChannel: ctx.buildProfile.releaseChannel };
}
const releaseChannel = await getNativeReleaseChannelAsync(ctx);
return { releaseChannel };
}
async function getNativeReleaseChannelAsync<T extends Platform>(
ctx: BuildContext<T>
): Promise<string> {
switch (ctx.platform) {
case Platform.ANDROID: {
return (await readAndroidReleaseChannelSafelyAsync(ctx.projectDir)) ?? 'default';
}
case Platform.IOS: {
return (await readIosReleaseChannelSafelyAsync(ctx.projectDir)) ?? 'default';
}
default:
return 'default';
}
}
async function getNativeChannelAsync<T extends Platform>(
+12 -24
View File
@@ -1,5 +1,5 @@
import { ExpoConfig } from '@expo/config-types';
import { Env, Platform, Workflow } from '@expo/eas-build-job';
import { Platform, Workflow } from '@expo/eas-build-job';
import {
AppVersionSource,
BuildProfile,
@@ -71,6 +71,7 @@ import {
} from '../submit/submit';
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
import { ensureEASUpdateIsConfiguredAsync } from '../update/configure';
import { validateBuildProfileConfigMatchesProjectConfigAsync } from '../update/utils';
import { Actor } from '../user/User';
import { downloadAndMaybeExtractAppAsync } from '../utils/download';
import { truthy } from '../utils/expodash/filter';
@@ -94,8 +95,6 @@ export interface BuildFlags {
resourceClass?: ResourceClass;
message?: string;
buildLoggerLevel?: LoggerLevel;
freezeCredentials: boolean;
repack: boolean;
}
export async function runBuildAndSubmitAsync(
@@ -106,9 +105,7 @@ export async function runBuildAndSubmitAsync(
flags: BuildFlags,
actor: Actor,
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn
): Promise<{
buildIds: string[];
}> {
): Promise<void> {
await vcsClient.ensureRepoExistsAsync();
await ensureRepoIsCleanAsync(vcsClient, flags.nonInteractive);
@@ -216,9 +213,7 @@ export async function runBuildAndSubmitAsync(
}
if (flags.localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN) {
return {
buildIds: startedBuilds.map(({ build }) => build.id),
};
return;
}
if (flags.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL) {
@@ -274,18 +269,14 @@ export async function runBuildAndSubmitAsync(
}
if (flags.localBuildOptions.localBuildMode) {
return {
buildIds: startedBuilds.map(({ build }) => build.id),
};
return;
}
if (!flags.wait) {
if (flags.json) {
printJsonOnlyOutput(startedBuilds.map(buildInfo => buildInfo.build));
}
return {
buildIds: startedBuilds.map(({ build }) => build.id),
};
return;
}
const { accountName } = Object.values(buildCtxByPlatform)[0];
@@ -326,9 +317,6 @@ export async function runBuildAndSubmitAsync(
}
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish(completedSubmissions);
}
return {
buildIds: startedBuilds.map(({ build }) => build.id),
};
}
async function prepareAndStartBuildAsync({
@@ -375,8 +363,6 @@ async function prepareAndStartBuildAsync({
getDynamicPrivateProjectConfigAsync,
customBuildConfigMetadata,
buildLoggerLevel: flags.buildLoggerLevel,
freezeCredentials: flags.freezeCredentials,
repack: flags.repack,
});
if (moreBuilds) {
@@ -389,6 +375,12 @@ async function prepareAndStartBuildAsync({
);
}
await validateBuildProfileConfigMatchesProjectConfigAsync(
buildCtx.exp,
buildProfile,
buildCtx.projectId,
flags.nonInteractive
);
if (buildProfile.profile.channel) {
await validateExpoUpdatesInstalledAsProjectDependencyAsync({
exp: buildCtx.exp,
@@ -398,7 +390,6 @@ async function prepareAndStartBuildAsync({
sdkVersion: buildCtx.exp.sdkVersion,
nonInteractive: flags.nonInteractive,
buildProfile,
env: buildProfile.profile.env,
});
if (isUsingEASUpdate(buildCtx.exp, buildCtx.projectId)) {
const doesChannelExist = await doesChannelExistAsync(graphqlClient, {
@@ -543,7 +534,6 @@ async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
buildProfile,
nonInteractive,
sdkVersion,
env,
}: {
exp: ExpoConfig;
projectId: string;
@@ -552,7 +542,6 @@ async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
buildProfile: ProfileData<'build'>;
nonInteractive: boolean;
sdkVersion?: string;
env: Env | undefined;
}): Promise<void> {
if (isExpoUpdatesInstalledOrAvailable(projectDir, sdkVersion)) {
return;
@@ -580,7 +569,6 @@ async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
projectDir,
platform: RequestedPlatform.All,
vcsClient,
env,
});
Log.withTick('Installed expo-updates and configured EAS Update.');
throw new Error('Command must be re-run to pick up new updates configuration.');
@@ -39,10 +39,6 @@ export function formatGraphQLBuild(build: BuildFragment): string {
}
},
},
{
label: 'Profile',
value: build.buildProfile,
},
{
label: 'Message',
value: build.message,
@@ -55,6 +51,10 @@ export function formatGraphQLBuild(build: BuildFragment): string {
label: 'Enterprise Provisioning',
value: build.iosEnterpriseProvisioning?.toLowerCase(),
},
{
label: 'Release Channel',
value: build.releaseChannel,
},
{
label: 'Channel',
value: build.channel,
+34 -11
View File
@@ -100,21 +100,44 @@ export type LocalFile = {
};
export async function makeProjectMetadataFileAsync(archivePath: string): Promise<LocalFile> {
const spinner = ora('Creating project metadata file');
const timerLabel = 'makeProjectMetadataFileAsync';
const timer = setTimeout(
() => {
spinner.start();
},
Log.isDebug ? 1 : 1000
);
startTimer(timerLabel);
const metadataLocation = path.join(getTmpDirectory(), `${uuidv4()}-eas-build-metadata.json`);
const archiveContent: string[] = [];
await tar.list({
file: archivePath,
onentry: (entry: tar.ReadEntry) => {
if (entry.type === 'File' && !entry.path.includes('.git/')) {
archiveContent.push(entry.path);
}
},
});
try {
await tar.list({
file: archivePath,
onentry: (entry: tar.ReadEntry) => {
if (entry.type === 'File' && !entry.path.includes('.git/')) {
archiveContent.push(entry.path);
}
},
});
await fs.writeJSON(metadataLocation, {
archiveContent,
});
await fs.writeJSON(metadataLocation, {
archiveContent,
});
} catch (e) {
clearTimeout(timer);
if (spinner.isSpinning) {
spinner.fail();
}
throw e;
}
clearTimeout(timer);
const duration = endTimer(timerLabel);
const prettyTime = formatMilliseconds(duration);
spinner.succeed(`Created project metadata file ${chalk.dim(prettyTime)}`);
return { path: metadataLocation, size: await fs.stat(metadataLocation).then(stat => stat.size) };
}
@@ -1,6 +1,5 @@
import { Command } from '@oclif/core';
import { CombinedError } from '@urql/core';
import chalk from 'chalk';
import { GraphQLError } from 'graphql/error';
import nullthrows from 'nullthrows';
@@ -201,7 +200,7 @@ export default abstract class EasCommand extends Command {
if (err instanceof EasCommandError) {
Log.error(err.message);
} else if (err instanceof CombinedError && err?.graphQLErrors) {
const cleanGQLErrorsMessage = err?.graphQLErrors
const cleanMessage = err?.graphQLErrors
.map((graphQLError: GraphQLError) => {
const messageLine = graphQLError.message.replace('[GraphQL] ', '');
const requestIdLine = graphQLError.extensions?.requestId
@@ -211,21 +210,12 @@ export default abstract class EasCommand extends Command {
const defaultMsg = `${messageLine}${requestIdLine}`;
if (graphQLError.extensions?.errorCode === 'UNAUTHORIZED_ERROR') {
return `${chalk.bold(
`You don't have the required permissions to perform this operation.`
)}\n\nThis can sometimes happen if you are logged in as incorrect user.\nRun ${chalk.bold(
'eas whoami'
)} to check the username you are logged in as.\nRun ${chalk.bold(
'eas login'
)} to change the account.\n\nOriginal error message: ${defaultMsg}`;
return `You don't have the required permissions to perform this operation.\n\n${defaultMsg}`;
}
return defaultMsg;
})
.join('\n');
const cleanMessage = err.networkError
? `${cleanGQLErrorsMessage}\n${err.networkError.message}`
: cleanGQLErrorsMessage;
Log.error(cleanMessage);
baseMessage = BASE_GRAPHQL_ERROR_MESSAGE;
} else {
@@ -55,7 +55,6 @@ describe(getProjectIdAsync, () => {
],
isExpoAdmin: false,
featureGates: {},
preferences: {},
},
authenticationInfo: { accessToken: 'fake', sessionSecret: null },
});
@@ -80,7 +79,6 @@ describe(getProjectIdAsync, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
fullName: '@notnotbrent/test',
name: 'test',
slug: 'test',
ownerAccount: { name: 'notnotbrent' } as any,
});
@@ -101,7 +99,6 @@ describe(getProjectIdAsync, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
fullName: '@notnotbrent/test',
name: 'test',
slug: 'test',
ownerAccount: { name: 'notnotbrent' } as any,
});
@@ -126,7 +123,6 @@ describe(getProjectIdAsync, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
fullName: '@totallybrent/test',
name: 'test',
slug: 'test',
ownerAccount: { name: 'totallybrent' } as any,
});
@@ -175,7 +171,6 @@ describe(getProjectIdAsync, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
fullName: '@totallybrent/test',
name: 'test',
slug: 'test',
ownerAccount: { name: 'totallybrent' } as any,
});
@@ -200,7 +195,6 @@ describe(getProjectIdAsync, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
fullName: '@notnotbrent/test',
name: 'test',
slug: 'test',
ownerAccount: { name: 'notnotbrent' } as any,
});
@@ -270,7 +264,6 @@ describe(getProjectIdAsync, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
fullName: '@notnotbrent/test',
name: 'test',
slug: 'test',
ownerAccount: { name: 'notnotbrent' } as any,
});
@@ -15,11 +15,7 @@ async function applyCliConfigAsync(projectDir: string): Promise<void> {
const config = await EasJsonUtils.getCliConfigAsync(easJsonAccessor);
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 defined in eas.json (${
config.version
}).\n\nThis error probably means that you need update your eas-cli to a newer version.\nRun ${chalk.bold(
'npm install -g eas-cli'
)} to update the eas-cli to the latest version.`
`You are on eas-cli@${easCliVersion} which does not satisfy the CLI version constraint in eas.json (${config.version})`
);
}
}
@@ -3,7 +3,7 @@ import { ExpoConfig } from '@expo/config-types';
import { Env } from '@expo/eas-build-job';
import chalk from 'chalk';
import { ExpoGraphqlClient, createGraphqlClient } from './createGraphqlClient';
import { createGraphqlClient } from './createGraphqlClient';
import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync';
import { AppQuery } from '../../../graphql/queries/AppQuery';
import Log, { learnMore } from '../../../log';
@@ -87,23 +87,6 @@ export async function getProjectIdAsync(
});
const graphqlClient = createGraphqlClient(authenticationInfo);
const projectId = await validateOrSetProjectIdAsync({ exp, graphqlClient, actor, options });
return projectId;
}
export async function validateOrSetProjectIdAsync({
exp,
graphqlClient,
actor,
options,
cwd,
}: {
exp: ExpoConfig;
graphqlClient: ExpoGraphqlClient;
actor: Actor;
options: { env?: Env; nonInteractive: boolean };
cwd?: string;
}): Promise<string> {
const localProjectId = exp.extra?.eas?.projectId;
if (localProjectId) {
if (typeof localProjectId !== 'string') {
@@ -163,9 +146,7 @@ export async function validateOrSetProjectIdAsync({
return localProjectId;
}
const projectDir = await findProjectRootAsync({
cwd,
});
const projectDir = await findProjectRootAsync();
if (!projectDir) {
throw new Error('This command must be run inside a project directory.');
}
@@ -13,6 +13,7 @@ export default class AccountLogin extends EasCommand {
description: 'Login with SSO',
char: 's',
default: false,
hidden: true,
}),
};
@@ -74,20 +74,14 @@ export default class BuildConfigure extends EasCommand {
if ([RequestedPlatform.Android, RequestedPlatform.All].includes(platform)) {
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
if (workflow === Workflow.GENERIC) {
await syncAndroidUpdatesConfigurationAsync({ projectDir, exp, workflow, env: undefined });
await syncAndroidUpdatesConfigurationAsync(projectDir, exp);
}
}
if ([RequestedPlatform.Ios, RequestedPlatform.All].includes(platform)) {
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS, vcsClient);
if (workflow === Workflow.GENERIC) {
await syncIosUpdatesConfigurationAsync({
vcsClient,
projectDir,
exp,
workflow,
env: undefined,
});
await syncIosUpdatesConfigurationAsync(vcsClient, projectDir, exp);
}
}
}
@@ -35,8 +35,6 @@ interface RawBuildFlags {
'resource-class'?: ResourceClass;
message?: string;
'build-logger-level'?: LoggerLevel;
'freeze-credentials': boolean;
repack: boolean;
}
export default class Build extends EasCommand {
@@ -106,15 +104,6 @@ export default class Build extends EasCommand {
description: 'The level of logs to output during the build process. Defaults to "info".',
options: Object.values(LoggerLevel),
}),
'freeze-credentials': Flags.boolean({
default: false,
description: 'Prevent the build from updating credentials in non-interactive mode',
}),
repack: Flags.boolean({
default: false,
hidden: true,
description: 'Use the golden dev client build repack flow as it works for onboarding',
}),
...EasNonInteractiveAndJsonFlags,
};
@@ -228,8 +217,6 @@ export default class Build extends EasCommand {
resourceClass: flags['resource-class'],
message,
buildLoggerLevel: flags['build-logger-level'],
freezeCredentials: flags['freeze-credentials'],
repack: flags.repack,
};
}
@@ -105,7 +105,6 @@ export default class BuildInspect extends EasCommand {
projectDir,
{
nonInteractive: false,
freezeCredentials: false,
wait: true,
clearCache: false,
json: false,
@@ -120,7 +119,6 @@ export default class BuildInspect extends EasCommand {
workingdir: tmpWorkingdir,
artifactsDir: path.join(tmpWorkingdir, 'artifacts'),
},
repack: false,
},
actor,
getDynamicPrivateProjectConfigAsync
@@ -77,7 +77,6 @@ export default class BuildInternal extends EasCommand {
requestedPlatform: flags.platform as RequestedPlatform,
profile: flags.profile,
nonInteractive: true,
freezeCredentials: false,
wait: false,
clearCache: false,
json: true,
@@ -86,7 +85,6 @@ export default class BuildInternal extends EasCommand {
localBuildMode: LocalBuildMode.INTERNAL,
},
submitProfile: flags['auto-submit-with-profile'] ?? flags.profile,
repack: false,
},
actor,
getDynamicPrivateProjectConfigAsync
+29 -42
View File
@@ -21,47 +21,34 @@ export default class BuildList extends EasCommand {
static override flags = {
platform: Flags.enum({
options: Object.values(RequestedPlatform),
char: 'p',
options: [RequestedPlatform.All, RequestedPlatform.Android, RequestedPlatform.Ios],
}),
status: Flags.enum({
options: Object.values(BuildStatus),
description: 'Filter only builds with the specified status',
options: [
BuildStatus.NEW,
BuildStatus.IN_QUEUE,
BuildStatus.IN_PROGRESS,
BuildStatus.PENDING_CANCEL,
BuildStatus.ERRORED,
BuildStatus.FINISHED,
BuildStatus.CANCELED,
],
}),
distribution: Flags.enum({
options: Object.values(BuildDistributionType),
description: 'Filter only builds with the specified distribution type',
options: [
BuildDistributionType.STORE,
BuildDistributionType.INTERNAL,
BuildDistributionType.SIMULATOR,
],
}),
channel: Flags.string(),
'app-version': Flags.string({
aliases: ['appVersion'],
description: 'Filter only builds created with the specified main app version',
}),
'app-build-version': Flags.string({
aliases: ['appBuildVersion'],
description: 'Filter only builds created with the specified app build version',
}),
'sdk-version': Flags.string({
aliases: ['sdkVersion'],
description: 'Filter only builds created with the specified Expo SDK version',
}),
'runtime-version': Flags.string({
aliases: ['runtimeVersion'],
description: 'Filter only builds created with the specified runtime version',
}),
'app-identifier': Flags.string({
aliases: ['appIdentifier'],
description: 'Filter only builds created with the specified app identifier',
}),
'build-profile': Flags.string({
char: 'e',
aliases: ['profile', 'buildProfile'],
description: 'Filter only builds created with the specified build profile',
}),
'git-commit-hash': Flags.string({
aliases: ['gitCommitHash'],
description: 'Filter only builds created with the specified git commit hash',
}),
appVersion: Flags.string(),
appBuildVersion: Flags.string(),
sdkVersion: Flags.string(),
runtimeVersion: Flags.string(),
appIdentifier: Flags.string(),
buildProfile: Flags.string(),
gitCommitHash: Flags.string(),
...EasPaginatedQueryFlags,
limit: getLimitFlagWithCustomValues({ defaultTo: 10, limit: BUILDS_LIMIT }),
...EasNonInteractiveAndJsonFlags,
@@ -122,13 +109,13 @@ export default class BuildList extends EasCommand {
status: graphqlBuildStatus,
distribution: graphqlBuildDistribution,
channel: flags.channel,
appVersion: flags['app-version'],
appBuildVersion: flags['app-build-version'],
sdkVersion: flags['sdk-version'],
runtimeVersion: flags['runtime-version'],
appIdentifier: flags['app-identifier'],
buildProfile: flags['build-profile'],
gitCommitHash: flags['git-commit-hash'],
appVersion: flags.appVersion,
appBuildVersion: flags.appBuildVersion,
sdkVersion: flags.sdkVersion,
runtimeVersion: flags.runtimeVersion,
appIdentifier: flags.appIdentifier,
buildProfile: flags.buildProfile,
gitCommitHash: flags.gitCommitHash,
simulator: flags.simulator,
},
paginatedQueryOptions,
+7 -27
View File
@@ -42,8 +42,7 @@ interface BuildResignFlags {
offset?: number;
limit?: number;
platform?: Platform;
targetProfile?: string;
sourceProfile?: string;
profile?: string;
maybeBuildId?: string;
wait: boolean;
}
@@ -53,8 +52,7 @@ interface RawBuildResignFlags {
offset: number | undefined;
limit: number | undefined;
platform: 'android' | 'ios' | undefined;
'target-profile': string | undefined;
'source-profile': string | undefined;
profile: string | undefined;
wait: boolean;
id: string | undefined;
}
@@ -67,16 +65,10 @@ export default class BuildResign extends EasCommand {
char: 'p',
options: ['android', 'ios'],
}),
'target-profile': Flags.string({
profile: Flags.string({
char: 'e',
description:
'Name of the target build profile from eas.json. Credentials and environment variables from this profile will be used when re-signing. Defaults to "production" if defined in eas.json.',
helpValue: 'PROFILE_NAME',
aliases: ['profile'],
}),
'source-profile': Flags.string({
description:
'Name of the source build profile from eas.json. Used to filter builds eligible for re-signing.',
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
helpValue: 'PROFILE_NAME',
}),
wait: Flags.boolean({
@@ -138,20 +130,12 @@ export default class BuildResign extends EasCommand {
const buildProfile = await EasJsonUtils.getBuildProfileAsync(
easJsonAccessor,
platform,
flags.targetProfile ?? 'production'
flags.profile ?? 'production'
);
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env });
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
const build = await this.ensureBuildSelectedAsync(
{
graphqlClient,
projectId,
platform,
nonInteractive,
limit,
offset,
buildProfile: flags.sourceProfile,
},
{ graphqlClient, projectId, platform, nonInteractive, limit, offset },
maybeBuild
);
const credentialsCtx = new CredentialsContext({
@@ -244,8 +228,7 @@ export default class BuildResign extends EasCommand {
offset: flags.offset,
limit: flags.limit,
platform: flags.platform as Platform | undefined,
sourceProfile: flags['source-profile'],
targetProfile: flags['target-profile'],
profile: flags.profile,
maybeBuildId: flags.id,
wait: flags.wait,
};
@@ -259,7 +242,6 @@ export default class BuildResign extends EasCommand {
nonInteractive,
limit,
offset,
buildProfile,
}: {
graphqlClient: ExpoGraphqlClient;
projectId: string;
@@ -267,7 +249,6 @@ export default class BuildResign extends EasCommand {
nonInteractive: boolean;
limit?: number;
offset?: number;
buildProfile?: string;
},
maybeBuild?: BuildFragment
): Promise<BuildFragment> {
@@ -287,7 +268,6 @@ export default class BuildResign extends EasCommand {
distribution: DistributionType.Internal,
platform: toAppPlatform(platform),
status: BuildStatus.Finished,
buildProfile,
},
});
if (!build) {
@@ -88,7 +88,6 @@ export default class BuildVersionGetView extends EasCommand {
buildProfile: profile,
platform,
vcsClient,
nonInteractive: flags['non-interactive'],
});
const remoteVersions = await AppVersionQuery.latestVersionAsync(
graphqlClient,
@@ -77,7 +77,6 @@ export default class BuildVersionSetView extends EasCommand {
buildProfile: profile,
platform,
vcsClient,
nonInteractive: false,
});
const remoteVersions = await AppVersionQuery.latestVersionAsync(
graphqlClient,
@@ -102,7 +102,6 @@ export default class BuildVersionSyncView extends EasCommand {
buildProfile: profileInfo.profile,
platform: profileInfo.platform,
vcsClient,
nonInteractive: false,
});
const remoteVersions = await AppVersionQuery.latestVersionAsync(
graphqlClient,
@@ -125,7 +125,6 @@ export default class ChannelRollout extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.Vcs,
...this.ContextOptions.LoggedIn,
};
@@ -134,7 +133,6 @@ export default class ChannelRollout extends EasCommand {
const argsAndFlags = this.sanitizeArgsAndFlags({ ...flags, ...args });
const {
privateProjectConfig: { exp, projectId, projectDir },
vcsClient,
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelRollout, {
nonInteractive: argsAndFlags.nonInteractive,
@@ -148,7 +146,6 @@ export default class ChannelRollout extends EasCommand {
nonInteractive: argsAndFlags.nonInteractive,
graphqlClient,
app,
vcsClient,
};
if (argsAndFlags.nonInteractive) {
await new NonInteractiveRollout(argsAndFlags).runAsync(ctx);
@@ -58,8 +58,7 @@ export default class InitializeBuildCredentials extends EasCommand {
privateProjectConfig ?? null,
getDynamicPrivateProjectConfigAsync,
platform,
buildProfile,
process.cwd()
buildProfile
).runAsync();
}
}
+2 -6
View File
@@ -1,10 +1,8 @@
import { Platform } from '@expo/eas-build-job';
import chalk from 'chalk';
import envinfo from 'envinfo';
import EasCommand from '../commandUtils/EasCommand';
import Log from '../log';
import { ora } from '../ora';
import { resolveWorkflowAsync } from '../project/workflow';
import { easCliVersion } from '../utils/easCli';
import { Client } from '../vcs/vcs';
@@ -22,7 +20,6 @@ export default class Diagnostics extends EasCommand {
nonInteractive: true,
});
const spinner = ora().start(`Gathering diagnostic information...`);
const info = await envinfo.run(
{
System: ['OS', 'Shell'],
@@ -43,10 +40,9 @@ export default class Diagnostics extends EasCommand {
npmGlobalPackages: ['eas-cli', 'expo-cli'],
},
{
title: chalk.bold(`EAS CLI ${easCliVersion} environment info`),
title: `EAS CLI ${easCliVersion} environment info`,
}
);
spinner.succeed('All needed information gathered!');
Log.log(info.trimEnd());
await this.printWorkflowAsync(projectDir, vcsClient);
@@ -60,7 +56,7 @@ export default class Diagnostics extends EasCommand {
if (androidWorkflow === iosWorkflow) {
Log.log(` Project workflow: ${androidWorkflow}`);
} else {
Log.log(` Project workflow:`);
Log.log(` Project Workflow:`);
Log.log(` Android: ${androidWorkflow}`);
Log.log(` iOS: ${iosWorkflow}`);
}
@@ -110,7 +110,6 @@ describe(ProjectInit.name, () => {
it('is no-op if already configured for id', async () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
name: 'testing-123',
slug: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
@@ -122,7 +121,6 @@ describe(ProjectInit.name, () => {
it('prompts to overwrite when different', async () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
name: 'testing-123',
slug: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
@@ -137,7 +135,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -155,7 +152,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -190,7 +186,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -203,7 +198,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-124',
name: 'testing-123',
fullName: '@jester2/testing-124',
ownerAccount: jester2.accounts[0],
});
@@ -229,7 +223,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-124',
name: 'testing-123',
fullName: '@jester2/testing-124',
ownerAccount: jester2.accounts[0],
});
@@ -254,7 +247,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-124',
name: 'testing-123',
fullName: '@jester2/testing-124',
ownerAccount: jester2.accounts[0],
});
@@ -281,7 +273,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -297,7 +288,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -312,7 +302,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-124',
name: 'testing-123',
fullName: '@jester2/testing-124',
ownerAccount: jester2.accounts[0],
});
@@ -340,7 +329,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-124',
name: 'testing-123',
fullName: '@jester2/testing-124',
ownerAccount: jester2.accounts[0],
});
@@ -367,7 +355,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-124',
name: 'testing-123',
fullName: '@jester2/testing-124',
ownerAccount: jester2.accounts[0],
});
@@ -397,7 +384,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -412,7 +398,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-124',
name: 'testing-123',
fullName: '@jester2/testing-124',
ownerAccount: jester2.accounts[0],
});
@@ -452,7 +437,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -480,7 +464,6 @@ describe(ProjectInit.name, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '0129',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -1,494 +0,0 @@
import { ExpoConfig } from '@expo/config-types';
import { Platform } from '@expo/eas-build-job';
import { EasJson } from '@expo/eas-json';
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import { getEASUpdateURL, getExpoWebsiteBaseUrl } from '../../api';
import { runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
import { reviewAndCommitChangesAsync } from '../../build/utils/repository';
import EasCommand from '../../commandUtils/EasCommand';
import { DynamicConfigContextFn } from '../../commandUtils/context/DynamicProjectConfigContextField';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { validateOrSetProjectIdAsync } from '../../commandUtils/context/contextUtils/getProjectIdAsync';
import { CredentialsContextProjectInfo } from '../../credentials/context';
import { SetUpBuildCredentialsCommandAction } from '../../credentials/manager/SetUpBuildCredentialsCommandAction';
import {
AppFragment,
AppPlatform,
OnboardingDeviceType,
OnboardingEnvironment,
} from '../../graphql/generated';
import { UserPreferencesMutation } from '../../graphql/mutations/UserPreferencesMutation';
import { AppQuery } from '../../graphql/queries/AppQuery';
import Log, { learnMore, link } from '../../log';
import {
canAccessRepositoryUsingSshAsync,
runGitCloneAsync,
runGitPushAsync,
} from '../../onboarding/git';
import { installDependenciesAsync } from '../../onboarding/installDependencies';
import { runCommandAsync } from '../../onboarding/runCommand';
import { RequestedPlatform } from '../../platform';
import { ExpoConfigOptions, getPrivateExpoConfig } from '../../project/expoConfig';
import { promptAsync } from '../../prompts';
import { Actor } from '../../user/User';
import { easCliVersion } from '../../utils/easCli';
import GitClient from '../../vcs/clients/git';
export default class Onboarding extends EasCommand {
static override aliases = ['init:onboarding', 'onboarding'];
static override description =
'continue onboarding process started on the https://expo.new website.';
static override flags = {};
static override args = [{ name: 'TARGET_PROJECT_DIRECTORY' }];
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
...this.ContextOptions.Analytics,
};
async runAsync(): Promise<void> {
const {
args: { TARGET_PROJECT_DIRECTORY: targetProjectDirInput },
} = await this.parse(Onboarding);
const {
loggedIn: { actor, graphqlClient },
analytics,
} = await this.getContextAsync(Onboarding, {
nonInteractive: false,
});
if (actor.__typename === 'Robot') {
throw new Error(
'This command is not available for robot users. Make sure you are not using a robot token and try again.'
);
}
if (!actor.preferences.onboarding) {
throw new Error(
'This command can only be run as part of the onboarding process started on the Expo website. Visit https://expo.new to start a new project.'
);
}
if (!actor.preferences.onboarding.platform) {
throw new Error(
'This command can only be run as part of the onboarding process started on the Expo website. It seems like you started an onboarding process, but we are missing some information needed to be filled in before running the eas init:onboarding command (selected platform). Continue the onboarding process on the Expo website.'
);
}
if (!actor.preferences.onboarding.environment) {
throw new Error(
'This command can only be run as part of the onboarding process started on the Expo website. It seems like you started an onboarding process, but we are missing some information needed to be filled in before running the eas init:onboarding command (selected environment). Continue the onboarding process on the Expo website.'
);
}
if (!actor.preferences.onboarding.deviceType) {
throw new Error(
'This command can only be run as part of the onboarding process started on the Expo website. It seems like you started an onboarding process, but we are missing some information needed to be filled in before running the eas init:onboarding command (selected device type). Continue the onboarding process on the Expo website.'
);
}
if (
new Date(actor.preferences.onboarding.lastUsed) < new Date(Date.now() - 1000 * 60 * 60 * 24)
) {
Log.warn(
'It seems like you started an onboarding process, but it has been a while since you last used it. If you want to start a new onboarding process, visit https://expo.new.'
);
Log.log();
}
const platform =
actor.preferences.onboarding.platform === AppPlatform.Android
? Platform.ANDROID
: Platform.IOS;
const app = await AppQuery.byIdAsync(graphqlClient, actor.preferences.onboarding.appId);
const githubUsername = app.githubRepository
? app.githubRepository.metadata.githubRepoOwnerName
: 'expo';
const githubRepositoryName = app.githubRepository
? app.githubRepository.metadata.githubRepoName
: 'expo-template-default';
Log.log(`👋 Welcome to Expo, ${actor.username}!`);
Log.log();
Log.log('✨ We will continue your onboarding process in EAS CLI');
Log.log();
Log.log(
`🚚 Let's start by cloning ${
app.githubRepository
? `your project (${githubUsername}/${githubRepositoryName})`
: `default Expo template project (${githubUsername}/${githubRepositoryName})`
} from GitHub and installing dependencies.`
);
Log.log();
let initialTargetProjectDirectory: string;
if (targetProjectDirInput) {
initialTargetProjectDirectory = targetProjectDirInput;
Log.log(`📂 Cloning the project to ${initialTargetProjectDirectory}`);
} else {
const { selectedTargetProjectDirectory } = await promptAsync({
type: 'text',
name: 'selectedTargetProjectDirectory',
message: app.githubRepository
? '📂 Where would you like to clone the project to?'
: '📂 Where would you like to create your new project directory?',
initial: app.githubRepository
? path.join(process.cwd(), githubRepositoryName)
: path.join(process.cwd(), `${actor.username}-first-project`),
});
initialTargetProjectDirectory = selectedTargetProjectDirectory;
}
Log.log();
const cloneMethod = (await canAccessRepositoryUsingSshAsync({
githubUsername,
githubRepositoryName,
}))
? 'ssh'
: 'https';
Log.log(chalk.dim(`We detected that ${cloneMethod} is your preffered git clone method`));
Log.log();
const { targetProjectDir: finalTargetProjectDirectory } = await runGitCloneAsync({
githubUsername,
githubRepositoryName,
targetProjectDir: initialTargetProjectDirectory,
cloneMethod,
});
const vcsClient = new GitClient(finalTargetProjectDirectory);
if (!app.githubRepository) {
await fs.remove(path.join(finalTargetProjectDirectory, '.git'));
await runCommandAsync({
cwd: finalTargetProjectDirectory,
command: 'git',
args: ['init'],
});
Log.log();
await configureProjectFromBareDefaultExpoTemplateAsync({
app,
vcsClient,
targetDir: finalTargetProjectDirectory,
});
}
await installDependenciesAsync({
projectDir: finalTargetProjectDirectory,
});
const exp = await getPrivateExpoConfigWithProjectIdAsync({
projectDir: finalTargetProjectDirectory,
graphqlClient,
actor,
});
const getDynamicProjectConfigFn = getDynamicPrivateProjectConfigGetter({
projectDir: finalTargetProjectDirectory,
graphqlClient,
actor,
});
if (!app.githubRepository) {
await runCommandAsync({
cwd: finalTargetProjectDirectory,
command: 'npx',
args: ['expo', 'install', 'expo-updates'],
});
Log.log();
await runCommandAsync({
cwd: finalTargetProjectDirectory,
command: 'npx',
args: ['expo', 'install', 'expo-insights'],
});
Log.log();
await runCommandAsync({
cwd: finalTargetProjectDirectory,
command: 'npx',
args: ['expo', 'install', 'expo-dev-client'],
});
Log.log();
}
await vcsClient.trackFileAsync('package-lock.json');
const shouldSetupCredentials =
((platform === Platform.IOS &&
actor.preferences.onboarding.deviceType === OnboardingDeviceType.Device) ||
platform === Platform.ANDROID) &&
actor.preferences.onboarding.environment === OnboardingEnvironment.DevBuild;
if (shouldSetupCredentials) {
Log.log('🔑 Now we need to set up build credentials for your project:');
await new SetUpBuildCredentialsCommandAction(
actor,
graphqlClient,
vcsClient,
analytics,
exp,
getDynamicProjectConfigFn,
platform,
actor.preferences.onboarding.deviceType === OnboardingDeviceType.Simulator
? 'development-simulator'
: 'development',
finalTargetProjectDirectory
).runAsync();
}
if (app.githubRepository && (await vcsClient.hasUncommittedChangesAsync())) {
Log.log(
'📦 We will now commit the changes made by the configuration process and push them to GitHub:'
);
Log.log();
Log.log('🔍 Checking for changes in the repository...');
await vcsClient.showChangedFilesAsync();
await reviewAndCommitChangesAsync(
vcsClient,
`[eas-onboarding] Install dependencies${
shouldSetupCredentials ? ' and set up build credentials' : ''
}`,
{ nonInteractive: false }
);
Log.log('📤 Pushing changes to GitHub...');
await runGitPushAsync({
targetProjectDir: finalTargetProjectDirectory,
});
} else if (!app.githubRepository) {
await runCommandAsync({
cwd: finalTargetProjectDirectory,
command: 'git',
args: ['add', '.'],
});
Log.log();
await runCommandAsync({
cwd: finalTargetProjectDirectory,
command: 'git',
args: ['commit', '-m', 'Initial commit'],
});
Log.log();
}
Log.log();
Log.log('🎉 We finished configuring your project.');
Log.log();
if (
app.githubRepository ||
actor.preferences.onboarding.environment === OnboardingEnvironment.ExpoGo
) {
Log.log('🚀 You can now go back to the website to continue:');
const url = new URL(
`/onboarding/develop/set-up-project-on-your-machine?project=${app.slug}&accountId=${app.ownerAccount.id}`,
getExpoWebsiteBaseUrl()
).toString();
Log.log(`👉 ${link(url)}`);
} else {
Log.log('🚀 Now we are going to trigger your first build');
Log.log();
const { buildIds } = await runBuildAndSubmitAsync(
graphqlClient,
analytics,
vcsClient,
finalTargetProjectDirectory,
{
nonInteractive: true,
requestedPlatform:
platform === Platform.ANDROID ? RequestedPlatform.Android : RequestedPlatform.Ios,
profile:
actor.preferences.onboarding.deviceType === OnboardingDeviceType.Simulator
? 'development-simulator'
: 'development',
wait: false,
clearCache: false,
json: false,
autoSubmit: false,
localBuildOptions: {},
freezeCredentials: false,
repack: true,
},
actor,
getDynamicProjectConfigFn
);
const buildId = buildIds[0];
Log.log();
Log.log('🚀 You can now go back to the website to continue:');
const url = new URL(
`/onboarding/develop/set-up-project-on-your-machine?project=${app.slug}&accountId=${app.ownerAccount.id}&buildId=${buildId}`,
getExpoWebsiteBaseUrl()
).toString();
Log.log(`👉 ${link(url)}`);
}
const { __typename, ...previousPreferences } = actor.preferences.onboarding;
await UserPreferencesMutation.markCliDoneInOnboardingUserPreferencesAsync(graphqlClient, {
...previousPreferences,
appId: app.id,
});
}
}
// we can't get this automated by using command context because when we run a command the project directory doesn't exist yet
async function getPrivateExpoConfigWithProjectIdAsync({
projectDir,
graphqlClient,
actor,
options,
}: {
projectDir: string;
graphqlClient: ExpoGraphqlClient;
actor: Actor;
options?: ExpoConfigOptions;
}): Promise<CredentialsContextProjectInfo> {
const expBefore = getPrivateExpoConfig(projectDir, options);
const projectId = await validateOrSetProjectIdAsync({
exp: expBefore,
graphqlClient,
actor,
options: {
nonInteractive: false,
},
cwd: projectDir,
});
const exp = getPrivateExpoConfig(projectDir, options);
return {
exp,
projectId,
};
}
// we can't get this automated by using command context because when we run a command the project directory doesn't exist yet
function getDynamicPrivateProjectConfigGetter({
projectDir,
graphqlClient,
actor,
}: {
projectDir: string;
graphqlClient: ExpoGraphqlClient;
actor: Actor;
}): DynamicConfigContextFn {
return async (options?: ExpoConfigOptions) => {
return {
...(await getPrivateExpoConfigWithProjectIdAsync({
projectDir,
graphqlClient,
actor,
options,
})),
projectDir,
};
};
}
async function configureProjectFromBareDefaultExpoTemplateAsync({
app,
vcsClient,
targetDir,
}: {
app: AppFragment;
vcsClient: GitClient;
targetDir: string;
}): Promise<void> {
// Android package name requires each component to start with a lowercase letter.
const isUsernameValidSegment = /^[^a-z]/.test(app.ownerAccount.name);
const userPrefix = isUsernameValidSegment ? 'user' : '';
const isSlugValidSegment = /^[^a-z]/.test(app.slug);
const slugPrefix = isSlugValidSegment ? 'app' : '';
const bundleIdentifier = `com.${userPrefix}${stripInvalidCharactersForBundleIdentifier(
app.ownerAccount.name
)}.${slugPrefix}${stripInvalidCharactersForBundleIdentifier(app.slug)}`;
const updateUrl = getEASUpdateURL(app.id);
const easBuildGitHubConfig = {
android: {
image: 'latest',
},
ios: {
image: 'latest',
},
};
const easJson: EasJson = {
cli: {
version: `>= ${easCliVersion}`,
},
build: {
development: {
developmentClient: true,
distribution: 'internal',
...easBuildGitHubConfig,
},
'development-simulator': {
extends: 'development',
ios: {
simulator: true,
},
},
preview: {
distribution: 'internal',
channel: 'main',
...easBuildGitHubConfig,
},
production: {
channel: 'production',
...easBuildGitHubConfig,
},
},
submit: {
production: {},
},
};
const easJsonPath = path.join(targetDir, 'eas.json');
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
await vcsClient.trackFileAsync(easJsonPath);
Log.log(
`✅ Generated ${chalk.bold('eas.json')}. ${learnMore(
'https://docs.expo.dev/build-reference/eas-json/'
)}`
);
Log.log();
const baseExpoConfig = JSON.parse(await fs.readFile(path.join(targetDir, 'app.json'), 'utf8'))
.expo as ExpoConfig;
const expoConfig: ExpoConfig = {
...baseExpoConfig,
name: app.name ?? app.slug,
slug: app.slug,
extra: {
eas: {
projectId: app.id,
},
},
owner: app.ownerAccount.name,
updates: {
url: updateUrl,
},
runtimeVersion: {
policy: 'appVersion',
},
ios: {
...baseExpoConfig.ios,
bundleIdentifier,
},
android: {
...baseExpoConfig.android,
package: bundleIdentifier,
},
};
const appJsonPath = path.join(targetDir, 'app.json');
await fs.writeFile(appJsonPath, `${JSON.stringify({ expo: expoConfig }, null, 2)}\n`);
await vcsClient.trackFileAsync(appJsonPath);
Log.log(
`✅ Generated ${chalk.bold('app.json')}. ${learnMore(
'https://docs.expo.dev/versions/latest/config/app/'
)}`
);
Log.log();
}
function stripInvalidCharactersForBundleIdentifier(string: string): string {
return string.replaceAll(/[^A-Za-z0-9]/g, '');
}
@@ -7,6 +7,7 @@ import { instance, mock } from 'ts-mockito';
import UpdatePublish from '..';
import { ensureBranchExistsAsync } from '../../../branch/queries';
import { ensureChannelExistsAsync } from '../../../channel/queries';
import {
DynamicPrivateProjectConfigContextField,
DynamicPublicProjectConfigContextField,
@@ -45,6 +46,7 @@ jest.mock('fs');
jest.mock('@expo/config');
jest.mock('@expo/config-plugins');
jest.mock('../../../branch/queries');
jest.mock('../../../channel/queries');
jest.mock('../../../commandUtils/context/contextUtils/getProjectIdAsync');
jest.mock('../../../update/configure');
jest.mock('../../../update/getBranchNameFromChannelNameAsync');
@@ -94,6 +96,16 @@ describe(UpdatePublish.name, () => {
);
});
it('errors with both --channel and --no-channel', async () => {
const flags = ['--channel=channel123', '--no-channel'];
mockTestProject();
await expect(new UpdatePublish(flags, commandOptions).run()).rejects.toThrow(
'Cannot use --channel with --no-channel'
);
});
it('creates a new update with --non-interactive, --branch, and --message', async () => {
const flags = ['--non-interactive', '--branch=branch123', '--message=abc'];
@@ -147,6 +159,31 @@ describe(UpdatePublish.name, () => {
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalled();
});
it('creates a new update with --no-channel', async () => {
const flags = ['--non-interactive', '--branch=branch123', '--message=abc', '--no-channel'];
const { platforms, runtimeVersion } = mockTestExport();
jest.mocked(getBranchNameFromChannelNameAsync).mockResolvedValue('branchFromChannel');
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
branchId: 'branch123',
createdBranch: true,
});
jest.mocked(PublishMutation.publishUpdateGroupAsync).mockResolvedValue(
platforms.map(platform => ({
...updateStub,
runtimeVersion,
platform,
}))
);
await new UpdatePublish(flags, commandOptions).run();
expect(ensureChannelExistsAsync).not.toHaveBeenCalled();
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalled();
});
it('creates a new update with the public expo config', async () => {
const flags = ['--non-interactive', '--branch=branch123', '--message=abc'];
@@ -274,7 +311,6 @@ function mockTestProject({
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -367,7 +367,6 @@ function mockTestProject({
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -6,6 +6,7 @@ import path from 'path';
import { instance, mock } from 'ts-mockito';
import { ensureBranchExistsAsync } from '../../../branch/queries';
import { ensureChannelExistsAsync } from '../../../channel/queries';
import {
DynamicPrivateProjectConfigContextField,
DynamicPublicProjectConfigContextField,
@@ -44,6 +45,7 @@ jest.mock('fs');
jest.mock('@expo/config');
jest.mock('@expo/config-plugins');
jest.mock('../../../branch/queries');
jest.mock('../../../channel/queries');
jest.mock('../../../commandUtils/context/contextUtils/getProjectIdAsync');
jest.mock('../../../project/projectUtils', () => ({
...jest.requireActual('../../../project/projectUtils'),
@@ -80,6 +82,16 @@ describe(UpdateRollBackToEmbedded.name, () => {
);
});
it('errors with both --channel and --no-channel', async () => {
const flags = ['--channel=channel123', '--no-channel'];
mockTestProject();
await expect(new UpdateRollBackToEmbedded(flags, commandOptions).run()).rejects.toThrow(
'Cannot use --channel with --no-channel'
);
});
it('creates a roll back to embedded with --non-interactive, --branch, and --message', async () => {
const flags = ['--non-interactive', '--branch=branch123', '--message=abc'];
@@ -137,6 +149,42 @@ describe(UpdateRollBackToEmbedded.name, () => {
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalled();
});
it('creates a roll back to embedded with --no-channel', async () => {
const flags = ['--non-interactive', '--branch=branch123', '--message=abc', '--no-channel'];
const { projectId } = mockTestProject();
const platforms = ['android', 'ios'];
const runtimeVersion = 'exposdk:47.0.0';
jest.mocked(Updates.getRuntimeVersionAsync).mockResolvedValue(runtimeVersion);
jest.mocked(getBranchNameFromChannelNameAsync).mockResolvedValue('branchFromChannel');
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
branchId: 'branch123',
createdBranch: true,
});
jest.mocked(PublishMutation.publishUpdateGroupAsync).mockResolvedValue(
platforms.map(platform => ({
...updateStub,
runtimeVersion,
platform,
}))
);
await new UpdateRollBackToEmbedded(flags, commandOptions).run();
expect(ensureChannelExistsAsync).not.toHaveBeenCalled();
expect(ensureBranchExistsAsync).toHaveBeenCalledWith(
expect.any(Object), // graphql client
{
appId: projectId,
branchName: 'branchFromChannel',
}
);
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalled();
});
it('creates a roll back to embedded with the public expo config', async () => {
const flags = ['--non-interactive', '--branch=branch123', '--message=abc'];
@@ -259,7 +307,6 @@ function mockTestProject({
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -52,7 +52,6 @@ export default class UpdateConfigure extends EasCommand {
projectDir,
platform,
vcsClient,
env: undefined,
});
await ensureEASUpdateIsConfiguredInEasJsonAsync(projectDir);
+26 -11
View File
@@ -1,5 +1,4 @@
import { Platform as PublishPlatform } from '@expo/config';
import { Workflow } from '@expo/eas-build-job';
import { Errors, Flags } from '@oclif/core';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
@@ -7,6 +6,7 @@ import nullthrows from 'nullthrows';
import { ensureBranchExistsAsync } from '../../branch/queries';
import { ensureRepoIsCleanAsync } from '../../build/utils/repository';
import { getUpdateGroupUrl } from '../../build/utils/url';
import { ensureChannelExistsAsync } from '../../channel/queries';
import EasCommand from '../../commandUtils/EasCommand';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
@@ -40,7 +40,6 @@ import {
resolveInputDirectoryAsync,
uploadAssetsAsync,
} from '../../project/publish';
import { resolveWorkflowPerPlatformAsync } from '../../project/workflow';
import { ensureEASUpdateIsConfiguredAsync } from '../../update/configure';
import { getUpdateJsonInfosForUpdates } from '../../update/utils';
import {
@@ -65,6 +64,7 @@ type RawUpdateFlags = {
'skip-bundler': boolean;
'clear-cache': boolean;
'private-key-path'?: string;
'no-channel': boolean;
'non-interactive': boolean;
json: boolean;
/** @deprecated see UpdateRepublish command */
@@ -82,6 +82,7 @@ type UpdateFlags = {
inputDir: string;
skipBundler: boolean;
clearCache: boolean;
noChannel: boolean;
privateKeyPath?: string;
json: boolean;
nonInteractive: boolean;
@@ -144,6 +145,10 @@ export default class UpdatePublish extends EasCommand {
description: `File containing the PEM-encoded private key corresponding to the certificate in expo-updates' configuration. Defaults to a file named "private-key.pem" in the certificate's directory.`,
required: false,
}),
'no-channel': Flags.boolean({
description: `Skip EAS channel creation.`,
default: false,
}),
...EasNonInteractiveAndJsonFlags,
};
@@ -165,6 +170,7 @@ export default class UpdatePublish extends EasCommand {
skipBundler,
clearCache,
privateKeyPath,
noChannel: noChannelFlag,
json: jsonFlag,
nonInteractive,
branchName: branchNameArg,
@@ -200,7 +206,6 @@ export default class UpdatePublish extends EasCommand {
projectDir,
projectId,
vcsClient,
env: undefined,
});
const { exp } = await getDynamicPublicProjectConfigAsync();
@@ -361,24 +366,29 @@ export default class UpdatePublish extends EasCommand {
throw e;
}
const workflows = await resolveWorkflowPerPlatformAsync(projectDir, vcsClient);
const runtimeVersions = await getRuntimeVersionObjectAsync({
exp,
platforms: realizedPlatforms,
projectDir,
workflows: {
...workflows,
web: Workflow.UNKNOWN,
},
env: undefined,
vcsClient,
});
const runtimeToPlatformMapping =
getRuntimeToPlatformMappingFromRuntimeVersions(runtimeVersions);
const { branchId } = await ensureBranchExistsAsync(graphqlClient, {
const { branchId, createdBranch } = await ensureBranchExistsAsync(graphqlClient, {
appId: projectId,
branchName,
});
if (createdBranch && !noChannelFlag) {
await ensureChannelExistsAsync(graphqlClient, {
appId: projectId,
branchId,
channelName: branchName,
});
Log.withTick(
`Channel: ${chalk.bold(branchName)} pointed at branch: ${chalk.bold(branchName)}`
);
}
const gitCommitHash = await vcsClient.getCommitHashAsync();
const isGitWorkingTreeDirty = await vcsClient.hasUncommittedChangesAsync();
@@ -514,7 +524,7 @@ export default class UpdatePublish extends EasCommand {
},
]
: []),
{ label: 'EAS Dashboard', value: updateGroupLink },
{ label: 'Website link', value: updateGroupLink },
])
);
Log.addNewLineIfNone();
@@ -545,6 +555,10 @@ export default class UpdatePublish extends EasCommand {
);
}
if (channelName && flags['no-channel']) {
Errors.error('Cannot use --channel with --no-channel', { exit: 1 });
}
if (flags.group || flags.republish) {
// Pick the first flag set that is defined, in this specific order
const args = [
@@ -572,6 +586,7 @@ export default class UpdatePublish extends EasCommand {
clearCache: flags['clear-cache'],
platform: flags.platform as RequestedPlatform,
privateKeyPath: flags['private-key-path'],
noChannel: flags['no-channel'],
nonInteractive,
json: flags.json ?? false,
};
@@ -1,10 +1,11 @@
import { Platform as PublishPlatform } from '@expo/config';
import { Workflow } from '@expo/eas-build-job';
import { Errors, Flags } from '@oclif/core';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
import { ensureBranchExistsAsync } from '../../branch/queries';
import { getUpdateGroupUrl } from '../../build/utils/url';
import { ensureChannelExistsAsync } from '../../channel/queries';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
@@ -32,7 +33,6 @@ import {
getRuntimeVersionObjectAsync,
getUpdateMessageForCommandAsync,
} from '../../project/publish';
import { resolveWorkflowPerPlatformAsync } from '../../project/workflow';
import { ensureEASUpdateIsConfiguredAsync } from '../../update/configure';
import { getUpdateJsonInfosForUpdates } from '../../update/utils';
import {
@@ -54,6 +54,7 @@ type RawUpdateFlags = {
message?: string;
platform: string;
'private-key-path'?: string;
'no-channel': boolean;
'non-interactive': boolean;
json: boolean;
};
@@ -65,6 +66,7 @@ type UpdateFlags = {
channelName?: string;
updateMessage?: string;
privateKeyPath?: string;
noChannel: boolean;
json: boolean;
nonInteractive: boolean;
};
@@ -104,6 +106,10 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
description: `File containing the PEM-encoded private key corresponding to the certificate in expo-updates' configuration. Defaults to a file named "private-key.pem" in the certificate's directory.`,
required: false,
}),
'no-channel': Flags.boolean({
description: `Skip EAS channel creation.`,
default: false,
}),
...EasNonInteractiveAndJsonFlags,
};
@@ -122,6 +128,7 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
channelName: channelNameArg,
updateMessage: updateMessageArg,
privateKeyPath,
noChannel: noChannelFlag,
json: jsonFlag,
nonInteractive,
branchName: branchNameArg,
@@ -154,7 +161,6 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
projectDir,
projectId,
vcsClient,
env: undefined,
});
// check that the expo-updates package version supports roll back to embedded
@@ -185,24 +191,29 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
const realizedPlatforms: PublishPlatform[] =
platformFlag === 'all' ? defaultPublishPlatforms : [platformFlag];
const { branchId } = await ensureBranchExistsAsync(graphqlClient, {
const { branchId, createdBranch } = await ensureBranchExistsAsync(graphqlClient, {
appId: projectId,
branchName,
});
if (createdBranch && !noChannelFlag) {
await ensureChannelExistsAsync(graphqlClient, {
appId: projectId,
branchId,
channelName: branchName,
});
Log.withTick(
`Channel: ${chalk.bold(branchName)} pointed at branch: ${chalk.bold(branchName)}`
);
}
const gitCommitHash = await vcsClient.getCommitHashAsync();
const isGitWorkingTreeDirty = await vcsClient.hasUncommittedChangesAsync();
const workflows = await resolveWorkflowPerPlatformAsync(projectDir, vcsClient);
const runtimeVersions = await getRuntimeVersionObjectAsync({
exp,
platforms: realizedPlatforms,
projectDir,
workflows: {
...workflows,
web: Workflow.UNKNOWN,
},
env: undefined,
vcsClient,
});
let newUpdates: UpdatePublishMutation['updateBranch']['publishUpdateGroups'];
@@ -276,7 +287,7 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
},
]
: []),
{ label: 'EAS Dashboard', value: updateGroupLink },
{ label: 'Website link', value: updateGroupLink },
])
);
Log.addNewLineIfNone();
@@ -384,6 +395,10 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
);
}
if (channelName && flags['no-channel']) {
Errors.error('Cannot use --channel with --no-channel', { exit: 1 });
}
return {
auto,
branchName,
@@ -391,6 +406,7 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
updateMessage,
platform: flags.platform as RequestedPlatform,
privateKeyPath: flags['private-key-path'],
noChannel: flags['no-channel'],
nonInteractive,
json: flags.json ?? false,
};
@@ -55,7 +55,6 @@ export const testPKCS12EmptyPasswordKeystore: Keystore = {
export const testAppFragment: AppFragment = {
id: 'test-app-id',
fullName: '@testuser/testapp',
name: 'testapp',
slug: 'testapp',
ownerAccount: {
id: 'test-account-id',
@@ -1,5 +1,5 @@
import AppStoreApi from '../ios/appstore/AppStoreApi';
import { AuthCtx, AuthenticationMode } from '../ios/appstore/authenticateTypes';
import { AuthCtx } from '../ios/appstore/authenticateTypes';
export const testAuthCtx: AuthCtx = {
appleId: 'test-apple-id',
@@ -9,7 +9,6 @@ export const testAuthCtx: AuthCtx = {
export function getAppstoreMock(): AppStoreApi {
return {
defaultAuthenticationMode: AuthenticationMode.USER,
ensureAuthenticatedAsync: jest.fn(),
ensureBundleIdExistsAsync: jest.fn(),
listDistributionCertificatesAsync: jest.fn(),
@@ -1,5 +1,3 @@
import assert from 'node:assert';
import { AppFragment, Role } from '../../graphql/generated';
import { Actor } from '../../user/User';
@@ -26,7 +24,6 @@ export const jester: Actor = {
],
isExpoAdmin: false,
featureGates: {},
preferences: {},
};
export const jester2 = {
@@ -44,7 +41,6 @@ export const jester2 = {
featureGates: {},
};
assert(jester.__typename === 'User');
export const testUsername = jester.username;
export const testSlug = 'testApp';
export const testProjectId = '7ef93448-3bc7-4b57-be32-99326dcf24f0';
@@ -80,7 +76,6 @@ export const testAppJsonWithDifferentOwner = {
export const testAppQueryByIdResponse: AppFragment = {
id: testProjectId,
slug: testSlug,
name: 'testing 123',
fullName: testExperienceName,
ownerAccount: jester.accounts[0],
};
@@ -55,7 +55,6 @@ export const testAppleAppIdentifierFragment: AppleAppIdentifierFragment = {
export const testAppFragment: AppFragment = {
id: 'test-app-id',
fullName: '@testuser/testapp',
name: 'testapp',
slug: 'testapp',
ownerAccount: {
id: 'test-account-id',
@@ -25,7 +25,6 @@ export class CredentialsContext {
public readonly appStore = new AppStoreApi();
public readonly ios = IosGraphqlClient;
public readonly nonInteractive: boolean;
public readonly freezeCredentials: boolean = false;
public readonly projectDir: string;
public readonly user: Actor;
public readonly graphqlClient: ExpoGraphqlClient;
@@ -48,7 +47,6 @@ export class CredentialsContext {
graphqlClient: ExpoGraphqlClient;
analytics: Analytics;
vcsClient: Client;
freezeCredentials?: boolean;
env?: Env;
}
) {
@@ -60,7 +58,6 @@ export class CredentialsContext {
this.vcsClient = options.vcsClient;
this.nonInteractive = options.nonInteractive ?? false;
this.projectInfo = options.projectInfo;
this.freezeCredentials = options.freezeCredentials ?? false;
}
get hasProjectContext(): boolean {
@@ -1,31 +1,9 @@
import { learnMore } from '../log';
export class MissingCredentialsNonInteractiveError extends Error {
constructor(message?: string) {
super(message ?? 'Credentials are not set up. Run this command again in interactive mode.');
}
}
export class InsufficientAuthenticationNonInteractiveError extends Error {
constructor(message?: string) {
super(
message ??
`Authentication with an ASC API key is required in non-interactive mode. ${learnMore(
'https://docs.expo.dev/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team'
)}`
);
}
}
export class ForbidCredentialModificationError extends Error {
constructor(message?: string) {
super(
message ??
'Credentials cannot be modified. Run this command again without the --freeze-credentials flag.'
);
}
}
export class MissingCredentialsError extends Error {
constructor(message?: string) {
super(message ?? 'Credentials are not set up.');
@@ -14,7 +14,6 @@ import { isAdHocProfile, isEnterpriseUniversalProfile } from './utils/provisioni
import { CommonIosAppCredentialsFragment } from '../../graphql/generated';
import Log from '../../log';
import { findApplicationTarget } from '../../project/ios/target';
import { isExpoNotificationsInstalled } from '../../project/projectUtils';
import { selectAsync } from '../../prompts';
import { CredentialsContext } from '../context';
import * as credentialsJsonReader from '../credentialsJson/read';
@@ -106,11 +105,6 @@ export default class IosCredentialsProvider {
if (ctx.easJsonCliConfig?.promptToConfigurePushNotifications === false) {
return null;
} else if (
ctx.easJsonCliConfig?.promptToConfigurePushNotifications === undefined &&
!(await isExpoNotificationsInstalled(ctx.projectDir))
) {
return null;
}
const setupOption = await selectAsync(
@@ -5,18 +5,15 @@ import {
AppleDistributionCertificateFragment,
AppleProvisioningProfileFragment,
} from '../../../graphql/generated';
import Log, { learnMore } from '../../../log';
import Log from '../../../log';
import { ora } from '../../../ora';
import { getApplePlatformFromTarget } from '../../../project/ios/target';
import { CredentialsContext } from '../../context';
import {
ForbidCredentialModificationError,
InsufficientAuthenticationNonInteractiveError,
} from '../../errors';
import { MissingCredentialsNonInteractiveError } from '../../errors';
import { AppleProvisioningProfileMutationResult } from '../api/graphql/mutations/AppleProvisioningProfileMutation';
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
import { ProvisioningProfileStoreInfo } from '../appstore/Credentials.types';
import { AuthCtx, AuthenticationMode } from '../appstore/authenticateTypes';
import { AuthCtx } from '../appstore/authenticateTypes';
import { Target } from '../types';
export class ConfigureProvisioningProfile {
@@ -30,18 +27,9 @@ export class ConfigureProvisioningProfile {
public async runAsync(
ctx: CredentialsContext
): Promise<AppleProvisioningProfileMutationResult | null> {
if (ctx.freezeCredentials) {
throw new ForbidCredentialModificationError(
'Remove the --freeze-credentials flag to configure a Provisioning Profile.'
);
} else if (
ctx.nonInteractive &&
ctx.appStore.defaultAuthenticationMode !== AuthenticationMode.API_KEY
) {
throw new InsufficientAuthenticationNonInteractiveError(
`In order to configure your Provisioning Profile, authentication with an ASC API key is required in non-interactive mode. ${learnMore(
'https://docs.expo.dev/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team'
)}`
if (ctx.nonInteractive) {
throw new MissingCredentialsNonInteractiveError(
'Configuring Provisioning Profiles is only supported in interactive mode.'
);
}
const { developerPortalIdentifier, provisioningProfile } = this.originalProvisioningProfile;
@@ -58,7 +46,7 @@ export class ConfigureProvisioningProfile {
return null;
}
const applePlatform = getApplePlatformFromTarget(this.target);
const applePlatform = await getApplePlatformFromTarget(this.target);
const profilesFromApple = await ctx.appStore.listProvisioningProfilesAsync(
this.app.bundleIdentifier,
applePlatform
@@ -4,17 +4,14 @@ import nullthrows from 'nullthrows';
import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
import { generateProvisioningProfileAsync } from './ProvisioningProfileUtils';
import { AppleDistributionCertificateFragment } from '../../../graphql/generated';
import Log, { learnMore } from '../../../log';
import Log from '../../../log';
import { CredentialsContext } from '../../context';
import {
ForbidCredentialModificationError,
InsufficientAuthenticationNonInteractiveError,
} from '../../errors';
import { MissingCredentialsNonInteractiveError } from '../../errors';
import { askForUserProvidedAsync } from '../../utils/promptForCredentials';
import { AppleProvisioningProfileMutationResult } from '../api/graphql/mutations/AppleProvisioningProfileMutation';
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
import { ProvisioningProfile } from '../appstore/Credentials.types';
import { AuthCtx, AuthenticationMode } from '../appstore/authenticateTypes';
import { AuthCtx } from '../appstore/authenticateTypes';
import { provisioningProfileSchema } from '../credentials';
import { Target } from '../types';
@@ -26,18 +23,9 @@ export class CreateProvisioningProfile {
) {}
async runAsync(ctx: CredentialsContext): Promise<AppleProvisioningProfileMutationResult> {
if (ctx.freezeCredentials) {
throw new ForbidCredentialModificationError(
'Run this command again without the --freeze-credentials flag in order to generate a new Provisioning Profile.'
);
} else if (
ctx.nonInteractive &&
ctx.appStore.defaultAuthenticationMode !== AuthenticationMode.API_KEY
) {
throw new InsufficientAuthenticationNonInteractiveError(
`In order to generate a new Provisioning Profile, authentication with an ASC API key is required in non-interactive mode. ${learnMore(
'https://docs.expo.dev/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team'
)}`
if (ctx.nonInteractive) {
throw new MissingCredentialsNonInteractiveError(
'Creating Provisioning Profiles is only supported in interactive mode.'
);
}
const appleAuthCtx = await ctx.appStore.ensureAuthenticatedAsync();
@@ -61,31 +49,16 @@ export class CreateProvisioningProfile {
return provisioningProfileMutationResult;
}
private async maybeGetUserProvidedAsync(
ctx: CredentialsContext
): Promise<ProvisioningProfile | null> {
if (ctx.nonInteractive) {
return null;
}
private async provideOrGenerateAsync(
ctx: CredentialsContext,
appleAuthCtx: AuthCtx
): Promise<ProvisioningProfile> {
const userProvided = await askForUserProvidedAsync(provisioningProfileSchema);
if (userProvided) {
// userProvided profiles don't come with ProvisioningProfileId's (only accessible from Apple Portal API)
Log.warn('Provisioning profile: Unable to validate specified profile.');
return userProvided;
}
return null;
}
private async provideOrGenerateAsync(
ctx: CredentialsContext,
appleAuthCtx: AuthCtx
): Promise<ProvisioningProfile> {
const maybeUserProvided = await this.maybeGetUserProvidedAsync(ctx);
if (maybeUserProvided) {
// userProvided profiles don't come with ProvisioningProfileId's (only accessible from Apple Portal API)
Log.warn('Provisioning profile: Unable to validate specified profile.');
return maybeUserProvided;
}
assert(
this.distributionCertificate.certificateP12,
'Distribution Certificate must have p12 certificate'
@@ -259,10 +259,6 @@ function formatDistributionCertificateFromApple(
const { name, status, id, expires, created, ownerName, serialNumber } = appleInfo;
const expiresDate = new Date(expires * 1000).toDateString();
const createdDate = new Date(created * 1000).toDateString();
return [
`🍏 ${chalk.bold(name)} (${status})`,
`${chalk.bold('ID:')} ${id} ${chalk.bold('Serial Number:')} ${serialNumber}`,
`${chalk.bold('Apple Team:')} ${appleInfo.ownerId} (${ownerName})`,
`${chalk.bold('Expires:')} ${expiresDate} ${chalk.bold('Created:')} ${createdDate}`,
].join('\n\t');
return `${name} (${status}) - Cert ID: ${id}, Serial number: ${serialNumber}, Team ID: ${appleInfo.ownerId}, Team name: ${ownerName}
expires: ${expiresDate}, created: ${createdDate}`;
}
@@ -352,6 +352,7 @@ export class SetUpAdhocProvisioningProfile {
while (true) {
if (method === RegistrationMethod.WEBSITE) {
Log.newLine();
Log.log(chalk.bold("Press any key if you've already finished device registration."));
await pressAnyKeyToContinueAsync();
}
@@ -15,17 +15,12 @@ import {
IosAppBuildCredentialsFragment,
IosDistributionType,
} from '../../../graphql/generated';
import { learnMore } from '../../../log';
import { getApplePlatformFromTarget } from '../../../project/ios/target';
import { confirmAsync } from '../../../prompts';
import { CredentialsContext } from '../../context';
import {
ForbidCredentialModificationError,
InsufficientAuthenticationNonInteractiveError,
} from '../../errors';
import { MissingCredentialsNonInteractiveError } from '../../errors';
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
import { ProvisioningProfileStoreInfo } from '../appstore/Credentials.types';
import { AuthenticationMode } from '../appstore/authenticateTypes';
import { Target } from '../types';
import { validateProvisioningProfileAsync } from '../validators/validateProvisioningProfile';
@@ -107,18 +102,9 @@ export class SetUpProvisioningProfile {
if (areBuildCredentialsSetup) {
return nullthrows(await getBuildCredentialsAsync(ctx, this.app, this.distributionType));
}
if (ctx.freezeCredentials) {
throw new ForbidCredentialModificationError(
'Provisioning profile is not configured correctly. Remove the --freeze-credentials flag to configure it.'
);
} else if (
ctx.nonInteractive &&
ctx.appStore.defaultAuthenticationMode !== AuthenticationMode.API_KEY
) {
throw new InsufficientAuthenticationNonInteractiveError(
`In order to configure your Provisioning Profile, authentication with an ASC API key is required in non-interactive mode. ${learnMore(
'https://docs.expo.dev/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team'
)}`
if (ctx.nonInteractive) {
throw new MissingCredentialsNonInteractiveError(
'Provisioning profile is not configured correctly. Run this command again in interactive mode.'
);
}
@@ -141,18 +127,11 @@ export class SetUpProvisioningProfile {
return await this.assignNewAndDeleteOldProfileAsync(ctx, distCert, currentProfile);
}
const isNonInteractiveOrUserDidConfirmAsync = async (): Promise<boolean> => {
if (ctx.nonInteractive) {
return true;
}
return await confirmAsync({
message: `${formatProvisioningProfileFromApple(
currentProfileFromServer
)} \n Would you like to reuse the original profile?`,
});
};
const confirm = await isNonInteractiveOrUserDidConfirmAsync();
const confirm = await confirmAsync({
message: `${formatProvisioningProfileFromApple(
currentProfileFromServer
)} \n Would you like to reuse the original profile?`,
});
if (!confirm) {
return await this.assignNewAndDeleteOldProfileAsync(ctx, distCert, currentProfile);
}
@@ -11,11 +11,7 @@ import {
testTarget,
testTargets,
} from '../../../__tests__/fixtures-ios';
import {
ForbidCredentialModificationError,
InsufficientAuthenticationNonInteractiveError,
} from '../../../errors';
import { AuthenticationMode } from '../../appstore/authenticateTypes';
import { MissingCredentialsNonInteractiveError } from '../../../errors';
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
import { ConfigureProvisioningProfile } from '../ConfigureProvisioningProfile';
@@ -28,18 +24,15 @@ describe('ConfigureProvisioningProfile', () => {
beforeEach(() => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue(testAppQueryByIdResponse);
});
const testCases = ['NON_INTERACTIVE', 'INTERACTIVE'];
test.each(testCases)('configures a Provisioning Profile in %s Mode', async mode => {
it('configures a Provisioning Profile in Interactive Mode', async () => {
const mockProvisioningProfileFromApple = {
provisioningProfileId: testProvisioningProfileFragment.developerPortalIdentifier,
provisioningProfile: testProvisioningProfileFragment.provisioningProfile,
};
const ctx = createCtxMock({
nonInteractive: mode === 'NON_INTERACTIVE',
nonInteractive: false,
appStore: {
...getAppstoreMock(),
defaultAuthenticationMode:
mode === 'NON_INTERACTIVE' ? AuthenticationMode.API_KEY : AuthenticationMode.USER,
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
authCtx: testAuthCtx,
createProvisioningProfileAsync: jest.fn(() => testProvisioningProfile),
@@ -64,30 +57,7 @@ describe('ConfigureProvisioningProfile', () => {
// expect provisioning profile not to be updated on apple portal
expect(jest.mocked(ctx.appStore.useExistingProvisioningProfileAsync).mock.calls.length).toBe(1);
});
it('errors with --freeze-credentials flag', async () => {
const ctx = createCtxMock({
freezeCredentials: true,
});
const appLookupParams = await getAppLookupParamsFromContextAsync(
ctx,
findApplicationTarget(testTargets)
);
const provProfConfigurator = new ConfigureProvisioningProfile(
appLookupParams,
testTarget,
testDistCertFragmentNoDependencies,
testProvisioningProfileFragment
);
await expect(provProfConfigurator.runAsync(ctx)).rejects.toThrowError(
ForbidCredentialModificationError
);
// expect provisioning profile not to be updated on expo servers
expect(jest.mocked(ctx.ios.updateProvisioningProfileAsync).mock.calls.length).toBe(0);
// expect provisioning profile not to be updated on apple portal
expect(jest.mocked(ctx.appStore.useExistingProvisioningProfileAsync).mock.calls.length).toBe(0);
});
it('errors with wrong authentication type in nonInteractive mode', async () => {
it('errors in Non Interactive Mode', async () => {
const ctx = createCtxMock({
nonInteractive: true,
});
@@ -102,7 +72,7 @@ describe('ConfigureProvisioningProfile', () => {
testProvisioningProfileFragment
);
await expect(provProfConfigurator.runAsync(ctx)).rejects.toThrowError(
InsufficientAuthenticationNonInteractiveError
MissingCredentialsNonInteractiveError
);
// expect provisioning profile not to be updated on expo servers
@@ -10,11 +10,7 @@ import {
testTarget,
testTargets,
} from '../../../__tests__/fixtures-ios';
import {
ForbidCredentialModificationError,
InsufficientAuthenticationNonInteractiveError,
} from '../../../errors';
import { AuthenticationMode } from '../../appstore/authenticateTypes';
import { MissingCredentialsNonInteractiveError } from '../../../errors';
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
import { CreateProvisioningProfile } from '../CreateProvisioningProfile';
@@ -26,15 +22,11 @@ describe('CreateProvisioningProfile', () => {
beforeEach(() => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue(testAppQueryByIdResponse);
});
const testCases = ['NON_INTERACTIVE', 'INTERACTIVE'];
test.each(testCases)('creates a Provisioning Profile in in %s Mode', async mode => {
it('creates a Provisioning Profile in Interactive Mode', async () => {
const ctx = createCtxMock({
nonInteractive: mode === 'NON_INTERACTIVE',
nonInteractive: false,
appStore: {
...getAppstoreMock(),
defaultAuthenticationMode:
mode === 'NON_INTERACTIVE' ? AuthenticationMode.API_KEY : AuthenticationMode.USER,
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
authCtx: testAuthCtx,
createProvisioningProfileAsync: jest.fn(() => testProvisioningProfile),
@@ -56,29 +48,7 @@ describe('CreateProvisioningProfile', () => {
// expect provisioning profile to be created on apple portal
expect(jest.mocked(ctx.appStore.createProvisioningProfileAsync).mock.calls.length).toBe(1);
});
it('errors with --freeze-credentials flag', async () => {
const ctx = createCtxMock({
freezeCredentials: true,
});
const appLookupParams = await getAppLookupParamsFromContextAsync(
ctx,
findApplicationTarget(testTargets)
);
const createProvProfAction = new CreateProvisioningProfile(
appLookupParams,
testTarget,
testDistCertFragmentNoDependencies
);
await expect(createProvProfAction.runAsync(ctx)).rejects.toThrowError(
ForbidCredentialModificationError
);
// expect provisioning profile not to be created on expo servers
expect(jest.mocked(ctx.ios.createProvisioningProfileAsync).mock.calls.length).toBe(0);
// expect provisioning profile not to be created on apple portal
expect(jest.mocked(ctx.appStore.createProvisioningProfileAsync).mock.calls.length).toBe(0);
});
it('errors with wrong authentication type in nonInteractive mode', async () => {
it('errors in Non Interactive Mode', async () => {
const ctx = createCtxMock({
nonInteractive: true,
});
@@ -92,7 +62,7 @@ describe('CreateProvisioningProfile', () => {
testDistCertFragmentNoDependencies
);
await expect(createProvProfAction.runAsync(ctx)).rejects.toThrowError(
InsufficientAuthenticationNonInteractiveError
MissingCredentialsNonInteractiveError
);
// expect provisioning profile not to be created on expo servers
@@ -15,11 +15,7 @@ import {
testTarget,
testTargets,
} from '../../../__tests__/fixtures-ios';
import {
ForbidCredentialModificationError,
InsufficientAuthenticationNonInteractiveError,
} from '../../../errors';
import { AuthenticationMode } from '../../appstore/authenticateTypes';
import { MissingCredentialsNonInteractiveError } from '../../../errors';
import { validateProvisioningProfileAsync } from '../../validators/validateProvisioningProfile';
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
import { SetUpProvisioningProfile } from '../SetUpProvisioningProfile';
@@ -36,185 +32,32 @@ describe('SetUpProvisioningProfile', () => {
beforeEach(() => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue(testAppQueryByIdResponse);
});
const testCases = ['NON_INTERACTIVE', 'INTERACTIVE'];
test.each(testCases)(
'repairs existing Provisioning Profile with bad build credentials in %s Mode',
async mode => {
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
const ctx = createCtxMock({
nonInteractive: mode === 'NON_INTERACTIVE',
appStore: {
...getAppstoreMock(),
defaultAuthenticationMode:
mode === 'NON_INTERACTIVE' ? AuthenticationMode.API_KEY : AuthenticationMode.USER,
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
authCtx: testAuthCtx,
listProvisioningProfilesAsync: jest.fn(() => [
{
provisioningProfileId: nullthrows(
testCommonIosAppCredentialsFragment.iosAppBuildCredentialsList[0]
.provisioningProfile
).developerPortalIdentifier,
},
]),
},
ios: {
...getNewIosApiMock(),
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
() => testCommonIosAppCredentialsFragment
),
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
createOrUpdateIosAppBuildCredentialsAsync: jest.fn(
() => testIosAppBuildCredentialsFragment
),
},
});
const appLookupParams = await getAppLookupParamsFromContextAsync(
ctx,
findApplicationTarget(testTargets)
);
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
appLookupParams,
testTarget,
IosDistributionType.AppStore
);
await setupProvisioningProfileAction.runAsync(ctx);
// expect build credentials to be created or updated on expo servers
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
1
);
// expect provisioning profile not to be deleted on expo servers
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
}
);
test.each(testCases)(
'sets up a new Provisioning Profile with bad build credentials in %s Mode',
async mode => {
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
const ctx = createCtxMock({
nonInteractive: mode === 'NON_INTERACTIVE',
appStore: {
...getAppstoreMock(),
defaultAuthenticationMode:
mode === 'NON_INTERACTIVE' ? AuthenticationMode.API_KEY : AuthenticationMode.USER,
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
authCtx: testAuthCtx,
listProvisioningProfilesAsync: jest.fn(() => []),
},
ios: {
...getNewIosApiMock(),
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
() => testCommonIosAppCredentialsFragment
),
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
},
});
const appLookupParams = await getAppLookupParamsFromContextAsync(
ctx,
findApplicationTarget(testTargets)
);
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
appLookupParams,
testTarget,
IosDistributionType.AppStore
);
await setupProvisioningProfileAction.runAsync(ctx);
// expect build credentials to be created or updated on expo servers
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
1
);
// expect provisioning profile to be deleted on expo servers
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(1);
}
);
test.each(testCases)(
'skips setting up a Provisioning Profile with prior build credentials configured properly in %s Mode',
async mode => {
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => true);
const ctx = createCtxMock({
nonInteractive: mode === 'NON_INTERACTIVE',
appStore: {
...getAppstoreMock(),
defaultAuthenticationMode:
mode === 'NON_INTERACTIVE' ? AuthenticationMode.API_KEY : AuthenticationMode.USER,
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
authCtx: testAuthCtx,
},
ios: {
...getNewIosApiMock(),
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
() => testCommonIosAppCredentialsFragment
),
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
},
});
const appLookupParams = await getAppLookupParamsFromContextAsync(
ctx,
findApplicationTarget(testTargets)
);
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
appLookupParams,
testTarget,
IosDistributionType.AppStore
);
await setupProvisioningProfileAction.runAsync(ctx);
// expect build credentials not to be created or updated on expo servers
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
0
);
// expect provisioning profile not to be deleted on expo servers
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
}
);
test.each(testCases)(
'sets up a Provisioning Profile with no prior build credentials configured in %s Mode',
async mode => {
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
const ctx = createCtxMock({
nonInteractive: mode === 'NON_INTERACTIVE',
appStore: {
...getAppstoreMock(),
defaultAuthenticationMode:
mode === 'NON_INTERACTIVE' ? AuthenticationMode.API_KEY : AuthenticationMode.USER,
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
authCtx: testAuthCtx,
},
ios: {
...getNewIosApiMock(),
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
},
});
const appLookupParams = await getAppLookupParamsFromContextAsync(
ctx,
findApplicationTarget(testTargets)
);
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
appLookupParams,
testTarget,
IosDistributionType.AppStore
);
await setupProvisioningProfileAction.runAsync(ctx);
// expect build credentials to be created or updated on expo servers
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
1
);
// expect provisioning profile not to be deleted on expo servers
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
}
);
it('errors with --freeze-credentials flag', async () => {
it('repairs existing Provisioning Profile with bad build credentials in Interactive Mode', async () => {
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
const ctx = createCtxMock({
freezeCredentials: true,
nonInteractive: false,
appStore: {
...getAppstoreMock(),
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
authCtx: testAuthCtx,
listProvisioningProfilesAsync: jest.fn(() => [
{
provisioningProfileId: nullthrows(
testCommonIosAppCredentialsFragment.iosAppBuildCredentialsList[0].provisioningProfile
).developerPortalIdentifier,
},
]),
},
ios: {
...getNewIosApiMock(),
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
() => testCommonIosAppCredentialsFragment
),
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
createOrUpdateIosAppBuildCredentialsAsync: jest.fn(
() => testIosAppBuildCredentialsFragment
),
},
});
const appLookupParams = await getAppLookupParamsFromContextAsync(
ctx,
@@ -225,9 +68,79 @@ describe('SetUpProvisioningProfile', () => {
testTarget,
IosDistributionType.AppStore
);
await expect(setupProvisioningProfileAction.runAsync(ctx)).rejects.toThrowError(
ForbidCredentialModificationError
await setupProvisioningProfileAction.runAsync(ctx);
// expect build credentials to be created or updated on expo servers
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
1
);
// expect provisioning profile not to be deleted on expo servers
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
});
it('sets up a new Provisioning Profile with bad build credentials in Interactive Mode', async () => {
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
const ctx = createCtxMock({
nonInteractive: false,
appStore: {
...getAppstoreMock(),
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
authCtx: testAuthCtx,
listProvisioningProfilesAsync: jest.fn(() => []),
},
ios: {
...getNewIosApiMock(),
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
() => testCommonIosAppCredentialsFragment
),
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
},
});
const appLookupParams = await getAppLookupParamsFromContextAsync(
ctx,
findApplicationTarget(testTargets)
);
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
appLookupParams,
testTarget,
IosDistributionType.AppStore
);
await setupProvisioningProfileAction.runAsync(ctx);
// expect build credentials to be created or updated on expo servers
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
1
);
// expect provisioning profile to be deleted on expo servers
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(1);
});
it('skips setting up a Provisioning Profile with prior build credentials configured properly in Interactive Mode', async () => {
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => true);
const ctx = createCtxMock({
nonInteractive: false,
appStore: {
...getAppstoreMock(),
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
authCtx: testAuthCtx,
//listProvisioningProfilesAsync: jest.fn(() => []),
},
ios: {
...getNewIosApiMock(),
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
() => testCommonIosAppCredentialsFragment
),
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
},
});
const appLookupParams = await getAppLookupParamsFromContextAsync(
ctx,
findApplicationTarget(testTargets)
);
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
appLookupParams,
testTarget,
IosDistributionType.AppStore
);
await setupProvisioningProfileAction.runAsync(ctx);
// expect build credentials not to be created or updated on expo servers
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
@@ -236,8 +149,39 @@ describe('SetUpProvisioningProfile', () => {
// expect provisioning profile not to be deleted on expo servers
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
});
it('sets up a Provisioning Profile with no prior build credentials configured in Interactive Mode', async () => {
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
const ctx = createCtxMock({
nonInteractive: false,
appStore: {
...getAppstoreMock(),
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
authCtx: testAuthCtx,
},
ios: {
...getNewIosApiMock(),
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
},
});
const appLookupParams = await getAppLookupParamsFromContextAsync(
ctx,
findApplicationTarget(testTargets)
);
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
appLookupParams,
testTarget,
IosDistributionType.AppStore
);
await setupProvisioningProfileAction.runAsync(ctx);
it('errors with wrong authentication type in nonInteractive mode', async () => {
// expect build credentials to be created or updated on expo servers
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
1
);
// expect provisioning profile not to be deleted on expo servers
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
});
it('errors in Non Interactive Mode', async () => {
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
const ctx = createCtxMock({
nonInteractive: true,
@@ -252,7 +196,7 @@ describe('SetUpProvisioningProfile', () => {
IosDistributionType.AppStore
);
await expect(setupProvisioningProfileAction.runAsync(ctx)).rejects.toThrowError(
InsufficientAuthenticationNonInteractiveError
MissingCredentialsNonInteractiveError
);
// expect build credentials not to be created or updated on expo servers
@@ -18,18 +18,13 @@ export class SetUpBuildCredentialsCommandAction {
public readonly projectInfo: CredentialsContextProjectInfo | null,
public readonly getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn,
private readonly platform: Platform,
private readonly profileName: string,
private readonly projectDir: string
private readonly profileName: string
) {}
async runAsync(): Promise<void> {
if (this.platform === Platform.IOS) {
return await new SetUpIosBuildCredentials(this, this.projectDir, this.profileName).runAsync();
return await new SetUpIosBuildCredentials(this, process.cwd(), this.profileName).runAsync();
}
return await new SetUpAndroidBuildCredentials(
this,
this.projectDir,
this.profileName
).runAsync();
return await new SetUpAndroidBuildCredentials(this, process.cwd(), this.profileName).runAsync();
}
}
@@ -38,7 +38,7 @@ export class SetUpIosBuildCredentials extends ManageIos {
}
const ctx = new CredentialsContext({
projectDir: this.projectDir,
projectDir: process.cwd(),
projectInfo,
user: this.callingAction.actor,
graphqlClient: this.callingAction.graphqlClient,
@@ -44,7 +44,6 @@ describe(AccountResolver, () => {
],
isExpoAdmin: false,
featureGates: {},
preferences: {},
};
describe('when inside project dir', () => {
@@ -52,7 +51,6 @@ describe(AccountResolver, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: 'test-project-id',
fullName: '@foo/wat',
name: 'wat',
slug: 'wat',
ownerAccount: {
id: 'account_id_888',
@@ -26,6 +26,7 @@ export async function runRegistrationUrlMethodAsync(
Log.log(
'Open the following link on your iOS devices (or scan the QR code) and follow the instructions to install the development profile:'
);
Log.newLine();
Log.log(chalk.green(`${registrationURL}`));
}
@@ -6,6 +6,5 @@ export function createCtxMock(options: { nonInteractive?: boolean } = {}): EASUp
graphqlClient: jest.fn() as any,
nonInteractive: options.nonInteractive ?? false,
app: { exp: testAppJson, projectId: 'test-project-id', projectDir: '/' },
vcsClient: jest.fn() as any,
};
}
-2
View File
@@ -1,13 +1,11 @@
import { ExpoConfig } from '@expo/config-types';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { Client } from '../vcs/vcs';
export type EASUpdateContext = {
graphqlClient: ExpoGraphqlClient;
nonInteractive: boolean;
app: { exp: ExpoConfig; projectId: string; projectDir: string };
vcsClient: Client;
};
export interface EASUpdateAction<T = any> {
+227 -354
View File
File diff suppressed because it is too large. Load diff
@@ -14,8 +14,6 @@ import {
} from '../generated';
import { UpdateFragmentNode } from '../types/Update';
const turtleJobRunId = process.env.EAS_BUILD_ID;
export const PublishMutation = {
async getUploadURLsAsync(
graphqlClient: ExpoGraphqlClient,
@@ -59,12 +57,7 @@ export const PublishMutation = {
}
${print(UpdateFragmentNode)}
`,
{
publishUpdateGroupsInput: publishUpdateGroupsInput.map(input => ({
...input,
turtleJobRunId,
})),
}
{ publishUpdateGroupsInput }
)
.toPromise()
);
@@ -1,61 +0,0 @@
import assert from 'assert';
import gql from 'graphql-tag';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { withErrorHandlingAsync } from '../client';
import {
MarkCliDoneInOnboardingUserPreferencesMutation,
MarkCliDoneInOnboardingUserPreferencesMutationVariables,
UserPreferencesOnboardingInput,
} from '../generated';
export const UserPreferencesMutation = {
async markCliDoneInOnboardingUserPreferencesAsync(
graphqlClient: ExpoGraphqlClient,
userPreferencesData: Partial<UserPreferencesOnboardingInput> & { appId: string }
): Promise<{
isCLIDone: boolean;
appId: string;
}> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<
MarkCliDoneInOnboardingUserPreferencesMutation,
MarkCliDoneInOnboardingUserPreferencesMutationVariables
>(
gql`
mutation MarkCliDoneInOnboardingUserPreferencesMutation(
$preferences: UserPreferencesInput!
) {
me {
setPreferences(preferences: $preferences) {
onboarding {
appId
isCLIDone
}
}
}
}
`,
{
preferences: {
onboarding: {
...userPreferencesData,
isCLIDone: true,
lastUsed: new Date().toISOString(),
},
},
}
)
.toPromise()
);
const appId = data.me.setPreferences.onboarding?.appId;
assert(appId, 'App ID must be defined');
const isCLIDone = data.me.setPreferences.onboarding?.isCLIDone;
assert(isCLIDone, 'isCLIDone must be defined and true');
return {
appId,
isCLIDone,
};
},
};
@@ -22,16 +22,6 @@ export const UserQuery = {
id
...AccountFragment
}
preferences {
onboarding {
appId
platform
deviceType
environment
isCLIDone
lastUsed
}
}
}
... on Robot {
firstName
@@ -5,7 +5,6 @@ import { AccountFragmentNode } from './Account';
export const AppFragmentNode = gql`
fragment AppFragment on App {
id
name
fullName
slug
ownerAccount {
@@ -13,13 +12,6 @@ export const AppFragmentNode = gql`
name
...AccountFragment
}
githubRepository {
id
metadata {
githubRepoOwnerName
githubRepoName
}
}
}
${AccountFragmentNode}
`;
@@ -37,6 +37,7 @@ export const BuildFragmentNode = gql`
}
}
channel
releaseChannel
distribution
iosEnterpriseProvisioning
buildProfile
-106
View File
@@ -1,106 +0,0 @@
import spawnAsync from '@expo/spawn-async';
import { runCommandAsync } from './runCommand';
import Log from '../log';
import { confirmAsync, promptAsync } from '../prompts';
export async function runGitCloneAsync({
targetProjectDir,
githubRepositoryName,
githubUsername,
cloneMethod,
}: {
githubUsername: string;
githubRepositoryName: string;
targetProjectDir: string;
cloneMethod: 'ssh' | 'https';
}): Promise<{
targetProjectDir: string;
}> {
const url =
cloneMethod === 'ssh'
? `[email protected]:${githubUsername}/${githubRepositoryName}.git`
: `https://github.com/${githubUsername}/${githubRepositoryName}.git`;
try {
await runCommandAsync({
command: 'git',
args: ['clone', url, targetProjectDir],
shouldPrintStderrLineAsStdout: line => {
return line.includes('Cloning into');
},
showSpinner: false,
});
return { targetProjectDir };
} catch (error: any) {
if (error.stderr.includes('already exists')) {
Log.warn(`Directory ${targetProjectDir} already exists.`);
const shouldContinue = await confirmAsync({
message: 'Do you want to clone your project to some other destination?',
});
if (!shouldContinue) {
throw new Error('Directory already exists. Aborting...');
}
const { newTargetProjectDir } = await promptAsync({
type: 'text',
name: 'newTargetProjectDir',
message: 'New target directory path:',
validate: (input: string) => input !== '',
});
return await runGitCloneAsync({
githubRepositoryName,
githubUsername,
targetProjectDir: newTargetProjectDir,
cloneMethod,
});
} else if (error.stderr.includes('Permission denied')) {
Log.warn(
`It seems like you do not have permission to clone the repository using ${cloneMethod}.`
);
const newMethod = cloneMethod === 'ssh' ? 'https' : 'ssh';
const shouldContinue = await confirmAsync({
message: `Do you want to clone the repository using ${newMethod} instead?`,
});
if (!shouldContinue) {
throw new Error('Permission denied. Aborting...');
}
return await runGitCloneAsync({
githubRepositoryName,
githubUsername,
targetProjectDir,
cloneMethod: newMethod,
});
} else {
throw error;
}
}
}
export async function runGitPushAsync({
targetProjectDir,
}: {
targetProjectDir: string;
}): Promise<void> {
await runCommandAsync({
command: 'git',
args: ['push'],
cwd: targetProjectDir,
});
}
export async function canAccessRepositoryUsingSshAsync({
githubUsername,
githubRepositoryName,
}: {
githubUsername: string;
githubRepositoryName: string;
}): Promise<boolean> {
try {
await spawnAsync('git', [
'ls-remote',
`[email protected]:${githubUsername}/${githubRepositoryName}.git`,
]);
return true;
} catch {
return false;
}
}
@@ -1,23 +0,0 @@
import { runCommandAsync } from './runCommand';
export async function installDependenciesAsync({
projectDir,
}: {
projectDir: string;
}): Promise<void> {
// TODO: add support for other package managers
await runCommandAsync({
command: 'npm',
args: ['install'],
cwd: projectDir,
shouldShowStderrLine: line => {
return (
!line.includes('WARN') &&
!line.includes('deprecated') &&
!line.includes('no longer maintained') &&
!line.includes('has been moved') &&
!(line === 'npm')
);
},
});
}
@@ -1,75 +0,0 @@
import spawnAsync from '@expo/spawn-async';
import chalk from 'chalk';
import Log from '../log';
import { Ora, ora } from '../ora';
export async function runCommandAsync({
cwd,
args,
command,
shouldShowStderrLine,
shouldPrintStderrLineAsStdout,
showSpinner = true,
}: {
cwd?: string;
args: string[];
command: string;
shouldShowStderrLine?: (line: string) => boolean;
shouldPrintStderrLineAsStdout?: (line: string) => boolean;
showSpinner?: boolean;
}): Promise<void> {
Log.log(`🏗️ Running ${chalk.bold(`${command} ${args.join(' ')}`)}...`);
let spinner: Ora | undefined;
if (showSpinner) {
spinner = ora(`${chalk.bold(`${command} ${args.join(' ')}`)}`).start();
}
const spawnPromise = spawnAsync(command, args, {
stdio: ['inherit', 'pipe', 'pipe'],
cwd,
});
const {
child: { stdout, stderr },
} = spawnPromise;
if (!stdout || !stderr) {
throw new Error(`Failed to spawn ${command}`);
}
stdout.on('data', data => {
for (const line of data.toString().trim().split('\n')) {
Log.log(`${chalk.gray(`[${command}]`)} ${line}`);
}
});
stderr.on('data', data => {
for (const line of data.toString().trim().split('\n')) {
if (shouldShowStderrLine && !shouldShowStderrLine(line)) {
continue;
}
const log = `${chalk.gray(`[${command}]`)} ${line}`;
if (shouldPrintStderrLineAsStdout && shouldPrintStderrLineAsStdout(line)) {
Log.log(log);
} else {
Log.warn(`${chalk.gray(`[${command}]`)} ${line}`);
}
}
});
try {
await spawnPromise;
} catch (error) {
if (showSpinner) {
spinner?.fail(`${chalk.bold(`${command} ${args.join(' ')}`)} failed`);
} else {
Log.error(`❌ ${chalk.bold(`${command} ${args.join(' ')}`)} failed`);
}
throw error;
}
if (showSpinner) {
spinner?.succeed(`${chalk.bold(`${command} ${args.join(' ')}`)} succeeded`);
} else {
Log.log(`✅ ${chalk.bold(`${command} ${args.join(' ')}`)} succeeded`);
}
Log.log('');
}
+1 -18
View File
@@ -67,24 +67,7 @@ export function ora(options?: Options | string): Ora {
};
spinner.start = (text): Ora => {
// wrapNativeLogs wraps calls to console so they always:
// 1. stop the spinner
// 2. log the message
// 3. start the spinner again
// Every restart of the spinner causes the spinner message to be logged again
// which makes logs look like
//
// - Exporting...
// [expo-cli] Starting Metro Bundler
// - Exporting...
// [expo-cli] Android Bundling complete 3492ms
// - Exporting...
//
// Skipping wrapping native logs removes the repeated interleaved "Exporting..." messages.
if (!disabled) {
wrapNativeLogs();
}
wrapNativeLogs();
return oraStart(text);
};
@@ -133,36 +133,14 @@ describe(ensureApplicationIdIsDefinedForManagedProjectAsync, () => {
projectId: '',
exp: {} as any,
vcsClient,
nonInteractive: false,
})
).rejects.toThrowError(/we can't update this file programmatically/);
});
it('throws an error in non-interactive mode', async () => {
const graphqlClient = instance(mock<ExpoGraphqlClient>());
vol.fromJSON(
{
'app.json': '{ "blah": {} }',
},
'/app'
);
await expect(
ensureApplicationIdIsDefinedForManagedProjectAsync({
graphqlClient,
projectDir: '/app',
projectId: '',
exp: {} as any,
vcsClient,
nonInteractive: true,
})
).rejects.toThrowError(/non-interactive/);
});
it('prompts for the Android package if using app.json', async () => {
const graphqlClient = instance(mock<ExpoGraphqlClient>());
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -185,7 +163,6 @@ describe(ensureApplicationIdIsDefinedForManagedProjectAsync, () => {
projectId: '',
exp: {} as any,
vcsClient,
nonInteractive: false,
})
).resolves.toBe('com.expo.notdominik');
expect(promptAsync).toHaveBeenCalled();
@@ -197,7 +174,6 @@ describe(ensureApplicationIdIsDefinedForManagedProjectAsync, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -220,7 +196,6 @@ describe(ensureApplicationIdIsDefinedForManagedProjectAsync, () => {
projectId: '',
exp: {} as any,
vcsClient,
nonInteractive: false,
})
).resolves.toBe('com.expo.notdominik');
const appJson = JSON.parse(await fs.readFile('/app/app.json', 'utf-8'));
@@ -28,14 +28,12 @@ export async function ensureApplicationIdIsDefinedForManagedProjectAsync({
projectId,
exp,
vcsClient,
nonInteractive,
}: {
graphqlClient: ExpoGraphqlClient;
projectDir: string;
projectId: string;
exp: ExpoConfig;
vcsClient: Client;
nonInteractive: boolean;
}): Promise<string> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
assert(workflow === Workflow.MANAGED, 'This function should be called only for managed projects');
@@ -45,13 +43,7 @@ export async function ensureApplicationIdIsDefinedForManagedProjectAsync({
moduleName: gradleUtils.DEFAULT_MODULE_NAME,
});
} catch {
return await configureApplicationIdAsync({
graphqlClient,
projectDir,
projectId,
exp,
nonInteractive,
});
return await configureApplicationIdAsync({ graphqlClient, projectDir, projectId, exp });
}
}
@@ -136,22 +128,12 @@ async function configureApplicationIdAsync({
projectDir,
projectId,
exp,
nonInteractive,
}: {
graphqlClient: ExpoGraphqlClient;
projectDir: string;
projectId: string;
exp: ExpoConfig;
nonInteractive: boolean;
}): Promise<string> {
if (nonInteractive) {
throw new Error(
`The "android.package" is required to be set in app config when running in non-interactive mode. ${learnMore(
'https://docs.expo.dev/versions/latest/config/app/#package'
)}`
);
}
const paths = getConfigFilePaths(projectDir);
// we can't automatically update app.config.js
if (paths.dynamicConfigPath) {
@@ -25,7 +25,6 @@ export async function getApplicationIdentifierAsync({
buildProfile,
platform,
vcsClient,
nonInteractive,
}: {
graphqlClient: ExpoGraphqlClient;
projectDir: string;
@@ -34,7 +33,6 @@ export async function getApplicationIdentifierAsync({
buildProfile: BuildProfile;
platform: Platform;
vcsClient: Client;
nonInteractive: boolean;
}): Promise<string> {
if (platform === Platform.ANDROID) {
const profile = buildProfile as BuildProfile<Platform.ANDROID>;
@@ -47,7 +45,6 @@ export async function getApplicationIdentifierAsync({
projectId,
exp,
vcsClient,
nonInteractive,
});
}
@@ -63,7 +60,6 @@ export async function getApplicationIdentifierAsync({
projectId,
exp,
vcsClient,
nonInteractive,
});
}
@@ -111,35 +111,14 @@ describe(ensureBundleIdentifierIsDefinedForManagedProjectAsync, () => {
projectId: '1234',
exp: {} as any,
vcsClient,
nonInteractive: false,
})
).rejects.toThrowError(/we can't update this file programmatically/);
});
it('throws an error in non-interactive mode', async () => {
const graphqlClient = instance(mock<ExpoGraphqlClient>());
vol.fromJSON(
{
'app.json': '{ "blah": {} }',
},
'/app'
);
await expect(
ensureBundleIdentifierIsDefinedForManagedProjectAsync({
graphqlClient,
projectDir: '/app',
projectId: '1234',
exp: {} as any,
vcsClient,
nonInteractive: true,
})
).rejects.toThrowError(/non-interactive/);
});
it('prompts for the bundle identifier if using app.json', async () => {
const graphqlClient = instance(mock<ExpoGraphqlClient>());
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -162,7 +141,6 @@ describe(ensureBundleIdentifierIsDefinedForManagedProjectAsync, () => {
projectId: '1234',
exp: {} as any,
vcsClient,
nonInteractive: false,
})
).resolves.toBe('com.expo.notdominik');
expect(promptAsync).toHaveBeenCalled();
@@ -172,7 +150,6 @@ describe(ensureBundleIdentifierIsDefinedForManagedProjectAsync, () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
slug: 'testing-123',
name: 'testing-123',
fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0],
});
@@ -195,7 +172,6 @@ describe(ensureBundleIdentifierIsDefinedForManagedProjectAsync, () => {
projectId: '1234',
exp: {} as any,
vcsClient,
nonInteractive: false,
})
).resolves.toBe('com.expo.notdominik');
const appJson = JSON.parse(await fs.readFile('/app/app.json', 'utf-8'));
@@ -21,14 +21,12 @@ export async function ensureBundleIdentifierIsDefinedForManagedProjectAsync({
projectId,
exp,
vcsClient,
nonInteractive,
}: {
graphqlClient: ExpoGraphqlClient;
projectDir: string;
projectId: string;
exp: ExpoConfig;
vcsClient: Client;
nonInteractive: boolean;
}): Promise<string> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS, vcsClient);
assert(workflow === Workflow.MANAGED, 'This function should be called only for managed projects');
@@ -41,7 +39,6 @@ export async function ensureBundleIdentifierIsDefinedForManagedProjectAsync({
projectDir,
exp,
projectId,
nonInteractive,
});
}
}
@@ -123,22 +120,12 @@ async function configureBundleIdentifierAsync({
projectDir,
projectId,
exp,
nonInteractive,
}: {
graphqlClient: ExpoGraphqlClient;
projectDir: string;
projectId: string;
exp: ExpoConfig;
nonInteractive: boolean;
}): Promise<string> {
if (nonInteractive) {
throw new Error(
`The "ios.bundleIdentifier" is required to be set in app config when running in non-interactive mode. ${learnMore(
'https://docs.expo.dev/versions/latest/config/app/#bundleidentifier'
)}`
);
}
const paths = getConfigFilePaths(projectDir);
// we can't automatically update app.config.js
if (paths.dynamicConfigPath) {
@@ -161,7 +148,6 @@ async function configureBundleIdentifierAsync({
exp,
projectId
);
const { bundleIdentifier } = await promptAsync({
name: 'bundleIdentifier',
type: 'text',
+15 -7
View File
@@ -67,11 +67,6 @@ export function isExpoUpdatesInstalled(projectDir: string): boolean {
return !!(packageJson.dependencies && 'expo-updates' in packageJson.dependencies);
}
export function isExpoNotificationsInstalled(projectDir: string): boolean {
const packageJson = getPackageJson(projectDir);
return !!(packageJson.dependencies && 'expo-notifications' in packageJson.dependencies);
}
export function isExpoUpdatesInstalledAsDevDependency(projectDir: string): boolean {
const packageJson = getPackageJson(projectDir);
return !!(packageJson.devDependencies && 'expo-updates' in packageJson.devDependencies);
@@ -145,6 +140,19 @@ export async function enforceRollBackToEmbeddedUpdateSupportAsync(
);
}
export async function isClassicUpdatesSupportedAsync(projectDir: string): Promise<boolean> {
const expoUpdatesPackageVersion = await getExpoUpdatesPackageVersionIfInstalledAsync(projectDir);
if (expoUpdatesPackageVersion === null) {
return false;
}
if (expoUpdatesPackageVersion.includes('canary')) {
return false;
}
return semver.lt(expoUpdatesPackageVersion, '0.19.0');
}
export async function isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync(
projectDir: string
): Promise<boolean> {
@@ -157,8 +165,8 @@ export async function isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAs
return true;
}
// Anything SDK 51 or greater uses the expo-updates CLI
return semver.gte(expoUpdatesPackageVersion, '0.25.4');
// TODO(wschurman): add semver check once we know the SDK51 version of expo-updates that supports this
return false;
}
export async function installExpoUpdatesAsync(
+18 -16
View File
@@ -1,6 +1,6 @@
import { ExpoConfig, Platform } from '@expo/config';
import { Updates } from '@expo/config-plugins';
import { Env, Workflow } from '@expo/eas-build-job';
import { Platform as EASBuildJobPlatform, Workflow } from '@expo/eas-build-job';
import JsonFile from '@expo/json-file';
import assert from 'assert';
import chalk from 'chalk';
@@ -13,6 +13,7 @@ import path from 'path';
import promiseLimit from 'promise-limit';
import { isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync } from './projectUtils';
import { resolveWorkflowAsync } from './workflow';
import { selectBranchOnAppAsync } from '../branch/queries';
import { getDefaultBranchNameAsync } from '../branch/utils';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
@@ -682,15 +683,13 @@ export function getRequestedPlatform(
export async function getRuntimeVersionObjectAsync({
exp,
platforms,
workflows,
projectDir,
env,
vcsClient,
}: {
exp: ExpoConfig;
platforms: Platform[];
workflows: Record<Platform, Workflow>;
projectDir: string;
env: Env | undefined;
vcsClient: Client;
}): Promise<{ platform: string; runtimeVersion: string }[]> {
return await Promise.all(
platforms.map(async platform => {
@@ -699,9 +698,8 @@ export async function getRuntimeVersionObjectAsync({
runtimeVersion: await getRuntimeVersionForPlatformAsync({
exp,
platform,
workflow: workflows[platform],
projectDir,
env,
vcsClient,
}),
};
})
@@ -711,15 +709,13 @@ export async function getRuntimeVersionObjectAsync({
async function getRuntimeVersionForPlatformAsync({
exp,
platform,
workflow,
projectDir,
env,
vcsClient,
}: {
exp: ExpoConfig;
platform: Platform;
workflow: Workflow;
projectDir: string;
env: Env | undefined;
vcsClient: Client;
}): Promise<string> {
if (platform === 'web') {
return 'UNVERSIONED';
@@ -731,11 +727,12 @@ async function getRuntimeVersionForPlatformAsync({
const extraArgs = Log.isDebug ? ['--debug'] : [];
const resolvedRuntimeVersionJSONResult = await expoUpdatesCommandAsync(
projectDir,
['runtimeversion:resolve', '--platform', platform, '--workflow', workflow, ...extraArgs],
{ env }
);
const resolvedRuntimeVersionJSONResult = await expoUpdatesCommandAsync(projectDir, [
'runtimeversion:resolve',
'--platform',
platform,
...extraArgs,
]);
const runtimeVersionResult = JSON.parse(resolvedRuntimeVersionJSONResult);
Log.debug('runtimeversion:resolve output:');
@@ -758,6 +755,11 @@ async function getRuntimeVersionForPlatformAsync({
const runtimeVersion = exp[platform]?.runtimeVersion ?? exp.runtimeVersion;
if (typeof runtimeVersion === 'object') {
const workflow = await resolveWorkflowAsync(
projectDir,
platform as EASBuildJobPlatform,
vcsClient
);
if (workflow !== Workflow.MANAGED) {
throw new Error(
`You're currently using the bare workflow, where runtime version policies are not supported. You must set your runtime version manually. For example, define your runtime version as "1.0.0", not {"policy": "appVersion"} in your app config. ${learnMore(
@@ -1,6 +1,5 @@
import { ExpoConfig } from '@expo/config';
import { Updates } from '@expo/config-plugins';
import { Env, Workflow } from '@expo/eas-build-job';
import { isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync } from './projectUtils';
import Log from '../log';
@@ -12,17 +11,11 @@ import {
export async function resolveRuntimeVersionAsync({
exp,
platform,
workflow,
projectDir,
env,
cwd,
}: {
exp: ExpoConfig;
platform: 'ios' | 'android';
workflow: Workflow;
projectDir: string;
env: Env | undefined;
cwd?: string;
}): Promise<string | null> {
if (!(await isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync(projectDir))) {
// fall back to the previous behavior (using the @expo/config-plugins eas-cli dependency rather
@@ -35,11 +28,12 @@ export async function resolveRuntimeVersionAsync({
const extraArgs = Log.isDebug ? ['--debug'] : [];
const resolvedRuntimeVersionJSONResult = await expoUpdatesCommandAsync(
projectDir,
['runtimeversion:resolve', '--platform', platform, '--workflow', workflow, ...extraArgs],
{ env, cwd }
);
const resolvedRuntimeVersionJSONResult = await expoUpdatesCommandAsync(projectDir, [
'runtimeversion:resolve',
'--platform',
platform,
...extraArgs,
]);
const runtimeVersionResult = JSON.parse(resolvedRuntimeVersionJSONResult);
Log.debug('runtimeversion:resolve output:');
@@ -24,7 +24,6 @@ import {
import { UpdateQuery } from '../../graphql/queries/UpdateQuery';
import Log from '../../log';
import { resolveRuntimeVersionAsync } from '../../project/resolveRuntimeVersionAsync';
import { resolveWorkflowPerPlatformAsync } from '../../project/workflow';
import { confirmAsync, promptAsync } from '../../prompts';
import { truthy } from '../../utils/expodash/filter';
import {
@@ -273,17 +272,10 @@ export class CreateRollout implements EASUpdateAction<UpdateChannelBasicInfoFrag
async selectRuntimeVersionFromProjectConfigAsync(ctx: EASUpdateContext): Promise<string> {
const platforms: ('ios' | 'android')[] = ['ios', 'android'];
const workflows = await resolveWorkflowPerPlatformAsync(ctx.app.projectDir, ctx.vcsClient);
const runtimes = (
await Promise.all(
platforms.map(platform =>
resolveRuntimeVersionAsync({
projectDir: ctx.app.projectDir,
exp: ctx.app.exp,
platform,
workflow: workflows[platform],
env: undefined,
})
resolveRuntimeVersionAsync({ projectDir: ctx.app.projectDir, exp: ctx.app.exp, platform })
)
)
).filter(truthy);
+6 -4
View File
@@ -270,6 +270,8 @@ async function handleBuildListSourceAsync(
): Promise<ResolvedArchiveSource> {
try {
const appPlatform = toAppPlatform(ctx.platform);
const expiryDate = new Date(); // artifacts expire after 30 days
expiryDate.setDate(expiryDate.getDate() - 30);
const recentBuilds = await getRecentBuildsForSubmissionAsync(
ctx.graphqlClient,
@@ -292,7 +294,7 @@ async function handleBuildListSourceAsync(
});
}
if (recentBuilds.every(it => new Date(it.expirationDate) <= new Date())) {
if (recentBuilds.every(it => new Date(it.updatedAt) < expiryDate)) {
Log.error(
chalk.bold(
'It looks like all of your build artifacts have expired. ' +
@@ -304,7 +306,7 @@ async function handleBuildListSourceAsync(
});
}
const choices = recentBuilds.map(build => formatBuildChoice(build));
const choices = recentBuilds.map(build => formatBuildChoice(build, expiryDate));
choices.push({
title: 'None of the above (select another option)',
value: null,
@@ -334,7 +336,7 @@ async function handleBuildListSourceAsync(
}
}
function formatBuildChoice(build: BuildFragment): prompts.Choice {
function formatBuildChoice(build: BuildFragment, expiryDate: Date): prompts.Choice {
const {
id,
updatedAt,
@@ -378,7 +380,7 @@ function formatBuildChoice(build: BuildFragment): prompts.Choice {
title,
description: filteredDescriptionArray.length > 0 ? filteredDescriptionArray.join('\n') : '',
value: build,
disabled: new Date(build.expirationDate) < new Date(),
disabled: buildDate < expiryDate,
};
}
@@ -273,9 +273,7 @@ describe(getArchiveAsync, () => {
jest.mocked(getRecentBuildsForSubmissionAsync).mockResolvedValueOnce([
{
...MOCK_BUILD_FRAGMENT,
// We're setting expirationDate to be in the past,
// because we want to build to appear expired.
expirationDate: new Date(Date.now() - 31 * 24 * 3600 * 1000),
updatedAt: new Date(Date.now() - 31 * 24 * 3600 * 1000),
} as BuildFragment,
]);
jest
@@ -1,6 +1,5 @@
import { ExpoConfig } from '@expo/config';
import { AndroidConfig, AndroidManifest, XML } from '@expo/config-plugins';
import { Env, Workflow } from '@expo/eas-build-job';
import { RequestedPlatform } from '../../platform';
import { isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync } from '../../project/projectUtils';
@@ -10,25 +9,18 @@ import { ensureValidVersions } from '../utils';
/**
* Synchronize updates configuration to native files. This needs to do essentially the same thing as `withUpdates`
*/
export async function syncUpdatesConfigurationAsync({
projectDir,
exp,
workflow,
env,
}: {
projectDir: string;
exp: ExpoConfig;
workflow: Workflow;
env: Env | undefined;
}): Promise<void> {
export async function syncUpdatesConfigurationAsync(
projectDir: string,
exp: ExpoConfig
): Promise<void> {
ensureValidVersions(exp, RequestedPlatform.Android);
if (await isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync(projectDir)) {
await expoUpdatesCommandAsync(
projectDir,
['configuration:syncnative', '--platform', 'android', '--workflow', workflow],
{ env }
);
await expoUpdatesCommandAsync(projectDir, [
'configuration:syncnative',
'--platform',
'android',
]);
return;
}
@@ -61,6 +53,18 @@ export async function syncUpdatesConfigurationAsync({
await XML.writeXMLAsync({ path: stringsJSONPath, xml: updatedStringsResourceXML });
}
export async function readReleaseChannelSafelyAsync(projectDir: string): Promise<string | null> {
try {
const androidManifest = await getAndroidManifestAsync(projectDir);
return AndroidConfig.Manifest.getMainApplicationMetaDataValue(
androidManifest,
AndroidConfig.Updates.Config.RELEASE_CHANNEL
);
} catch {
return null;
}
}
export async function readChannelSafelyAsync(projectDir: string): Promise<string | null> {
try {
const androidManifest = await getAndroidManifestAsync(projectDir);
+5 -20
View File
@@ -1,5 +1,5 @@
import { ExpoConfig } from '@expo/config-types';
import { Env, Platform, Workflow } from '@expo/eas-build-job';
import { Platform, Workflow } from '@expo/eas-build-job';
import { EasJsonAccessor } from '@expo/eas-json';
import chalk from 'chalk';
import fs from 'fs-extra';
@@ -264,33 +264,20 @@ async function ensureEASUpdateIsConfiguredNativelyAsync(
projectDir,
platform,
workflows,
env,
}: {
exp: ExpoConfig;
projectDir: string;
platform: RequestedPlatform;
workflows: Record<Platform, Workflow>;
env: Env | undefined;
}
): Promise<void> {
if (['all', 'android'].includes(platform) && workflows.android === Workflow.GENERIC) {
await syncAndroidUpdatesConfigurationAsync({
projectDir,
exp,
workflow: workflows.android,
env,
});
await syncAndroidUpdatesConfigurationAsync(projectDir, exp);
Log.withTick(`Configured ${chalk.bold('AndroidManifest.xml')} for EAS Update`);
}
if (['all', 'ios'].includes(platform) && workflows.ios === Workflow.GENERIC) {
await syncIosUpdatesConfigurationAsync({
vcsClient,
projectDir,
exp,
workflow: workflows.ios,
env,
});
await syncIosUpdatesConfigurationAsync(vcsClient, projectDir, exp);
Log.withTick(`Configured ${chalk.bold('Expo.plist')} for EAS Update`);
}
}
@@ -319,7 +306,8 @@ export async function ensureEASUpdateIsConfiguredInEasJsonAsync(projectDir: stri
const easBuildProfilesWithChannels = Object.keys(easJsonRawObject.build).reduce(
(acc, profileNameKey) => {
const buildProfile = easJsonRawObject.build[profileNameKey];
const isNotAlreadyConfigured = !buildProfile.channel;
const isNotAlreadyConfigured = !buildProfile.channel && !buildProfile.releaseChannel;
if (isNotAlreadyConfigured) {
return {
...acc,
@@ -369,14 +357,12 @@ export async function ensureEASUpdateIsConfiguredAsync({
projectDir,
vcsClient,
platform,
env,
}: {
exp: ExpoConfig;
projectId: string;
projectDir: string;
vcsClient: Client;
platform: RequestedPlatform | null;
env: Env | undefined;
}): Promise<void> {
const hasExpoUpdates = isExpoUpdatesInstalledOrAvailable(
projectDir,
@@ -414,7 +400,6 @@ export async function ensureEASUpdateIsConfiguredAsync({
projectDir,
platform,
workflows,
env,
});
}
@@ -1,6 +1,5 @@
import { ExpoConfig } from '@expo/config';
import { IOSConfig } from '@expo/config-plugins';
import { Env, Workflow } from '@expo/eas-build-job';
import { RequestedPlatform } from '../../platform';
import { isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync } from '../../project/projectUtils';
@@ -9,27 +8,15 @@ import { readPlistAsync, writePlistAsync } from '../../utils/plist';
import { Client } from '../../vcs/vcs';
import { ensureValidVersions } from '../utils';
export async function syncUpdatesConfigurationAsync({
vcsClient,
projectDir,
exp,
workflow,
env,
}: {
vcsClient: Client;
projectDir: string;
exp: ExpoConfig;
workflow: Workflow;
env: Env | undefined;
}): Promise<void> {
export async function syncUpdatesConfigurationAsync(
vcsClient: Client,
projectDir: string,
exp: ExpoConfig
): Promise<void> {
ensureValidVersions(exp, RequestedPlatform.Ios);
if (await isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync(projectDir)) {
await expoUpdatesCommandAsync(
projectDir,
['configuration:syncnative', '--platform', 'ios', '--workflow', workflow],
{ env }
);
await expoUpdatesCommandAsync(projectDir, ['configuration:syncnative', '--platform', 'ios']);
return;
}
@@ -58,6 +45,15 @@ async function writeExpoPlistAsync(
await vcsClient.trackFileAsync(expoPlistPath);
}
export async function readReleaseChannelSafelyAsync(projectDir: string): Promise<string | null> {
try {
const expoPlist = await readExpoPlistAsync(projectDir);
return expoPlist[IOSConfig.Updates.Config.RELEASE_CHANNEL] ?? null;
} catch {
return null;
}
}
export async function readChannelSafelyAsync(projectDir: string): Promise<string | null> {
try {
const expoPlist = await readExpoPlistAsync(projectDir);
@@ -54,6 +54,8 @@ const ExpoPlistWithChannel = `
<true/>
<key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer>
<key>EXUpdatesReleaseChannel</key>
<string>default</string>
<key>EXUpdatesSDKVersion</key>
<string>40.0.0</string>
<key>EXUpdatesURL</key>
@@ -75,6 +77,8 @@ const ExpoPlistWithOutChannel = `
<true/>
<key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer>
<key>EXUpdatesReleaseChannel</key>
<string>default</string>
<key>EXUpdatesSDKVersion</key>
<string>40.0.0</string>
<key>EXUpdatesURL</key>
+1 -1
View File
@@ -195,7 +195,7 @@ export async function republishAsync({
? [{ label: 'iOS update ID', value: updatesRepublishedByPlatform.ios.id }]
: []),
{ label: 'Message', value: updateMessage },
{ label: 'EAS Dashboard', value: link(updateGroupUrl, { dim: false }) },
{ label: 'Website link', value: link(updateGroupUrl, { dim: false }) },
])
);
}
+31 -1
View File
@@ -12,11 +12,13 @@ import {
UpdateFragment,
User,
} from '../graphql/generated';
import { learnMore } from '../log';
import Log, { learnMore } from '../log';
import { RequestedPlatform } from '../platform';
import { confirmAsync } from '../prompts';
import { getActorDisplayName } from '../user/User';
import groupBy from '../utils/expodash/groupBy';
import formatFields from '../utils/formatFields';
import { ProfileData } from '../utils/profiles';
export type FormatUpdateParameter = Pick<Update, 'id' | 'createdAt' | 'message'> & {
actor?:
@@ -272,3 +274,31 @@ export async function checkEASUpdateURLIsSetAsync(
return configuredURL === expectedURL;
}
export async function validateBuildProfileConfigMatchesProjectConfigAsync(
exp: ExpoConfig,
buildProfile: ProfileData<'build'>,
projectId: string,
nonInteractive: boolean
): Promise<void> {
if ((await checkEASUpdateURLIsSetAsync(exp, projectId)) && buildProfile.profile.releaseChannel) {
const warning = `» Your project is configured for EAS Update, but build profile "${
buildProfile.profileName
}" in ${chalk.bold('eas.json')} specifies the \`releaseChannel\` property.
For EAS Update, you need to specify the \`channel\` property, or your build will not be able to receive any updates
${learnMore('https://docs.expo.dev/eas-update/getting-started/#configure-your-project')}`;
Log.warn(warning);
if (!nonInteractive) {
const answer = await confirmAsync({
message: `Would you like to proceed?`,
});
if (!answer) {
Log.log('Aborting...');
process.exit(1);
}
}
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ export async function uploadFileAtPathToGCSAsync(
graphqlClient: ExpoGraphqlClient,
type: UploadSessionType,
path: string,
handleProgressEvent: ProgressHandler = () => {}
handleProgressEvent: ProgressHandler
): Promise<string> {
const signedUrl = await UploadSessionMutation.createUploadSessionAsync(graphqlClient, type);
+2 -2
View File
@@ -171,8 +171,8 @@ export default class SessionManager {
Log.log(
`Log in to EAS with email or username (exit and run ${chalk.bold(
'eas login --help'
)} to see other login options)`
'eas login'
)} for other options)`
);
const { username, password } = await promptAsync([
@@ -19,7 +19,6 @@ const userStub: Actor = {
],
isExpoAdmin: false,
featureGates: {},
preferences: {},
};
const ssoUserStub: Actor = {
@@ -40,7 +39,6 @@ const ssoUserStub: Actor = {
],
isExpoAdmin: false,
featureGates: {},
preferences: {},
};
const robotStub: Actor = {
Loaded 100 of 111 files, more files were not shown because too many files have changed in this diff. Show more