Compare commits

..
Author SHA1 Message Date
evanbacon 2d1bf434ec feat(eas-cli): Add support for VisionOS in provisioning profiles and tests 2025-01-29 13:30:41 -08:00
Evan Bacon ab507d0bdd feat(eas-cli): Use a fallback generated name during EAS Submit (#2842) 2025-01-28 16:49:35 -08:00
Radosław Krzemień 7505b8d40a [eas-cli] [ENG-14633] Start autoIncremented builds at 1 (#2828)
* [eas-cli] Don't bump default version for Android

When the versionCode is initialized with 1, don't bump it even if autoIncrement=true

See: https://linear.app/expo/issue/ENG-14633/start-autoincrement-counter-at-0-so-first-build-has-buildnumber-==-1

* [eas-cli] Add tests for Android version

Testing the version resolving behavior for Android

See: https://linear.app/expo/issue/ENG-14633/start-autoincrement-counter-at-0-so-first-build-has-buildnumber-==-1

* [eas-cli] Don't bump default version for Ios

When the buildNumber is 1, don't bump it even if autoIncrement=true

See: https://linear.app/expo/issue/ENG-14633/start-autoincrement-counter-at-0-so-first-build-has-buildnumber-==-1

* [eas-cli] Add tests for Ios version

Testing the version resolving behavior for Ios

See: https://linear.app/expo/issue/ENG-14633/start-autoincrement-counter-at-0-so-first-build-has-buildnumber-==-1

* [eas-cli] Lint

Lint

See: https://linear.app/expo/issue/ENG-14633/start-autoincrement-counter-at-0-so-first-build-has-buildnumber-==-1

* [eas-cli] Add tests

Added tests for remote version autoIncrement=true

See: https://linear.app/expo/issue/ENG-14633/start-autoincrement-counter-at-0-so-first-build-has-buildnumber-==-1

* [eas-cli] Add changelog

Updated CHANGELOG.md

See: https://linear.app/expo/issue/ENG-14633/start-autoincrement-counter-at-0-so-first-build-has-buildnumber-==-1
2025-01-28 15:56:40 +01:00
Expo Bot b31ddee6e7 Upgrade eas-build dependencies (#2840) 2025-01-27 13:58:25 +01:00
Expo CI 01ce802b73 update CHANGELOG.md 2025-01-22 13:31:34 +00:00
Radoslaw Krzemien f75ef22154 v14.5.0 2025-01-22 14:24:59 +01:00
Radosław Krzemień 7d3f4e8fd4 [eas-json] [ENG-14636] Allow longer submit profile extension chains (#2831)
* [eas-json] Extend submit profile expansion chain

Extended the limit for expansion chain for submit profiles to equal that of build profiles - 5

See: https://linear.app/expo/issue/ENG-14636/allow-longer-profile-extend-chains-in-easjson

* [eas-json] Add tests for submit profiles

Added tests for submit profiles extension chain

See: https://linear.app/expo/issue/ENG-14636/allow-longer-profile-extend-chains-in-easjson

* [eas-json] Add tests for build profiles

Added tests for build profiles extension chain

See: https://linear.app/expo/issue/ENG-14636/allow-longer-profile-extend-chains-in-easjson

* [eas-json] Lint

Lint

See: https://linear.app/expo/issue/ENG-14636/allow-longer-profile-extend-chains-in-easjson

* [eas-json] Add changelog

Updated CHANGELOG.md

See: https://linear.app/expo/issue/ENG-14636/allow-longer-profile-extend-chains-in-easjson
2025-01-22 14:23:07 +01:00
Radosław Krzemień 004caf4bb7 [eas-cli] [ENG-14629] Improve AppleID validation (#2830)
* [eas-cli] Remove invalid unprintable characters from AppleID

Clears the AppleID received from the prompt

See: https://linear.app/expo/issue/ENG-14629/improve-apple-id-input-validation

* [eas-cli] Add tests

Adds tests for `resolveUserCredentialsAsync` function, including reading malformed AppleID from the prompt

See: https://linear.app/expo/issue/ENG-14629/improve-apple-id-input-validation

* [eas-cli] Remove line

Removed redundant expect()

See: https://linear.app/expo/issue/ENG-14629/improve-apple-id-input-validation

* [eas-cli] Add context

Added a comment with context for the change

See: https://linear.app/expo/issue/ENG-14629/improve-apple-id-input-validation

* [eas-cli] Add changelog

Updated CHANGELOG.md

See: https://linear.app/expo/issue/ENG-14629/improve-apple-id-input-validation
2025-01-22 14:17:14 +01:00
Kadi Kraman 5a16f3d6d8 [eas-cli] Detect valid JSON files during hosting upload (#2829)
* Use application/json as content type for valid JSON without an extension

* Add changelog

* Pull getContentTypeAsync to a separate function
2025-01-21 10:51:32 +00:00
Expo Bot d2e4cb5fc6 Upgrade eas-build dependencies (#2826) 2025-01-18 16:29:59 +01:00
Kadi Kraman 2c1c9b91cb [eas-cli] Use deploy as top level hosting command (#2824)
* Replace preview with help

* Remove redundant alias

* Update description

* Rename top level command from worker -> deploy

* Add changelog

* End sentence with a .

* Add worker:deploy as an alias to deploy
2025-01-17 12:16:02 +00:00
Will Schurman b447194c85 [eas-cli] Make variable naming more explicit, remove deprecated runtimeFingerprintSource uses (#2816) 2025-01-16 08:51:32 -08:00
Kadi Kraman 9ab095597a [eas-cli] fix deploy command typos (#2822)
* EAS hosting is in preview (not beta)

* Remove errant curly

* Add changelog
2025-01-16 13:09:44 +00:00
Szymon Dziedzic b1edff7242 [eas-cli] allow filtering by fingerprint-hash in eas build:list command (#2818)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

Allow filtering by `--fingerprint-hash` in the `eas build:list` command

# How

Update GQL schema to add a new filter field, and add a new flag to use it.

# Test Plan

Test locally
2025-01-16 00:38:54 +01:00
Expo CI 4d9e37269c update CHANGELOG.md 2025-01-15 09:48:15 +00:00
Szymon Dziedzic e7b48949db v14.4.1 2025-01-15 10:43:15 +01:00
Mubin Ansari 1c3edbe492 Bump eas-cli-local-build-plugin to latest version to support new bun lock file (#2817)
* Bump eas-cli-local-build-plugin to latest version to support new bun lock file

* Update changelog
2025-01-15 10:42:38 +01:00
Will Schurman deaa3a9105 [eas-cli] Enhance eas env:exec command by enabling shell execution for commands (#2811) 2025-01-13 09:03:06 -08:00
Quinlan Jung cbfcdaec5d [eas-cli] upload fingerprint from local project state (#2808)
* [eas-cli] upload fingerprint from local project state

* Temporary Commit at 1/10/2025, 8:37:46 AM
2025-01-10 11:38:24 -08:00
Expo Bot 37a32c93ee Upgrade eas-build dependencies (#2810) 2025-01-10 15:46:42 +01:00
Szymon Dziedzic 6461a78fa2 [ENG-14644][eas-cli] make "No remote versions are configured" message green instead of yellow (#2805)
* [eas-cli] make "No remote versions are configured" message green instead of yellow

* add changelog
2025-01-10 11:09:18 +01:00
Szymon Dziedzic 86c8ac093f [ENG-14643][eas-cli] make automatic env resolution message shorter (#2806)
* [eas-cli] make automatic env resolution message shorter

* add changelog entry
2025-01-10 11:03:52 +01:00
Expo CI 608cdff981 update CHANGELOG.md 2025-01-09 16:53:02 +00:00
Phil Pluckthun 4dc4ef85fa v14.4.0 2025-01-09 16:47:28 +00:00
Phil Pluckthun eaf15acb76 feat(eas-cli): Unhide deploy and deploy:alias commands (#2807)
* Unhide deploy command

* Add CHANGELOG entry
2025-01-09 12:47:46 +00:00
Will Schurman ae4ca91658 [eas-cli] Silence all non-command output in non-interactive mode of eas env:exec (#2800) 2025-01-08 09:09:23 -08:00
Phil PluckthunandKadi Kraman 23ebb77952 [eas-cli] Combine .env and EAS environment variables for deploy command (#2783)
* Allow env vars to be loaded from .env and EAS env simultaneously

* Add CHANGELOG entry

* Apply lints

* Switch to new @expo/[email protected] API

* Update packages/eas-cli/src/commands/worker/deploy.ts

Co-authored-by: Kadi Kraman <[email protected]>

---------

Co-authored-by: Kadi Kraman <[email protected]>
2025-01-08 17:02:27 +00:00
Expo CI ed2352f289 update CHANGELOG.md 2025-01-08 13:53:33 +00:00
Szymon Dziedzic 38ae8b68f1 v14.3.1 2025-01-08 14:48:06 +01:00
Kudo Chien 5e02807ac9 [eas-cli] bump package-manager version (#2801)
# Why

fixes https://github.com/expo/eas-cli/issues/2782

# How

`@expo/[email protected]` supports bun text-based lock file. this pr bumps the version
2025-01-08 21:44:38 +08:00
Piotr Szeremeta f05d6ca3a2 ENG-14518 Add keystoreName field to build profile (#2775)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

[ENG-14518: Add `keystoreName` field to build profile to allow selecting/creating named keystore to use for a build](https://linear.app/expo/issue/ENG-14518/add-keystorename-field-to-build-profile-to-allow-selectingcreating)

Allow specifying keystore name of credentials to be used with a build.

# How

* Added `keystoreName` to eas.json schema
* Pass `name` option to `AndroidCredentialsProvider`

# Test Plan

Added test, tested manually.

1. Create a new android credentials with `eas credentials`
2. Specify `keystoreName` in build profile in `eas.json`
3. Ensure that it works only for android builds
4. Ensure it works only for `remote` `credentialsSource`
5. Ensure that the correct credentials are used

<img width="326" alt="image" src="https://github.com/user-attachments/assets/1245c459-5fa1-4253-b5fc-bdf7a68b814a" />

<img width="628" alt="image" src="https://github.com/user-attachments/assets/5bca4cf7-c895-462c-b527-ef37ce659aca" />
2025-01-07 13:07:47 +01:00
Expo CI b57082647d update CHANGELOG.md 2025-01-07 10:50:10 +00:00
Stanisław Chmiela 9d4fdc3c75 v14.3.0 2025-01-07 11:44:47 +01:00
Stanisław Chmiela d1af9e385f [eas-cli] Upload package.json alongside eas.json when creating a workflow run (#2786)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

Thanks to @szdziedzic's changes to `www`, if we send a `package.json` along with `eas.json` when setting up a workflow run, EAS will pick the right images based on SDK and RN versions as `eas build` would.

# How

Added uploading `package.json` after uploading `eas.json`, added the new argument to the mutation.

# Test Plan

Tested manually.

<img width="874" alt="Zrzut ekranu 2024-12-20 o 22 27 20" src="https://github.com/user-attachments/assets/383c4a19-17a1-4f46-8113-bc6dc8aaba2e" />
<img width="833" alt="Zrzut ekranu 2024-12-20 o 22 27 58" src="https://github.com/user-attachments/assets/be9e2396-0779-4043-925f-d784b5168764" />
2025-01-07 11:05:46 +01:00
Quinlan Jung 59bbe19dde [eas-cli][eas-json] use node18 as tsconfig base (#2739)
* [eas-cli][eas-json] use node18 as tsconfig base

* Temporary Commit at 1/6/2025, 9:06:36 AM

* Temporary Commit at 1/6/2025, 9:17:06 AM
2025-01-06 09:44:24 -08:00
Bartosz Kaszubowski 5e5b55f9e4 [eas-cli] wording tweaks for messages (#2790) 2025-01-02 15:40:00 +01:00
Kadi Kraman a1d086ffc6 [eas-cli] parse request body for error only when needed (#2784)
* Parse error only when needed

* Add changelog

* Fix linting
2024-12-20 15:36:39 +00:00
Phil Pluckthun 2a96d04be3 [eas-cli] Update deploy/alias log output copy (#2780)
* Update product name

* Highlight `Project export` text

* Update export copy

* Update product name for alias

* Add changelog entry
2024-12-19 13:04:33 +00:00
Phil Pluckthun d126ee988a [eas-cli] Prevent eas-cli dependencies check from running repeatedly (#2781)
* Only check for dependencies once

* Add CHANGELOG entry

* Apply lints
2024-12-19 13:03:58 +00:00
Expo Bot 4bb550e3bb Upgrade eas-build dependencies (#2776) 2024-12-18 13:12:27 +01:00
Kadi Kraman 6a55c65908 [eas-cli] show deployment errors (#2771)
* Show upload errors

* Add changelog

* Fix changelog

* Show error from request body when applicable
2024-12-13 17:08:53 +00:00
Expo CI ab3495da5e update CHANGELOG.md 2024-12-13 15:25:41 +00:00
Szymon Dziedzic 63b67635e0 v14.2.0 2024-12-13 16:20:07 +01:00
Szymon Dziedzic 4b5cda3bbc [eas-cli] fix request for switching providers (#2769)
* [eas-cli] fix request for switching providers

* add changelog entry
2024-12-13 16:19:04 +01:00
Szymon Dziedzic 63e3c1f2ab [eas-cli] improvements to eas env:exec command (#2762)
* [eas-cli] improvements to eas env:exec command

* add changelog
2024-12-13 16:05:02 +01:00
Expo Bot 3f1543989f Upgrade eas-build dependencies (#2768) 2024-12-13 16:04:25 +01:00
Piotr Szeremeta 1c964d2ab1 [ENG-14328] Pass environment to PublishMutation.publishUpdateGroupAsync (#2757)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

[ENG-14328: store `environment` info on update entity](https://linear.app/expo/issue/ENG-14328/store-environment-info-on-update-entity)

Pass `environment` to `PublishMutation.publishUpdateGroupAsync` to store in the db.

Waits for changes on the `www`. https://github.com/expo/universe/pull/17921
2024-12-13 10:41:08 +01:00
Kadi Kraman bc282b6262 [eas-cli] Add environment flag to deploy script (#2763)
* Ensure worker env flag is passed through

* Add changelog
2024-12-11 16:43:43 +00:00
Phil Pluckthun 17661d1cf0 feat(eas-cli): Add --dry-run flag to deploy command (#2761)
* Add dry-run option to deploy command

* Add `--dry-run` flag to deploy command

* Update CHANGELOG
2024-12-11 16:28:12 +00:00
Will Schurman 8d35745124 [eas-cli] Upgrade @expo/multipart-body-parser (#2751) 2024-12-10 10:10:25 -08:00
Will Schurman 3740ee4ef9 [eas-cli] Remove random branch name generation for --auto branch name non-vcs fallback (#2747) 2024-12-10 08:56:01 -08:00
Expo CI 751d3d1287 update CHANGELOG.md 2024-12-10 10:30:23 +00:00
57 changed files with 4264 additions and 482 deletions

No files matched your search

+93 -1
View File
@@ -8,12 +8,104 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features
- Add support for enabeling broadcast Push Notifications capability option, by setting `ios.usesBroadcastPushNotifications` to `true` in app config. ([#2748](https://github.com/expo/eas-cli/pull/2748) by [@szdziedzic](https://github.com/szdziedzic))
- Sanitize and generate names for EAS Submit to prevent failures due to invalid characters or taken names. ([#2842](https://github.com/expo/eas-cli/pull/2842) by [@evanbacon](https://github.com/evanbacon))
### 🐛 Bug fixes
### 🧹 Chores
- Fix tests for VisionOS type.
- Make new autoIncremented builds start at nr 1 by default ([#2828](https://github.com/expo/eas-cli/pull/2828) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
## [14.5.0](https://github.com/expo/eas-cli/releases/tag/v14.5.0) - 2025-01-22
### 🎉 New features
- Allow filtering by `--fingerprint_hash` in `eas build:list` command. ([#2818](https://github.com/expo/eas-cli/pull/2818) by [@szdziedzic](https://github.com/szdziedzic))
### 🐛 Bug fixes
- Ensure that the AASA file is served with content type application/json ([#2829](https://github.com/expo/eas-cli/pull/2829) by [@kadikraman](https://github.com/kadikraman))
- Ensure that the AppleID provided in prompt or saved to cache does not contain invalid unprintable characters ([#2830](https://github.com/expo/eas-cli/pull/2830) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
### 🧹 Chores
- Fix logs typos in the `eas deploy` command. ([#2822](https://github.com/expo/eas-cli/pull/2822) by [@kadikraman](https://github.com/kadikraman))
- Make `deploy` the top level command for hosting. ([#2824](https://github.com/expo/eas-cli/pull/2824) by [@kadikraman](https://github.com/kadikraman))
- Allow longer submit profile extension chain (up to 5, same as build profile) ([#2831](https://github.com/expo/eas-cli/pull/2831) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
- Make variable naming more explicit, remove deprecated runtimeFingerprintSource uses. ([#2816](https://github.com/expo/eas-cli/pull/2816) by [@wschurman](https://github.com/wschurman))
## [14.4.1](https://github.com/expo/eas-cli/releases/tag/v14.4.1) - 2025-01-15
### 🐛 Bug fixes
- Enable shell execution for env:exec commands. ([#2788](https://github.com/expo/eas-cli/pull/2788) by [@tharakadesilva](https://github.com/tharakadesilva))
### 🧹 Chores
- Make automatic env resolution message shorter. ([#2806](https://github.com/expo/eas-cli/pull/2806) by [@szdziedzic](https://github.com/szdziedzic))
- Make "No remote versions are configured" message green instead of yellow. ([#2805](https://github.com/expo/eas-cli/pull/2805) by [@szdziedzic](https://github.com/szdziedzic))
- Upload local fingerprint on `eas fingerprint:compare`. ([#2808](https://github.com/expo/eas-cli/pull/2808) by [@quinlanj](https://github.com/quinlanj))
- Upgrade `eas-cli-local-build-plugin` to `1.0.163` to support Bun's new text-based lock file in local builds. ([#2817](https://github.com/expo/eas-cli/pull/2817) by [@shiroyasha9](https://github.com/shiroyasha9))
## [14.4.0](https://github.com/expo/eas-cli/releases/tag/v14.4.0) - 2025-01-09
### 🎉 New features
- Load `.env` variables even when `--environment` is specified for `deploy` command. Conflicts will be highlighted by a warning message. ([#2783](https://github.com/expo/eas-cli/pull/2783) by [@kitten](https://github.com/kitten))
- Silence all non-command output in non-interactive mode of eas env:exec. ([#2800](https://github.com/expo/eas-cli/pull/2800) by [@wschurman](https://github.com/wschurman))
- Unhide `deploy` and `deploy:alias` commands ([#2807](https://github.com/expo/eas-cli/pull/2807) by [@kitten](https://github.com/kitten))
## [14.3.1](https://github.com/expo/eas-cli/releases/tag/v14.3.1) - 2025-01-08
### 🧹 Chores
- Bump `@expo/[email protected]` to support Bun text-based lock files. ([#2801](https://github.com/expo/eas-cli/pull/2801) by [@kudo](https://github.com/kudo))
## [14.3.0](https://github.com/expo/eas-cli/releases/tag/v14.3.0) - 2025-01-07
### 🎉 New features
- Upload `package.json` when uploading workflow sources. ([#2786](https://github.com/expo/eas-cli/pull/2786) by [@sjchmiela](https://github.com/sjchmiela))
### 🐛 Bug fixes
- Show `eas deploy` upload error messages. ([#2771](https://github.com/expo/eas-cli/pull/2771) by [@kadikraman](https://github.com/kadikraman))
- Prevent EAS CLI dependencies check from running repeatedly. ([#2781](https://github.com/expo/eas-cli/pull/2781) by [@kitten](https://github.com/kitten))
- Prevent optimistic request body parsing for `eas deploy`. ([#2784](https://github.com/expo/eas-cli/pull/2784) by [@kadikraman](https://github.com/kadikraman))
### 🧹 Chores
- Update log output for `worker` deploy and alias commands. ([#2780](https://github.com/expo/eas-cli/pull/2780) by [@kitten](https://github.com/kitten))
- Update various messages wording. ([#2790](https://github.com/expo/eas-cli/pull/2790) by [@simek](https://github.com/simek))
- Use node18 as tsconfig base. ([#2739](https://github.com/expo/eas-cli/pull/2739) by [@quinlanj](https://github.com/quinlanj))
## [14.2.0](https://github.com/expo/eas-cli/releases/tag/v14.2.0) - 2024-12-13
### 🎉 New features
- Add `eas deploy --dry-run` flag to output tarball. ([#2761](https://github.com/expo/eas-cli/pull/2761) by [@kitten](https://github.com/kitten))
- Allow specifying credentials for android builds. ([#2775](https://github.com/expo/eas-cli/pull/2775) by [@khamilowicz](https://github.com/khamilowicz))
### 🐛 Bug fixes
- Remove random branch name generation for --auto branch name non-vcs fallback. ([#2747](https://github.com/expo/eas-cli/pull/2747) by [@wschurman](https://github.com/wschurman))
- Upgrade @expo/multipart-body-parser. ([#2751](https://github.com/expo/eas-cli/pull/2751) by [@wschurman](https://github.com/wschurman))
- Pass env var flag to worker deployments. ([#2763](https://github.com/expo/eas-cli/pull/2763) by [@kadikraman](https://github.com/kadikraman))
- Fix request for switching providers when doing Apple auth. ([#2769](https://github.com/expo/eas-cli/pull/2769) by [@szdziedzic](https://github.com/szdziedzic))
### 🧹 Chores
- Improve logging and validation in `eas env:exec` command. ([#2762](https://github.com/expo/eas-cli/pull/2762) by [@szdziedzic](https://github.com/szdziedzic))
## [14.1.0](https://github.com/expo/eas-cli/releases/tag/v14.1.0) - 2024-12-10
### 🎉 New features
- Add support for enabeling broadcast Push Notifications capability option, by setting `ios.usesBroadcastPushNotifications` to `true` in app config. ([#2748](https://github.com/expo/eas-cli/pull/2748) by [@szdziedzic](https://github.com/szdziedzic))
### 🧹 Chores
- Add link to FYI page mentioning the workaround for Apple SMS 2FA issue. ([#2752](https://github.com/expo/eas-cli/pull/2752) by [@szdziedzic](https://github.com/szdziedzic))
## [14.0.3](https://github.com/expo/eas-cli/releases/tag/v14.0.3) - 2024-12-09
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"version": "14.1.0",
"version": "14.5.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
+204 -72
View File
@@ -88,6 +88,9 @@ eas --help COMMAND
* [`eas config`](#eas-config)
* [`eas credentials`](#eas-credentials)
* [`eas credentials:configure-build`](#eas-credentialsconfigure-build)
* [`eas deploy [options]`](#eas-deploy-2moptions22m)
* [`eas deploy:alias`](#eas-deployalias)
* [`eas deploy:promote`](#eas-deploypromote)
* [`eas device:create`](#eas-devicecreate)
* [`eas device:delete`](#eas-devicedelete)
* [`eas device:list`](#eas-devicelist)
@@ -131,6 +134,8 @@ eas --help COMMAND
* [`eas webhook:update`](#eas-webhookupdate)
* [`eas webhook:view ID`](#eas-webhookview-id)
* [`eas whoami`](#eas-whoami)
* [`eas worker:alias`](#eas-workeralias)
* [`eas deploy [options]`](#eas-deploy-2moptions22m-1)
* [`eas workflow:create [NAME]`](#eas-workflowcreate-name)
* [`eas workflow:run [FILE]`](#eas-workflowrun-file)
* [`eas workflow:validate PATH`](#eas-workflowvalidate-path)
@@ -153,7 +158,7 @@ ALIASES
$ eas login
```
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -170,7 +175,7 @@ ALIASES
$ eas logout
```
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view`
@@ -187,7 +192,7 @@ ALIASES
$ eas whoami
```
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -201,7 +206,7 @@ DESCRIPTION
display or change analytics settings
```
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/analytics.ts)_
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]`
@@ -253,7 +258,7 @@ DESCRIPTION
create a branch
```
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -274,7 +279,7 @@ DESCRIPTION
delete a branch
```
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -294,7 +299,7 @@ DESCRIPTION
list all branches
```
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -314,7 +319,7 @@ DESCRIPTION
rename a branch
```
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -337,7 +342,7 @@ DESCRIPTION
view a branch
```
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build`
@@ -374,7 +379,7 @@ DESCRIPTION
start a build
```
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -393,7 +398,7 @@ DESCRIPTION
cancel a build
```
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -410,7 +415,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:delete [BUILD_ID]`
@@ -429,7 +434,7 @@ DESCRIPTION
delete a build
```
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/build/delete.ts)_
## `eas build:inspect`
@@ -464,7 +469,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -475,8 +480,8 @@ USAGE
$ eas build:list [-p android|ios|all] [--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]
<value>] [--app-identifier <value>] [-e <value>] [--git-commit-hash <value>] [--fingerprint-hash <value>] [--offset
<value>] [--limit <value>] [--json --non-interactive] [--simulator]
FLAGS
-e, --build-profile=<value> Filter only builds created with the
@@ -491,6 +496,8 @@ FLAGS
--channel=<value>
--distribution=(store|internal|simulator) Filter only builds with the specified
distribution type
--fingerprint-hash=<value> Filter only builds with the specified
fingerprint hash
--git-commit-hash=<value> Filter only builds created with the
specified git commit hash
--json Enable JSON output, non-JSON messages
@@ -514,7 +521,7 @@ DESCRIPTION
list all builds for your project
```
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:resign`
@@ -543,7 +550,7 @@ DESCRIPTION
re-sign a build archive
```
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/build/resign.ts)_
## `eas build:run`
@@ -569,7 +576,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/build/run.ts)_
## `eas build:submit`
@@ -619,7 +626,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/build/version/get.ts)_
## `eas build:version:set`
@@ -638,7 +645,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/build/version/set.ts)_
## `eas build:version:sync`
@@ -657,7 +664,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]`
@@ -674,7 +681,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -695,7 +702,7 @@ DESCRIPTION
create a channel
```
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:edit [NAME]`
@@ -717,7 +724,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list`
@@ -737,7 +744,7 @@ DESCRIPTION
list all channels
```
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:pause [NAME]`
@@ -759,7 +766,7 @@ DESCRIPTION
pause a channel to stop it from sending updates
```
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/channel/pause.ts)_
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/channel/pause.ts)_
## `eas channel:resume [NAME]`
@@ -781,7 +788,7 @@ DESCRIPTION
resume a channel to start sending updates
```
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/channel/resume.ts)_
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/channel/resume.ts)_
## `eas channel:rollout [CHANNEL]`
@@ -814,7 +821,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/channel/rollout.ts)_
## `eas channel:view [NAME]`
@@ -837,7 +844,7 @@ DESCRIPTION
view a channel
```
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -858,7 +865,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/v14.1.0/packages/eas-cli/src/commands/config.ts)_
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/config.ts)_
## `eas credentials`
@@ -875,7 +882,7 @@ DESCRIPTION
manage credentials
```
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/credentials/index.ts)_
## `eas credentials:configure-build`
@@ -893,7 +900,83 @@ 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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
## `eas deploy [options]`
Deploy your Expo Router web build and API Routes.
```
USAGE
$ eas deploy [options]
$ eas deploy --prod
FLAGS
--alias=name Custom alias to assign to the new deployment.
--dry-run Outputs a tarball of the new deployment instead of uploading it.
--environment=(development|preview|production) Environment variable's environment
--export-dir=dir [default: dist] Directory where the Expo project was exported.
--id=xyz123 Custom unique identifier for the new deployment.
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
--prod Create a new production deployment.
DESCRIPTION
Deploy your Expo Router web build and API Routes.
ALIASES
$ eas worker:deploy
```
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/deploy/index.ts)_
## `eas deploy:alias`
Assign deployment aliases.
```
USAGE
$ eas deploy:alias [--prod] [--alias <value>] [--id <value>] [--json --non-interactive]
FLAGS
--alias=name Custom alias to assign to the existing deployment.
--id=xyz123 Unique identifier of an existing deployment.
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
--prod Promote an existing deployment to production.
DESCRIPTION
Assign deployment aliases.
ALIASES
$ eas worker:alias
$ eas deploy:promote
```
_See code: [packages/eas-cli/src/commands/deploy/alias.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/deploy/alias.ts)_
## `eas deploy:promote`
Assign deployment aliases.
```
USAGE
$ eas deploy:promote [--prod] [--alias <value>] [--id <value>] [--json --non-interactive]
FLAGS
--alias=name Custom alias to assign to the existing deployment.
--id=xyz123 Unique identifier of an existing deployment.
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
--prod Promote an existing deployment to production.
DESCRIPTION
Assign deployment aliases.
ALIASES
$ eas worker:alias
$ eas deploy:promote
```
## `eas device:create`
@@ -907,7 +990,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -927,7 +1010,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -948,7 +1031,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:rename`
@@ -969,7 +1052,7 @@ DESCRIPTION
rename a registered device
```
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/device/rename.ts)_
## `eas device:view [UDID]`
@@ -983,7 +1066,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -997,7 +1080,7 @@ DESCRIPTION
display environment info
```
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/diagnostics.ts)_
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas env:create [ENVIRONMENT]`
@@ -1026,7 +1109,7 @@ DESCRIPTION
create an environment variable for the current project or account
```
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/env/create.ts)_
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/create.ts)_
## `eas env:delete [ENVIRONMENT]`
@@ -1050,7 +1133,7 @@ DESCRIPTION
delete an environment variable for the current project or account
```
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/env/delete.ts)_
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/delete.ts)_
## `eas env:exec ENVIRONMENT BASH_COMMAND`
@@ -1071,7 +1154,7 @@ DESCRIPTION
execute a command with environment variables from the selected environment
```
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/env/exec.ts)_
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/exec.ts)_
## `eas env:get [ENVIRONMENT]`
@@ -1096,7 +1179,7 @@ DESCRIPTION
view an environment variable for the current project or account
```
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/env/get.ts)_
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/get.ts)_
## `eas env:list [ENVIRONMENT]`
@@ -1121,7 +1204,7 @@ DESCRIPTION
list environment variables for the current project or account
```
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/env/list.ts)_
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/list.ts)_
## `eas env:pull [ENVIRONMENT]`
@@ -1144,7 +1227,7 @@ DESCRIPTION
pull environment variables for the selected environment to .env file
```
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/env/pull.ts)_
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/pull.ts)_
## `eas env:push [ENVIRONMENT]`
@@ -1165,7 +1248,7 @@ DESCRIPTION
push environment variables from .env file to the selected environment
```
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/env/push.ts)_
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/push.ts)_
## `eas env:update [ENVIRONMENT]`
@@ -1196,7 +1279,7 @@ DESCRIPTION
update an environment variable on the current project or account
```
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/env/update.ts)_
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/update.ts)_
## `eas help [COMMAND]`
@@ -1304,7 +1387,7 @@ DESCRIPTION
validate the local store configuration
```
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/metadata/lint.ts)_
## `eas metadata:pull`
@@ -1321,7 +1404,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push`
@@ -1338,7 +1421,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/v14.1.0/packages/eas-cli/src/commands/metadata/push.ts)_
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas onboarding [TARGET_PROJECT_DIRECTORY]`
@@ -1368,7 +1451,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/v14.1.0/packages/eas-cli/src/commands/open.ts)_
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/open.ts)_
## `eas project:info`
@@ -1382,7 +1465,7 @@ DESCRIPTION
information about the current project
```
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -1405,7 +1488,7 @@ ALIASES
$ eas init
```
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/project/init.ts)_
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/project/init.ts)_
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
@@ -1423,7 +1506,7 @@ ALIASES
$ eas onboarding
```
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/project/onboarding.ts)_
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/project/onboarding.ts)_
## `eas submit`
@@ -1454,7 +1537,7 @@ ALIASES
$ eas build:submit
```
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/submit.ts)_
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/submit.ts)_
## `eas update`
@@ -1496,7 +1579,7 @@ DESCRIPTION
publish an update group
```
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -1516,7 +1599,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -1537,7 +1620,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:edit [GROUPID]`
@@ -1561,7 +1644,7 @@ DESCRIPTION
edit all the updates in an update group
```
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/update/edit.ts)_
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/update/edit.ts)_
## `eas update:list`
@@ -1583,7 +1666,7 @@ DESCRIPTION
view the recent updates
```
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:republish`
@@ -1615,7 +1698,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/update/republish.ts)_
## `eas update:roll-back-to-embedded`
@@ -1643,7 +1726,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
## `eas update:rollback`
@@ -1663,7 +1746,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/v14.1.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/v14.5.0/packages/eas-cli/src/commands/update/rollback.ts)_
## `eas update:view GROUPID`
@@ -1683,7 +1766,7 @@ DESCRIPTION
update group details
```
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/update/view.ts)_
## `eas webhook:create`
@@ -1704,7 +1787,7 @@ DESCRIPTION
create a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -1724,7 +1807,7 @@ DESCRIPTION
delete a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -1742,7 +1825,7 @@ DESCRIPTION
list webhooks
```
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -1764,7 +1847,7 @@ DESCRIPTION
update a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -1781,7 +1864,7 @@ DESCRIPTION
view a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v14.1.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/v14.5.0/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
@@ -1798,6 +1881,55 @@ ALIASES
$ eas whoami
```
## `eas worker:alias`
Assign deployment aliases.
```
USAGE
$ eas worker:alias [--prod] [--alias <value>] [--id <value>] [--json --non-interactive]
FLAGS
--alias=name Custom alias to assign to the existing deployment.
--id=xyz123 Unique identifier of an existing deployment.
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
--prod Promote an existing deployment to production.
DESCRIPTION
Assign deployment aliases.
ALIASES
$ eas worker:alias
$ eas deploy:promote
```
## `eas deploy [options]`
Deploy your Expo Router web build and API Routes.
```
USAGE
$ eas deploy [options]
$ eas deploy --prod
FLAGS
--alias=name Custom alias to assign to the new deployment.
--dry-run Outputs a tarball of the new deployment instead of uploading it.
--environment=(development|preview|production) Environment variable's environment
--export-dir=dir [default: dist] Directory where the Expo project was exported.
--id=xyz123 Custom unique identifier for the new deployment.
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
--prod Create a new production deployment.
DESCRIPTION
Deploy your Expo Router web build and API Routes.
ALIASES
$ eas worker:deploy
```
## `eas workflow:create [NAME]`
create a new workflow configuration YAML file
@@ -1816,7 +1948,7 @@ DESCRIPTION
create a new workflow configuration YAML file
```
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/workflow/create.ts)_
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/workflow/create.ts)_
## `eas workflow:run [FILE]`
@@ -1836,7 +1968,7 @@ DESCRIPTION
Run an EAS workflow
```
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/workflow/run.ts)_
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/workflow/run.ts)_
## `eas workflow:validate PATH`
@@ -1856,5 +1988,5 @@ DESCRIPTION
validate a workflow configuration yaml file
```
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v14.1.0/packages/eas-cli/src/commands/workflow/validate.ts)_
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/workflow/validate.ts)_
<!-- commandsstop -->
+2125 -68
View File
File diff suppressed because it is too large. Load diff
+9 -8
View File
@@ -1,25 +1,25 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "14.1.0",
"version": "14.5.0",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
},
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@expo/apple-utils": "2.1.4",
"@expo/apple-utils": "2.1.5",
"@expo/code-signing-certificates": "0.0.5",
"@expo/config": "10.0.6",
"@expo/config-plugins": "9.0.12",
"@expo/eas-build-job": "1.0.151",
"@expo/eas-json": "14.0.3",
"@expo/env": "^0.4.0",
"@expo/eas-build-job": "1.0.165",
"@expo/eas-json": "14.5.0",
"@expo/env": "^1.0.0",
"@expo/json-file": "8.3.3",
"@expo/logger": "1.0.117",
"@expo/multipart-body-parser": "1.1.0",
"@expo/multipart-body-parser": "2.0.0",
"@expo/osascript": "2.1.4",
"@expo/package-manager": "1.6.1",
"@expo/package-manager": "1.7.0",
"@expo/pkcs12": "0.1.3",
"@expo/plist": "0.2.0",
"@expo/plugin-help": "5.1.23",
@@ -28,7 +28,7 @@
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.2",
"@expo/steps": "1.0.151",
"@expo/steps": "1.0.166",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "^1.26.2",
"@oclif/plugin-autocomplete": "^2.3.10",
@@ -95,6 +95,7 @@
"@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/typescript": "4.0.1",
"@graphql-codegen/typescript-operations": "4.0.1",
"@tsconfig/node18": "18.2.4",
"@types/cli-progress": "3.11.5",
"@types/dateformat": "3.0.1",
"@types/diff": "6.0.0",
+2 -4
View File
@@ -1,9 +1,7 @@
import { Client } from '../vcs/vcs';
export async function getDefaultBranchNameAsync(vcsClient: Client): Promise<string> {
return (
(await vcsClient.getBranchNameAsync()) || `branch-${Math.random().toString(36).substring(2, 4)}`
);
export async function getDefaultBranchNameAsync(vcsClient: Client): Promise<string | null> {
return await vcsClient.getBranchNameAsync();
}
export class BranchNotFoundError extends Error {
@@ -1,23 +1,43 @@
import { ExpoConfig } from '@expo/config';
import { Platform } from '@expo/eas-build-job';
import { BuildProfile } from '@expo/eas-json';
import { AndroidBuildProfile } from '@expo/eas-json/build/build/types';
import assert from 'assert';
import fs from 'fs-extra';
import { vol } from 'memfs';
import os from 'os';
import path from 'path';
import { instance, mock } from 'ts-mockito';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
import { getAppBuildGradleAsync, resolveConfigValue } from '../../../project/android/gradleUtils';
import { resolveVcsClient } from '../../../vcs';
import { Client } from '../../../vcs/vcs';
import {
BumpStrategy,
bumpVersionAsync,
bumpVersionInAppJsonAsync,
maybeResolveVersionsAsync,
resolveRemoteVersionCodeAsync,
} from '../version';
const fsReal = jest.requireActual('fs').promises as typeof fs;
jest.mock('fs');
jest.mock('../../../commandUtils/context/contextUtils/createGraphqlClient');
jest.mock('../../../vcs/vcs');
jest.mock('../../../graphql/queries/AppVersionQuery');
jest.mock('../../../graphql/mutations/AppVersionMutation');
jest.mock('../../../ora', () => ({
ora: jest.fn().mockImplementation(() => ({
start: jest.fn().mockImplementation(() => ({
succeed: jest.fn(),
stop: jest.fn(),
fail: jest.fn(),
})),
})),
}));
const vcsClient = resolveVcsClient();
@@ -216,12 +236,160 @@ describe(maybeResolveVersionsAsync, () => {
});
});
describe(resolveRemoteVersionCodeAsync, () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('uses current remote versionCode when remote version set, autoIncrement=false', async () => {
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
const vcsClientMock = instance(mock<Client>());
vcsClientMock.getRootPathAsync = async () => '/app';
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue({
buildVersion: '11',
storeVersion: '1.2.3',
});
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
const exp = initBareWorkflowProject();
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
projectDir: '/app',
projectId: 'fakeProjectId',
exp,
applicationId: 'fakeApplicationId',
buildProfile: {} as AndroidBuildProfile,
vcsClient: vcsClientMock,
});
expect(result).toBe('11');
expect(createAppVersionAsyncSpy).not.toHaveBeenCalled();
});
it('initializes versionCode from local files when remote version not set, autoIncrement=false', async () => {
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
const vcsClientMock = instance(mock<Client>());
vcsClientMock.getRootPathAsync = async () => '/app';
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
const exp = initBareWorkflowProject({ versionCode: 22, versionName: '2.3.4' });
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
projectDir: '/app',
projectId: 'fakeProjectId',
exp,
applicationId: 'fakeApplicationId',
buildProfile: {} as AndroidBuildProfile,
vcsClient: vcsClientMock,
});
expect(result).toBe('22');
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('2.3.4');
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('22');
});
it('initializes versionCode starting with 1 when remote version not set and not set in local files, autoIncrement=false', async () => {
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
const vcsClientMock = instance(mock<Client>());
vcsClientMock.getRootPathAsync = async () => '/app';
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
const exp = initBareWorkflowProject({ versionCode: null, versionName: null });
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
projectDir: '/app',
projectId: 'fakeProjectId',
exp,
applicationId: 'fakeApplicationId',
buildProfile: {} as AndroidBuildProfile,
vcsClient: vcsClientMock,
});
expect(result).toBe('1');
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('1');
});
it('increments current remote versionCode when remote version set, autoIncrement=true', async () => {
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
const vcsClientMock = instance(mock<Client>());
vcsClientMock.getRootPathAsync = async () => '/app';
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue({
buildVersion: '11',
storeVersion: '1.2.3',
});
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
const exp = initBareWorkflowProject();
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
projectDir: '/app',
projectId: 'fakeProjectId',
exp,
applicationId: 'fakeApplicationId',
buildProfile: { autoIncrement: true } as AndroidBuildProfile,
vcsClient: vcsClientMock,
});
expect(result).toBe('12');
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('3.0.0');
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('12');
});
it('increments versionCode from local files when remote version not set, autoIncrement=true', async () => {
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
const vcsClientMock = instance(mock<Client>());
vcsClientMock.getRootPathAsync = async () => '/app';
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
const exp = initBareWorkflowProject({ versionCode: 22, versionName: '2.3.4' });
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
projectDir: '/app',
projectId: 'fakeProjectId',
exp,
applicationId: 'fakeApplicationId',
buildProfile: { autoIncrement: true } as AndroidBuildProfile,
vcsClient: vcsClientMock,
});
expect(result).toBe('23');
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('2.3.4');
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('23');
});
it('initializes versionCode starting with 1 when remote version not set and not set in local files, autoIncrement=true', async () => {
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
const vcsClientMock = instance(mock<Client>());
vcsClientMock.getRootPathAsync = async () => '/app';
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
const exp = initBareWorkflowProject({ versionCode: null, versionName: null });
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
projectDir: '/app',
projectId: 'fakeProjectId',
exp,
applicationId: 'fakeApplicationId',
buildProfile: { autoIncrement: true } as AndroidBuildProfile,
vcsClient: vcsClientMock,
});
expect(result).toBe('1');
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('1');
});
});
function initBareWorkflowProject({
versionCode = 123,
versionName = '3.0.0',
}: {
versionCode?: number;
versionName?: string;
versionCode?: number | null;
versionName?: string | null;
} = {}): ExpoConfig {
const fakeExp: ExpoConfig = {
name: 'myproject',
@@ -239,8 +407,8 @@ function initBareWorkflowProject({
'./android/app/build.gradle': `android {
defaultConfig {
applicationId "com.expo.testapp"
versionCode ${versionCode}
versionName "${versionName}"
${versionCode ? `versionCode "${versionCode}"` : ''}
${versionName ? `versionName "${versionName}"` : ''}
}
}`,
'./android/app/src/main/AndroidManifest.xml': 'fake',
@@ -151,6 +151,7 @@ async function ensureAndroidCredentialsAsync(
ctx.android.gradleContext
);
const provider = new AndroidCredentialsProvider(ctx.credentialsCtx, {
name: ctx.buildProfile.keystoreName,
app: {
account: nullthrows(
ctx.user.accounts.find(a => a.name === ctx.accountName),
+16 -4
View File
@@ -99,7 +99,7 @@ export async function maybeResolveVersionsAsync(
exp: ExpoConfig,
buildProfile: BuildProfile<Platform.ANDROID>,
vcsClient: Client
): Promise<{ appVersion?: string; appBuildVersion?: string }> {
): Promise<{ appVersion?: string; appBuildVersion?: string; isVersionInitialized?: boolean }> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
if (workflow === Workflow.GENERIC) {
const buildGradle = await getAppBuildGradleAsync(projectDir);
@@ -113,6 +113,11 @@ export async function maybeResolveVersionsAsync(
resolveConfigValue(buildGradle, 'versionName', parsedGradleCommand?.flavor) ?? '1.0.0',
appBuildVersion:
resolveConfigValue(buildGradle, 'versionCode', parsedGradleCommand?.flavor) ?? '1',
isVersionInitialized: !resolveConfigValue(
buildGradle,
'versionCode',
parsedGradleCommand?.flavor
),
};
} catch {
return {};
@@ -204,14 +209,18 @@ export async function resolveRemoteVersionCodeAsync(
const localVersions = await maybeResolveVersionsAsync(projectDir, exp, buildProfile, vcsClient);
let currentBuildVersion: string;
let shouldInitializeVersionCode = false;
if (remoteVersions?.buildVersion) {
currentBuildVersion = remoteVersions.buildVersion;
} else {
if (localVersions.appBuildVersion) {
Log.warn(
'No remote versions are configured for this project, versionCode will be initialized based on the value from the local project.'
Log.log(
chalk.green(
'No remote versions are configured for this project, versionCode will be initialized based on the value from the local project.'
)
);
currentBuildVersion = localVersions.appBuildVersion;
shouldInitializeVersionCode = localVersions.isVersionInitialized ?? false;
} else {
Log.error(
`Remote versions are not configured and EAS CLI was not able to read the current version from your project. Use "eas build:version:set" to initialize remote versions.`
@@ -221,7 +230,10 @@ export async function resolveRemoteVersionCodeAsync(
}
if (!buildProfile.autoIncrement && remoteVersions?.buildVersion) {
return currentBuildVersion;
} else if (!buildProfile.autoIncrement && !remoteVersions?.buildVersion) {
} else if (
(!buildProfile.autoIncrement && !remoteVersions?.buildVersion) ||
shouldInitializeVersionCode
) {
const spinner = ora(
`Initializing versionCode with ${chalk.bold(currentBuildVersion)}.`
).start();
+6 -3
View File
@@ -709,7 +709,10 @@ async function computeAndMaybeUploadFingerprintFromExpoUpdatesAsync<T extends Pl
* It's ok for fingerprintSources or runtimeVersion to be empty
* fingerprintSources only exist if the project is using runtimeVersion.policy: fingerprint
*/
if (!resolvedRuntimeVersion.fingerprint || !resolvedRuntimeVersion.runtimeVersion) {
if (
!resolvedRuntimeVersion.expoUpdatesRuntimeFingerprint ||
!resolvedRuntimeVersion.runtimeVersion
) {
return {
runtimeVersion: resolvedRuntimeVersion.runtimeVersion ?? undefined,
};
@@ -717,14 +720,14 @@ async function computeAndMaybeUploadFingerprintFromExpoUpdatesAsync<T extends Pl
const uploadedFingerprint = await maybeUploadFingerprintAsync({
hash: resolvedRuntimeVersion.runtimeVersion,
fingerprint: resolvedRuntimeVersion.fingerprint,
fingerprint: resolvedRuntimeVersion.expoUpdatesRuntimeFingerprint,
graphqlClient: ctx.graphqlClient,
localBuildMode: ctx.localBuildOptions.localBuildMode,
});
return {
runtimeVersion: uploadedFingerprint.hash,
fingerprintSource: uploadedFingerprint.fingerprintSource,
fingerprintHash: resolvedRuntimeVersion.fingerprintHash ?? undefined,
fingerprintHash: resolvedRuntimeVersion.expoUpdatesRuntimeFingerprintHash ?? undefined,
};
}
@@ -84,13 +84,13 @@ async function resolveEnvVarsAsync({
if (Object.keys(serverEnvVars).length > 0) {
Log.log(
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS servers: ${Object.keys(
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS: ${Object.keys(
serverEnvVars
).join(', ')}.`
);
} else {
Log.log(
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS servers.`
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS.`
);
}
@@ -112,7 +112,7 @@ async function resolveEnvVarsAsync({
);
if (overlappingKeys.length > 0) {
Log.warn(
`The following environment variables are defined in both the "${buildProfileName}" build profile "env" configuration and the "${environment.toLowerCase()}" environment on EAS servers: ${overlappingKeys.join(
`The following environment variables are defined in both the "${buildProfileName}" build profile "env" configuration and the "${environment.toLowerCase()}" environment on EAS: ${overlappingKeys.join(
', '
)}. The values from the build profile configuration will be used.`
);
@@ -135,23 +135,18 @@ async function resolveEnvVarsAsync({
function resolveSuggestedEnvironmentForBuildProfileConfiguration(
buildProfile: BuildProfile
): EnvironmentVariableEnvironment {
const setEnvironmentMessage =
'Set the "environment" field in the build profile if you want to specify the environment manually.';
if (buildProfile.distribution === 'store') {
Log.log(
`We detected that you are building for the "store" distribution. Resolving the environment for environment variables used during the build to "production". ${setEnvironmentMessage}`
);
return EnvironmentVariableEnvironment.Production;
} else {
if (buildProfile.developmentClient) {
Log.log(
`We detected that you are building the development client. Resolving the environment for environment variables used during the build to "development". ${setEnvironmentMessage}`
);
return EnvironmentVariableEnvironment.Development;
}
Log.log(
`We detected that you are building for the "internal" distribution. Resolving the environment for environment variables used during the build to "preview". ${setEnvironmentMessage}`
);
return EnvironmentVariableEnvironment.Preview;
}
const environment =
buildProfile.distribution === 'store'
? EnvironmentVariableEnvironment.Production
: buildProfile.developmentClient
? EnvironmentVariableEnvironment.Development
: EnvironmentVariableEnvironment.Preview;
Log.log(
`Resolved "${environment.toLowerCase()}" environment for the build. ${learnMore(
'https://docs.expo.dev/eas/environment-variables/#setting-the-environment-for-your-builds'
)}`
);
return environment;
}
@@ -1,12 +1,19 @@
import { ExpoConfig } from '@expo/config';
import { IOSConfig } from '@expo/config-plugins';
import { IosBuildProfile } from '@expo/eas-json/build/build/types';
import fs from 'fs-extra';
import { vol } from 'memfs';
import os from 'os';
import { instance, mock } from 'ts-mockito';
import type { XCBuildConfiguration } from 'xcode';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import { Target } from '../../../credentials/ios/types';
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
import { readPlistAsync } from '../../../utils/plist';
import { resolveVcsClient } from '../../../vcs';
import { Client } from '../../../vcs/vcs';
import {
BumpStrategy,
bumpVersionAsync,
@@ -15,9 +22,23 @@ import {
getInfoPlistPath,
readBuildNumberAsync,
readShortVersionAsync,
resolveRemoteBuildNumberAsync,
} from '../version';
jest.mock('fs');
jest.mock('../../../commandUtils/context/contextUtils/createGraphqlClient');
jest.mock('../../../vcs/vcs');
jest.mock('../../../graphql/queries/AppVersionQuery');
jest.mock('../../../graphql/mutations/AppVersionMutation');
jest.mock('../../../ora', () => ({
ora: jest.fn().mockImplementation(() => ({
start: jest.fn().mockImplementation(() => ({
succeed: jest.fn(),
stop: jest.fn(),
fail: jest.fn(),
})),
})),
}));
const vcsClient = resolveVcsClient();
@@ -359,6 +380,154 @@ describe(getInfoPlistPath, () => {
});
});
describe(resolveRemoteBuildNumberAsync, () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('uses current remote buildNumber when remote version set, autoIncrement=false', async () => {
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
const vcsClientMock = instance(mock<Client>());
vcsClientMock.getRootPathAsync = async () => '/app';
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue({
buildVersion: '11',
storeVersion: '1.2.3',
});
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
const exp = initBareWorkflowProject();
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
projectDir: '/app',
projectId: 'fakeProjectId',
exp,
applicationTarget: {} as Target,
buildProfile: {} as IosBuildProfile,
vcsClient: vcsClientMock,
});
expect(result).toBe('11');
expect(createAppVersionAsyncSpy).not.toHaveBeenCalled();
});
it('initializes buildNumber from local files when remote version not set, autoIncrement=false', async () => {
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
const vcsClientMock = instance(mock<Client>());
vcsClientMock.getRootPathAsync = async () => '/app';
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
const exp = initBareWorkflowProject({ buildNumber: '22', appVersion: '2.3.4' });
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
projectDir: '/app',
projectId: 'fakeProjectId',
exp,
applicationTarget: {} as Target,
buildProfile: {} as IosBuildProfile,
vcsClient: vcsClientMock,
});
expect(result).toBe('22');
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('2.3.4');
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('22');
});
it('initializes buildNumber starting with 1 when remote version not set and local version is 1, autoIncrement=false', async () => {
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
const vcsClientMock = instance(mock<Client>());
vcsClientMock.getRootPathAsync = async () => '/app';
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
const exp = initBareWorkflowProject({ buildNumber: '1', appVersion: '1.0.0' });
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
projectDir: '/app',
projectId: 'fakeProjectId',
exp,
applicationTarget: {} as Target,
buildProfile: {} as IosBuildProfile,
vcsClient: vcsClientMock,
});
expect(result).toBe('1');
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('1');
});
it('increments current remote buildNumber when remote version set, autoIncrement=true', async () => {
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
const vcsClientMock = instance(mock<Client>());
vcsClientMock.getRootPathAsync = async () => '/app';
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue({
buildVersion: '11',
storeVersion: '1.2.3',
});
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
const exp = initBareWorkflowProject();
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
projectDir: '/app',
projectId: 'fakeProjectId',
exp,
applicationTarget: {} as Target,
buildProfile: { autoIncrement: true } as IosBuildProfile,
vcsClient: vcsClientMock,
});
expect(result).toBe('12');
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('12');
});
it('increments buildNumber from local files when remote version not set, autoIncrement=true', async () => {
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
const vcsClientMock = instance(mock<Client>());
vcsClientMock.getRootPathAsync = async () => '/app';
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
const exp = initBareWorkflowProject({ buildNumber: '22', appVersion: '2.3.4' });
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
projectDir: '/app',
projectId: 'fakeProjectId',
exp,
applicationTarget: {} as Target,
buildProfile: { autoIncrement: true } as IosBuildProfile,
vcsClient: vcsClientMock,
});
expect(result).toBe('23');
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('2.3.4');
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('23');
});
it('initializes buildNumber starting with 1 when remote version not set and local version is 1, autoIncrement=true', async () => {
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
const vcsClientMock = instance(mock<Client>());
vcsClientMock.getRootPathAsync = async () => '/app';
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
const exp = initBareWorkflowProject({ buildNumber: '1', appVersion: '1.0.0' });
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
projectDir: '/app',
projectId: 'fakeProjectId',
exp,
applicationTarget: {} as Target,
buildProfile: { autoIncrement: true } as IosBuildProfile,
vcsClient: vcsClientMock,
});
expect(result).toBe('1');
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('1');
});
});
function initBareWorkflowProject({
appVersion = '1.0.0',
buildNumber = '1',
+10 -3
View File
@@ -330,14 +330,18 @@ export async function resolveRemoteBuildNumberAsync(
vcsClient
);
let currentBuildVersion: string;
let shouldInitializeBuildNumber = false;
if (remoteVersions?.buildVersion) {
currentBuildVersion = remoteVersions.buildVersion;
} else {
if (localBuildNumber) {
Log.warn(
'No remote versions are configured for this project, buildNumber will be initialized based on the value from the local project.'
Log.log(
chalk.green(
'No remote versions are configured for this project, buildNumber will be initialized based on the value from the local project.'
)
);
currentBuildVersion = localBuildNumber;
shouldInitializeBuildNumber = localBuildNumber === '1';
} else {
Log.error(
`Remote versions are not configured and EAS CLI was not able to read the current version from your project. Use "eas build:version:set" to initialize remote versions.`
@@ -347,7 +351,10 @@ export async function resolveRemoteBuildNumberAsync(
}
if (!buildProfile.autoIncrement && remoteVersions?.buildVersion) {
return currentBuildVersion;
} else if (!buildProfile.autoIncrement && !remoteVersions?.buildVersion) {
} else if (
(!buildProfile.autoIncrement && !remoteVersions?.buildVersion) ||
shouldInitializeBuildNumber
) {
const spinner = ora(
`Initializing buildNumber with ${chalk.bold(currentBuildVersion)}.`
).start();
+1 -1
View File
@@ -7,7 +7,7 @@ import Log from '../log';
import { ora } from '../ora';
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
const PLUGIN_PACKAGE_VERSION = '1.0.152';
const PLUGIN_PACKAGE_VERSION = '1.0.163';
export enum LocalBuildMode {
/**
@@ -103,6 +103,8 @@ export async function findProjectRootAsync({
}
}
let ranEnsureEasCliIsNotInDependencies = false;
/**
* Determine the project root directory and ensure some constraints about the project setup
* like CLI version and dependencies.
@@ -113,6 +115,9 @@ export async function findProjectRootAsync({
export async function findProjectDirAndVerifyProjectSetupAsync(): Promise<string> {
const projectDir = await findProjectRootAsync();
await applyCliConfigAsync(projectDir);
await ensureEasCliIsNotInDependenciesAsync(projectDir);
if (!ranEnsureEasCliIsNotInDependencies) {
ranEnsureEasCliIsNotInDependencies = true;
await ensureEasCliIsNotInDependenciesAsync(projectDir);
}
return projectDir;
}
@@ -42,13 +42,13 @@ export async function loadServerSideEnvironmentVariablesAsync({
if (Object.keys(serverEnvVars).length > 0) {
Log.log(
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS servers: ${Object.keys(
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS: ${Object.keys(
serverEnvVars
).join(', ')}.`
);
} else {
Log.log(
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS servers.`
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS.`
);
}
Log.newLine();
@@ -58,7 +58,7 @@ export default class BranchCreate extends EasCommand {
type: 'text',
name: 'name',
message: 'Provide a branch name:',
initial: await getDefaultBranchNameAsync(vcsClient),
initial: (await getDefaultBranchNameAsync(vcsClient)) ?? undefined,
validate: value => (value ? true : validationMessage),
}));
}
@@ -62,6 +62,9 @@ export default class BuildList extends EasCommand {
aliases: ['gitCommitHash'],
description: 'Filter only builds created with the specified git commit hash',
}),
'fingerprint-hash': Flags.string({
description: 'Filter only builds with the specified fingerprint hash',
}),
...EasPaginatedQueryFlags,
limit: getLimitFlagWithCustomValues({ defaultTo: 10, limit: BUILDS_LIMIT }),
...EasNonInteractiveAndJsonFlags,
@@ -130,6 +133,7 @@ export default class BuildList extends EasCommand {
buildProfile: flags['build-profile'],
gitCommitHash: flags['git-commit-hash'],
simulator: flags.simulator,
fingerprintHash: flags['fingerprint-hash'],
},
paginatedQueryOptions,
});
@@ -120,7 +120,7 @@ export default class BuildVersionSyncView extends EasCommand {
const workflow = await resolveWorkflowAsync(projectDir, profileInfo.platform, vcsClient);
if (!remoteVersions?.buildVersion) {
Log.warn(
`Skipping versions sync for ${platformDisplayName}. There are no versions configured on Expo servers, use "eas build:version:set" or run a build to initialize it.`
`Skipping versions sync for ${platformDisplayName}. There are no versions configured on EAS, use "eas build:version:set" or run a build to initialize it.`
);
continue;
}
@@ -33,12 +33,9 @@ interface RawDeployAliasFlags {
}
export default class WorkerAlias extends EasCommand {
static override description = 'Assign deployment aliases';
static override aliases = ['deploy:alias', 'deploy:promote'];
// TODO(@kitten): Keep command hidden until worker deployments are live
static override hidden = true;
static override state = 'beta';
static override description = 'Assign deployment aliases.';
static override aliases = ['worker:alias', 'deploy:promote'];
static override state = 'preview';
static override flags = {
prod: Flags.boolean({
@@ -73,7 +70,7 @@ export default class WorkerAlias extends EasCommand {
enableJsonOutput();
}
Log.warn('EAS Worker Deployments are in beta and subject to breaking changes.');
Log.warn('EAS Hosting is still in preview and subject to changes.');
const {
getDynamicPrivateProjectConfigAsync,
@@ -43,16 +43,18 @@ interface DeployFlags {
environment?: EnvironmentVariableEnvironment;
deploymentIdentifier?: string;
exportDir: string;
dryRun: boolean;
}
interface RawDeployFlags {
'non-interactive': boolean;
environment?: string;
environment?: EnvironmentVariableEnvironment;
json: boolean;
prod: boolean;
alias?: string;
id?: string;
'export-dir': string;
'dry-run': boolean;
}
interface DeployInProgressParams {
@@ -63,13 +65,10 @@ interface DeployInProgressParams {
}
export default class WorkerDeploy extends EasCommand {
static override description = 'Deploy your Expo web build';
static override aliases = ['deploy'];
static override description = 'Deploy your Expo Router web build and API Routes.';
static override aliases = ['worker:deploy'];
static override usage = [chalk`deploy {dim [options]}`, `deploy --prod`];
// TODO(@kitten): Keep command hidden until worker deployments are live
static override hidden = true;
static override state = 'beta';
static override state = 'preview';
static override flags = {
prod: Flags.boolean({
@@ -90,10 +89,11 @@ export default class WorkerDeploy extends EasCommand {
helpValue: 'dir',
default: 'dist',
}),
environment: {
...EASEnvironmentFlag.environment,
description: 'Deploy with EAS Environment Variables matching the specified environment.',
},
'dry-run': Flags.boolean({
description: 'Outputs a tarball of the new deployment instead of uploading it.',
default: false,
}),
...EASEnvironmentFlag,
...EasNonInteractiveAndJsonFlags,
};
@@ -111,7 +111,7 @@ export default class WorkerDeploy extends EasCommand {
enableJsonOutput();
}
Log.warn('EAS Worker Deployments are in beta and subject to breaking changes.');
Log.warn('EAS Hosting is still in preview and subject to changes.');
const {
getDynamicPrivateProjectConfigAsync,
@@ -250,7 +250,7 @@ export default class WorkerDeploy extends EasCommand {
let progress = ora('Preparing project').start();
try {
const manifest = await WorkerAssets.createManifestAsync(
const manifestResult = await WorkerAssets.createManifestAsync(
{
environment: flags.environment,
projectDir,
@@ -258,16 +258,33 @@ export default class WorkerDeploy extends EasCommand {
},
graphqlClient
);
if (manifestResult.conflictingVariableNames?.length) {
Log.warn(
'> The following environment variables were present in local .env files as well as EAS environment variables. ' +
'In case of conflict, the EAS environment variable values will be used: ' +
manifestResult.conflictingVariableNames.join(' ')
);
}
assetMap = await WorkerAssets.createAssetMapAsync(
projectDist.type === 'server' ? projectDist.clientPath : projectDist.path
);
tarPath = await WorkerAssets.packFilesIterableAsync(
emitWorkerTarballAsync({
assetMap,
manifest,
manifest: manifestResult.manifest,
})
);
if (flags.dryRun) {
const DRY_RUN_OUTPUT_PATH = 'deploy.tar.gz';
await fs.promises.copyFile(tarPath, DRY_RUN_OUTPUT_PATH);
progress.succeed('Saved deploy.tar.gz tarball');
if (flags.json) {
printJsonOnlyOutput({ tarPath: DRY_RUN_OUTPUT_PATH });
}
return;
}
const uploadUrl = await getSignedDeploymentUrlAsync(graphqlClient, {
appId: projectId,
deploymentIdentifier: flags.deploymentIdentifier,
@@ -382,6 +399,8 @@ export default class WorkerDeploy extends EasCommand {
aliasName: flags.alias?.trim().toLowerCase(),
deploymentIdentifier: flags.id?.trim(),
exportDir: flags['export-dir'],
environment: flags['environment'],
dryRun: flags['dry-run'],
};
}
}
@@ -411,7 +430,7 @@ async function resolveExportedProjectAsync(
if (!exportDirStat?.isDirectory()) {
throw new Error(
`No "${flags.exportDir}/" folder found. Prepare your project for deployment with "npx expo export --platform web"`
`No "${flags.exportDir}/" folder found. Export your app with "npx expo export --platform web"`
);
}
@@ -436,7 +455,8 @@ function logExportedProjectInfo(
// Only show the timestamp for exports older than 1 minute
if (project.modifiedAt && Date.now() - project.modifiedAt.getTime() > 60_000) {
modifiedAgo = ` - exported ${formatTimeAgo(project.modifiedAt)}`;
Log.warn(`> Project export: ${project.type}${modifiedAgo}`);
} else {
Log.log(chalk`{dim > Project export: ${project.type}}`);
}
Log.log(chalk`{dim > Project export: ${project.type}${modifiedAgo}}`);
}
@@ -147,10 +147,10 @@ export default class DeviceDelete extends EasCommand {
graphqlClient: ExpoGraphqlClient,
chosenDevice: AppleDevice | AppleDeviceQueryResult
): Promise<void> {
const removalSpinner = ora(`Removing Apple device on Expo`).start();
const removalSpinner = ora(`Removing Apple device on EAS`).start();
try {
await AppleDeviceMutation.deleteAppleDeviceAsync(graphqlClient, chosenDevice.id);
removalSpinner.succeed('Removed Apple device from Expo');
removalSpinner.succeed('Removed Apple device from EAS');
} catch (err) {
removalSpinner.fail();
throw err;
@@ -105,12 +105,12 @@ export default class DeviceRename extends EasCommand {
chosenDevice: AppleDevice | AppleDeviceQueryResult,
newDeviceName: string
): Promise<void> {
const removalSpinner = ora(`Renaming Apple device on Expo`).start();
const removalSpinner = ora(`Renaming Apple device on EAS`).start();
try {
await AppleDeviceMutation.updateAppleDeviceAsync(graphqlClient, chosenDevice.id, {
name: newDeviceName,
});
removalSpinner.succeed('Renamed Apple device on Expo');
removalSpinner.succeed('Renamed Apple device on EAS');
} catch (err) {
removalSpinner.fail();
throw err;
+70 -28
View File
@@ -53,9 +53,10 @@ export default class EnvExec extends EasCommand {
},
];
private isNonInteractive: boolean = false;
async runAsync(): Promise<void> {
const { flags, args } = await this.parse(EnvExec);
const parsedFlags = this.sanitizeFlagsAndArgs(flags, args);
const {
@@ -65,6 +66,8 @@ export default class EnvExec extends EasCommand {
nonInteractive: parsedFlags.nonInteractive,
});
this.isNonInteractive = parsedFlags.nonInteractive;
const environment =
parsedFlags.environment ??
(await promptVariableEnvironmentAsync({ nonInteractive: parsedFlags.nonInteractive }));
@@ -72,12 +75,20 @@ export default class EnvExec extends EasCommand {
graphqlClient,
projectId,
environment,
nonInteractive: parsedFlags.nonInteractive,
});
await this.runCommandWithEnvVarsAsync({
command: parsedFlags.command,
environmentVariables,
});
if (parsedFlags.nonInteractive) {
await this.runCommandNonInteractiveWithEnvVarsAsync({
command: parsedFlags.command,
environmentVariables,
});
} else {
await this.runCommandWithEnvVarsAsync({
command: parsedFlags.command,
environmentVariables,
});
}
}
private sanitizeFlagsAndArgs(
@@ -96,13 +107,48 @@ export default class EnvExec extends EasCommand {
throw new Error("Invalid environment. Use one of 'production', 'preview', or 'development'.");
}
const firstChar = bash_command[0];
const lastChar = bash_command[bash_command.length - 1];
const cleanCommand =
(firstChar === '"' && lastChar === '"') || (firstChar === "'" && lastChar === "'")
? bash_command.slice(1, -1)
: bash_command;
return {
nonInteractive: rawFlags['non-interactive'],
environment,
command: bash_command,
command: cleanCommand,
};
}
// eslint-disable-next-line async-protect/async-suffix
protected override async catch(err: Error): Promise<any> {
// when in non-interactive, make the behavior of this command a pure passthrough, outputting only the subprocess' stdout and stderr and exiting with the
// sub-command's exit status
if (this.isNonInteractive) {
process.exitCode = process.exitCode ?? (err as any).status ?? 1;
} else {
await super.catch(err);
}
}
private async runCommandNonInteractiveWithEnvVarsAsync({
command,
environmentVariables,
}: {
command: string;
environmentVariables: Record<string, string>;
}): Promise<void> {
await spawnAsync(command, [], {
shell: true,
stdio: 'inherit',
env: {
...process.env,
...environmentVariables,
},
});
}
private async runCommandWithEnvVarsAsync({
command,
environmentVariables,
@@ -111,7 +157,8 @@ export default class EnvExec extends EasCommand {
environmentVariables: Record<string, string>;
}): Promise<void> {
Log.log(`Running command: ${chalk.bold(command)}`);
const spawnPromise = spawnAsync('bash', ['-c', command], {
const spawnPromise = spawnAsync(command, [], {
shell: true,
stdio: ['inherit', 'pipe', 'pipe'],
env: {
...process.env,
@@ -147,10 +194,12 @@ export default class EnvExec extends EasCommand {
graphqlClient,
projectId,
environment,
nonInteractive,
}: {
graphqlClient: ExpoGraphqlClient;
projectId: string;
environment: EnvironmentVariableEnvironment;
nonInteractive: boolean;
}): Promise<Record<string, string>> {
const environmentVariablesQueryResult =
await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(graphqlClient, {
@@ -158,31 +207,24 @@ export default class EnvExec extends EasCommand {
environment,
});
const secretEnvironmentVariables = environmentVariablesQueryResult.filter(
({ value }) => !value
);
if (secretEnvironmentVariables.length > 0) {
Log.warn(`The following environment variables are secret and cannot be downloaded locally:`);
for (const { name } of secretEnvironmentVariables) {
Log.warn(`- ${name}`);
}
Log.warn('Proceeding with the rest of the environment variables.');
Log.newLine();
}
const nonSecretEnvironmentVariables = environmentVariablesQueryResult.filter(
({ value }) => !!value
);
if (nonSecretEnvironmentVariables.length === 0) {
throw new Error('No readable environment variables found for the selected environment.');
if (!nonInteractive) {
if (nonSecretEnvironmentVariables.length > 0) {
Log.log(
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS: ${nonSecretEnvironmentVariables
.map(e => e.name)
.join(', ')}.`
);
} else {
Log.log(
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS.`
);
}
Log.newLine();
}
Log.log(
`Loaded environment variables for the selected environment "${environment.toLowerCase()}":`
);
for (const { name } of nonSecretEnvironmentVariables) {
Log.log(`- ${name}`);
}
Log.newLine();
const environmentVariables: Record<string, string> = {};
for (const { name, value } of nonSecretEnvironmentVariables) {
@@ -7,10 +7,12 @@ import { fetchBuildsAsync, formatBuild } from '../../commandUtils/builds';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { AppPlatform, BuildStatus } from '../../graphql/generated';
import { FingerprintMutation } from '../../graphql/mutations/FingerprintMutation';
import { BuildQuery } from '../../graphql/queries/BuildQuery';
import Log from '../../log';
import { ora } from '../../ora';
import { RequestedPlatform } from '../../platform';
import { maybeUploadFingerprintAsync } from '../../project/maybeUploadFingerprintAsync';
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
import { resolveWorkflowPerPlatformAsync } from '../../project/workflow';
import { selectAsync } from '../../prompts';
@@ -94,6 +96,20 @@ export default class FingerprintCompare extends EasCommand {
Log.error('Project fingerprints can only be computed for projects with SDK 52 or higher');
return;
}
const uploadedFingerprint = await maybeUploadFingerprintAsync({
hash: fingerprint.hash,
fingerprint: {
fingerprintSources: fingerprint.sources,
isDebugFingerprintSource: Log.isDebug,
},
graphqlClient,
});
await FingerprintMutation.createFingerprintAsync(graphqlClient, projectId, {
hash: uploadedFingerprint.hash,
source: uploadedFingerprint.fingerprintSource,
});
if (fingerprint.hash === projectFingerprint.hash) {
Log.log(`✅ Project fingerprint matches build`);
return;
@@ -410,11 +410,11 @@ export default class UpdatePublish extends EasCommand {
runtimeToPlatformsAndFingerprintInfoMapping.map(async info => {
return {
...info,
fingerprintSource: info.fingerprint
expoUpdatesRuntimeFingerprintSource: info.expoUpdatesRuntimeFingerprint
? (
await maybeUploadFingerprintAsync({
hash: info.runtimeVersion,
fingerprint: info.fingerprint,
fingerprint: info.expoUpdatesRuntimeFingerprint,
graphqlClient,
})
).fingerprintSource ?? null
@@ -449,7 +449,7 @@ export default class UpdatePublish extends EasCommand {
// Sort the updates into different groups based on their platform specific runtime versions
const updateGroups: PublishUpdateGroupInput[] =
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping.map(
({ runtimeVersion, platforms, fingerprintSource, fingerprintInfoGroup }) => {
({ runtimeVersion, platforms, fingerprintInfoGroup }) => {
const localUpdateInfoGroup = Object.fromEntries(
platforms.map(platform => [
platform,
@@ -485,15 +485,13 @@ export default class UpdatePublish extends EasCommand {
branchId: branch.id,
updateInfoGroup: localUpdateInfoGroup,
rolloutInfoGroup: localRolloutInfoGroup,
runtimeFingerprintSource: fingerprintSource
? transformFingerprintSource(fingerprintSource)
: null,
fingerprintInfoGroup: transformedFingerprintInfoGroup,
runtimeVersion,
message: updateMessage,
gitCommitHash,
isGitWorkingTreeDirty,
awaitingCodeSigningInfo: !!codeSigningInfo,
environment: environment ?? null,
};
}
);
@@ -208,8 +208,8 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
platform,
runtimeVersionInfo: {
runtimeVersion: selectedRuntime,
fingerprint: null,
fingerprintHash: null,
expoUpdatesRuntimeFingerprint: null,
expoUpdatesRuntimeFingerprintHash: null,
},
}))
);
+12 -3
View File
@@ -9,7 +9,7 @@ import { WorkflowRevisionMutation } from '../../graphql/mutations/WorkflowRevisi
import { WorkflowRunMutation } from '../../graphql/mutations/WorkflowRunMutation';
import Log, { link } from '../../log';
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
import { uploadAccountScopedEasJsonAsync } from '../../project/uploadAccountScopedEasJsonAsync';
import { uploadAccountScopedFileAsync } from '../../project/uploadAccountScopedFileAsync';
import { uploadAccountScopedProjectSourceAsync } from '../../project/uploadAccountScopedProjectSourceAsync';
import { WorkflowFile } from '../../utils/workflowFile';
@@ -81,6 +81,7 @@ export default class WorkflowRun extends EasCommand {
let projectArchiveBucketKey: string;
let easJsonBucketKey: string;
let packageJsonBucketKey: string;
try {
({ projectArchiveBucketKey } = await uploadAccountScopedProjectSourceAsync({
@@ -88,10 +89,17 @@ export default class WorkflowRun extends EasCommand {
vcsClient,
accountId: account.id,
}));
({ easJsonBucketKey } = await uploadAccountScopedEasJsonAsync({
({ fileBucketKey: easJsonBucketKey } = await uploadAccountScopedFileAsync({
graphqlClient,
accountId: account.id,
projectDir,
filePath: path.join(projectDir, 'eas.json'),
maxSizeBytes: 1024 * 1024,
}));
({ fileBucketKey: packageJsonBucketKey } = await uploadAccountScopedFileAsync({
graphqlClient,
accountId: account.id,
filePath: path.join(projectDir, 'package.json'),
maxSizeBytes: 1024 * 1024,
}));
} catch (err) {
Log.error('Failed to upload project sources.');
@@ -113,6 +121,7 @@ export default class WorkflowRun extends EasCommand {
type: WorkflowProjectSourceType.Gcs,
projectArchiveBucketKey,
easJsonBucketKey,
packageJsonBucketKey,
},
},
}
@@ -15,6 +15,7 @@ export interface AndroidCredentials {
interface Options {
app: AppLookupParams;
name?: string;
}
export default class AndroidCredentialsProvider {
@@ -37,7 +38,7 @@ export default class AndroidCredentialsProvider {
}
private async getRemoteAsync(): Promise<AndroidCredentials> {
const setupBuildCredentialsAction = new SetUpBuildCredentials({ app: this.options.app });
const setupBuildCredentialsAction = new SetUpBuildCredentials(this.options);
const buildCredentials = await setupBuildCredentialsAction.runAsync(this.ctx);
return this.toAndroidCredentials(buildCredentials);
}
@@ -0,0 +1,168 @@
import { Session } from '@expo/apple-utils';
import nock from 'nock';
import { createAppAsync } from '../ensureAppExists';
const FIXTURE_SUCCESS = {
data: {
type: 'apps',
id: '6741087677',
attributes: {
name: 'expo (xxx)',
bundleId: 'com.bacon.jan27.x',
},
},
};
const FIXTURE_INVALID_NAME = {
errors: [
{
id: 'b3e7ca18-e4ce-4e55-83ce-8fff35dbaeca',
status: '409',
code: 'ENTITY_ERROR.ATTRIBUTE.INVALID.INVALID_CHARACTERS',
title: 'An attribute value has invalid characters.',
detail:
'App Name contains certain Unicode symbols, emoticons, diacritics, special characters, or private use characters that are not permitted.',
source: {
pointer: '/included/1/name',
},
},
],
};
const FIXTURE_ALREADY_USED_ON_ACCOUNT = {
errors: [
{
id: 'b91aefc5-0e94-48d9-8613-5b1a464a20f0',
status: '409',
code: 'ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.SAME_ACCOUNT',
title:
'The provided entity includes an attribute with a value that has already been used on this account.',
detail:
'The app name you entered is already being used for another app in your account. If you would like to use the name for this app you will need to submit an update to your other app to change the name, or remove it from App Store Connect.',
source: {
pointer: '/included/1/name',
},
},
],
};
const FIXTURE_ALREADY_USED_ON_ANOTHER_ACCOUNT = {
errors: [
{
id: '72b960f2-9e51-4f19-8d83-7cc08d42fec4',
status: '409',
code: 'ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.DIFFERENT_ACCOUNT',
title:
'The provided entity includes an attribute with a value that has already been used on a different account.',
detail:
'The App Name you entered is already being used. If you have trademark rights to this name and would like it released for your use, submit a claim.',
source: {
pointer: '/included/1/name',
},
},
],
};
const MOCK_CONTEXT = {
providerId: 1337,
teamId: 'test-team-id',
token: 'test-token',
};
beforeAll(async () => {
// Mock setup cookies API calls.
nock('https://appstoreconnect.apple.com')
.get(`/olympus/v1/session`)
.reply(200, {
provider: {
providerId: 1337,
publicProviderId: 'xxx-xxx-xxx-xxx-xxx',
name: 'Evan Bacon',
contentTypes: ['SOFTWARE'],
subType: 'INDIVIDUAL',
},
});
await Session.fetchCurrentSessionInfoAsync();
});
function getNameFromBody(body: any): any {
return body.included.find((item: any) => item.id === '${new-appInfoLocalization-id}')?.attributes
?.name;
}
it('asserts invalid name cases', async () => {
const scope = nock('https://appstoreconnect.apple.com')
.post(`/iris/v1/apps`, body => {
expect(getNameFromBody(body)).toBe('Expo 🚀');
return true;
})
.reply(409, FIXTURE_INVALID_NAME);
// Already used on same account
nock('https://appstoreconnect.apple.com')
.post(`/iris/v1/apps`, body => {
expect(getNameFromBody(body)).toBe('Expo -');
return true;
})
.reply(409, FIXTURE_ALREADY_USED_ON_ACCOUNT);
// Already used on different account
nock('https://appstoreconnect.apple.com')
.post(`/iris/v1/apps`, body => {
expect(getNameFromBody(body)).toMatch(/Expo - \([\w\d]+\)/);
return true;
})
.reply(409, FIXTURE_ALREADY_USED_ON_ANOTHER_ACCOUNT);
// Success
nock('https://appstoreconnect.apple.com')
.post(`/iris/v1/apps`, body => {
expect(getNameFromBody(body)).toMatch(/Expo - \([\w\d]+\)/);
return true;
})
.reply(200, FIXTURE_SUCCESS);
await createAppAsync(MOCK_CONTEXT, {
bundleId: 'com.bacon.jan27.x',
name: 'Expo 🚀',
companyName: 'expo',
});
expect(scope.isDone()).toBeTruthy();
});
it('works on first try', async () => {
nock('https://appstoreconnect.apple.com').post(`/iris/v1/apps`).reply(200, FIXTURE_SUCCESS);
await createAppAsync(MOCK_CONTEXT, {
bundleId: 'com.bacon.jan27.x',
name: 'Expo',
companyName: 'expo',
});
});
it('doubles up entropy', async () => {
nock('https://appstoreconnect.apple.com')
.post(`/iris/v1/apps`)
.reply(409, FIXTURE_ALREADY_USED_ON_ANOTHER_ACCOUNT);
nock('https://appstoreconnect.apple.com')
.post(`/iris/v1/apps`)
.reply(409, FIXTURE_ALREADY_USED_ON_ANOTHER_ACCOUNT);
nock('https://appstoreconnect.apple.com')
.post(`/iris/v1/apps`, body => {
expect(getNameFromBody(body)).toMatch(/Expo \([\w\d]+\) \([\w\d]+\)/);
return true;
})
.reply(200, FIXTURE_SUCCESS);
await createAppAsync(MOCK_CONTEXT, {
bundleId: 'com.bacon.jan27.x',
name: 'Expo',
companyName: 'expo',
});
});
@@ -1,11 +1,26 @@
import { JsonFileCache } from '@expo/apple-utils';
import * as fs from 'fs-extra';
import { vol } from 'memfs';
import { promptAsync } from '../../../../prompts';
import { AppleTeamType } from '../authenticateTypes';
import { resolveAppleTeamAsync, resolveAscApiKeyAsync } from '../resolveCredentials';
import * as Keychain from '../keychain';
import {
resolveAppleTeamAsync,
resolveAscApiKeyAsync,
resolveUserCredentialsAsync,
} from '../resolveCredentials';
jest.mock('../../../../prompts');
jest.mock('fs');
jest.mock('@expo/apple-utils', () => {
return {
getCacheAsync: jest.fn(),
usernameCachePath: jest.fn(),
...jest.requireActual('@expo/apple-utils'),
};
});
jest.mock('../keychain');
const testAscApiKey = {
keyP8: 'test-key-p8',
@@ -89,3 +104,139 @@ describe(resolveAppleTeamAsync, () => {
});
});
});
describe(resolveUserCredentialsAsync, () => {
let KeychainMock: any;
beforeAll(() => {
JsonFileCache.getCacheAsync = jest.fn().mockImplementation(async (filePath: string) => {
const content = await fs.readFile(filePath, 'utf-8');
return content ? JSON.parse(content) : null;
});
KeychainMock = jest.mocked(Keychain);
});
beforeEach(() => {
jest.clearAllMocks();
KeychainMock.EXPO_NO_KEYCHAIN = '';
});
it('uses credentials resolved from options', async () => {
const result = await resolveUserCredentialsAsync({
username: 'fakeUsername',
password: 'fakePassword',
});
expect(result).toMatchObject({
username: 'fakeUsername',
password: 'fakePassword',
});
});
it('uses credentials resolved from environment', async () => {
process.env.EXPO_APPLE_ID = 'fakeUsername';
process.env.EXPO_APPLE_PASSWORD = 'fakePassword';
const result = await resolveUserCredentialsAsync({});
expect(result).toMatchObject({
username: 'fakeUsername',
password: 'fakePassword',
});
});
it('uses credentials from prompt, does not update cache if they match', async () => {
vol.fromJSON({
'./.app-store/auth/username.json': `
{
"username": "fakeUsername"
}
`,
});
jest.mocked(promptAsync).mockResolvedValueOnce({ username: 'fakeUsername' });
JsonFileCache.usernameCachePath = jest.fn().mockReturnValue('./.app-store/auth/username.json');
const cacheAsyncSpy = jest.spyOn(JsonFileCache, 'cacheAsync');
const result = await resolveUserCredentialsAsync({});
expect(result).toMatchObject({
username: 'fakeUsername',
});
expect(cacheAsyncSpy).not.toHaveBeenCalled();
});
it("uses credentials from prompt, updates cache if they don't match", async () => {
vol.fromJSON({
'./.app-store/auth/username.json': `
{
"username": "fakeUsername"
}
`,
});
jest.mocked(promptAsync).mockResolvedValueOnce({ username: 'newFakeUsername' });
JsonFileCache.usernameCachePath = jest.fn().mockReturnValue('./.app-store/auth/username.json');
const cacheAsyncSpy = jest.spyOn(JsonFileCache, 'cacheAsync');
expect(JSON.parse(await fs.readFile('./.app-store/auth/username.json', 'utf-8'))).toMatchObject(
{ username: 'fakeUsername' }
);
const result = await resolveUserCredentialsAsync({});
expect(result).toMatchObject({
username: 'newFakeUsername',
});
expect(cacheAsyncSpy).toHaveBeenCalledWith('./.app-store/auth/username.json', {
username: 'newFakeUsername',
});
expect(JSON.parse(await fs.readFile('./.app-store/auth/username.json', 'utf-8'))).toMatchObject(
{ username: 'newFakeUsername' }
);
});
it("uses credentials from prompt, doesn't read or update cache if EXPO_NO_KEYCHAIN is set", async () => {
vol.fromJSON({
'./.app-store/auth/username.json': `
{
"username": "fakeUsername"
}
`,
});
jest.mocked(promptAsync).mockResolvedValueOnce({ username: 'newFakeUsername' });
JsonFileCache.usernameCachePath = jest.fn().mockReturnValue('./.app-store/auth/username.json');
KeychainMock.EXPO_NO_KEYCHAIN = 'true';
const getCacheAsyncSpy = jest.spyOn(JsonFileCache, 'getCacheAsync');
const cacheAsyncSpy = jest.spyOn(JsonFileCache, 'cacheAsync');
expect(JSON.parse(await fs.readFile('./.app-store/auth/username.json', 'utf-8'))).toMatchObject(
{ username: 'fakeUsername' }
);
const result = await resolveUserCredentialsAsync({});
expect(result).toMatchObject({
username: 'newFakeUsername',
});
expect(getCacheAsyncSpy).not.toHaveBeenCalled();
expect(cacheAsyncSpy).not.toHaveBeenCalled();
await expect(fs.access('./.app-store/auth/username.json')).rejects.toThrow();
});
it('clears credentials from prompt if malformed before using and updating cache', async () => {
vol.fromJSON({
'./.app-store/auth/username.json': `
{
"username": "fakeUsername"
}
`,
});
jest.mocked(promptAsync).mockResolvedValueOnce({ username: '\x17newFake\x01Username\x1F' });
JsonFileCache.usernameCachePath = jest.fn().mockReturnValue('./.app-store/auth/username.json');
const cacheAsyncSpy = jest.spyOn(JsonFileCache, 'cacheAsync');
expect(JSON.parse(await fs.readFile('./.app-store/auth/username.json', 'utf-8'))).toMatchObject(
{ username: 'fakeUsername' }
);
const result = await resolveUserCredentialsAsync({});
expect(result).toMatchObject({
username: 'newFakeUsername',
});
expect(cacheAsyncSpy).toHaveBeenCalledWith('./.app-store/auth/username.json', {
username: 'newFakeUsername',
});
expect(JSON.parse(await fs.readFile('./.app-store/auth/username.json', 'utf-8'))).toMatchObject(
{ username: 'newFakeUsername' }
);
});
});
@@ -1,6 +1,7 @@
import { App, BundleId } from '@expo/apple-utils';
import { App, BundleId, RequestContext } from '@expo/apple-utils';
import { JSONObject } from '@expo/json-file';
import chalk from 'chalk';
import { randomBytes } from 'node:crypto';
import { getRequestContext, isUserAuthCtx } from './authenticate';
import { AuthCtx, UserAuthCtx } from './authenticateTypes';
@@ -188,7 +189,7 @@ export async function ensureAppExistsAsync(
/**
* **Does not support App Store Connect API (CI).**
*/
app = await App.createAsync(context, {
app = await createAppAsync(context, {
bundleId: bundleIdentifier,
name,
primaryLocale: language,
@@ -215,3 +216,120 @@ export async function ensureAppExistsAsync(
);
return app;
}
function sanitizeName(name: string): string {
return (
name
// Replace emojis with a `-`
.replace(/[\p{Emoji}]/gu, '-')
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.trim()
);
}
export async function createAppAsync(
context: RequestContext,
props: {
bundleId: string;
name: string;
primaryLocale?: string;
companyName?: string;
sku?: string;
},
retryCount = 0
): Promise<App> {
try {
/**
* **Does not support App Store Connect API (CI).**
*/
return await App.createAsync(context, props);
} catch (error) {
if (retryCount >= 5) {
throw error;
}
if (error instanceof Error) {
const handleDuplicateNameErrorAsync = async (): Promise<App> => {
const generatedName = props.name + ` (${randomBytes(3).toString('hex')})`;
Log.warn(
`App name "${props.name}" is already taken. Using generated name "${generatedName}" which can be changed later from https://appstoreconnect.apple.com.`
);
// Sanitize the name and try again.
return await createAppAsync(
context,
{
...props,
name: generatedName,
},
retryCount + 1
);
};
if (isAppleError(error)) {
// New error class that is thrown when the name is already taken but belongs to you.
if (
error.data.errors.some(
e =>
e.code === 'ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.SAME_ACCOUNT' ||
e.code === 'ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.DIFFERENT_ACCOUNT'
)
) {
return await handleDuplicateNameErrorAsync();
}
}
if ('code' in error && typeof error.code === 'string') {
if (
// Name is invalid
error.code === 'APP_CREATE_NAME_INVALID'
// UnexpectedAppleResponse: An attribute value has invalid characters. - App Name contains certain Unicode symbols, emoticons, diacritics, special characters, or private use characters that are not permitted.
// Name is taken
) {
const sanitizedName = sanitizeName(props.name);
if (sanitizedName === props.name) {
throw error;
}
Log.warn(
`App name "${props.name}" contains invalid characters. Using sanitized name "${sanitizedName}" which can be changed later from https://appstoreconnect.apple.com.`
);
// Sanitize the name and try again.
return await createAppAsync(
context,
{
...props,
name: sanitizedName,
},
retryCount + 1
);
}
if (
// UnexpectedAppleResponse: The provided entity includes an attribute with a value that has already been used on a different account. - The App Name you entered is already being used. If you have trademark rights to
// this name and would like it released for your use, submit a claim.
error.code === 'APP_CREATE_NAME_UNAVAILABLE'
) {
return await handleDuplicateNameErrorAsync();
}
}
}
throw error;
}
}
function isAppleError(error: any): error is {
data: {
errors: {
id: string;
status: string;
/** 'ENTITY_ERROR.ATTRIBUTE.INVALID.INVALID_CHARACTERS' */
code: string;
/** 'An attribute value has invalid characters.' */
title: string;
/** 'App Name contains certain Unicode symbols, emoticons, diacritics, special characters, or private use characters that are not permitted.' */
detail: string;
}[];
};
} {
return 'data' in error && 'errors' in error.data && Array.isArray(error.data.errors);
}
@@ -30,6 +30,7 @@ function resolveProfileType(
case ApplePlatform.TV_OS:
return resolveProfileTypeAppleTv(profileClass, isEnterprise);
case ApplePlatform.MAC_OS:
case ApplePlatform.VISION_OS:
throw new Error(`${applePlatform} profiles are not supported`);
}
}
@@ -208,13 +208,18 @@ async function promptUsernameAsync(): Promise<string> {
// the default value for quicker authentication.
const lastAppleId = await getCachedUsernameAsync();
const { username } = await promptAsync({
let { username } = await promptAsync({
type: 'text',
name: 'username',
message: `Apple ID:`,
validate: (val: string) => val !== '',
initial: lastAppleId ?? undefined,
});
// https://github.com/expo/eas-cli/issues/2254
// user got an invalid unprintable character in their username, which was saved to cache and used to prefill the prompt
// which made them accept the prefilled value not knowing it contains the character and fail to log in
// this replace makes sure no such characters are used or cached
username = username.replace(/[\x00-\x1F]/gi, '');
if (username && username !== lastAppleId) {
await cacheUsernameAsync(username);
@@ -48,7 +48,7 @@ async function collectDataAndRegisterDeviceAsync(
}
const { udid, deviceClass, name } = deviceData;
const spinner = ora(`Registering Apple device on Expo`).start();
const spinner = ora(`Registering Apple device on EAS`).start();
try {
await AppleDeviceMutation.createAppleDeviceAsync(
graphqlClient,
@@ -45,7 +45,7 @@ async function collectDataAndRegisterDeviceAsync(
): Promise<void> {
const { udid, deviceClass, name } = await collectDeviceDataAsync(appleTeam);
const spinner = ora(`Registering Apple device on Expo`).start();
const spinner = ora(`Registering Apple device on EAS`).start();
try {
await AppleDeviceMutation.createAppleDeviceAsync(
graphqlClient,
+284 -13
View File
@@ -24,6 +24,7 @@ export type Scalars = {
JSON: { input: any; output: any; }
JSONObject: { input: any; output: any; }
WorkerDeploymentIdentifier: { input: any; output: any; }
WorkerDeploymentRequestID: { input: any; output: any; }
};
export type AcceptUserInvitationResult = {
@@ -133,9 +134,12 @@ export type Account = {
id: Scalars['ID']['output'];
isCurrent: Scalars['Boolean']['output'];
isDisabled: Scalars['Boolean']['output'];
/** Whether an Account plan falls into AppDevDomainName's free or paid tier */
isFreeAppDevDomainTier: Scalars['Boolean']['output'];
/** Whether this account has SSO enabled. Can be queried by all members. */
isSSOEnabled: Scalars['Boolean']['output'];
lastDeletionAttemptTime?: Maybe<Scalars['DateTime']['output']>;
logRocketOrganization?: Maybe<LogRocketOrganization>;
name: Scalars['String']['output'];
/** Offers set on this account */
offers?: Maybe<Array<Offer>>;
@@ -143,6 +147,7 @@ export type Account = {
owner?: Maybe<User>;
/** Owning UserActor of this account if personal account */
ownerUserActor?: Maybe<UserActor>;
profileImageUrl: Scalars['String']['output'];
pushSecurityEnabled: Scalars['Boolean']['output'];
/** @deprecated Legacy access tokens are deprecated */
requiresAccessTokenForPushSecurity: Scalars['Boolean']['output'];
@@ -722,12 +727,7 @@ export type AccountUsageEasBuildMetadata = {
waiverType?: Maybe<EasBuildWaiverType>;
};
export type AccountUsageEasJobsMetadata = {
__typename?: 'AccountUsageEASJobsMetadata';
resourceClassDisplayName: Scalars['String']['output'];
};
export type AccountUsageMetadata = AccountUsageEasBuildMetadata | AccountUsageEasJobsMetadata;
export type AccountUsageMetadata = AccountUsageEasBuildMetadata;
export type AccountUsageMetric = {
__typename?: 'AccountUsageMetric';
@@ -1251,6 +1251,7 @@ export type App = Project & {
environmentVariables: Array<EnvironmentVariable>;
/** Environment variables for an app with decrypted secret values */
environmentVariablesIncludingSensitive: Array<EnvironmentVariableWithSecret>;
fingerprintsPaginated: AppFingerprintsConnection;
fullName: Scalars['String']['output'];
githubBuildTriggers: Array<GitHubBuildTrigger>;
githubJobRunTriggers: Array<GitHubJobRunTrigger>;
@@ -1300,6 +1301,7 @@ export type App = Project & {
likeCount: Scalars['Int']['output'];
/** @deprecated 'likes' have been deprecated. */
likedBy: Array<Maybe<User>>;
logRocketProject?: Maybe<LogRocketProject>;
name: Scalars['String']['output'];
ownerAccount: Account;
/** @deprecated No longer supported */
@@ -1366,6 +1368,8 @@ export type App = Project & {
/** EAS updates owned by an app */
updates: Array<Update>;
updatesPaginated: AppUpdatesConnection;
/** Project query object for querying EAS usage metrics */
usageMetrics: AppUsageMetrics;
/** @deprecated Use ownerAccount.name instead */
username: Scalars['String']['output'];
/** @deprecated No longer supported */
@@ -1479,6 +1483,16 @@ export type AppEnvironmentVariablesIncludingSensitiveArgs = {
};
/** Represents an Exponent App (or Experience in legacy terms) */
export type AppFingerprintsPaginatedArgs = {
after?: InputMaybe<Scalars['String']['input']>;
before?: InputMaybe<Scalars['String']['input']>;
filter?: InputMaybe<FingerprintFilterInput>;
first?: InputMaybe<Scalars['Int']['input']>;
last?: InputMaybe<Scalars['Int']['input']>;
};
/** Represents an Exponent App (or Experience in legacy terms) */
export type AppIosAppCredentialsArgs = {
filter?: InputMaybe<IosAppCredentialsFilter>;
@@ -1732,6 +1746,18 @@ export type AppDevDomainNameMutationChangeDevDomainNameArgs = {
name: Scalars['DevDomainName']['input'];
};
export type AppFingerprintEdge = {
__typename?: 'AppFingerprintEdge';
cursor: Scalars['String']['output'];
node: Fingerprint;
};
export type AppFingerprintsConnection = {
__typename?: 'AppFingerprintsConnection';
edges: Array<AppFingerprintEdge>;
pageInfo: PageInfo;
};
export type AppIcon = {
__typename?: 'AppIcon';
/** @deprecated No longer supported */
@@ -2053,6 +2079,44 @@ export enum AppUploadSessionType {
ProfileImageUpload = 'PROFILE_IMAGE_UPLOAD'
}
export type AppUsageMetric = {
__typename?: 'AppUsageMetric';
id: Scalars['ID']['output'];
metricType: UsageMetricType;
serviceMetric: EasServiceMetric;
timestamp: Scalars['DateTime']['output'];
value: Scalars['Float']['output'];
};
export type AppUsageMetricTotal = {
__typename?: 'AppUsageMetricTotal';
billingPeriod: BillingPeriod;
id: Scalars['ID']['output'];
planMetrics: Array<EstimatedUsage>;
/** Total cost of overages, in cents */
totalCost: Scalars['Float']['output'];
};
export type AppUsageMetrics = {
__typename?: 'AppUsageMetrics';
byBillingPeriod: AppUsageMetricTotal;
metricsForServiceMetric: Array<AppUsageMetric>;
};
export type AppUsageMetricsByBillingPeriodArgs = {
date: Scalars['DateTime']['input'];
service?: InputMaybe<EasService>;
};
export type AppUsageMetricsMetricsForServiceMetricArgs = {
filterParams?: InputMaybe<Scalars['JSONObject']['input']>;
granularity: UsageMetricsGranularity;
serviceMetric: EasServiceMetric;
timespan: UsageMetricsTimespan;
};
/** Represents Play Store/App Store version of an application */
export type AppVersion = {
__typename?: 'AppVersion';
@@ -2870,6 +2934,7 @@ export type BuildFilter = {
buildProfile?: InputMaybe<Scalars['String']['input']>;
channel?: InputMaybe<Scalars['String']['input']>;
distribution?: InputMaybe<DistributionType>;
fingerprintHash?: InputMaybe<Scalars['String']['input']>;
gitCommitHash?: InputMaybe<Scalars['String']['input']>;
hasFingerprint?: InputMaybe<Scalars['Boolean']['input']>;
platform?: InputMaybe<AppPlatform>;
@@ -2883,6 +2948,7 @@ export type BuildFilterInput = {
channel?: InputMaybe<Scalars['String']['input']>;
developmentClient?: InputMaybe<Scalars['Boolean']['input']>;
distributions?: InputMaybe<Array<DistributionType>>;
fingerprintHash?: InputMaybe<Scalars['String']['input']>;
hasFingerprint?: InputMaybe<Scalars['Boolean']['input']>;
platforms?: InputMaybe<Array<AppPlatform>>;
releaseChannel?: InputMaybe<Scalars['String']['input']>;
@@ -3294,6 +3360,7 @@ export enum CrashSampleFor {
}
export type CrashesFilters = {
crashKind?: InputMaybe<Array<WorkerDeploymentCrashKind>>;
name?: InputMaybe<Array<Scalars['String']['input']>>;
};
@@ -3346,6 +3413,11 @@ export type CreateEnvironmentVariableInput = {
visibility: EnvironmentVariableVisibility;
};
export type CreateFingerprintInput = {
hash: Scalars['String']['input'];
source?: InputMaybe<FingerprintSourceInput>;
};
export type CreateGitHubAppInstallationInput = {
accountId: Scalars['ID']['input'];
installationIdentifier: Scalars['Int']['input'];
@@ -3596,6 +3668,16 @@ export type DeleteIosAppCredentialsResult = {
id: Scalars['ID']['output'];
};
export type DeleteLogRocketOrganizationResult = {
__typename?: 'DeleteLogRocketOrganizationResult';
accountId: Scalars['ID']['output'];
};
export type DeleteLogRocketProjectResult = {
__typename?: 'DeleteLogRocketProjectResult';
id: Scalars['ID']['output'];
};
export type DeleteRobotResult = {
__typename?: 'DeleteRobotResult';
id: Scalars['ID']['output'];
@@ -3918,6 +4000,9 @@ export enum EntityTypeName {
IosAppCredentialsEntity = 'IosAppCredentialsEntity',
UserInvitationEntity = 'UserInvitationEntity',
UserPermissionEntity = 'UserPermissionEntity',
WorkerCustomDomainEntity = 'WorkerCustomDomainEntity',
WorkerDeploymentAliasEntity = 'WorkerDeploymentAliasEntity',
WorkerEntity = 'WorkerEntity',
WorkflowEntity = 'WorkflowEntity',
WorkflowRevisionEntity = 'WorkflowRevisionEntity'
}
@@ -4180,12 +4265,44 @@ export enum Feature {
export type Fingerprint = {
__typename?: 'Fingerprint';
app: App;
builds: AppBuildsConnection;
createdAt: Scalars['DateTime']['output'];
debugInfoUrl?: Maybe<Scalars['String']['output']>;
hash: Scalars['String']['output'];
id: Scalars['ID']['output'];
source?: Maybe<FingerprintSource>;
updatedAt: Scalars['DateTime']['output'];
updates: AppUpdatesConnection;
};
export type FingerprintBuildsArgs = {
after?: InputMaybe<Scalars['String']['input']>;
before?: InputMaybe<Scalars['String']['input']>;
filter?: InputMaybe<FingerprintBuildsFilterInput>;
first?: InputMaybe<Scalars['Int']['input']>;
last?: InputMaybe<Scalars['Int']['input']>;
};
export type FingerprintUpdatesArgs = {
after?: InputMaybe<Scalars['String']['input']>;
before?: InputMaybe<Scalars['String']['input']>;
first?: InputMaybe<Scalars['Int']['input']>;
last?: InputMaybe<Scalars['Int']['input']>;
};
export type FingerprintBuildsFilterInput = {
channel?: InputMaybe<Scalars['String']['input']>;
developmentClient?: InputMaybe<Scalars['Boolean']['input']>;
distributions?: InputMaybe<Array<DistributionType>>;
platforms?: InputMaybe<Array<AppPlatform>>;
releaseChannel?: InputMaybe<Scalars['String']['input']>;
simulator?: InputMaybe<Scalars['Boolean']['input']>;
};
export type FingerprintFilterInput = {
hashes?: InputMaybe<Array<Scalars['String']['input']>>;
};
export type FingerprintInfo = {
@@ -4199,6 +4316,18 @@ export type FingerprintInfoGroup = {
web?: InputMaybe<FingerprintInfo>;
};
export type FingerprintMutation = {
__typename?: 'FingerprintMutation';
/** Create or get an existing fingerprint for an App */
createOrGetExistingFingerprint: Fingerprint;
};
export type FingerprintMutationCreateOrGetExistingFingerprintArgs = {
appId: Scalars['ID']['input'];
fingerprintData: CreateFingerprintInput;
};
export type FingerprintSource = {
__typename?: 'FingerprintSource';
bucketKey: Scalars['String']['output'];
@@ -4226,6 +4355,22 @@ export type FutureSubscription = {
startDate: Scalars['DateTime']['output'];
};
export type GenerateLogRocketOrganizationLinkResult = {
__typename?: 'GenerateLogRocketOrganizationLinkResult';
url: Scalars['String']['output'];
};
export type GenerateLogRocketOrganizationLinkingUrlInput = {
accountId: Scalars['ID']['input'];
callbackUrl: Scalars['String']['input'];
};
export type GenerateLogRocketReplayTokenResult = {
__typename?: 'GenerateLogRocketReplayTokenResult';
orgSlug: Scalars['String']['output'];
replayToken: Scalars['String']['output'];
};
export type GetSignedAssetUploadSpecificationsResult = {
__typename?: 'GetSignedAssetUploadSpecificationsResult';
specifications: Array<Scalars['String']['output']>;
@@ -5106,6 +5251,15 @@ export type LineDataset = {
label: Scalars['String']['output'];
};
export type LinkLogRocketOrganizationToExpoAccountInput = {
accountId: Scalars['ID']['input'];
client_id: Scalars['String']['input'];
client_secret: Scalars['String']['input'];
orgName: Scalars['String']['input'];
orgSlug: Scalars['String']['input'];
state: Scalars['String']['input'];
};
export type LinkSharedEnvironmentVariableInput = {
appId: Scalars['ID']['input'];
environment?: InputMaybe<EnvironmentVariableEnvironment>;
@@ -5118,6 +5272,76 @@ export type LogNameTypeMapping = {
typeName: EntityTypeName;
};
export type LogRocketOrganization = {
__typename?: 'LogRocketOrganization';
account: Account;
createdAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
orgName: Scalars['String']['output'];
orgSlug: Scalars['String']['output'];
};
export type LogRocketOrganizationMutation = {
__typename?: 'LogRocketOrganizationMutation';
/** Delete a LogRocket organization by ID */
deleteLogRocketOrganization: DeleteLogRocketOrganizationResult;
/** Generate a LogRocket linking URL */
generateLogRocketOrganizationLinkingURL: GenerateLogRocketOrganizationLinkResult;
/** Generate a LogRocket replay token for an organization */
generateLogRocketReplayToken: GenerateLogRocketReplayTokenResult;
/** Link a LogRocket organization to an Expo account */
linkLogRocketOrganizationToExpoAccount: LogRocketOrganization;
};
export type LogRocketOrganizationMutationDeleteLogRocketOrganizationArgs = {
accountId: Scalars['ID']['input'];
};
export type LogRocketOrganizationMutationGenerateLogRocketOrganizationLinkingUrlArgs = {
input: GenerateLogRocketOrganizationLinkingUrlInput;
};
export type LogRocketOrganizationMutationGenerateLogRocketReplayTokenArgs = {
accountId: Scalars['ID']['input'];
};
export type LogRocketOrganizationMutationLinkLogRocketOrganizationToExpoAccountArgs = {
input: LinkLogRocketOrganizationToExpoAccountInput;
};
export type LogRocketProject = {
__typename?: 'LogRocketProject';
app: App;
createdAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
logRocketOrgId: Scalars['ID']['output'];
logRocketProjectSlug: Scalars['String']['output'];
updatedAt: Scalars['DateTime']['output'];
};
export type LogRocketProjectMutation = {
__typename?: 'LogRocketProjectMutation';
/** Create a LogRocket project */
createLogRocketProject: LogRocketProject;
/** Delete a LogRocket project by ID */
deleteLogRocketProject: DeleteLogRocketProjectResult;
};
export type LogRocketProjectMutationCreateLogRocketProjectArgs = {
appId: Scalars['ID']['input'];
logRocketProjectSlug: Scalars['String']['input'];
};
export type LogRocketProjectMutationDeleteLogRocketProjectArgs = {
logRocketProjectId: Scalars['ID']['input'];
};
export type LogsTimespan = {
end: Scalars['DateTime']['input'];
start?: InputMaybe<Scalars['DateTime']['input']>;
@@ -5512,6 +5736,7 @@ export type PublicArtifacts = {
export type PublishUpdateGroupInput = {
awaitingCodeSigningInfo?: InputMaybe<Scalars['Boolean']['input']>;
branchId: Scalars['String']['input'];
environment?: InputMaybe<EnvironmentVariableEnvironment>;
excludedAssets?: InputMaybe<Array<PartialManifestAsset>>;
fingerprintInfoGroup?: InputMaybe<FingerprintInfoGroup>;
gitCommitHash?: InputMaybe<Scalars['String']['input']>;
@@ -5541,10 +5766,12 @@ export type RequestsFilters = {
hasCustomDomainOrigin?: InputMaybe<Scalars['Boolean']['input']>;
isAsset?: InputMaybe<Scalars['Boolean']['input']>;
isCrash?: InputMaybe<Scalars['Boolean']['input']>;
isLimitExceeded?: InputMaybe<Scalars['Boolean']['input']>;
isVerifiedBot?: InputMaybe<Scalars['Boolean']['input']>;
method?: InputMaybe<Array<RequestMethod>>;
os?: InputMaybe<Array<UserAgentOs>>;
pathname?: InputMaybe<Scalars['String']['input']>;
requestId?: InputMaybe<Array<Scalars['WorkerDeploymentRequestID']['input']>>;
responseType?: InputMaybe<Array<ResponseType>>;
status?: InputMaybe<Array<Scalars['Int']['input']>>;
statusType?: InputMaybe<Array<ResponseStatusType>>;
@@ -5617,8 +5844,11 @@ export type Robot = Actor & {
*/
featureGates: Scalars['JSONObject']['output'];
firstName?: Maybe<Scalars['String']['output']>;
/** GitHub App Installations that manage this actor */
githubAppInstallations: Array<GitHubAppInstallation>;
id: Scalars['ID']['output'];
isExpoAdmin: Scalars['Boolean']['output'];
isManagedByGitHubApp: Scalars['Boolean']['output'];
lastDeletionAttemptTime?: Maybe<Scalars['DateTime']['output']>;
};
@@ -5731,6 +5961,8 @@ export type RootMutation = {
environmentSecret: EnvironmentSecretMutation;
/** Mutations that create and delete EnvironmentVariables */
environmentVariable: EnvironmentVariableMutation;
/** Mutations that modify App fingerprints */
fingerprint: FingerprintMutation;
/** Mutations that utilize services facilitated by the GitHub App */
githubApp: GitHubAppMutation;
/** Mutations for GitHub App installations */
@@ -5753,6 +5985,10 @@ export type RootMutation = {
/** Mutations that modify an EAS Build */
jobRun: JobRunMutation;
keystoreGenerationUrl: KeystoreGenerationUrlMutation;
/** Mutations for LogRocket organizations */
logRocketOrganization: LogRocketOrganizationMutation;
/** Mutations for LogRocket projects */
logRocketProject: LogRocketProjectMutation;
/** Mutations that modify the currently authenticated User */
me: MeMutation;
/** Mutations that modify a NotificationSubscription */
@@ -6383,7 +6619,7 @@ export enum SubmissionAndroidTrack {
}
export type SubmissionArchiveSourceInput = {
/** Required if the archive source type is GCS_BUILD_APPLICATION_ARCHIVE or GCS_SUBMIT_ARCHIVE */
/** Required if the archive source type is GCS_BUILD_APPLICATION_ARCHIVE, GCS_BUILD_APPLICATION_ARCHIVE_ORCHESTRATOR or GCS_SUBMIT_ARCHIVE */
bucketKey?: InputMaybe<Scalars['String']['input']>;
type: SubmissionArchiveSourceType;
/** Required if the archive source type is URL */
@@ -6392,6 +6628,7 @@ export type SubmissionArchiveSourceInput = {
export enum SubmissionArchiveSourceType {
GcsBuildApplicationArchive = 'GCS_BUILD_APPLICATION_ARCHIVE',
GcsBuildApplicationArchiveOrchestrator = 'GCS_BUILD_APPLICATION_ARCHIVE_ORCHESTRATOR',
GcsSubmitArchive = 'GCS_SUBMIT_ARCHIVE',
Url = 'URL'
}
@@ -6549,6 +6786,7 @@ export type Update = ActivityTimelineProjectActivity & {
codeSigningInfo?: Maybe<CodeSigningInfo>;
createdAt: Scalars['DateTime']['output'];
deployments: DeploymentResult;
environment?: Maybe<EnvironmentVariableEnvironment>;
expoGoSDKVersion?: Maybe<Scalars['String']['output']>;
fingerprint?: Maybe<Fingerprint>;
gitCommitHash?: Maybe<Scalars['String']['output']>;
@@ -7641,6 +7879,7 @@ export type WorkerDeployment = ActivityTimelineProjectActivity & {
activityTimestamp: Scalars['DateTime']['output'];
actor?: Maybe<Actor>;
aliases?: Maybe<Array<WorkerDeploymentAlias>>;
app: App;
crashes?: Maybe<WorkerDeploymentCrashes>;
createdAt: Scalars['DateTime']['output'];
deploymentDomain: Scalars['String']['output'];
@@ -7651,7 +7890,6 @@ export type WorkerDeployment = ActivityTimelineProjectActivity & {
logs?: Maybe<WorkerDeploymentLogs>;
requests?: Maybe<WorkerDeploymentRequests>;
subdomain: Scalars['String']['output'];
tier?: Maybe<WorkerDeploymentTier>;
url: Scalars['String']['output'];
};
@@ -7705,9 +7943,19 @@ export type WorkerDeploymentCrashEdge = {
request?: Maybe<WorkerDeploymentRequestNode>;
};
export enum WorkerDeploymentCrashKind {
ExceededCpu = 'EXCEEDED_CPU',
ExceededMemory = 'EXCEEDED_MEMORY',
ExceededSubrequests = 'EXCEEDED_SUBREQUESTS',
Generic = 'GENERIC',
Internal = 'INTERNAL',
ResponseStreamDisconnected = 'RESPONSE_STREAM_DISCONNECTED'
}
export type WorkerDeploymentCrashNode = {
__typename?: 'WorkerDeploymentCrashNode';
crashHash: Scalars['ID']['output'];
crashKind: WorkerDeploymentCrashKind;
crashTimestamp: Scalars['DateTime']['output'];
deploymentIdentifier: Scalars['String']['output'];
firstStackLine?: Maybe<Scalars['String']['output']>;
@@ -7819,6 +8067,7 @@ export type WorkerDeploymentRequestNode = {
hasCustomDomainOrigin: Scalars['Boolean']['output'];
isAsset: Scalars['Boolean']['output'];
isCrash: Scalars['Boolean']['output'];
isLimitExceeded: Scalars['Boolean']['output'];
isRejected: Scalars['Boolean']['output'];
isStaleIfError: Scalars['Boolean']['output'];
isStaleWhileRevalidate: Scalars['Boolean']['output'];
@@ -7828,6 +8077,7 @@ export type WorkerDeploymentRequestNode = {
os?: Maybe<UserAgentOs>;
pathname: Scalars['String']['output'];
region?: Maybe<Scalars['String']['output']>;
requestId: Scalars['WorkerDeploymentRequestID']['output'];
requestTimestamp: Scalars['DateTime']['output'];
responseType: ResponseType;
scriptName: Scalars['String']['output'];
@@ -7929,6 +8179,8 @@ export type WorkerDeploymentRequestsAggregationNode = {
durationP50: Scalars['Float']['output'];
durationP90: Scalars['Float']['output'];
durationP99: Scalars['Float']['output'];
limitExceededPerMs?: Maybe<Scalars['Float']['output']>;
limitExceededSum: Scalars['Int']['output'];
requestsPerMs?: Maybe<Scalars['Float']['output']>;
requestsSum: Scalars['Int']['output'];
sampleRate?: Maybe<Scalars['Float']['output']>;
@@ -8004,11 +8256,6 @@ export type WorkerDeploymentRequestsTimeseriesEdge = {
timestamp: Scalars['DateTime']['output'];
};
export enum WorkerDeploymentTier {
Free = 'FREE',
Paid = 'PAID'
}
export type WorkerDeploymentsConnection = {
__typename?: 'WorkerDeploymentsConnection';
edges: Array<WorkerDeploymentEdge>;
@@ -8112,6 +8359,7 @@ export enum WorkflowJobType {
AppleDeviceRegistrationRequest = 'APPLE_DEVICE_REGISTRATION_REQUEST',
Build = 'BUILD',
Custom = 'CUSTOM',
Deploy = 'DEPLOY',
GetBuild = 'GET_BUILD',
MaestroTest = 'MAESTRO_TEST',
RequireApproval = 'REQUIRE_APPROVAL',
@@ -8121,6 +8369,7 @@ export enum WorkflowJobType {
export type WorkflowProjectSourceInput = {
easJsonBucketKey: Scalars['String']['input'];
packageJsonBucketKey?: InputMaybe<Scalars['String']['input']>;
projectArchiveBucketKey: Scalars['String']['input'];
type: WorkflowProjectSourceType;
};
@@ -8204,7 +8453,10 @@ export type WorkflowRun = ActivityTimelineProjectActivity & {
name: Scalars['String']['output'];
pullRequestNumber?: Maybe<Scalars['Int']['output']>;
requestedGitRef?: Maybe<Scalars['String']['output']>;
retriedWorkflowRun?: Maybe<WorkflowRun>;
retries: Array<WorkflowRun>;
status: WorkflowRunStatus;
triggerEventType: WorkflowRunTriggerEventType;
updatedAt: Scalars['DateTime']['output'];
workflow: Workflow;
workflowRevision?: Maybe<WorkflowRevision>;
@@ -8230,6 +8482,7 @@ export type WorkflowRunMutation = {
__typename?: 'WorkflowRunMutation';
cancelWorkflowRun: WorkflowRun;
createWorkflowRun: WorkflowRun;
retryWorkflowRun: WorkflowRun;
};
@@ -8244,6 +8497,11 @@ export type WorkflowRunMutationCreateWorkflowRunArgs = {
workflowRunInput: WorkflowRunInput;
};
export type WorkflowRunMutationRetryWorkflowRunArgs = {
workflowRunId: Scalars['ID']['input'];
};
export type WorkflowRunQuery = {
__typename?: 'WorkflowRunQuery';
byId: WorkflowRun;
@@ -8264,6 +8522,11 @@ export enum WorkflowRunStatus {
Success = 'SUCCESS'
}
export enum WorkflowRunTriggerEventType {
Github = 'GITHUB',
Manual = 'MANUAL'
}
export type WorkflowRunsConnection = {
__typename?: 'WorkflowRunsConnection';
edges: Array<WorkflowRunEdge>;
@@ -8926,6 +9189,14 @@ export type CreateBulkEnvironmentVariablesForAppMutationVariables = Exact<{
export type CreateBulkEnvironmentVariablesForAppMutation = { __typename?: 'RootMutation', environmentVariable: { __typename?: 'EnvironmentVariableMutation', createBulkEnvironmentVariablesForApp: Array<{ __typename?: 'EnvironmentVariable', id: string }> } };
export type CreateFingeprintMutationVariables = Exact<{
fingerprintData: CreateFingerprintInput;
appId: Scalars['ID']['input'];
}>;
export type CreateFingeprintMutation = { __typename?: 'RootMutation', fingerprint: { __typename?: 'FingerprintMutation', createOrGetExistingFingerprint: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null } } };
export type CreateKeystoreGenerationUrlMutationVariables = Exact<{ [key: string]: never; }>;
@@ -0,0 +1,39 @@
import { FingerprintSource } from '@expo/eas-build-job';
import { print } from 'graphql';
import gql from 'graphql-tag';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { withErrorHandlingAsync } from '../client';
import { CreateFingeprintMutation, FingerprintFragment } from '../generated';
import { FingerprintFragmentNode } from '../types/Fingerprint';
export const FingerprintMutation = {
async createFingerprintAsync(
graphqlClient: ExpoGraphqlClient,
appId: string,
fingerprintData: { hash: string; source?: FingerprintSource }
): Promise<FingerprintFragment> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<CreateFingeprintMutation>(
gql`
mutation CreateFingeprintMutation(
$fingerprintData: CreateFingerprintInput!
$appId: ID!
) {
fingerprint {
createOrGetExistingFingerprint(fingerprintData: $fingerprintData, appId: $appId) {
id
...FingerprintFragment
}
}
}
${print(FingerprintFragmentNode)}
`,
{ appId, fingerprintData }
)
.toPromise()
);
return data.fingerprint.createOrGetExistingFingerprint;
},
};
@@ -84,13 +84,13 @@ export async function fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsyn
const projectDashboardUrl = getProjectDashboardUrl(accountName, projectName);
const projectLink = link(projectDashboardUrl, { text: projectFullName });
const spinner = ora(`Creating ${chalk.bold(projectFullName)} on Expo`).start();
const spinner = ora(`Creating ${chalk.bold(projectFullName)} on EAS`).start();
try {
const id = await AppMutation.createAppAsync(graphqlClient, {
accountId: account.id,
projectName,
});
spinner.succeed(`Created ${chalk.bold(projectLink)} on Expo`);
spinner.succeed(`Created ${chalk.bold(projectLink)} on EAS`);
return id;
} catch (err) {
spinner.fail();
@@ -29,7 +29,7 @@ describe(getApplePlatformFromSdkRoot, () => {
// Update `getApplePlatformFromSdkRoot` to be compatible with new Apple Platforms
test('all enumerations work with the function', () => {
expect(Object.values(ApplePlatform).sort()).toEqual(
[ApplePlatform.IOS, ApplePlatform.TV_OS, ApplePlatform.MAC_OS].sort()
[ApplePlatform.IOS, ApplePlatform.TV_OS, ApplePlatform.MAC_OS, ApplePlatform.VISION_OS].sort()
);
});
test('existing SDKs work with the function', () => {
+30 -20
View File
@@ -634,7 +634,13 @@ export async function getBranchNameForCommandAsync({
}
if (autoFlag) {
return await getDefaultBranchNameAsync(vcsClient);
const defaultBranchNameFromVcs = await getDefaultBranchNameAsync(vcsClient);
if (!defaultBranchNameFromVcs) {
throw new Error(
'Must supply --branch or --channel for branch name as auto-detection of branch name via --auto is not supported when no VCS is present.'
);
}
return defaultBranchNameFromVcs;
} else if (nonInteractive) {
throw new Error('Must supply --channel, --branch or --auto when in non-interactive mode.');
} else {
@@ -658,7 +664,7 @@ export async function getBranchNameForCommandAsync({
type: 'text',
name: 'name',
message: 'No branches found. Provide a branch name:',
initial: await getDefaultBranchNameAsync(vcsClient),
initial: (await getDefaultBranchNameAsync(vcsClient)) ?? undefined,
validate: value => (value ? true : 'Branch name may not be empty.'),
});
branchName = name;
@@ -727,11 +733,11 @@ export const defaultPublishPlatforms: UpdatePublishPlatform[] = ['android', 'ios
export type RuntimeVersionInfo = {
runtimeVersion: string;
fingerprint: {
expoUpdatesRuntimeFingerprint: {
fingerprintSources: object[];
isDebugFingerprintSource: boolean;
} | null;
fingerprintHash: string | null;
expoUpdatesRuntimeFingerprintHash: string | null;
};
type FingerprintInfoGroup = {
@@ -791,11 +797,11 @@ async function getRuntimeVersionInfoForPlatformAsync({
env: Env | undefined;
}): Promise<{
runtimeVersion: string;
fingerprint: {
expoUpdatesRuntimeFingerprint: {
fingerprintSources: object[];
isDebugFingerprintSource: boolean;
} | null;
fingerprintHash: string | null;
expoUpdatesRuntimeFingerprintHash: string | null;
}> {
if (await isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync(projectDir)) {
try {
@@ -846,8 +852,8 @@ async function getRuntimeVersionInfoForPlatformAsync({
return {
runtimeVersion: resolvedRuntimeVersion,
fingerprint: null,
fingerprintHash: null,
expoUpdatesRuntimeFingerprint: null,
expoUpdatesRuntimeFingerprintHash: null,
};
}
@@ -869,13 +875,15 @@ export function getRuntimeToPlatformsAndFingerprintInfoMappingFromRuntimeVersion
platforms: runtimeVersionInfoObjects.map(
runtimeVersionInfoObject => runtimeVersionInfoObject.platform
),
fingerprint:
expoUpdatesRuntimeFingerprint:
runtimeVersionInfoObjects.map(
runtimeVersionInfoObject => runtimeVersionInfoObject.runtimeVersionInfo.fingerprint
runtimeVersionInfoObject =>
runtimeVersionInfoObject.runtimeVersionInfo.expoUpdatesRuntimeFingerprint
)[0] ?? null,
fingerprintHash:
expoUpdatesRuntimeFingerprintHash:
runtimeVersionInfoObjects.map(
runtimeVersionInfoObject => runtimeVersionInfoObject.runtimeVersionInfo.fingerprintHash
runtimeVersionInfoObject =>
runtimeVersionInfoObject.runtimeVersionInfo.expoUpdatesRuntimeFingerprintHash
)[0] ?? null,
};
}
@@ -893,20 +901,20 @@ export async function maybeCalculateFingerprintForRuntimeVersionInfoObjectsWitho
graphqlClient: ExpoGraphqlClient;
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping: (RuntimeVersionInfo & {
platforms: UpdatePublishPlatform[];
fingerprintSource: FingerprintSource | null;
expoUpdatesRuntimeFingerprintSource: FingerprintSource | null;
})[];
workflowsByPlatform: Record<Platform, Workflow>;
env: Env | undefined;
}): Promise<
(RuntimeVersionInfo & {
platforms: UpdatePublishPlatform[];
fingerprintSource: FingerprintSource | null;
expoUpdatesRuntimeFingerprintSource: FingerprintSource | null;
fingerprintInfoGroup: FingerprintInfoGroup;
})[]
> {
const runtimesToComputeFingerprintsFor =
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping.filter(
infoGroup => !infoGroup.fingerprintHash
infoGroup => !infoGroup.expoUpdatesRuntimeFingerprintHash
);
const fingerprintOptionsByRuntimeAndPlatform = new Map<string, FingerprintOptions>();
for (const infoGroup of runtimesToComputeFingerprintsFor) {
@@ -969,9 +977,11 @@ export async function maybeCalculateFingerprintForRuntimeVersionInfoObjectsWitho
infoGroup
): infoGroup is RuntimeVersionInfo & {
platforms: UpdatePublishPlatform[];
fingerprintSource: FingerprintSource;
fingerprintHash: string;
} => !!infoGroup.fingerprintHash && !!infoGroup.fingerprintSource
expoUpdatesRuntimeFingerprintSource: FingerprintSource;
expoUpdatesRuntimeFingerprintHash: string;
} =>
!!infoGroup.expoUpdatesRuntimeFingerprintHash &&
!!infoGroup.expoUpdatesRuntimeFingerprintSource
)
.map(infoGroup => {
const platform = infoGroup.platforms[0];
@@ -979,8 +989,8 @@ export async function maybeCalculateFingerprintForRuntimeVersionInfoObjectsWitho
...infoGroup,
fingerprintInfoGroup: {
[platform]: {
fingerprintHash: infoGroup.fingerprintHash,
fingerprintSource: infoGroup.fingerprintSource,
fingerprintHash: infoGroup.expoUpdatesRuntimeFingerprintHash,
fingerprintSource: infoGroup.expoUpdatesRuntimeFingerprintSource,
},
},
};
@@ -23,11 +23,11 @@ export async function resolveRuntimeVersionUsingCLIAsync({
cwd?: string;
}): Promise<{
runtimeVersion: string | null;
fingerprint: {
expoUpdatesRuntimeFingerprint: {
fingerprintSources: object[];
isDebugFingerprintSource: boolean;
} | null;
fingerprintHash: string | null;
expoUpdatesRuntimeFingerprintHash: string | null;
}> {
Log.debug('Using expo-updates runtimeversion:resolve CLI for runtime version resolution');
@@ -47,13 +47,13 @@ export async function resolveRuntimeVersionUsingCLIAsync({
return {
runtimeVersion: runtimeVersionResult.runtimeVersion ?? null,
fingerprint: runtimeVersionResult.fingerprintSources
expoUpdatesRuntimeFingerprint: runtimeVersionResult.fingerprintSources
? {
fingerprintSources: runtimeVersionResult.fingerprintSources,
isDebugFingerprintSource: useDebugFingerprintSource,
}
: null,
fingerprintHash: runtimeVersionResult.fingerprintSources
expoUpdatesRuntimeFingerprintHash: runtimeVersionResult.fingerprintSources
? runtimeVersionResult.runtimeVersion
: null,
};
@@ -75,19 +75,19 @@ export async function resolveRuntimeVersionAsync({
cwd?: string;
}): Promise<{
runtimeVersion: string | null;
fingerprint: {
expoUpdatesRuntimeFingerprint: {
fingerprintSources: object[];
isDebugFingerprintSource: boolean;
} | null;
fingerprintHash: string | null;
expoUpdatesRuntimeFingerprintHash: string | null;
} | null> {
if (!(await isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync(projectDir))) {
// fall back to the previous behavior (using the @expo/config-plugins eas-cli dependency rather
// than the versioned @expo/config-plugins dependency in the project)
return {
runtimeVersion: await Updates.getRuntimeVersionNullableAsync(projectDir, exp, platform),
fingerprint: null,
fingerprintHash: null,
expoUpdatesRuntimeFingerprint: null,
expoUpdatesRuntimeFingerprintHash: null,
};
}
@@ -1,47 +0,0 @@
import chalk from 'chalk';
import fs from 'node:fs';
import path from 'node:path';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { AccountUploadSessionType } from '../graphql/generated';
import { uploadAccountScopedFileAtPathToGCSAsync } from '../uploads';
import { formatBytes } from '../utils/files';
import { createProgressTracker } from '../utils/progress';
/**
* Uploads the `eas.json` file to GCS as account-scoped object.
* Used in workflows. Takes care of logging progress.
*/
export async function uploadAccountScopedEasJsonAsync({
graphqlClient,
accountId,
projectDir,
}: {
graphqlClient: ExpoGraphqlClient;
accountId: string;
projectDir: string;
}): Promise<{ easJsonBucketKey: string }> {
const easJsonFilePath = path.join(projectDir, 'eas.json');
const easJsonFileStat = await fs.promises.stat(easJsonFilePath);
if (easJsonFileStat.size > 1024 * 1024) {
throw new Error('eas.json is too big. Maximum allowed size is 1MB.');
}
const easJsonBucketKey = await uploadAccountScopedFileAtPathToGCSAsync(graphqlClient, {
accountId,
type: AccountUploadSessionType.WorkflowsProjectSources,
path: easJsonFilePath,
handleProgressEvent: createProgressTracker({
total: easJsonFileStat.size,
message: ratio =>
`Uploading eas.json to EAS (${formatBytes(easJsonFileStat.size * ratio)} / ${formatBytes(
easJsonFileStat.size
)})`,
completedMessage: (duration: string) => `Uploaded eas.json to EAS ${chalk.dim(duration)}`,
}),
});
return { easJsonBucketKey };
}
@@ -0,0 +1,49 @@
import chalk from 'chalk';
import fs from 'node:fs';
import path from 'node:path';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { AccountUploadSessionType } from '../graphql/generated';
import { uploadAccountScopedFileAtPathToGCSAsync } from '../uploads';
import { formatBytes } from '../utils/files';
import { createProgressTracker } from '../utils/progress';
/**
* Uploads a file to GCS as account-scoped object.
* Used in workflows. Takes care of logging progress.
* (Uses file name when mentioning file in logs.)
*/
export async function uploadAccountScopedFileAsync({
graphqlClient,
accountId,
filePath,
maxSizeBytes,
}: {
graphqlClient: ExpoGraphqlClient;
accountId: string;
filePath: string;
maxSizeBytes: number;
}): Promise<{ fileBucketKey: string }> {
const fileName = path.basename(filePath);
const fileStat = await fs.promises.stat(filePath);
if (fileStat.size > maxSizeBytes) {
throw new Error(`File is too big. Maximum allowed size is ${formatBytes(maxSizeBytes)}.`);
}
const fileBucketKey = await uploadAccountScopedFileAtPathToGCSAsync(graphqlClient, {
accountId,
type: AccountUploadSessionType.WorkflowsProjectSources,
path: filePath,
handleProgressEvent: createProgressTracker({
total: fileStat.size,
message: ratio =>
`Uploading ${fileName} to EAS (${formatBytes(fileStat.size * ratio)} / ${formatBytes(
fileStat.size
)})`,
completedMessage: (duration: string) => `Uploaded ${fileName} to EAS ${chalk.dim(duration)}`,
}),
});
return { fileBucketKey };
}
+8 -34
View File
@@ -1,6 +1,5 @@
import { App, RequestContext, Session, User } from '@expo/apple-utils';
import { RequestContext, Session, User } from '@expo/apple-utils';
import { Platform } from '@expo/eas-build-job';
import chalk from 'chalk';
import { sanitizeLanguage } from './utils/language';
import { getRequestContext } from '../../credentials/ios/appstore/authenticate';
@@ -85,38 +84,13 @@ async function createAppStoreConnectAppAsync(
);
}
let app: App | null = null;
try {
app = await ensureAppExistsAsync(userAuthCtx, {
name: appName,
language,
companyName,
bundleIdentifier: bundleId,
sku,
});
} catch (error: any) {
if (
// Name is invalid
error.message.match(
/App Name contains certain Unicode(.*)characters that are not permitted/
) ||
// UnexpectedAppleResponse: An attribute value has invalid characters. - App Name contains certain Unicode symbols, emoticons, diacritics, special characters, or private use characters that are not permitted.
// Name is taken
error.message.match(/The App Name you entered is already being used/)
// UnexpectedAppleResponse: The provided entity includes an attribute with a value that has already been used on a different account. - The App Name you entered is already being used. If you have trademark rights to
// this name and would like it released for your use, submit a claim.
) {
Log.addNewLineIfNone();
Log.warn(
`Change the name in your app config, or use a custom name with the ${chalk.bold(
'--app-name'
)} flag`
);
Log.newLine();
}
throw error;
}
const app = await ensureAppExistsAsync(userAuthCtx, {
name: appName,
language,
companyName,
bundleIdentifier: bundleId,
sku,
});
return {
ascAppIdentifier: app.id,
@@ -42,13 +42,13 @@ function warnAboutServiceOutage(service: StatuspageServiceFragment): void {
if (service.incidents.length > 0) {
const [currentIncident] = service.incidents;
Log.warn(`Reason: ${currentIncident.name}`);
Log.warn(`Reason: ${currentIncident.name}.`);
}
Log.warn(
`All information on service status and incidents available at ${link(
'https://status.expo.dev/'
)}.`
)}`
);
Log.newLine();
}
+29 -15
View File
@@ -1,4 +1,4 @@
import { get as getEnv } from '@expo/env';
import { parseProjectEnv } from '@expo/env';
import { Gzip, GzipOptions } from 'minizlib';
import { HashOptions, createHash, randomBytes } from 'node:crypto';
import fs, { createWriteStream } from 'node:fs';
@@ -98,6 +98,11 @@ export interface Manifest {
env: Record<string, string | undefined>;
}
export interface CreateManifestResult {
conflictingVariableNames: string[] | undefined;
manifest: Manifest;
}
interface CreateManifestParams {
projectId: string;
projectDir: string;
@@ -108,23 +113,32 @@ interface CreateManifestParams {
export async function createManifestAsync(
params: CreateManifestParams,
graphqlClient: ExpoGraphqlClient
): Promise<Manifest> {
let env: Record<string, string | undefined>;
): Promise<CreateManifestResult> {
// Resolve .env file variables
const { env } = parseProjectEnv(params.projectDir, { mode: 'production' });
// Maybe load EAS Environment Variables (based on `--environment` arg)
let conflictingVariableNames: string[] | undefined;
if (params.environment) {
env = Object.fromEntries(
(
await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(graphqlClient, {
appId: params.projectId,
environment: params.environment,
})
).map(variable => [variable.name, variable.value ?? undefined])
const loadedVariables = await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(
graphqlClient,
{
appId: params.projectId,
environment: params.environment,
}
);
} else {
// NOTE: This is required for the .env resolution
process.env.NODE_ENV = 'production';
env = getEnv(params.projectDir).env;
// Load EAS Env vars into `env` object, keeping track of conflicts
conflictingVariableNames = [];
for (const variable of loadedVariables) {
if (variable.value != null) {
if (env[variable.name] != null) {
conflictingVariableNames.push(variable.name);
}
env[variable.name] = variable.value;
}
}
}
return { env };
const manifest: Manifest = { env };
return { conflictingVariableNames, manifest };
}
interface WorkerFileEntry {
+30 -7
View File
@@ -4,6 +4,7 @@ import mime from 'mime';
import { Gzip } from 'minizlib';
import fetch, { Headers, HeadersInit, RequestInit, Response } from 'node-fetch';
import fs, { createReadStream } from 'node:fs';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import promiseRetry from 'promise-retry';
@@ -28,6 +29,24 @@ const isCompressible = (contentType: string | null, size: number): boolean => {
}
};
const getContentTypeAsync = async (filePath: string): Promise<string | null> => {
let contentType = mime.getType(path.basename(filePath));
if (!contentType) {
const fileContent = await readFile(filePath, 'utf-8');
try {
// check if file is valid JSON without an extension, e.g. for the apple app site association file
const parsedData = JSON.parse(fileContent);
if (parsedData) {
contentType = 'application/json';
}
} catch {}
}
return contentType;
};
export interface UploadParams extends Omit<RequestInit, 'signal' | 'body'> {
filePath: string;
compress?: boolean;
@@ -70,14 +89,15 @@ export async function uploadAsync(params: UploadParams): Promise<UploadResult> {
headers: headersInit,
...requestInit
} = params;
const stat = await fs.promises.stat(params.filePath);
const stat = await fs.promises.stat(filePath);
if (stat.size > MAX_UPLOAD_SIZE) {
throw new Error(
`Upload of "${params.filePath}" aborted: File size is greater than the upload limit (>500MB)`
`Upload of "${filePath}" aborted: File size is greater than the upload limit (>500MB)`
);
}
const contentType = mime.getType(path.basename(params.filePath));
const contentType = await getContentTypeAsync(filePath);
return await promiseRetry(
async retry => {
const headers = new Headers(headersInit);
@@ -109,20 +129,23 @@ export async function uploadAsync(params: UploadParams): Promise<UploadResult> {
return retry(error);
}
const getErrorMessageAsync = async (): Promise<string> => {
const body = await response.json().catch(() => null);
return body?.error ?? `Upload of "${filePath}" failed: ${response.statusText}`;
};
if (
response.status === 408 ||
response.status === 409 ||
response.status === 429 ||
(response.status >= 500 && response.status <= 599)
) {
const message = `Upload of "${filePath}" failed: ${response.statusText}`;
const text = await response.text().catch(() => null);
return retry(new Error(text ? `${message}\n${text}` : message));
return retry(new Error(await getErrorMessageAsync()));
} else if (response.status === 413) {
const message = `Upload of "${filePath}" failed: File size exceeded the upload limit`;
throw new Error(message);
} else if (!response.ok) {
throw new Error(`Upload of "${filePath}" failed: ${response.statusText}`);
throw new Error(await getErrorMessageAsync());
}
return {
+2 -3
View File
@@ -1,16 +1,15 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"declaration": true,
"importHelpers": true,
"module": "commonjs",
"esModuleInterop": true,
"moduleResolution": "node",
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strict": true,
"target": "ES2022",
"outDir": "build",
"rootDir": "src",
"typeRoots": ["../../ts-declarations", "../../node_modules/@types", "./node_modules/@types"]
+3 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@expo/eas-json",
"description": "A library for interacting with eas.json",
"version": "14.0.3",
"version": "14.5.0",
"author": "Expo <[email protected]>",
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@babel/code-frame": "7.23.5",
"@expo/eas-build-job": "1.0.151",
"@expo/eas-build-job": "1.0.165",
"chalk": "4.1.2",
"env-string": "1.0.1",
"fs-extra": "11.2.0",
@@ -18,6 +18,7 @@
"tslib": "2.4.1"
},
"devDependencies": {
"@tsconfig/node18": "18.2.4",
"@types/babel__code-frame": "7.0.3",
"@types/fs-extra": "11.0.4",
"memfs": "3.4.13",
@@ -129,6 +129,33 @@ test('valid eas.json with specified environment', async () => {
});
});
test('valid eas.json with specified keystoreName', async () => {
const keystoreName = 'this-is-a-keystore-name';
await fs.writeJson('/project/eas.json', {
build: {
development: {
android: {
credentialsSource: 'remote',
keystoreName,
},
},
},
});
const accessor = EasJsonAccessor.fromProjectPath('/project');
const androidProfile = await EasJsonUtils.getBuildProfileAsync(
accessor,
Platform.ANDROID,
'development'
);
expect(androidProfile).toEqual(
expect.objectContaining({
keystoreName,
})
);
});
test('valid eas.json with top level withoutCredentials property', async () => {
await fs.writeJson('/project/eas.json', {
build: {
@@ -274,37 +301,124 @@ test('valid profile for internal distribution on Android', async () => {
});
});
test('valid profile extending other profile', async () => {
test('valid profile with extension chain not exceeding 5', async () => {
await fs.writeJson('/project/eas.json', {
build: {
base: {
node: '12.0.0',
},
extension: {
extension1: {
extends: 'base',
distribution: 'internal',
node: '13.0.0',
},
extension2: {
extends: 'extension1',
distribution: 'internal',
node: '14.0.0',
},
extension3: {
extends: 'extension2',
distribution: 'internal',
node: '15.0.0',
},
extension4: {
extends: 'extension3',
distribution: 'internal',
node: '16.0.0',
},
},
});
const accessor = EasJsonAccessor.fromProjectPath('/project');
const baseProfile = await EasJsonUtils.getBuildProfileAsync(accessor, Platform.ANDROID, 'base');
const extendedProfile = await EasJsonUtils.getBuildProfileAsync(
const extendedProfile1 = await EasJsonUtils.getBuildProfileAsync(
accessor,
Platform.ANDROID,
'extension'
'extension1'
);
const extendedProfile2 = await EasJsonUtils.getBuildProfileAsync(
accessor,
Platform.ANDROID,
'extension2'
);
const extendedProfile3 = await EasJsonUtils.getBuildProfileAsync(
accessor,
Platform.ANDROID,
'extension3'
);
const extendedProfile4 = await EasJsonUtils.getBuildProfileAsync(
accessor,
Platform.ANDROID,
'extension4'
);
expect(baseProfile).toEqual({
distribution: 'store',
credentialsSource: 'remote',
node: '12.0.0',
});
expect(extendedProfile).toEqual({
expect(extendedProfile1).toEqual({
distribution: 'internal',
credentialsSource: 'remote',
node: '13.0.0',
});
expect(extendedProfile2).toEqual({
distribution: 'internal',
credentialsSource: 'remote',
node: '14.0.0',
});
expect(extendedProfile3).toEqual({
distribution: 'internal',
credentialsSource: 'remote',
node: '15.0.0',
});
expect(extendedProfile4).toEqual({
distribution: 'internal',
credentialsSource: 'remote',
node: '16.0.0',
});
});
test('valid profile with extension chain exceeding 5 - too long', async () => {
await fs.writeJson('/project/eas.json', {
build: {
base: {
node: '12.0.0',
},
extension1: {
extends: 'base',
distribution: 'internal',
node: '13.0.0',
},
extension2: {
extends: 'extension1',
distribution: 'internal',
node: '14.0.0',
},
extension3: {
extends: 'extension2',
distribution: 'internal',
node: '15.0.0',
},
extension4: {
extends: 'extension3',
distribution: 'internal',
node: '16.0.0',
},
extension5: {
extends: 'extension4',
distribution: 'internal',
node: '17.0.0',
},
},
});
const accessor = EasJsonAccessor.fromProjectPath('/project');
await expect(
EasJsonUtils.getBuildProfileAsync(accessor, Platform.ANDROID, 'extension5')
).rejects.toThrow(
'Too long chain of profile extensions, make sure "extends" keys do not make a cycle'
);
});
test('valid profile extending other profile with platform specific envs', async () => {
@@ -169,7 +169,7 @@ test('ios config with ascApiKey fields set to env var', async () => {
}
});
test('valid profile extending other profile', async () => {
test('valid profile with extension chain not exceeding 5', async () => {
await fs.writeJson('/project/eas.json', {
submit: {
base: {
@@ -179,7 +179,7 @@ test('valid profile extending other profile', async () => {
appleTeamId: 'AB32CZE81F',
},
},
extension: {
extension1: {
extends: 'base',
ios: {
appleTeamId: 'AB32CZE81F',
@@ -188,15 +188,57 @@ test('valid profile extending other profile', async () => {
ascApiKeyId: 'AB32CZE81F',
},
},
extension2: {
extends: 'extension1',
ios: {
appleTeamId: 'AB32CZE81E',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CZE81E',
},
},
extension3: {
extends: 'extension2',
ios: {
appleTeamId: 'AB32CZE81D',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CZE81D',
},
},
extension4: {
extends: 'extension3',
ios: {
appleTeamId: 'AB32CZE81C',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CZE81C',
},
},
},
});
const accessor = EasJsonAccessor.fromProjectPath('/project');
const baseProfile = await EasJsonUtils.getSubmitProfileAsync(accessor, Platform.IOS, 'base');
const extendedProfile = await EasJsonUtils.getSubmitProfileAsync(
const extendedProfile1 = await EasJsonUtils.getSubmitProfileAsync(
accessor,
Platform.IOS,
'extension'
'extension1'
);
const extendedProfile2 = await EasJsonUtils.getSubmitProfileAsync(
accessor,
Platform.IOS,
'extension2'
);
const extendedProfile3 = await EasJsonUtils.getSubmitProfileAsync(
accessor,
Platform.IOS,
'extension3'
);
const extendedProfile4 = await EasJsonUtils.getSubmitProfileAsync(
accessor,
Platform.IOS,
'extension4'
);
expect(baseProfile).toEqual({
language: 'en-US',
@@ -204,7 +246,7 @@ test('valid profile extending other profile', async () => {
ascAppId: '1223423523',
appleTeamId: 'AB32CZE81F',
});
expect(extendedProfile).toEqual({
expect(extendedProfile1).toEqual({
language: 'en-US',
appleId: '[email protected]',
ascAppId: '1223423523',
@@ -213,6 +255,99 @@ test('valid profile extending other profile', async () => {
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CZE81F',
});
expect(extendedProfile2).toEqual({
language: 'en-US',
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'AB32CZE81E',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CZE81E',
});
expect(extendedProfile3).toEqual({
language: 'en-US',
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'AB32CZE81D',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CZE81D',
});
expect(extendedProfile4).toEqual({
language: 'en-US',
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'AB32CZE81C',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CZE81C',
});
});
test('valid profile with extension chain exceeding 5 - too long', async () => {
await fs.writeJson('/project/eas.json', {
submit: {
base: {
ios: {
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'AB32CZE81F',
},
},
extension1: {
extends: 'base',
ios: {
appleTeamId: 'AB32CZE81F',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CZE81F',
},
},
extension2: {
extends: 'extension1',
ios: {
appleTeamId: 'AB32CZE81E',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CZE81E',
},
},
extension3: {
extends: 'extension2',
ios: {
appleTeamId: 'AB32CZE81D',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CZE81D',
},
},
extension4: {
extends: 'extension3',
ios: {
appleTeamId: 'AB32CZE81C',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CZE81C',
},
},
extension5: {
extends: 'extension4',
ios: {
appleTeamId: 'AB32CZE81B',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CZE81B',
},
},
},
});
const accessor = EasJsonAccessor.fromProjectPath('/project');
await expect(
EasJsonUtils.getSubmitProfileAsync(accessor, Platform.IOS, 'extension5')
).rejects.toThrow(
'Too long chain of profile extensions, make sure "extends" keys do not make a cycle'
);
});
test('ios config with with invalid appleId', async () => {
+8
View File
@@ -101,6 +101,14 @@ const AndroidBuildProfileSchema = PlatformBuildProfileSchema.concat(
Joi.boolean(),
Joi.string().valid('version', 'versionCode')
),
keystoreName: Joi.when('credentialsSource', {
is: 'remote',
then: Joi.string(),
otherwise: Joi.forbidden().messages({
'any.unknown': 'keystoreName is not allowed when credentialsSource is not remote',
}),
}),
})
);
+2
View File
@@ -93,6 +93,8 @@ export interface AndroidBuildProfile extends PlatformBuildProfile {
// versions
autoIncrement?: AndroidVersionAutoIncrement;
keystoreName?: string;
}
export interface IosBuildProfile extends PlatformBuildProfile {
+1 -1
View File
@@ -47,7 +47,7 @@ function resolveProfile<T extends Platform>({
profileName: string;
depth?: number;
}): SubmitProfile<T> | undefined {
if (depth >= 2) {
if (depth >= 5) {
throw new Error(
'Too long chain of profile extensions, make sure "extends" keys do not make a cycle'
);
+3 -4
View File
@@ -1,19 +1,18 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"declaration": true,
"importHelpers": true,
"module": "commonjs",
"esModuleInterop": true,
"moduleResolution": "node",
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strict": true,
"target": "ES2022",
"outDir": "build",
"rootDir": "src",
"typeRoots": ["../../node_modules/@types", "../../ts-declarations", "./node_modules/@types"]
},
"include": ["src/**/*"]
}
}
Generated
+64 -43
View File
@@ -1359,10 +1359,10 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b"
integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==
"@expo/[email protected].4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@expo/apple-utils/-/apple-utils-2.1.4.tgz#387a8d1e5c5cd064416f0b9ebea12006e4291922"
integrity sha512-VFbfuljSDvjrgDYngs7tE1xNYpJOHpqOSdTFHF4d1Bl5jGcDE3KAfs0YzRVpVoEJ9blQAetInnk5R4YmUWyl8Q==
"@expo/[email protected].5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@expo/apple-utils/-/apple-utils-2.1.5.tgz#a947697f10a7ab0f07f2d2fd075efdae9825e85e"
integrity sha512-Yy4BWVoViYePwQk8rqvhosolS5MTTMgcsl9Aq3rzJrd1BH8C1V/h6N5xelqo+/V+ww2k8Bom7UTE1NlGcKHxRw==
"@expo/bunyan@^4.0.0":
version "4.0.0"
@@ -1465,20 +1465,20 @@
slugify "^1.3.4"
sucrase "3.35.0"
"@expo/[email protected]1":
version "1.0.151"
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-1.0.151.tgz#25055a82a67b33b57b0c1308156dca967f9b1716"
integrity sha512-9RsCCX5Sct23OzKqZ7adliBqm1sBl7sCBcdBvFAwyIUpWmQAaDhGFRm6hA+9ti7kvgJlHK+Mxm7n6BmVUMHjDg==
"@expo/[email protected]65":
version "1.0.165"
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-1.0.165.tgz#71ec010330d23bb6f6c257ce1ca7c0258e8feaaa"
integrity sha512-l4bPVRmc6SlrgiC7AOid6s+o3kyP47x17cGIPQ0d4FOaOxu6pP3bPZ3BuPjts1mfvrKg/c/Phe1fJ1nnNlo3Xw==
dependencies:
"@expo/logger" "1.0.117"
joi "^17.13.1"
semver "^7.6.2"
zod "^3.23.8"
"@expo/env@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@expo/env/-/env-0.4.0.tgz#1ff3a15084566d12ca92cb67e5b0a9796a9f0aa7"
integrity sha512-g2JYFqck3xKIwJyK+8LxZ2ENZPWtRgjFWpeht9abnKgzXVXBeSNECFBkg+WQjQocSIdxXhEWM6hz4ZAe7Tc4ng==
"@expo/env@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@expo/env/-/env-1.0.0.tgz#8e22894fae04ba6dc41fe0f1351a4973d410273a"
integrity sha512-43R5rxlaLJQOWunyfnQEpgHJCsSLCUWDoU/wJNABEYkHdt1Sjrlf+ieH3MG1f/RZYE+1Wss3sYiO9oPps1aMNg==
dependencies:
chalk "^4.0.0"
debug "^4.3.4"
@@ -1528,14 +1528,12 @@
"@types/bunyan" "^1.8.11"
bunyan "^1.8.15"
"@expo/multipart-body-parser@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@expo/multipart-body-parser/-/multipart-body-parser-1.1.0.tgz#ba402b3047cf14ef17b4d785ec030dd6474ae6e4"
integrity sha512-XOaS79wFIJgx0J7oUzRb+kZsnZmFqGpisu0r8RPO3b0wjbW7xpWgiXmRR4RavKeGiVAPauZOi4vad7cJ3KCspg==
"@expo/multipart-body-parser@2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@expo/multipart-body-parser/-/multipart-body-parser-2.0.0.tgz#dcd2034f3bbc29b9a6fa6ff31e5ba0caea0a2b38"
integrity sha512-yS/wsqlj0d8ZKETEN7ro3dZtjdMhpte8wp+xUzjUQC3jizxcE0E62xgvGquJObiYUMGoCF5qRYr2t78STPEaSw==
dependencies:
dicer "^0.3.1"
nullthrows "^1.1.1"
structured-headers "^0.4.1"
multipasta "^0.2.5"
"@expo/[email protected]":
version "2.1.4"
@@ -1545,10 +1543,10 @@
"@expo/spawn-async" "^1.7.2"
exec-async "^2.2.0"
"@expo/package-manager@1.6.1":
version "1.6.1"
resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-1.6.1.tgz#ab845238dec10bb48bca2b90e060dfe8c1525602"
integrity sha512-4rT46wP/94Ll+CWXtFKok1Lbo9XncSUtErFOo/9/3FVughGbIfdG4SKZOAWIpr9wxwEfkyhHfAP9q71ONlWODw==
"@expo/package-manager@1.7.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-1.7.0.tgz#6d98090fdfa9d31ab885acb012d6615741594fd1"
integrity sha512-yWn5TIjd42wLHZjNtdZkvCkcxqUGxlI4YHb+bQmgm3tWZ8aBHnLhPb0rgU8+hVHCofmRvVUXfVZv8Uh+kkLXgw==
dependencies:
"@expo/json-file" "^9.0.0"
"@expo/spawn-async" "^1.7.2"
@@ -1647,12 +1645,12 @@
dependencies:
cross-spawn "^7.0.3"
"@expo/[email protected]51":
version "1.0.151"
resolved "https://registry.yarnpkg.com/@expo/steps/-/steps-1.0.151.tgz#fde4655b87ace3add306ca81cf544de741ec56f4"
integrity sha512-4DJD2DORbR6HPUB62tsz3W+CnO3X22Z61jui6iUrF1SjFLrYDkURmmVaiVv8tfbWhTrTvwMQR8cndN3rNqSBqQ==
"@expo/[email protected]66":
version "1.0.166"
resolved "https://registry.yarnpkg.com/@expo/steps/-/steps-1.0.166.tgz#b606ed72def361860ef5bfd3bd2df601112612af"
integrity sha512-dzEFjbQr4ji65XeSrUgesE+lnnhJYMNdXMZV5PRb19mMGTYYbyeT20+RvSo1a+TOPdO6sPdSBZqfiQ7P4mGBmQ==
dependencies:
"@expo/eas-build-job" "1.0.151"
"@expo/eas-build-job" "1.0.165"
"@expo/logger" "1.0.117"
"@expo/spawn-async" "^1.7.2"
arg "^5.0.2"
@@ -3579,6 +3577,11 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e"
integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==
"@tsconfig/[email protected]":
version "18.2.4"
resolved "https://registry.yarnpkg.com/@tsconfig/node18/-/node18-18.2.4.tgz#094efbdd70f697d37c09f34067bf41bc4a828ae3"
integrity sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==
"@tufjs/[email protected]":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz#eade9fd1f537993bc1f0949f3aea276ecc4fab31"
@@ -6124,13 +6127,6 @@ dezalgo@^1.0.0:
asap "^2.0.0"
wrappy "1"
dicer@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.1.tgz#abf28921e3475bc5e801e74e0159fd94f927ba97"
integrity sha512-ObioMtXnmjYs3aRtpIJt9rgQSPCIhKVkFPip+E9GUDyWl8N435znUxK/JfNwGZJ2wnn5JKQ7Ly3vOK5Q5dylGA==
dependencies:
streamsearch "^1.1.0"
diff-sequences@^29.2.0:
version "29.2.0"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.2.0.tgz#4c55b5b40706c7b5d2c5c75999a50c56d214e8f6"
@@ -10750,6 +10746,11 @@ [email protected], multimatch@^5.0.0:
arrify "^2.0.1"
minimatch "^3.0.4"
multipasta@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/multipasta/-/multipasta-0.2.5.tgz#fb3564c4c619b73f98e72e2a4bbd4f7fba153edf"
integrity sha512-c8eMDb1WwZcE02WVjHoOmUVk7fnKU/RmUcosHACglrWAuPQsEJv+E8430sXj6jNc1jHw0zrS16aCjQh4BcEb4A==
[email protected]:
version "0.0.8"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
@@ -13232,7 +13233,16 @@ string-length@^5.0.1:
char-regex "^2.0.0"
strip-ansi "^7.0.1"
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -13388,7 +13398,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -13409,6 +13419,13 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
@@ -13474,11 +13491,6 @@ [email protected], strong-log-transformer@^2.1.0:
minimist "^1.2.0"
through "^2.3.4"
structured-headers@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/structured-headers/-/structured-headers-0.4.1.tgz#77abd9410622c6926261c09b9d16cf10592694d1"
integrity sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==
[email protected]:
version "0.9.18"
resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.18.tgz#bcf02320c911fbadb054f7f928e51c6041a37b97"
@@ -14620,7 +14632,16 @@ wordwrap@^1.0.0:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", [email protected], wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
[email protected], wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==