Compare commits

..
Author SHA1 Message Date
Quinlan JungandWill Schurman d2889571fe Update packages/eas-cli/src/project/projectUtils.ts
Co-authored-by: Will Schurman <[email protected]>
2023-06-30 16:57:29 -07:00
Quinlan Jung 224a5b288c update CHANGELOG.md 2023-06-30 23:35:11 +00:00
Quinlan Jung 16629e1ab5 remove draft function 2023-06-30 16:30:13 -07:00
Quinlan Jung d5a744b938 [eas-cli] remove useClassicUpdates upon configuration 2023-06-30 16:17:10 -07:00
Quinlan Jung 8f39b303c9 [eas-cli] bump config dependencies 2023-06-30 16:16:00 -07:00
c199173704 [eas-cli] Added alias -m for --message in eas update (#1904)
* added alias -m for --message

* align with the other messages

* updated README

* moving change in update/republish to a seperate PR

* removed README comment

* update CHANGELOG.md

---------

Co-authored-by: Steven Pu <[email protected]>
Co-authored-by: Steven Pu <[email protected]>
2023-06-28 11:36:18 -07:00
800098b958 [eas-cli] Added alias -m for --message in update/republish (#1905)
* added alias in republish

* reverted change in README

* update CHANGELOG.md

---------

Co-authored-by: Steven Pu <[email protected]>
Co-authored-by: Steven Pu <[email protected]>
2023-06-28 11:16:07 -07:00
Keith Kurak 146468914f Show build profile on eas build:run (#1901)
* [eas-cli] Add build profile to build:run list items

* say "simulator" or "emulator" in warning b/c we know which one

* update CHANGELOG.md
2023-06-27 08:40:57 -04:00
Szymon Dziedzic 0e48417983 [eas-cli] print better error message when uploading the project archive tarball fails (#1897)
* [eas-cli] print better error message when uplaoding tarball failed

* use better error class

* update CHANGELOG.md
2023-06-23 11:48:08 +02:00
Wojciech Kozyra cf61f585e6 Update CODEOWNERS (#1896) 2023-06-22 13:52:23 +02:00
Quinlan Jung cc700ae263 [eas-cli] better runtime output (#1894)
* [eas-cli] better runtime output

* update CHANGELOG.md
2023-06-21 12:47:33 -07:00
Expo CI 4e94c60c7f update CHANGELOG.md 2023-06-20 09:24:16 +00:00
Szymon Dziedzic 37f8e117e1 v3.14.0 2023-06-20 11:00:45 +02:00
Szymon Dziedzic 46095d2da4 upgrade eas-cli-local-build-plugin to 1.0.24 2023-06-20 11:00:20 +02:00
Szymon Dziedzic 6c01856617 [ENG-7183][eas-cli] validate that expo-updates are in project dependencies when somebody tries to run a build with channel set (#1885)
* [eas-cli] validate that `expo-updates` are in project dependencies when somebody tries to run a build with channel set

* apply suggested changes

* remove unnecessary change

* fix warning

* improve validation logic

* improve insatlled message

* warn when expo-updates are in dev dependencies for update command

* impreve warn messsages

* improve warning messages

* one more warn message improvement

* update CHANGELOG.md
2023-06-20 10:52:21 +02:00
Wojciech Kozyra f8cd50e0ed [eas-cli] Add minimal validation for app config returned by @expo/config (#1893)
* [eas-cli] Add minimal validation for app config returned by @expo/config

* update CHANGELOG.md
2023-06-20 10:41:52 +02:00
lzkb fd0a129a4f [eas-cli] change sso flag display (#1890) 2023-06-19 13:08:27 -07:00
Quinlan Jung c44e07bfeb [eas-cli] update:configure less verbose output (#1888)
* [eas-cli] less verbose output

* update CHANGELOG.md

* pr feedback
2023-06-19 11:12:37 -07:00
Piotr Szeremeta 951583e6ea [eas-cli][ENG-8763] Select default android keystore (#1889)
* [eas-cli][ENG-8763] Select default android keystore

- Add option to change default keystore from CLI (previously available only from dashboard)
- Streamline setting up new keystore

Instead of overwriting existing keystore, new one is created every time. Also, option to immediately set it as default is added, since it is also easier to change it from CLI.

* update CHANGELOG.md
2023-06-19 16:01:14 +02:00
Quinlan Jung 3f9247ca84 [eas-cli] build:configure add channels to eas.json if using eas updates (#1887)
* [eas-cli] configure build: amend eas.json for eas updates

* update CHANGELOG.md

* pr feedback
2023-06-17 16:14:23 -07:00
lzkb c38638e94e [eas-cli] Changes for SSO user type and SSO login. (#1875)
* [eas-cli] Changes for SSO user type and SSO login.
2023-06-16 10:44:13 -07:00
Brent VatneandQuinlan Jung 3b46466a9d [eas-cli] Initialize app.json if it does not exist, add expo key if missing (#1881)
Co-authored-by: Quinlan Jung <[email protected]>
2023-06-15 19:54:31 -07:00
Brent Vatne 806085cbf1 [eas-cli] Ensure that re-thrown error includes original stack (#1882) 2023-06-15 19:45:00 -07:00
Quinlan Jung 398b41cc81 [eas-cli] updates: error gracefully if no git repo (#1884)
* [eas-cli] updates: error gracefully if no git repo

* update CHANGELOG.md
2023-06-15 12:54:37 -07:00
Quinlan Jung d23a97238b [eas-cli] error gracefully if expo pkg not installed (#1883)
* [eas-cli] error gracefully if expo pkg not installed

* update CHANGELOG.md

* remove canadian language
2023-06-15 12:52:29 -07:00
Szymon Dziedzic 5b6f312ccb [eas-cli] bump @expo/steps version (#1886) 2023-06-15 15:20:54 +02:00
Aman MittalandWojciech Kozyra 2bcab0f2b4 [eas-cli] Update eas build:configure command to show link of eas.json when generated (#1878)
Co-authored-by: Wojciech Kozyra <[email protected]>
2023-06-14 16:15:53 +05:30
Stanisław Chmiela b90ce63067 Remove direct @types/bunyan dependency (#1880)
<!-- 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

Cleanup after https://github.com/expo/eas-build/pull/233.

# How

Upgraded `@expo/steps` package. It is the only `eas-build` package in here.

# Test Plan

It's a type-only change so CI success will be enough.
2023-06-13 17:05:37 +02:00
Szymon Dziedzic be02aced9d [eas-cli] update @expo/steps (#1876)
* [eas-cli] update `@expo/steps`

* update lockfile

* install @types/bunyan
2023-06-13 13:48:49 +02:00
Evan Bacon f788b081c6 feat(eas-cli): add new public entitlements from wwdc23 (#1870)
* feat(eas-cli): add new public entitlements from wwdc23

* update CHANGELOG.md
2023-06-07 13:29:01 -05:00
Expo CI 654f973d0e update CHANGELOG.md 2023-06-05 08:34:53 +00:00
Wojciech Kozyra 2e614cbde9 v3.13.3 2023-06-05 10:27:49 +02:00
Wojciech Kozyra b7150163d9 upgrade eas-cli-local-build-plugin to 1.0.18 2023-06-05 10:27:37 +02:00
Will Schurman 0922121e84 [ENG-8790][eas-cli] Fix updates synchronization of native files to include strings.xml (#1865) 2023-06-02 11:46:26 -07:00
Will Schurman 4ace7d20fb [eas-cli] Print assets that timed out during upload or processing (#1849) 2023-05-30 12:26:27 -07:00
Dominik Sokal 4ae9aed8f6 update CODEOWNERS 2023-05-30 09:50:59 +02:00
Dominik Sokal 54d6079406 print graphql error message (#1862)
* fix tests

* update CHANGELOG.md
2023-05-30 09:22:42 +02:00
Expo CI f08adad1e7 update CHANGELOG.md 2023-05-26 08:57:33 +00:00
Radoslaw Krzemien 346e47448a v3.13.2 2023-05-26 10:49:00 +02:00
Radosław Krzemień b0fde7b465 [eas-cli] [ENG-8506] Handle pending cancel build status in cli (#1855)
* [eas-cli] Update gql schema

Updated the graphql schema based on the changes in locally run www instance

See: https://linear.app/expo/issue/ENG-8506/attempt-to-upload-xcode-logs-when-build-fails-for-unexpected-reason-or

* [eas-cli] Handle new status

Updated the code to handle the pending cancel build status

See: https://linear.app/expo/issue/ENG-8506/attempt-to-upload-xcode-logs-when-build-fails-for-unexpected-reason-or

* [eas-cli] Regenerate gql schema

Merged main in www and cli and regenerated schema

See: https://linear.app/expo/issue/ENG-8506/attempt-to-upload-xcode-logs-when-build-fails-for-unexpected-reason-or

* [eas-cli] Fix failing test

Updating gql schema added a new field `App` in some places which made the test fail

See: https://linear.app/expo/issue/ENG-8506/attempt-to-upload-xcode-logs-when-build-fails-for-unexpected-reason-or

* [eas-cli] Reformat conditionals

Updated the way multiple status checks are done after review

See: https://linear.app/expo/issue/ENG-8506/attempt-to-upload-xcode-logs-when-build-fails-for-unexpected-reason-or

* update CHANGELOG.md

* [eas-cli] Regenerate gql

After merging changes in www back to main regenerated the graphql schema to make sure it is compliant with the current version

See: https://linear.app/expo/issue/ENG-8506/attempt-to-upload-xcode-logs-when-build-fails-for-unexpected-reason-or
2023-05-26 10:40:10 +02:00
Will Schurman d42efadf51 [eas-cli] Refactor getExpoConfig to remove dangerous default (#1857) 2023-05-25 08:58:34 -07:00
Expo CI e302390324 update CHANGELOG.md 2023-05-24 12:26:56 +00:00
Szymon Dziedzic 3a239f8cf9 v3.13.1 2023-05-24 14:18:36 +02:00
Szymon Dziedzic b5bc8a8f6e [eas-cli] make large resource class only available for paid tier users (#1848)
* [eas-cli] make large resource class only available for paid users

* update eas.schema.json

* Revert "update eas.schema.json"

This reverts commit 319d998b76a4c6c87d137174d6cf9f90d64b3886.

* apply suggested changes

* update CHANGELOG.md
2023-05-24 14:02:31 +02:00
Will Schurman 6cbff89b75 [eas-cli] Improve types for update display (#1851) 2023-05-23 10:46:19 -07:00
Piotr Szeremeta 118c008917 Validate CFBundleShortVersionString (#1846)
* [eas-cli][bugfix] Fix ios version-test

Tests were passing, but were not testing the right thing, essentially always defaulting to 'managed' workflow. `projectDir` should be set to the same value as EAS_PROJECT_ROOT.

* [eas-cli][ENG-8571] Validate CFBundleShortVersionString

* update CHANGELOG.md
2023-05-22 09:22:11 +02:00
Quinlan Jung 3b24d37f3c [eas-cli] remove asp prompt (#1843)
* [eas-cli] remove asp prompt

* update CHANGELOG.md
2023-05-17 11:05:06 -07:00
Szymon Dziedzic df425201b3 fix changelog 2023-05-17 15:47:14 +02:00
Piotr Szeremeta 9636507130 [eas-cli][ENG-8528] Don't print all selected devices. (#1840)
* [eas-cli][ENG-8528] Don't print all selected devices.

* update CHANGELOG.md
2023-05-17 15:04:49 +02:00
Szymon Dziedzic 2dd50ad21a v3.13.0 2023-05-17 14:28:30 +02:00
Szymon Dziedzic 30441b3db4 [eas-cli][eas-json] update @expo/steps and @expo/eas-build-job version (#1845) 2023-05-17 12:47:57 +02:00
Dominik Sokal 789c314a8d don't generate ios credentials for non-signable targets (#1835)
* don't generate ios credentials for non-signable targets

* upgrade @expo/config-plugins

* update CHANGELOG.md
2023-05-17 09:30:30 +02:00
Wojciech Kozyra 4760314ddf [eas-cli] ignore dirty workingdir when building from GitHub (#1842)
* [eas-cli] builds from GitHub are never in a dirty state

* update CHANGELOG.md
2023-05-16 16:28:44 +02:00
Quinlan Jung c8e57f000c [eas-cli] add clear cache option to updates (#1839)
* [eas-cli] add clear cache option to updates

* update CHANGELOG.md
2023-05-15 14:57:50 -07:00
Expo CI 3c82a2f8a8 update CHANGELOG.md 2023-05-15 13:26:43 +00:00
Dominik Sokal 0e7e926cec v3.12.1 2023-05-15 14:19:49 +02:00
Dominik Sokal 30364fd679 check build expiration date before submitting (#1837)
* check build expiration date before submitting

* add tests

* update CHANGELOG.md
2023-05-15 10:00:25 +02:00
Wojciech Kozyra 267fa0e43b [eas-cli] Fix random test failures (#1834)
* increase max-old-space-size

* limit number of workers
2023-05-11 12:11:45 +02:00
Wojciech Kozyra e154f19751 [eas-cli] add tests for eas build:version:* commands (#1829)
* [eas-cli] add utils for testing eas commands and write tests for build:version:*

* review feedback
2023-05-11 10:48:15 +02:00
Dominik Sokal 4bca79f828 fix CHANGELOG.md 2023-05-11 09:39:00 +02:00
dchhetri 7e01661f70 [eas-cli] Change red apple icon to green apple icon (#1828) 2023-05-11 09:29:04 +02:00
Dominik Sokal ab18e3e27e [ENG-7650] add custom workflow name to build metadata (#1830)
* generate graphql code

* upgrade @expo/eas-build-job

* add custom workflow name to build metadata
2023-05-10 10:32:55 +02:00
Expo CI 3e7a112097 update CHANGELOG.md 2023-05-08 10:26:39 +00:00
Dominik Sokal 4cd6b8d228 v3.12.0 2023-05-08 12:20:06 +02:00
Dominik Sokal 400244194b [ENG-7740][eas-cli] upgrade @expo/steps (#1824)
* [eas-cli] upgrade @expo/steps

* update CHANGELOG.md
2023-05-08 12:17:01 +02:00
Wojciech Kozyra dcf3a2d4fd [eas-cli] Add build:version:get command (#1815)
* [eas-cli] Add `build:version:get` command

* rename command class name

* review feedback

* update CHANGELOG.md
2023-05-08 11:04:45 +02:00
Expo CI 0464ac27cd update CHANGELOG.md 2023-05-05 09:48:45 +00:00
Szymon Dziedzic e0d2126f0a v3.11.0 2023-05-05 11:39:15 +02:00
Szymon Dziedzic bd46da748d [eas-cli][eas-json] add large resource class for iOS as allowed value in eas.json (#1817)
* [eas-cli][eas-json] add `large` resource class for iOS as allowed value in `eas.json`

* add deprecation warning for `m-large`

* add test

* update CHANGELOG.md
2023-05-05 11:32:22 +02:00
Radosław Krzemień d8c68561c5 [eas-cli] [ENG-8153] Print requestId and original error message on unexpected graphQL-related error (#1813)
* Change error message

Upon unexpected GraphQL-related error a new message is printed to the user. Now it also has the request ID (if returned with the error from `www`) and the original error message. This hopefully will help to identify the issues users have when unsuccessfully submitting build requests

See: https://linear.app/expo/issue/ENG-8153/if-the-build-request-fails-with-unknown-error-print-request-id

* Remove original error message

After some discussion it was decided to remove the original error message since it is not vetted against possibly leaking unintentional information to the users

See: https://linear.app/expo/issue/ENG-8153/if-the-build-request-fails-with-unknown-error-print-request-id

* Log request ID globally

After discussion under the PR it was decided to print the request ID after all graphql errors

See: https://linear.app/expo/issue/ENG-8153/if-the-build-request-fails-with-unknown-error-print-request-id

* Don't print requestId on foreseen errors

Foreseen graphQL errors with custom messages no longer print the request id to the user

See: https://linear.app/expo/issue/ENG-8153/if-the-build-request-fails-with-unknown-error-print-request-id

* update CHANGELOG.md
2023-04-28 10:22:24 +02:00
Will Schurman dfc314d5be [eas-cli] Add account list to eas whoami/eas account:view (#1814) 2023-04-27 06:52:24 -07:00
Expo CI 2285d5771a update CHANGELOG.md 2023-04-25 14:31:02 +00:00
Szymon Dziedzic aa22163688 v3.10.2 2023-04-25 16:23:04 +02:00
Szymon Dziedzic 5e5395b0c6 [ENG-7676][eas-cli] don't resolve default resource class for iOS based on metadata in CLI (#1734)
* [eas-cli] don't resolve default resource class for iOS based on metadata

* don't set m1-medium as default in `eas build:configure`

* pass sdkVersion and reactNativeVersion in buildParams

* convert it to sync function

* update gql schema

* update CHANGELOG.md
2023-04-25 15:51:41 +02:00
Szymon Dziedzic c94a1e7d9c [eas-cli] allow users to select app to run if multiple apps are found in the build tarball (#1807)
* [eas-cli] allow users to select app to run if multiple apps are found in the build tarball

* rename variable

* improve error message

* apply suggested changes

* add dot

* update CHANGELOG.md
2023-04-25 15:42:50 +02:00
Szymon Dziedzic aefadf9376 [ENG-8074][eas-json] add info about new image (#1808)
* [eas-json] add info about new image

* update eas.schema.json

* update images descriptions

* update CHANGELOG.md
2023-04-25 15:28:10 +02:00
Expo CI 7acea869ff update CHANGELOG.md 2023-04-25 13:07:56 +00:00
Radoslaw Krzemien 02af3f587f v3.10.1 2023-04-25 14:59:42 +02:00
0d0da1a47a [eas-cli] [eas-json] [ENG-8120] Use cache.paths and deprecate cache.customPaths (#1794)
* Update dependencies

Bump version of `@expo/eas-build-job` to use `cache.paths` and deprecate `cache.customPaths`

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Update default cache values

Updated the default cache values in `eas-cli` `prepareJobAsync` functions to use `paths` instead of `customPaths`

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Add tests

Added tests for `prepareJobAsync` functions both for Android and iOS to check if they handle both deprecated `customPaths` and new `paths`

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Add `paths` and deprecate `customPaths`

Updated the schema in `eas-json` to add the new field `cache.paths` and deprecate the old `cache.customPaths`

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Add deprecation warning

Added a warning message to be returned when the eas.json contains deprecated field `cache.customPaths`

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Add tests

Added tests for `EasJsonUtils.getBuildProfileDeprecationWarnings()` checking if it correctly returns deprecation warnings for `cache.customPaths`

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Add tests

Added tests for printing the deprecation messages

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Uncomment and fix test

Had previously one test that was broken and commented out

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Update dependencies

Updated `@expo/eas-build-job` version after removing default value from `cache.customPaths`

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Stop passing `customPaths` to `www`

Remove `cache.customPaths` from the job and add its values to `paths` if present

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Refactor getting cache settings

Moved getting the cache settings to `eas-json`, changed appending elements from `customPaths` to `paths` to overwriting them, added an error thrown when both `paths` and `customPaths` are present

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Fix deprecation message

Replaced hardcoded profile name with dynamic one

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Revert "Refactor getting cache settings"

This reverts commit 4727eba210.

* Revert changes and update Joi schema

Replaced the procedural code moving values of customPaths to paths into Joi schema validation

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Check for deprecation in raw EAS json

Changed the method checking for deprecated fields in eas.json to look into the raw EAS json instead of the validated build profile, because the validated build profile will always lack the deprecated field

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Update error message

Updated the message after user provides both `paths` and `customPaths` to be more understandable

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Fix broken test

One of the tests broke after the last adjustment of the error message

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* update CHANGELOG.md

* Fix import

Fixed incorrect import

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Pass accessor as argument

Passing eas json accessor as an argument instead of the results of its method. Also, removed a redundant check and made the accessor a mandatory parameter

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Check extended profile

Checking extended profile as well for deprecated `customPaths`

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Update packages/eas-json/src/utils.ts

Co-authored-by: Wojciech Kozyra <[email protected]>

* Add comment

Added a comment to a function made for testing only

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Update packages/eas-json/src/build/schema.ts

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

* Fix errors

Fixed failing test and a syntax error after committing a review suggestion

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Make the `profileName` mandatory

Made the `profileName` param in `getBuildProfileDeprecationWarningsAsync` mandatory and let the cli decide what the default profile name is

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Create build profile in eas-json

Instead of passing the build profile into `getBuildProfileDeprecationWarningsAsync` it is created using the already passed accessor

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Add type

Added variable type

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Move doc comment

Moved the comment outside of function

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Replace try/catch

Replaced try/catch blocks in test with expect().rejects.toThrow()

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Use toMatchSnapshot

Replaced checking the calls to warn() with expect().toMatchSnapshot()

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Update snapshots

Updated the test snapshots to be up to date

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Print base profile name

When checking for deprecation messages the profile name printed is the name of the base profile not the extending one

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Force chalk color in CI

Chalk behaves differently when env var CI is set to true. It can be forced to behave normally with `FORCE_COLOR` env and this should solve the problem with CI tests failing

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Force chalk color in CI

Chalk behaves differently when env var CI is set to true. It can be forced to behave normally with `FORCE_COLOR` env and this should solve the problem with CI tests failing

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Add comment

Added a comment to explain why the `FORCE_COLOR` env is necessary in CI

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

* Move env var

Moved the `FORCE_COLOR` env var from github actions workflow to jest config

See: https://linear.app/expo/issue/ENG-8120/rename-cachecustompaths-to-cachepaths-in-easjson-and-job-schema

---------

Co-authored-by: Wojciech Kozyra <[email protected]>
Co-authored-by: Dominik Sokal <[email protected]>
2023-04-25 14:21:19 +02:00
Expo CI eec0b88258 update CHANGELOG.md 2023-04-21 07:56:25 +00:00
Dominik Sokal 98be401942 v3.10.0 2023-04-21 09:49:21 +02:00
Keith Kurak 8c2689a27b [eas-cli] Device delete not disabling on Apple side (#1803)
* [device:delete] fix matching Expo device to Apple device

* Clarify when a registered device will show up on Apple side

* update CHANGELOG.md
2023-04-20 14:31:30 -04:00
Keith KurakandDominik Sokal 1b34118d85 Add eas device:rename command (#1787)
* [device] rename stubbed out with existing graphql fns

* added update device mutation

* Device rename working on Expo side

* restore codegen endpoint

* tweaked some text output

* Update graphql

* Remove debugging log

* get inputMethod.ts off the change graph

* update CHANGELOG.md

* Update packages/eas-cli/src/commands/device/rename.ts

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

* Update packages/eas-cli/src/commands/device/rename.ts

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

* Warn when device not renamed (and fix device lookup)

* make warning slightly less wordy

---------

Co-authored-by: Dominik Sokal <[email protected]>
2023-04-20 14:24:02 -04:00
Keith Kurak 0f75fe407e [eas-cli] make ora recognize CI env variable (#1775) 2023-04-20 14:15:20 -04:00
Will Schurman efb7e534f4 [eas-cli] Fix message truncation for update message (#1801) 2023-04-20 09:25:01 -07:00
Szymon Dziedzic 53f89c0df1 [eas-cli] throw error if somebody tries to run iOS with large resource class selected using deprecated --resource-class flag (#1795)
* [eas-cli] throw error if somebody tries to run iOS with `large` resource class selected through deprecated `--resource-class` flag

* apply suggested changes

* improve error message

* update CHANGELOG.md
2023-04-20 13:52:05 +02:00
Dominik Sokal 1bbe7dfb72 [gha] add PR changelog check (#1798)
* [gha] add PR changelog check

* update PR template
2023-04-20 10:46:14 +02:00
174 changed files with 5531 additions and 1690 deletions

No files matched your search

+2 -5
View File
@@ -1,8 +1,5 @@
<!-- Thanks for contributing to _EAS CLI_! Before you submit your pull request, please make sure to check the following boxes by putting an x in the [ ] (don't: [x ], [ x], do: [x]) --> <!-- 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". -->
# Checklist
- [ ] I've added an entry to [CHANGELOG.md](https://github.com/expo/eas-cli/blob/main/CHANGELOG.md#main) if necessary. You can comment this pull request with `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]` and CHANGELOG.md will be updated automatically.
# Why # Why
+48
View File
@@ -0,0 +1,48 @@
name: Changelog bot
on:
issue_comment:
types: [created]
jobs:
pr-commented:
runs-on: ubuntu-latest
if: ${{ github.event.issue.pull_request && github.event.issue.user.login == github.event.comment.user.login && startsWith(github.event.comment.body, '/changelog-entry ') }}
steps:
- uses: actions/checkout@v2
with:
token: ${{ secrets.EXPO_BOT_PAT }}
- uses: actions/setup-node@v2
with:
node-version: 18
- name: Checkout Pull Request
run: hub pr checkout ${{ github.event.issue.number }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install dependencies
working-directory: ./scripts
run: yarn install --frozen-lockfile --check-files
- name: Add changelog entry
working-directory: ./scripts
run: |
PARAMS=${PR_COMMENT_BODY#/changelog-entry }
if [[ ! "$PARAMS" =~ " " ]]; then
echo "Invalid /changelog-entry command"
exit 1
fi
CATEGORY=$( cut -d ' ' -f 1 <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
MESSAGE=$( cut -d ' ' -f 2- <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
yarn changelog-entry "$CATEGORY" "$MESSAGE"
env:
GITHUB_PR_AUTHOR: ${{ github.event.issue.user.login }}
GITHUB_PR_NUMBER: ${{ github.event.issue.number }}
PR_COMMENT_BODY: ${{ github.event.comment.body }}
- name: Commit and push
run: |
# use the last commit author name and email
git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)"
git config --global user.email "$(git --no-pager log --format=format:'%ae' -n 1)"
git add CHANGELOG.md
git commit -m "update CHANGELOG.md"
git push
+29 -40
View File
@@ -1,48 +1,37 @@
on: name: CHANGELOG.md PR check
issue_comment:
types: [created]
name: Changelog on:
pull_request:
types:
- opened
- synchronize
- labeled
- unlabeled
jobs: jobs:
pr-commented: pr-changelog-entry:
name: Check CHANGELOG.md updated
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event.issue.pull_request && github.event.issue.user.login == github.event.comment.user.login && startsWith(github.event.comment.body, '/changelog-entry ') }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Check if CHANGELOG.md was updated
id: changelog-updated
uses: tj-actions/changed-files@v35
with: with:
token: ${{ secrets.EXPO_BOT_PAT }} files: CHANGELOG.md
- uses: actions/setup-node@v2 - name: Fail if CHANGELOG.md was not updated and the "no changelog" label is absent
if: steps.changelog-updated.outputs.any_changed == 'false' && !contains(github.event.pull_request.labels.*.name, 'no changelog')
uses: actions/github-script@v6
with: with:
node-version: 18 script: |
- name: Checkout Pull Request core.setFailed('Please add a changelog entry!')
run: hub pr checkout ${{ github.event.issue.number }} - uses: mshick/[email protected]
env: if: always()
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with:
- name: Install dependencies message-id: changelog-entry-check
working-directory: ./scripts refresh-message-position: true
run: yarn install --frozen-lockfile --check-files message: ${{ contains(github.event.pull_request.labels.*.name, 'no changelog') && '⏩ The changelog entry check has been skipped since the "no changelog" label is present.' || '✅ Thank you for adding the changelog entry!' }}
- name: Add changelog entry message-failure: |
working-directory: ./scripts ❌ It looks like a changelog entry is missing for this PR. You have two options: you can add it manually, or you can use the changelog bot to do it for you.
run: | 🤖 To use the bot, simply comment on this PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`.
PARAMS=${PR_COMMENT_BODY#/changelog-entry } ⏩ If this PR doesn't require a changelog entry, such as if it's an internal change that doesn't affect the user experience, you can add the "no changelog" label to the PR.
if [[ ! "$PARAMS" =~ " " ]]; then
echo "Invalid /changelog-entry command"
exit 1
fi
CATEGORY=$( cut -d ' ' -f 1 <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
MESSAGE=$( cut -d ' ' -f 2- <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
yarn changelog-entry "$CATEGORY" "$MESSAGE"
env:
GITHUB_PR_AUTHOR: ${{ github.event.issue.user.login }}
GITHUB_PR_NUMBER: ${{ github.event.issue.number }}
PR_COMMENT_BODY: ${{ github.event.comment.body }}
- name: Commit and push
run: |
# use the last commit author name and email
git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)"
git config --global user.email "$(git --no-pager log --format=format:'%ae' -n 1)"
git add CHANGELOG.md
git commit -m "update CHANGELOG.md"
git push
+6 -2
View File
@@ -1,4 +1,5 @@
name: Run tests name: Run tests
on: on:
push: push:
branches: [main] branches: [main]
@@ -25,20 +26,23 @@ jobs:
- run: yarn typecheck - run: yarn typecheck
- run: yarn test - run: yarn test
if: ${{ !matrix.coverage }} if: ${{ !matrix.coverage }}
env:
NODE_OPTIONS: --max-old-space-size=8192
- run: yarn test --coverage - run: yarn test --coverage
if: ${{ matrix.coverage }} if: ${{ matrix.coverage }}
env: env:
# Oclif seems to cause OOM errors when testing with coverage enabled. # Oclif seems to cause OOM errors when testing with coverage enabled.
# Remove this whenever we solved those OOM errors. # Remove this whenever we solved those OOM errors.
NODE_OPTIONS: --max-old-space-size=4096 NODE_OPTIONS: --max-old-space-size=8192
- run: yarn lint --max-warnings=0 - run: yarn lint --max-warnings=0
- run: yarn install --frozen-lockfile --check-files - run: yarn install --frozen-lockfile --check-files
working-directory: ./scripts working-directory: ./scripts
- run: yarn lint-changelog
- uses: codecov/codecov-action@v1 - uses: codecov/codecov-action@v1
if: ${{ matrix.coverage }} if: ${{ matrix.coverage }}
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
- run: yarn lint-changelog
notify-slack: notify-slack:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: test needs: test
+128 -1
View File
@@ -8,10 +8,137 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features ### 🎉 New features
- Show build profile when selecting a build for eas build:run. ([#1901](https://github.com/expo/eas-cli/pull/1901) by [@keith-kurak](https://github.com/keith-kurak))
- Adds -m alias to --message in update/republish and removed README comment. ([#1905](https://github.com/expo/eas-cli/pull/1905) by [@pusongqi](https://github.com/pusongqi))
### 🐛 Bug fixes ### 🐛 Bug fixes
### 🧹 Chores ### 🧹 Chores
- Better runtimeVersion output. ([#1894](https://github.com/expo/eas-cli/pull/1894) by [@quinlanj](https://github.com/quinlanj))
- Print better error message when uploading project archive tarball fails. ([#1897](https://github.com/expo/eas-cli/pull/1897) by [@szdziedzic](https://github.com/szdziedzic))
- Set useClassicUpdates to false on `eas update` and `eas update:configure`. ([#1914](https://github.com/expo/eas-cli/pull/1914) by [@quinlanj](https://github.com/quinlanj))
## [3.14.0](https://github.com/expo/eas-cli/releases/tag/v3.14.0) - 2023-06-20
### 🎉 New features
- Added support for SSO users. ([#1875](https://github.com/expo/eas-cli/pull/1875) by [@lzkb](https://github.com/lzkb))
- Added new bundle identifier capabilities and entitlements from WWDC23. ([#1870](https://github.com/expo/eas-cli/pull/1870) by [@EvanBacon](https://github.com/EvanBacon))
- Selecting default keystore via CLI. ([#1889](https://github.com/expo/eas-cli/pull/1889) by [@khamilowicz](https://github.com/khamilowicz))
### 🧹 Chores
- Change sso flag display. ([#1890](https://github.com/expo/eas-cli/pull/1890) by [@lzkb](https://github.com/lzkb))
- Build:configure -- add channels to eas.json if using eas updates. ([#1887](https://github.com/expo/eas-cli/pull/1887) by [@quinlanj](https://github.com/quinlanj))
- Eas update: Error gracefully if no git repo. ([#1884](https://github.com/expo/eas-cli/pull/1884) by [@quinlanj](https://github.com/quinlanj))
- Error gracefully if expo pkg not installed. ([#1883](https://github.com/expo/eas-cli/pull/1883) by [@quinlanj](https://github.com/quinlanj))
- Update `eas build:configure` command to show the link of `eas.json` when generated. ([#1878](https://github.com/expo/eas-cli/pull/1878) by [@amandeepmittal](https://github.com/amandeepmittal))
- Create app.json or add the "expo" key if either are missing, before modifying or reading the file. ([#1881](https://github.com/expo/eas-cli/pull/1881) by [@brentvatne](https://github.com/brentvatne))
- Include the original stack in re-thrown errors thrown from EAS CLI commands. ([#1882](https://github.com/expo/eas-cli/pull/1882) by [@brentvatne](https://github.com/brentvatne))
- Make `update:configure` less verbose. ([#1888](https://github.com/expo/eas-cli/pull/1888) by [@quinlanj](https://github.com/quinlanj))
- Improve validation for values from app config. ([#1893](https://github.com/expo/eas-cli/pull/1893) by [@wkozyra95](https://github.com/wkozyra95))
- Improve `expo-updates` validation for builds with `channel` property set. ([#1885](https://github.com/expo/eas-cli/pull/1885) by [@szdziedzic](https://github.com/szdziedzic))
## [3.13.3](https://github.com/expo/eas-cli/releases/tag/v3.13.3) - 2023-06-05
### 🐛 Bug fixes
- Show original GraphQL error message in case of an unexpected error. ([#1862](https://github.com/expo/eas-cli/pull/1862) by [@dsokal](https://github.com/dsokal))
- Fix updates synchronization of native files to include strings.xml for bare projects. ([#1865](https://github.com/expo/eas-cli/pull/1865) by [@wschurman](https://github.com/wschurman))
## [3.13.2](https://github.com/expo/eas-cli/releases/tag/v3.13.2) - 2023-05-26
### 🧹 Chores
- Refactor getExpoConfig to remove dangerous default. ([#1857](https://github.com/expo/eas-cli/pull/1857) by [@wschurman](https://github.com/wschurman))
- Add support for pending-cancel build status. ([#1855](https://github.com/expo/eas-cli/pull/1855) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
## [3.13.1](https://github.com/expo/eas-cli/releases/tag/v3.13.1) - 2023-05-24
### 🧹 Chores
- Short format for selecting devices prompt. ([#1840](https://github.com/expo/eas-cli/pull/1840) by [@khamilowicz](https://github.com/khamilowicz))
- Improve typescript types for user display. ([#1851](https://github.com/expo/eas-cli/pull/1851) by [@wschurman](https://github.com/wschurman))
- Add error for `large` resource class not available on the free plan to server-side defined errors. ([#1848](https://github.com/expo/eas-cli/pull/1848) by [@szdziedzic](https://github.com/szdziedzic))
## [3.13.0](https://github.com/expo/eas-cli/releases/tag/v3.13.0) - 2023-05-17
### 🎉 New features
- Add clear cache flag to eas update. ([#1839](https://github.com/expo/eas-cli/pull/1839) by [@quinlanj](https://github.com/quinlanj))
- Print EAS Update assets that timed out during upload or processing. ([#1849](https://github.com/expo/eas-cli/pull/1849) by [@wschurman](https://github.com/wschurman))
### 🐛 Bug fixes
- Fix building iOS projects with framework targets. ([#1835](https://github.com/expo/eas-cli/pull/1835) by [@dsokal](https://github.com/dsokal))
### 🧹 Chores
- Ignore dirty workingdir when building from GitHub. ([#1842](https://github.com/expo/eas-cli/pull/1842) by [@wkozyra95](https://github.com/wkozyra95))
- Remove App Specific Password prompt. ([#1843](https://github.com/expo/eas-cli/pull/1843) by [@quinlanj](https://github.com/quinlanj))
- Validate `CFBundleShortVersionString`. ([#1846](https://github.com/expo/eas-cli/pull/1846) by [@khamilowicz](https://github.com/khamilowicz))
## [3.12.1](https://github.com/expo/eas-cli/releases/tag/v3.12.1) - 2023-05-15
### 🐛 Bug fixes
- Don't submit expired builds. ([#1837](https://github.com/expo/eas-cli/pull/1837) by [@dsokal](https://github.com/dsokal))
### 🧹 Chores
- Change red apple icon to green apple icon on successful build. ([#1828](https://github.com/expo/eas-cli/pull/1828) by [@dchhetri](https://github.com/dchhetri))
## [3.12.0](https://github.com/expo/eas-cli/releases/tag/v3.12.0) - 2023-05-08
### 🎉 New features
- Add `build:version:get` command. ([#1815](https://github.com/expo/eas-cli/pull/1815) by [@wkozyra95](https://github.com/wkozyra95))
### 🧹 Chores
- Upgrade dependencies. ([#1824](https://github.com/expo/eas-cli/pull/1824) by [@dsokal](https://github.com/dsokal))
## [3.11.0](https://github.com/expo/eas-cli/releases/tag/v3.11.0) - 2023-05-05
### 🎉 New features
- Add account list to `eas whoami`/`eas account:view`. ([#1814](https://github.com/expo/eas-cli/pull/1814) by [@wschurman](https://github.com/wschurman))
- Add `large` resource class for iOS as allowed value in `eas.json`. ([#1817](https://github.com/expo/eas-cli/pull/1817) by [@szdziedzic](https://github.com/szdziedzic))
### 🧹 Chores
- Print request ID on unforeseen GraphQL error for easier tracking and follow up. ([#1813](https://github.com/expo/eas-cli/pull/1813) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
## [3.10.2](https://github.com/expo/eas-cli/releases/tag/v3.10.2) - 2023-04-25
### 🧹 Chores
- Add info about Xcode 14.3 image to `eas.schema.json`. ([#1808](https://github.com/expo/eas-cli/pull/1808) by [@szdziedzic](https://github.com/szdziedzic))
- Allow users to select an app to run using the `build:run` command if multiple apps are found in the tarball. ([#1807](https://github.com/expo/eas-cli/pull/1807) by [@szdziedzic](https://github.com/szdziedzic))
- Don't resolve `default` resource class for iOS in CLI. ([#1734](https://github.com/expo/eas-cli/pull/1734) by [@szdziedzic](https://github.com/szdziedzic))
## [3.10.1](https://github.com/expo/eas-cli/releases/tag/v3.10.1) - 2023-04-25
### 🧹 Chores
- Add `cache.paths` field and deprecate `cache.customPaths`. ([#1794](https://github.com/expo/eas-cli/pull/1794) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
## [3.10.0](https://github.com/expo/eas-cli/releases/tag/v3.10.0) - 2023-04-21
### 🎉 New features
- Added eas device:rename command. ([#1787](https://github.com/expo/eas-cli/pull/1787) by [@keith-kurak](https://github.com/keith-kurak))
### 🐛 Bug fixes
- Fix `device:delete` not disabling on Apple. ([#1803](https://github.com/expo/eas-cli/pull/1803) by [@keith-kurak](https://github.com/keith-kurak))
- Fix message truncation for updates. ([#1801](https://github.com/expo/eas-cli/pull/1801) by [@wschurman](https://github.com/wschurman))
### 🧹 Chores
- Throw an error if somebody tries to start iOS build with the `large` resource class selected using the deprecated `--resource-class` flag. ([#1795](https://github.com/expo/eas-cli/pull/1795) by [@szdziedzic](https://github.com/szdziedzic))
## [3.9.3](https://github.com/expo/eas-cli/releases/tag/v3.9.3) - 2023-04-18 ## [3.9.3](https://github.com/expo/eas-cli/releases/tag/v3.9.3) - 2023-04-18
### 🐛 Bug fixes ### 🐛 Bug fixes
@@ -89,7 +216,7 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features ### 🎉 New features
- Support variant detection for package*UniversalApk Gradle Commands. ([#1708](https://github.com/expo/eas-cli/pull/1708) by [@frw](https://github.com/frw)) - Support variant detection for package\*UniversalApk Gradle Commands. ([#1708](https://github.com/expo/eas-cli/pull/1708) by [@frw](https://github.com/frw))
## [3.6.1](https://github.com/expo/eas-cli/releases/tag/v3.6.1) - 2023-02-20 ## [3.6.1](https://github.com/expo/eas-cli/releases/tag/v3.6.1) - 2023-02-20
+6 -6
View File
@@ -1,10 +1,10 @@
* @dsokal @wkozyra95 * @szdziedzic @khamilowicz
packages/eas-cli/src/build @dsokal @wkozyra95 packages/eas-cli/src/build @szdziedzic @khamilowicz
packages/eas-cli/src/commands/build @dsokal @wkozyra95 packages/eas-cli/src/commands/build @szdziedzic @khamilowicz
packages/eas-cli/src/submit @dsokal packages/eas-cli/src/submit @szdziedzic @khamilowicz
packages/eas-cli/src/commands/submit.ts @dsokal packages/eas-cli/src/commands/submit.ts @szdziedzic @khamilowicz
packages/eas-cli/src/update @EvanBacon @byCedric @wschurman packages/eas-cli/src/update @EvanBacon @byCedric @wschurman
packages/eas-cli/src/commands/update @EvanBacon @byCedric @wschurman packages/eas-cli/src/commands/update @EvanBacon @byCedric @wschurman
@@ -13,4 +13,4 @@ packages/eas-cli/schema @bycedric
packages/eas-cli/src/metadata @bycedric packages/eas-cli/src/metadata @bycedric
packages/eas-cli/src/commands/metadata @bycedric packages/eas-cli/src/commands/metadata @bycedric
packages/eas-json @dsokal @wkozyra95 packages/eas-json @szdziedzic @khamilowicz
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"npmClient": "yarn", "npmClient": "yarn",
"useWorkspaces": true, "useWorkspaces": true,
"version": "3.9.3", "version": "3.14.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json" "$schema": "node_modules/lerna/schemas/lerna-schema.json"
} }
+178 -130
View File
@@ -55,66 +55,69 @@ eas --help COMMAND
# Commands # Commands
<!-- commands --> <!-- commands -->
* [`eas account:login`](#eas-accountlogin)
* [`eas account:logout`](#eas-accountlogout) - [`eas account:login`](#eas-accountlogin)
* [`eas account:view`](#eas-accountview) - [`eas account:logout`](#eas-accountlogout)
* [`eas analytics [STATUS]`](#eas-analytics-status) - [`eas account:view`](#eas-accountview)
* [`eas autocomplete [SHELL]`](#eas-autocomplete-shell) - [`eas analytics [STATUS]`](#eas-analytics-status)
* [`eas branch:create [NAME]`](#eas-branchcreate-name) - [`eas autocomplete [SHELL]`](#eas-autocomplete-shell)
* [`eas branch:delete [NAME]`](#eas-branchdelete-name) - [`eas branch:create [NAME]`](#eas-branchcreate-name)
* [`eas branch:list`](#eas-branchlist) - [`eas branch:delete [NAME]`](#eas-branchdelete-name)
* [`eas branch:rename`](#eas-branchrename) - [`eas branch:list`](#eas-branchlist)
* [`eas branch:view [NAME]`](#eas-branchview-name) - [`eas branch:rename`](#eas-branchrename)
* [`eas build`](#eas-build) - [`eas branch:view [NAME]`](#eas-branchview-name)
* [`eas build:cancel [BUILD_ID]`](#eas-buildcancel-build_id) - [`eas build`](#eas-build)
* [`eas build:configure`](#eas-buildconfigure) - [`eas build:cancel [BUILD_ID]`](#eas-buildcancel-build_id)
* [`eas build:inspect`](#eas-buildinspect) - [`eas build:configure`](#eas-buildconfigure)
* [`eas build:list`](#eas-buildlist) - [`eas build:inspect`](#eas-buildinspect)
* [`eas build:resign`](#eas-buildresign) - [`eas build:list`](#eas-buildlist)
* [`eas build:run`](#eas-buildrun) - [`eas build:resign`](#eas-buildresign)
* [`eas build:submit`](#eas-buildsubmit) - [`eas build:run`](#eas-buildrun)
* [`eas build:version:set`](#eas-buildversionset) - [`eas build:submit`](#eas-buildsubmit)
* [`eas build:version:sync`](#eas-buildversionsync) - [`eas build:version:get`](#eas-buildversionget)
* [`eas build:view [BUILD_ID]`](#eas-buildview-build_id) - [`eas build:version:set`](#eas-buildversionset)
* [`eas channel:create [NAME]`](#eas-channelcreate-name) - [`eas build:version:sync`](#eas-buildversionsync)
* [`eas channel:edit [NAME]`](#eas-channeledit-name) - [`eas build:view [BUILD_ID]`](#eas-buildview-build_id)
* [`eas channel:list`](#eas-channellist) - [`eas channel:create [NAME]`](#eas-channelcreate-name)
* [`eas channel:rollout [CHANNEL]`](#eas-channelrollout-channel) - [`eas channel:edit [NAME]`](#eas-channeledit-name)
* [`eas channel:view [NAME]`](#eas-channelview-name) - [`eas channel:list`](#eas-channellist)
* [`eas config`](#eas-config) - [`eas channel:rollout [CHANNEL]`](#eas-channelrollout-channel)
* [`eas credentials`](#eas-credentials) - [`eas channel:view [NAME]`](#eas-channelview-name)
* [`eas device:create`](#eas-devicecreate) - [`eas config`](#eas-config)
* [`eas device:delete`](#eas-devicedelete) - [`eas credentials`](#eas-credentials)
* [`eas device:list`](#eas-devicelist) - [`eas device:create`](#eas-devicecreate)
* [`eas device:view [UDID]`](#eas-deviceview-udid) - [`eas device:delete`](#eas-devicedelete)
* [`eas diagnostics`](#eas-diagnostics) - [`eas device:list`](#eas-devicelist)
* [`eas help [COMMAND]`](#eas-help-command) - [`eas device:rename`](#eas-devicerename)
* [`eas init`](#eas-init) - [`eas device:view [UDID]`](#eas-deviceview-udid)
* [`eas login`](#eas-login) - [`eas diagnostics`](#eas-diagnostics)
* [`eas logout`](#eas-logout) - [`eas help [COMMAND]`](#eas-help-command)
* [`eas metadata:lint`](#eas-metadatalint) - [`eas init`](#eas-init)
* [`eas metadata:pull`](#eas-metadatapull) - [`eas login`](#eas-login)
* [`eas metadata:push`](#eas-metadatapush) - [`eas logout`](#eas-logout)
* [`eas open`](#eas-open) - [`eas metadata:lint`](#eas-metadatalint)
* [`eas project:info`](#eas-projectinfo) - [`eas metadata:pull`](#eas-metadatapull)
* [`eas project:init`](#eas-projectinit) - [`eas metadata:push`](#eas-metadatapush)
* [`eas secret:create`](#eas-secretcreate) - [`eas open`](#eas-open)
* [`eas secret:delete`](#eas-secretdelete) - [`eas project:info`](#eas-projectinfo)
* [`eas secret:list`](#eas-secretlist) - [`eas project:init`](#eas-projectinit)
* [`eas secret:push`](#eas-secretpush) - [`eas secret:create`](#eas-secretcreate)
* [`eas submit`](#eas-submit) - [`eas secret:delete`](#eas-secretdelete)
* [`eas update`](#eas-update) - [`eas secret:list`](#eas-secretlist)
* [`eas update:configure`](#eas-updateconfigure) - [`eas secret:push`](#eas-secretpush)
* [`eas update:delete GROUPID`](#eas-updatedelete-groupid) - [`eas submit`](#eas-submit)
* [`eas update:list`](#eas-updatelist) - [`eas update`](#eas-update)
* [`eas update:republish`](#eas-updaterepublish) - [`eas update:configure`](#eas-updateconfigure)
* [`eas update:view GROUPID`](#eas-updateview-groupid) - [`eas update:delete GROUPID`](#eas-updatedelete-groupid)
* [`eas webhook:create`](#eas-webhookcreate) - [`eas update:list`](#eas-updatelist)
* [`eas webhook:delete [ID]`](#eas-webhookdelete-id) - [`eas update:republish`](#eas-updaterepublish)
* [`eas webhook:list`](#eas-webhooklist) - [`eas update:view GROUPID`](#eas-updateview-groupid)
* [`eas webhook:update`](#eas-webhookupdate) - [`eas webhook:create`](#eas-webhookcreate)
* [`eas webhook:view ID`](#eas-webhookview-id) - [`eas webhook:delete [ID]`](#eas-webhookdelete-id)
* [`eas whoami`](#eas-whoami) - [`eas webhook:list`](#eas-webhooklist)
- [`eas webhook:update`](#eas-webhookupdate)
- [`eas webhook:view ID`](#eas-webhookview-id)
- [`eas whoami`](#eas-whoami)
## `eas account:login` ## `eas account:login`
@@ -131,7 +134,7 @@ ALIASES
$ eas login $ eas login
``` ```
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/account/login.ts)_ _See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout` ## `eas account:logout`
@@ -148,7 +151,7 @@ ALIASES
$ eas logout $ eas logout
``` ```
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/account/logout.ts)_ _See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view` ## `eas account:view`
@@ -165,7 +168,7 @@ ALIASES
$ eas whoami $ eas whoami
``` ```
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/account/view.ts)_ _See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]` ## `eas analytics [STATUS]`
@@ -179,7 +182,7 @@ DESCRIPTION
display or change analytics settings display or change analytics settings
``` ```
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/analytics.ts)_ _See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]` ## `eas autocomplete [SHELL]`
@@ -229,7 +232,7 @@ DESCRIPTION
create a branch create a branch
``` ```
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/branch/create.ts)_ _See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]` ## `eas branch:delete [NAME]`
@@ -250,7 +253,7 @@ DESCRIPTION
delete a branch delete a branch
``` ```
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/branch/delete.ts)_ _See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list` ## `eas branch:list`
@@ -270,7 +273,7 @@ DESCRIPTION
list all branches list all branches
``` ```
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/branch/list.ts)_ _See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename` ## `eas branch:rename`
@@ -290,7 +293,7 @@ DESCRIPTION
rename a branch rename a branch
``` ```
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/branch/rename.ts)_ _See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]` ## `eas branch:view [NAME]`
@@ -313,7 +316,7 @@ DESCRIPTION
view a branch view a branch
``` ```
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/branch/view.ts)_ _See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build` ## `eas build`
@@ -343,7 +346,7 @@ DESCRIPTION
start a build start a build
``` ```
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/index.ts)_ _See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]` ## `eas build:cancel [BUILD_ID]`
@@ -360,7 +363,7 @@ DESCRIPTION
cancel a build cancel a build
``` ```
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/cancel.ts)_ _See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure` ## `eas build:configure`
@@ -377,7 +380,7 @@ DESCRIPTION
configure the project to support EAS Build configure the project to support EAS Build
``` ```
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/configure.ts)_ _See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:inspect` ## `eas build:inspect`
@@ -412,7 +415,7 @@ DESCRIPTION
inspect the state of the project at specific build stages, useful for troubleshooting inspect the state of the project at specific build stages, useful for troubleshooting
``` ```
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/inspect.ts)_ _See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list` ## `eas build:list`
@@ -420,10 +423,11 @@ list all builds for your project
``` ```
USAGE USAGE
$ eas build:list [--platform all|android|ios] [--status new|in-queue|in-progress|errored|finished|canceled] $ eas build:list [--platform all|android|ios] [--status
[--distribution store|internal|simulator] [--channel <value>] [--appVersion <value>] [--appBuildVersion <value>] new|in-queue|in-progress|pending-cancel|errored|finished|canceled] [--distribution store|internal|simulator]
[--sdkVersion <value>] [--runtimeVersion <value>] [--appIdentifier <value>] [--buildProfile <value>] [--channel <value>] [--appVersion <value>] [--appBuildVersion <value>] [--sdkVersion <value>] [--runtimeVersion
[--gitCommitHash <value>] [--offset <value>] [--limit <value>] [--json --non-interactive] <value>] [--appIdentifier <value>] [--buildProfile <value>] [--gitCommitHash <value>] [--offset <value>] [--limit
<value>] [--json --non-interactive]
FLAGS FLAGS
--appBuildVersion=<value> --appBuildVersion=<value>
@@ -433,23 +437,23 @@ FLAGS
--channel=<value> --channel=<value>
--distribution=(store|internal|simulator) --distribution=(store|internal|simulator)
--gitCommitHash=<value> --gitCommitHash=<value>
--json Enable JSON output, non-JSON messages will be printed --json Enable JSON output, non-JSON messages
to stderr. will be printed to stderr.
--limit=<value> The number of items to fetch each query. Defaults to 10 --limit=<value> The number of items to fetch each query.
and is capped at 50. Defaults to 10 and is capped at 50.
--non-interactive Run the command in non-interactive mode. --non-interactive Run the command in non-interactive mode.
--offset=<value> Start queries from specified index. Use for paginating --offset=<value> Start queries from specified index. Use
results. Defaults to 0. for paginating results. Defaults to 0.
--platform=(all|android|ios) --platform=(all|android|ios)
--runtimeVersion=<value> --runtimeVersion=<value>
--sdkVersion=<value> --sdkVersion=<value>
--status=(new|in-queue|in-progress|errored|finished|canceled) --status=(new|in-queue|in-progress|pending-cancel|errored|finished|canceled)
DESCRIPTION DESCRIPTION
list all builds for your project list all builds for your project
``` ```
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/list.ts)_ _See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:resign` ## `eas build:resign`
@@ -475,7 +479,7 @@ DESCRIPTION
re-sign a build archive re-sign a build archive
``` ```
_See code: [src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/resign.ts)_ _See code: [src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/resign.ts)_
## `eas build:run` ## `eas build:run`
@@ -499,7 +503,7 @@ DESCRIPTION
run simulator/emulator builds from eas-cli run simulator/emulator builds from eas-cli
``` ```
_See code: [src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/run.ts)_ _See code: [src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/run.ts)_
## `eas build:submit` ## `eas build:submit`
@@ -529,9 +533,30 @@ ALIASES
$ eas build:submit $ eas build:submit
``` ```
## `eas build:version:get`
get the latest version from EAS servers
```
USAGE
$ eas build:version:get [-p android|ios|all] [-e <value>] [--json --non-interactive]
FLAGS
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined in
eas.json.
-p, --platform=(android|ios|all)
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
DESCRIPTION
get the latest version from EAS servers
```
_See code: [src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/version/get.ts)_
## `eas build:version:set` ## `eas build:version:set`
Update version of an app. update version of an app
``` ```
USAGE USAGE
@@ -543,14 +568,14 @@ FLAGS
-p, --platform=(android|ios) -p, --platform=(android|ios)
DESCRIPTION DESCRIPTION
Update version of an app. update version of an app
``` ```
_See code: [src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/version/set.ts)_ _See code: [src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/version/set.ts)_
## `eas build:version:sync` ## `eas build:version:sync`
Update a version in native code with a value stored on EAS servers update a version in native code with a value stored on EAS servers
``` ```
USAGE USAGE
@@ -562,10 +587,10 @@ FLAGS
-p, --platform=(android|ios|all) -p, --platform=(android|ios|all)
DESCRIPTION DESCRIPTION
Update a version in native code with a value stored on EAS servers update a version in native code with a value stored on EAS servers
``` ```
_See code: [src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/version/sync.ts)_ _See code: [src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]` ## `eas build:view [BUILD_ID]`
@@ -582,7 +607,7 @@ DESCRIPTION
view a build for your project view a build for your project
``` ```
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/view.ts)_ _See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]` ## `eas channel:create [NAME]`
@@ -603,7 +628,7 @@ DESCRIPTION
create a channel create a channel
``` ```
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/channel/create.ts)_ _See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:edit [NAME]` ## `eas channel:edit [NAME]`
@@ -625,7 +650,7 @@ DESCRIPTION
point a channel at a new branch point a channel at a new branch
``` ```
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/channel/edit.ts)_ _See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list` ## `eas channel:list`
@@ -645,7 +670,7 @@ DESCRIPTION
list all channels list all channels
``` ```
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/channel/list.ts)_ _See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:rollout [CHANNEL]` ## `eas channel:rollout [CHANNEL]`
@@ -669,7 +694,7 @@ DESCRIPTION
Roll a new branch out on a channel incrementally. Roll a new branch out on a channel incrementally.
``` ```
_See code: [src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/channel/rollout.ts)_ _See code: [src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/channel/rollout.ts)_
## `eas channel:view [NAME]` ## `eas channel:view [NAME]`
@@ -692,7 +717,7 @@ DESCRIPTION
view a channel view a channel
``` ```
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/channel/view.ts)_ _See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config` ## `eas config`
@@ -713,7 +738,7 @@ DESCRIPTION
display project configuration (app.json + eas.json) display project configuration (app.json + eas.json)
``` ```
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/config.ts)_ _See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/config.ts)_
## `eas credentials` ## `eas credentials`
@@ -730,7 +755,7 @@ DESCRIPTION
manage credentials manage credentials
``` ```
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/credentials.ts)_ _See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/credentials.ts)_
## `eas device:create` ## `eas device:create`
@@ -744,7 +769,7 @@ DESCRIPTION
register new Apple Devices to use for internal distribution register new Apple Devices to use for internal distribution
``` ```
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/device/create.ts)_ _See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete` ## `eas device:delete`
@@ -764,7 +789,7 @@ DESCRIPTION
remove a registered device from your account remove a registered device from your account
``` ```
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/device/delete.ts)_ _See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list` ## `eas device:list`
@@ -785,7 +810,28 @@ DESCRIPTION
list all registered devices for your account list all registered devices for your account
``` ```
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/device/list.ts)_ _See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:rename`
rename a registered device
```
USAGE
$ eas device:rename [--apple-team-id <value>] [--udid <value>] [--name <value>] [--json --non-interactive]
FLAGS
--apple-team-id=<value> The Apple team ID on which to find the device
--json Enable JSON output, non-JSON messages will be printed to stderr.
--name=<value> The new name for the device
--non-interactive Run the command in non-interactive mode.
--udid=<value> The Apple device ID to rename
DESCRIPTION
rename a registered device
```
_See code: [src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/device/rename.ts)_
## `eas device:view [UDID]` ## `eas device:view [UDID]`
@@ -799,7 +845,7 @@ DESCRIPTION
view a device for your project view a device for your project
``` ```
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/device/view.ts)_ _See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics` ## `eas diagnostics`
@@ -813,7 +859,7 @@ DESCRIPTION
display environment info display environment info
``` ```
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/diagnostics.ts)_ _See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas help [COMMAND]` ## `eas help [COMMAND]`
@@ -901,7 +947,7 @@ DESCRIPTION
validate the local store configuration validate the local store configuration
``` ```
_See code: [src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/metadata/lint.ts)_ _See code: [src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/metadata/lint.ts)_
## `eas metadata:pull` ## `eas metadata:pull`
@@ -918,7 +964,7 @@ DESCRIPTION
generate the local store configuration from the app stores generate the local store configuration from the app stores
``` ```
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/metadata/pull.ts)_ _See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push` ## `eas metadata:push`
@@ -935,7 +981,7 @@ DESCRIPTION
sync the local store configuration to the app stores sync the local store configuration to the app stores
``` ```
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/metadata/push.ts)_ _See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas open` ## `eas open`
@@ -949,7 +995,7 @@ DESCRIPTION
open the project page in a web browser open the project page in a web browser
``` ```
_See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/open.ts)_ _See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/open.ts)_
## `eas project:info` ## `eas project:info`
@@ -963,7 +1009,7 @@ DESCRIPTION
information about the current project information about the current project
``` ```
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/project/info.ts)_ _See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init` ## `eas project:init`
@@ -985,7 +1031,7 @@ ALIASES
$ eas init $ eas init
``` ```
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/project/init.ts)_ _See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/project/init.ts)_
## `eas secret:create` ## `eas secret:create`
@@ -1008,7 +1054,7 @@ DESCRIPTION
create an environment secret on the current project or owner account create an environment secret on the current project or owner account
``` ```
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/secret/create.ts)_ _See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/secret/create.ts)_
## `eas secret:delete` ## `eas secret:delete`
@@ -1026,7 +1072,7 @@ DESCRIPTION
delete an environment secret by ID delete an environment secret by ID
``` ```
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/secret/delete.ts)_ _See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/secret/delete.ts)_
## `eas secret:list` ## `eas secret:list`
@@ -1040,7 +1086,7 @@ DESCRIPTION
list environment secrets available for your current app list environment secrets available for your current app
``` ```
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/secret/list.ts)_ _See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/secret/list.ts)_
## `eas secret:push` ## `eas secret:push`
@@ -1060,7 +1106,7 @@ DESCRIPTION
read environment secrets from env file and store on the server read environment secrets from env file and store on the server
``` ```
_See code: [src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/secret/push.ts)_ _See code: [src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/secret/push.ts)_
## `eas submit` ## `eas submit`
@@ -1090,7 +1136,7 @@ ALIASES
$ eas build:submit $ eas build:submit
``` ```
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/submit.ts)_ _See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/submit.ts)_
## `eas update` ## `eas update`
@@ -1099,14 +1145,15 @@ publish an update group
``` ```
USAGE USAGE
$ eas update [--branch <value>] [--channel <value>] [--message <value>] [--republish | --input-dir <value> $ eas update [--branch <value>] [--channel <value>] [--message <value>] [--republish | --input-dir <value>
| --skip-bundler] [--group <value> | | ] [-p android|ios|all] [--auto] [--private-key-path <value>] [--json | --skip-bundler] [--group <value> | | ] [--clear-cache] [-p android|ios|all] [--auto] [--private-key-path <value>]
--non-interactive] [--json --non-interactive]
FLAGS FLAGS
-p, --platform=(android|ios|all) [default: all] -p, --platform=(android|ios|all) [default: all]
--auto Use the current git branch and commit message for the EAS branch and update message --auto Use the current git branch and commit message for the EAS branch and update message
--branch=<value> Branch to publish the update group on --branch=<value> Branch to publish the update group on
--channel=<value> Channel that the published update should affect --channel=<value> Channel that the published update should affect
--clear-cache Clear the bundler cache before publishing
--group=<value> Update group to republish (deprecated, see republish command) --group=<value> Update group to republish (deprecated, see republish command)
--input-dir=<value> [default: dist] Location of the bundle --input-dir=<value> [default: dist] Location of the bundle
--json Enable JSON output, non-JSON messages will be printed to stderr. --json Enable JSON output, non-JSON messages will be printed to stderr.
@@ -1122,7 +1169,7 @@ DESCRIPTION
publish an update group publish an update group
``` ```
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/update/index.ts)_ _See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure` ## `eas update:configure`
@@ -1140,7 +1187,7 @@ DESCRIPTION
configure the project to support EAS Update configure the project to support EAS Update
``` ```
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/update/configure.ts)_ _See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID` ## `eas update:delete GROUPID`
@@ -1161,7 +1208,7 @@ DESCRIPTION
delete all the updates in an update group delete all the updates in an update group
``` ```
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/update/delete.ts)_ _See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:list` ## `eas update:list`
@@ -1183,7 +1230,7 @@ DESCRIPTION
view the recent updates view the recent updates
``` ```
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/update/list.ts)_ _See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:republish` ## `eas update:republish`
@@ -1207,7 +1254,7 @@ DESCRIPTION
roll back to an existing update roll back to an existing update
``` ```
_See code: [src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/update/republish.ts)_ _See code: [src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/update/republish.ts)_
## `eas update:view GROUPID` ## `eas update:view GROUPID`
@@ -1227,7 +1274,7 @@ DESCRIPTION
update group details update group details
``` ```
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/update/view.ts)_ _See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/update/view.ts)_
## `eas webhook:create` ## `eas webhook:create`
@@ -1248,7 +1295,7 @@ DESCRIPTION
create a webhook create a webhook
``` ```
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/webhook/create.ts)_ _See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]` ## `eas webhook:delete [ID]`
@@ -1268,7 +1315,7 @@ DESCRIPTION
delete a webhook delete a webhook
``` ```
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/webhook/delete.ts)_ _See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list` ## `eas webhook:list`
@@ -1286,7 +1333,7 @@ DESCRIPTION
list webhooks list webhooks
``` ```
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/webhook/list.ts)_ _See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update` ## `eas webhook:update`
@@ -1308,7 +1355,7 @@ DESCRIPTION
update a webhook update a webhook
``` ```
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/webhook/update.ts)_ _See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID` ## `eas webhook:view ID`
@@ -1325,7 +1372,7 @@ DESCRIPTION
view a webhook view a webhook
``` ```
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/webhook/view.ts)_ _See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami` ## `eas whoami`
@@ -1341,4 +1388,5 @@ DESCRIPTION
ALIASES ALIASES
$ eas whoami $ eas whoami
``` ```
<!-- commandsstop --> <!-- commandsstop -->
+984 -206
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -2,4 +2,5 @@ module.exports = async () => {
process.env.TZ = 'UTC'; process.env.TZ = 'UTC';
process.env.EAS_NO_VCS = '1'; process.env.EAS_NO_VCS = '1';
process.env.EAS_PROJECT_ROOT = '/app'; process.env.EAS_PROJECT_ROOT = '/app';
process.env.FORCE_COLOR = '1';
}; };
+9 -8
View File
@@ -1,20 +1,20 @@
{ {
"name": "eas-cli", "name": "eas-cli",
"description": "EAS command line tool", "description": "EAS command line tool",
"version": "3.9.3", "version": "3.14.0",
"author": "Expo <[email protected]>", "author": "Expo <[email protected]>",
"bin": { "bin": {
"eas": "./bin/run" "eas": "./bin/run"
}, },
"bugs": "https://github.com/expo/eas-cli/issues", "bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": { "dependencies": {
"@expo/apple-utils": "1.2.0", "@expo/apple-utils": "1.3.1",
"@expo/code-signing-certificates": "0.0.5", "@expo/code-signing-certificates": "0.0.5",
"@expo/config": "7.0.3", "@expo/config": "8.1.2",
"@expo/config-plugins": "5.0.4", "@expo/config-plugins": "7.2.3",
"@expo/config-types": "47.0.0", "@expo/config-types": "49.0.0",
"@expo/eas-build-job": "1.0.7", "@expo/eas-build-job": "1.0.13",
"@expo/eas-json": "3.9.2", "@expo/eas-json": "3.13.0",
"@expo/json-file": "8.2.37", "@expo/json-file": "8.2.37",
"@expo/multipart-body-parser": "1.1.0", "@expo/multipart-body-parser": "1.1.0",
"@expo/osascript": "2.0.33", "@expo/osascript": "2.0.33",
@@ -27,7 +27,7 @@
"@expo/results": "1.0.0", "@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1", "@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.0", "@expo/spawn-async": "1.7.0",
"@expo/steps": "1.0.8", "@expo/steps": "1.0.23",
"@expo/timeago.js": "1.0.0", "@expo/timeago.js": "1.0.0",
"@oclif/core": "1.23.2", "@oclif/core": "1.23.2",
"@oclif/plugin-autocomplete": "1.3.10", "@oclif/plugin-autocomplete": "1.3.10",
@@ -47,6 +47,7 @@
"fast-glob": "3.2.12", "fast-glob": "3.2.12",
"figures": "3.2.0", "figures": "3.2.0",
"form-data": "4.0.0", "form-data": "4.0.0",
"freeport-async": "2.0.0",
"fs-extra": "10.1.0", "fs-extra": "10.1.0",
"getenv": "1.0.0", "getenv": "1.0.0",
"gradle-to-js": "2.0.1", "gradle-to-js": "2.0.1",
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BuildVersionGetView reading version when appVersionSource is set to local 1`] = `"This project is not configured for using remote version source. Add {"cli": { "appVersionSource": "remote" }} in eas.json or re-run this command without "--non-interactive" flag."`;
@@ -0,0 +1,161 @@
import { AppVersionSource, EasJson } from '@expo/eas-json';
import chalk from 'chalk';
import BuildVersionGetView from '../../commands/build/version/get';
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
import Log from '../../log';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { getMockEasJson, mockCommandContext, mockProjectId, mockTestCommand } from './utils';
jest.mock('../../project/applicationIdentifier');
jest.mock('fs');
jest.mock('../../log');
jest.mock('../../utils/json');
jest.mock('../../graphql/queries/AppVersionQuery');
function withRemoteVersionSource(easJson: EasJson): EasJson {
return {
...easJson,
cli: {
...easJson.cli,
appVersionSource: AppVersionSource.REMOTE,
},
};
}
describe(BuildVersionGetView, () => {
afterEach(() => {
jest.clearAllMocks();
});
test('reading version for platform android', async () => {
const ctx = mockCommandContext(BuildVersionGetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '100',
storeVersion: '1.0.0',
}));
const cmd = mockTestCommand(BuildVersionGetView, ['--platform=android'], ctx);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
expect(Log.log).toHaveBeenCalledWith(`Android versionCode - ${chalk.bold('100')}`);
expect(enableJsonOutput).not.toHaveBeenCalled();
expect(printJsonOnlyOutput).not.toHaveBeenCalled();
});
test('reading version for platform android when no remote version is set', async () => {
const ctx = mockCommandContext(BuildVersionGetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
const cmd = mockTestCommand(BuildVersionGetView, ['--platform=android'], ctx);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
expect(Log.log).toHaveBeenCalledWith(`No remote versions are configured for this project.`);
expect(enableJsonOutput).not.toHaveBeenCalled();
expect(printJsonOnlyOutput).not.toHaveBeenCalled();
});
test('reading version with --json flag for platform android', async () => {
const ctx = mockCommandContext(BuildVersionGetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '100',
storeVersion: '1.0.0',
}));
const cmd = mockTestCommand(
BuildVersionGetView,
['--non-interactive', '--json', '--platform=android'],
ctx
);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
expect(enableJsonOutput).toHaveBeenCalled();
expect(printJsonOnlyOutput).toHaveBeenCalledWith({
versionCode: '100',
});
});
test('reading version with --json flag for platform ios', async () => {
const ctx = mockCommandContext(BuildVersionGetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '100',
storeVersion: '1.0.0',
}));
const cmd = mockTestCommand(
BuildVersionGetView,
['--non-interactive', '--json', '--platform=ios'],
ctx
);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'IOS',
'eas.test.com'
);
expect(enableJsonOutput).toHaveBeenCalled();
expect(printJsonOnlyOutput).toHaveBeenCalledWith({
buildNumber: '100',
});
});
test('reading version with --json flag when no remote version is set', async () => {
const ctx = mockCommandContext(BuildVersionGetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
const cmd = mockTestCommand(
BuildVersionGetView,
['--non-interactive', '--json', '--platform=android'],
ctx
);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
expect(enableJsonOutput).toHaveBeenCalled();
expect(printJsonOnlyOutput).toHaveBeenCalledWith({});
});
test('reading version when appVersionSource is set to local ', async () => {
const ctx = mockCommandContext(BuildVersionGetView, {});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '100',
storeVersion: '1.0.0',
}));
const cmd = mockTestCommand(
BuildVersionGetView,
['--non-interactive', '--json', '--platform=android'],
ctx
);
await expect(cmd.run()).rejects.toThrowErrorMatchingSnapshot();
});
});
@@ -0,0 +1,140 @@
import { AppVersionSource, EasJson } from '@expo/eas-json';
import fs from 'fs-extra';
import path from 'path';
import BuildVersionSetView from '../../commands/build/version/set';
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
import { AppQuery } from '../../graphql/queries/AppQuery';
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
import Log from '../../log';
import * as prompts from '../../prompts';
import {
getMockAppFragment,
getMockEasJson,
mockCommandContext,
mockProjectId,
mockTestCommand,
} from './utils';
jest.mock('../../project/applicationIdentifier');
jest.mock('../../graphql/queries/AppVersionQuery');
jest.mock('../../graphql/queries/AppQuery');
jest.mock('../../graphql/mutations/AppVersionMutation');
jest.mock('fs');
jest.mock('../../log');
jest.mock('../../prompts');
jest.mock('../../utils/json');
function withRemoteVersionSource(easJson: EasJson): EasJson {
return {
...easJson,
cli: {
...easJson.cli,
appVersionSource: AppVersionSource.REMOTE,
},
};
}
describe(BuildVersionSetView, () => {
afterEach(() => {
jest.clearAllMocks();
});
test('setting version for platform android', async () => {
const ctx = mockCommandContext(BuildVersionSetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
expect(AppVersionMutation.createAppVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
expect.objectContaining({
buildVersion: '1000',
storeVersion: '1.0.0',
})
);
});
test('printing current remote version before prompting for a new one', async () => {
const ctx = mockCommandContext(BuildVersionSetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '100',
storeVersion: '1.0.0',
}));
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
await cmd.run();
expect(Log.log).toHaveBeenCalledWith(expect.stringMatching('configured with versionCode 100'));
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
});
test('printing info that there is no remote version configured', async () => {
const ctx = mockCommandContext(BuildVersionSetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
await cmd.run();
expect(Log.log).toHaveBeenCalledWith(
expect.stringMatching('does not have any versionCode configured')
);
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
});
test('setting version aborts when appVersionSource is set to local and users refuse auto configuration', async () => {
const ctx = mockCommandContext(BuildVersionSetView, {});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(prompts.confirmAsync).mockImplementation(async () => false);
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
await expect(cmd.run()).rejects.toThrowError('Aborting...');
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
});
test('setting version when appVersionSource is set to local and user allows auto configuration', async () => {
const ctx = mockCommandContext(BuildVersionSetView, {});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(prompts.confirmAsync).mockImplementation(async () => true);
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
await cmd.run();
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
});
});
@@ -0,0 +1,204 @@
import { Workflow } from '@expo/eas-build-job';
import { AppVersionSource, EasJson } from '@expo/eas-json';
import fs from 'fs-extra';
import path from 'path';
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../build/android/version';
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../build/ios/version';
import BuildVersionSyncView from '../../commands/build/version/sync';
import { Target } from '../../credentials/ios/types';
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
import { AppQuery } from '../../graphql/queries/AppQuery';
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
import { getAppBuildGradleAsync } from '../../project/android/gradleUtils';
import { resolveTargetsAsync } from '../../project/ios/target';
import { resolveWorkflowAsync } from '../../project/workflow';
import * as prompts from '../../prompts';
import {
getMockAppFragment,
getMockEasJson,
mockCommandContext,
mockProjectId,
mockTestCommand,
} from './utils';
jest.mock('../../build/android/version');
jest.mock('../../build/ios/version');
jest.mock('../../project/applicationIdentifier');
jest.mock('../../graphql/queries/AppVersionQuery');
jest.mock('../../graphql/queries/AppQuery');
jest.mock('../../graphql/mutations/AppVersionMutation');
jest.mock('../../project/workflow');
jest.mock('../../project/android/gradleUtils');
jest.mock('../../project/ios/target');
jest.mock('../../project/ios/scheme');
jest.mock('fs');
jest.mock('../../log');
jest.mock('../../prompts');
jest.mock('../../utils/json');
function withRemoteVersionSource(easJson: EasJson): EasJson {
return {
...easJson,
cli: {
...easJson.cli,
appVersionSource: AppVersionSource.REMOTE,
},
};
}
describe(BuildVersionSyncView, () => {
afterEach(() => {
jest.clearAllMocks();
});
test('syncing version for managed project on platform android', async () => {
const ctx = mockCommandContext(BuildVersionSyncView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '1000',
storeVersion: '0.0.1',
}));
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
jest.mocked(resolveWorkflowAsync).mockImplementation(async () => Workflow.MANAGED);
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
const syncAndroidAsync = jest.spyOn(cmd, 'syncAndroidAsync' as any);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
expect(syncAndroidAsync).not.toHaveBeenCalled();
});
test('syncing version for bare project on platform android', async () => {
const ctx = mockCommandContext(BuildVersionSyncView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '1000',
storeVersion: '0.0.1',
}));
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
jest.mocked(resolveWorkflowAsync).mockImplementation(async () => Workflow.GENERIC);
jest.mocked(getAppBuildGradleAsync).mockImplementation(async () => ({
android: {},
}));
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
const syncAndroidAsync = jest.spyOn(cmd, 'syncAndroidAsync' as any);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
expect(syncAndroidAsync).toHaveBeenCalled();
expect(updateAndroidNativeVersionsAsync).toHaveBeenCalledWith({
projectDir: ctx.projectDir,
versionCode: 1000,
});
});
test('syncing version for managed project on platform ios', async () => {
const ctx = mockCommandContext(BuildVersionSyncView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '1000',
storeVersion: '0.0.1',
}));
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
jest.mocked(resolveWorkflowAsync).mockImplementation(async () => Workflow.MANAGED);
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=ios'], ctx);
const syncIosAsync = jest.spyOn(cmd, 'syncIosAsync' as any);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'IOS',
'eas.test.com'
);
expect(syncIosAsync).not.toHaveBeenCalled();
});
test('syncing version for bare project on platform ios', async () => {
const ctx = mockCommandContext(BuildVersionSyncView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
const fakeTarget: Target = {
targetName: 'testapp',
bundleIdentifier: 'eas.test.com',
entitlements: {},
};
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '1000',
storeVersion: '0.0.1',
}));
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
jest.mocked(resolveWorkflowAsync).mockImplementation(async () => Workflow.GENERIC);
jest.mocked(resolveTargetsAsync).mockImplementation(async () => [fakeTarget]);
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=ios'], ctx);
const syncIosAsync = jest.spyOn(cmd, 'syncIosAsync' as any);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'IOS',
'eas.test.com'
);
expect(syncIosAsync).toHaveBeenCalled();
expect(updateIosNativeVersionsAsync).toHaveBeenCalledWith({
buildNumber: '1000',
projectDir: ctx.projectDir,
targets: [fakeTarget],
});
});
test('syncing version aborts when appVersionSource is set to local and users refuse auto configuration', async () => {
const ctx = mockCommandContext(BuildVersionSyncView, {});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(prompts.confirmAsync).mockImplementation(async () => false);
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
await expect(cmd.run()).rejects.toThrowError('Aborting...');
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
});
test('syncing version when appVersionSource is set to local and user allows auto configuration', async () => {
const ctx = mockCommandContext(BuildVersionSyncView, {});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(prompts.confirmAsync).mockImplementation(async () => true);
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
await cmd.run();
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
});
});
@@ -0,0 +1,149 @@
import { ExpoConfig } from '@expo/config-types';
import { EasJson } from '@expo/eas-json';
import { Command, Config } from '@oclif/core';
import { vol } from 'memfs';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { ContextInput, ContextOutput } from '../../commandUtils/EasCommand';
import { DynamicConfigContextFn } from '../../commandUtils/context/DynamicProjectConfigContextField';
import { AppFragment, Role } from '../../graphql/generated';
export function getMockEasJson(): EasJson {
return {
cli: {},
build: {
production: {},
},
};
}
export const mockProjectId = 'fake-project-id';
export function getMockAppFragment(): AppFragment {
return {
id: mockProjectId,
slug: 'testapp',
fullName: '@testuser/testpp',
ownerAccount: {
id: 'test-account-id',
name: 'testuser',
users: [
{
role: Role.Owner,
actor: {
id: 'test-user-id',
},
},
],
},
};
}
export function getMockExpoConfig(): ExpoConfig {
return {
name: 'testapp',
slug: 'tetsapp',
extra: {
projectId: mockProjectId,
},
};
}
export interface LocalProjectContext {
projectDir: string;
exp?: ExpoConfig;
appId?: string;
}
export function mockCommandContext<
C extends {
[name: string]: any;
} = object
>(
commandClass: { contextDefinition: ContextInput<C> },
overrides: {
easJson?: EasJson;
exp?: ExpoConfig;
projectId?: string;
getDynamicPrivateProjectConfigAsync?: DynamicConfigContextFn;
getDynamicPublicProjectConfigAsync?: DynamicConfigContextFn;
}
): ContextOutput<C> {
const projectDir = path.join('/test', uuidv4());
vol.reset();
vol.fromJSON({ 'eas.json': JSON.stringify(overrides.easJson ?? getMockEasJson()) }, projectDir);
const contextDefinition = commandClass.contextDefinition;
const result: any = {};
for (const [contextKey] of Object.entries(contextDefinition)) {
if (contextKey === 'loggedIn') {
result.loggedIn = {};
}
if (contextKey === 'projectDir') {
result.projectDir = projectDir;
}
if (contextKey === 'maybeLoggedIn') {
result.maybeLoggedIn = {};
}
if (contextKey === 'sessionManager') {
result.sessionManager = {};
}
if (contextKey === 'projectConfig') {
result.projectConfig = {
exp: overrides.exp ?? getMockExpoConfig(),
projectId: overrides.projectId ?? mockProjectId,
projectDir,
};
}
if (contextKey === 'getDynamicPrivateProjectConfigAsync') {
result.getDynamicPrivateProjectConfigAsync = () => ({
exp: overrides.exp ?? getMockExpoConfig(),
projectId: overrides.projectId ?? mockProjectId,
projectDir,
});
}
if (contextKey === 'getDynamicPublicProjectConfigAsync') {
result.getDynamicPublicProjectConfigAsync = () => ({
exp: overrides.exp ?? getMockExpoConfig(),
projectId: overrides.projectId ?? mockProjectId,
projectDir,
});
}
}
return result;
}
export function mockTestCommand<T extends Command>(
commandConstructor: new (argv: string[], config: Config) => T,
argv: string[],
ctx: any
): T {
const cmd = new commandConstructor(argv, new Config({ root: __dirname }));
(cmd as any).getContextAsync = jest.fn(() => ctx);
return cmd;
}
export class NoErrorThrownError extends Error {}
export const getErrorAsync = async <TError = any>(
call: () => unknown
): Promise<TError | NoErrorThrownError> => {
try {
await call();
throw new NoErrorThrownError();
} catch (error: unknown) {
return error as TError;
}
};
export const getError = <TError = any>(call: () => unknown): TError | NoErrorThrownError => {
try {
call();
throw new NoErrorThrownError();
} catch (error: unknown) {
return error as TError;
}
};
+12
View File
@@ -2,6 +2,7 @@ import { JSONValue } from '@expo/json-file';
import { ApiV2Error } from './ApiV2Error'; import { ApiV2Error } from './ApiV2Error';
import fetch, { RequestError, RequestInit } from './fetch'; import fetch, { RequestError, RequestInit } from './fetch';
import { getFreePortAsync } from './utils/port';
interface RequestOptions { interface RequestOptions {
body: JSONValue; body: JSONValue;
@@ -104,3 +105,14 @@ export function getEASUpdateURL(projectId: string): string {
return new URL(projectId, `https://u.expo.dev`).href; return new URL(projectId, `https://u.expo.dev`).href;
} }
} }
export async function getSsoLocalServerPortAsync(): Promise<number> {
let startPort: number;
if (process.env.SSO_LOCAL_SERVER_PORT) {
startPort = Number(process.env.SSO_LOCAL_SERVER_PORT);
} else {
startPort = 19300;
}
const port = await getFreePortAsync(startPort);
return port;
}
@@ -1,7 +1,9 @@
import { Platform } from '@expo/eas-build-job'; import { Platform } from '@expo/eas-build-job';
import { CombinedError } from '@urql/core'; import { CombinedError } from '@urql/core';
import { GraphQLError } from 'graphql/error'; import { GraphQLError } from 'graphql/error';
import { v4 as uuidv4 } from 'uuid';
import { getError } from '../../__tests__/commands/utils';
import { EasCommandError } from '../../commandUtils/errors'; import { EasCommandError } from '../../commandUtils/errors';
import Build from '../../commands/build'; import Build from '../../commands/build';
import Log, { link } from '../../log'; import Log, { link } from '../../log';
@@ -11,6 +13,7 @@ import {
EasBuildFreeTierDisabledAndroidError, EasBuildFreeTierDisabledAndroidError,
EasBuildFreeTierDisabledError, EasBuildFreeTierDisabledError,
EasBuildFreeTierDisabledIOSError, EasBuildFreeTierDisabledIOSError,
EasBuildResourceClassNotAvailableInFreeTierError,
EasBuildTooManyPendingBuildsError, EasBuildTooManyPendingBuildsError,
RequestValidationError, RequestValidationError,
TurtleDeprecatedJobFormatError, TurtleDeprecatedJobFormatError,
@@ -42,9 +45,11 @@ const EXPECTED_MAX_BUILD_COUNT_MESSAGE_IOS = `You have already reached the maxim
const EXPECTED_GENERIC_MESSAGE = const EXPECTED_GENERIC_MESSAGE =
'Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, report the issue.'; 'Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, report the issue.';
const mockRequestId = uuidv4();
const getGraphQLError = (message: string, errorCode: string): GraphQLError => { const getGraphQLError = (message: string, errorCode: string): GraphQLError => {
return new GraphQLError(message, null, null, null, null, null, { return new GraphQLError(message, null, null, null, null, null, {
errorCode, errorCode,
requestId: mockRequestId,
}); });
}; };
@@ -114,64 +119,135 @@ describe(Build.name, () => {
}); });
describe('error is server-side defined error', () => { describe('error is server-side defined error', () => {
describe('throws correct EasCommandError subclass', () => {
describe('for TURTLE_DEPRECATED_JOB_FORMAT', () => { describe('for TURTLE_DEPRECATED_JOB_FORMAT', () => {
it('does it with Android', async () => { describe('with Android', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'TURTLE_DEPRECATED_JOB_FORMAT'); const graphQLError = getGraphQLError('Error 1', 'TURTLE_DEPRECATED_JOB_FORMAT');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
try { const handleBuildRequestErrorThrownError = getError<TurtleDeprecatedJobFormatError>(
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, TurtleDeprecatedJobFormatError);
} }
);
assertReThrownError(
handleBuildRequestErrorThrownError,
TurtleDeprecatedJobFormatError,
'Error 1'
);
}); });
it('does it with iOS', async () => { it('throws correct EasBuildResourceClassNotAvailableInFreeTierError', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError(
'Error 1',
'EAS_BUILD_RESOURCE_CLASS_NOT_AVAILABLE_IN_FREE_TIER'
);
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError =
getError<EasBuildResourceClassNotAvailableInFreeTierError>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildResourceClassNotAvailableInFreeTierError,
'Error 1'
);
});
});
describe('with iOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'TURTLE_DEPRECATED_JOB_FORMAT'); const graphQLError = getGraphQLError('Error 1', 'TURTLE_DEPRECATED_JOB_FORMAT');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
try { const handleBuildRequestErrorThrownError = getError<TurtleDeprecatedJobFormatError>(
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, TurtleDeprecatedJobFormatError);
} }
);
assertReThrownError(
handleBuildRequestErrorThrownError,
TurtleDeprecatedJobFormatError,
'Error 1'
);
});
it('throws correct EasBuildResourceClassNotAvailableInFreeTierError', async () => {
const platform = Platform.IOS;
const graphQLError = getGraphQLError(
'Error 1',
'EAS_BUILD_RESOURCE_CLASS_NOT_AVAILABLE_IN_FREE_TIER'
);
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError =
getError<EasBuildResourceClassNotAvailableInFreeTierError>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildResourceClassNotAvailableInFreeTierError,
'Error 1'
);
});
}); });
}); });
describe('for EAS_BUILD_FREE_TIER_DISABLED', () => { describe('for EAS_BUILD_FREE_TIER_DISABLED', () => {
it('does it with Android', async () => { describe('with Android', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
try { const handleBuildRequestErrorThrownError = getError<EasBuildFreeTierDisabledError>(
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, EasBuildFreeTierDisabledError);
} }
}); );
it('does it with iOS', async () => { assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildFreeTierDisabledError,
'Error 1'
);
});
});
describe('with iOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
try { const handleBuildRequestErrorThrownError = getError<EasBuildFreeTierDisabledError>(
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, EasBuildFreeTierDisabledError);
} }
}); );
});
it('does it for EAS_BUILD_FREE_TIER_DISABLED_IOS', async () => { assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildFreeTierDisabledError,
'Error 1'
);
});
});
});
describe('for EAS_BUILD_FREE_TIER_DISABLED_IOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED_IOS'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED_IOS');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
@@ -180,181 +256,247 @@ describe(Build.name, () => {
try { try {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) { } catch (caughtError) {
assertReThrownError(caughtError as Error, EasBuildFreeTierDisabledIOSError); assertReThrownError(caughtError as Error, EasBuildFreeTierDisabledIOSError, 'Error 1');
} }
}); });
});
it('does it for EAS_BUILD_FREE_TIER_DISABLED_ANDROID', async () => { describe('for EAS_BUILD_FREE_TIER_DISABLED_ANDROID', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED_ANDROID'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED_ANDROID');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
try { const handleBuildRequestErrorThrownError = getError<EasBuildFreeTierDisabledAndroidError>(
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, EasBuildFreeTierDisabledAndroidError);
} }
);
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildFreeTierDisabledAndroidError,
'Error 1'
);
});
}); });
describe('for VALIDATION_ERROR', () => { describe('for VALIDATION_ERROR', () => {
it('does it with Android', async () => { describe('with Android', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'VALIDATION_ERROR'); const graphQLError = getGraphQLError('Error 1', 'VALIDATION_ERROR');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
try { const handleBuildRequestErrorThrownError = getError<RequestValidationError>(() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, RequestValidationError);
}
}); });
it('does it with iOS', async () => { assertReThrownError(
handleBuildRequestErrorThrownError,
RequestValidationError,
'Error 1'
);
});
});
describe('with iOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'VALIDATION_ERROR'); const graphQLError = getGraphQLError('Error 1', 'VALIDATION_ERROR');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
try { const handleBuildRequestErrorThrownError = getError<RequestValidationError>(() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) { });
assertReThrownError(caughtError as Error, RequestValidationError);
} assertReThrownError(
handleBuildRequestErrorThrownError,
RequestValidationError,
'Error 1'
);
}); });
}); });
}); });
}); });
describe('error is other expected graphQL-related error', () => { describe('error is other expected graphQL-related error', () => {
describe('throws correct EasCommandError subclass', () => {
describe('for EAS_BUILD_DOWN_FOR_MAINTENANCE', () => { describe('for EAS_BUILD_DOWN_FOR_MAINTENANCE', () => {
it('does it with Android', async () => { describe('with Android', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_DOWN_FOR_MAINTENANCE'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_DOWN_FOR_MAINTENANCE');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
try { const handleBuildRequestErrorThrownError = getError<EasBuildDownForMaintenanceError>(
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) { }
);
assertReThrownError( assertReThrownError(
caughtError as Error, handleBuildRequestErrorThrownError,
EasBuildDownForMaintenanceError, EasBuildDownForMaintenanceError,
EXPECTED_EAS_BUILD_DOWN_MESSAGE EXPECTED_EAS_BUILD_DOWN_MESSAGE
); );
}
}); });
});
it('does it with iOS', async () => { describe('with iOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_DOWN_FOR_MAINTENANCE'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_DOWN_FOR_MAINTENANCE');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
try { const handleBuildRequestErrorThrownError = getError<EasBuildDownForMaintenanceError>(
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) { }
);
assertReThrownError( assertReThrownError(
caughtError as Error, handleBuildRequestErrorThrownError,
EasBuildDownForMaintenanceError, EasBuildDownForMaintenanceError,
EXPECTED_EAS_BUILD_DOWN_MESSAGE EXPECTED_EAS_BUILD_DOWN_MESSAGE
); );
} });
}); });
}); });
describe('for EAS_BUILD_TOO_MANY_PENDING_BUILDS', () => { describe('for EAS_BUILD_TOO_MANY_PENDING_BUILDS', () => {
it('does it with Android', async () => { describe('with Android', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_TOO_MANY_PENDING_BUILDS'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_TOO_MANY_PENDING_BUILDS');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
try { const handleBuildRequestErrorThrownError = getError<EasBuildTooManyPendingBuildsError>(
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) { }
);
assertReThrownError( assertReThrownError(
caughtError as Error, handleBuildRequestErrorThrownError,
EasBuildTooManyPendingBuildsError, EasBuildTooManyPendingBuildsError,
EXPECTED_MAX_BUILD_COUNT_MESSAGE_ANDROID EXPECTED_MAX_BUILD_COUNT_MESSAGE_ANDROID
); );
}
}); });
});
it('does it with iOS', async () => { describe('with iOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_TOO_MANY_PENDING_BUILDS'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_TOO_MANY_PENDING_BUILDS');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
try { const handleBuildRequestErrorThrownError = getError<EasBuildTooManyPendingBuildsError>(
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) { }
);
assertReThrownError( assertReThrownError(
caughtError as Error, handleBuildRequestErrorThrownError,
EasBuildTooManyPendingBuildsError, EasBuildTooManyPendingBuildsError,
EXPECTED_MAX_BUILD_COUNT_MESSAGE_IOS EXPECTED_MAX_BUILD_COUNT_MESSAGE_IOS
); );
}
}); });
}); });
}); });
}); });
describe('error is unexpected graphQL-related error', () => { describe('error is unexpected graphQL-related error', () => {
describe('throws base Error class with custom message', () => { describe('with Android', () => {
it('does it with Android', async () => { it('throws base Error class with custom message', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR'); const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const expectedMessage = `${EXPECTED_GENERIC_MESSAGE}\nRequest ID: ${mockRequestId}\nError message: Error 1`;
try { const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, Error, EXPECTED_GENERIC_MESSAGE);
}
}); });
it('does it with iOS', async () => { assertReThrownError(handleBuildRequestErrorThrownError, Error, expectedMessage);
});
describe('without request ID', () => {
it('throws base Error class with custom message without request ID line', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR');
delete graphQLError.extensions.requestId;
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const expectedMessage = `${EXPECTED_GENERIC_MESSAGE}\nError message: Error 1`;
const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(handleBuildRequestErrorThrownError, Error, expectedMessage);
});
});
});
describe('with iOS', () => {
it('throws base Error class with custom message', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR'); const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const expectedMessage = `${EXPECTED_GENERIC_MESSAGE}\nRequest ID: ${mockRequestId}\nError message: Error 1`;
try { const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) { });
assertReThrownError(caughtError as Error, Error, EXPECTED_GENERIC_MESSAGE);
} assertReThrownError(handleBuildRequestErrorThrownError, Error, expectedMessage);
});
describe('without request ID', () => {
it('throws base Error class with custom message without request ID line', async () => {
const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR');
delete graphQLError.extensions.requestId;
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const expectedMessage = `${EXPECTED_GENERIC_MESSAGE}\nError message: Error 1`;
const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(handleBuildRequestErrorThrownError, Error, expectedMessage);
});
}); });
}); });
}); });
describe('error is unexpected non-graphQL-related error', () => { describe('error is unexpected non-graphQL-related error', () => {
describe('throws base Error class with propagated message', () => { describe('with Android', () => {
it('does it with Android', async () => { it('throws base Error class with propagated message', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const error = new Error('Non-graphQL-related error'); const error = new Error('Non-graphQL-related error');
try { const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
expect(caughtError).toStrictEqual(error);
}
}); });
it('does it with iOS', async () => { expect(handleBuildRequestErrorThrownError).toStrictEqual(error);
});
});
describe('with iOS', () => {
it('throws base Error class with propagated message', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const error = new Error('Non-graphQL-related error'); const error = new Error('Non-graphQL-related error');
try { const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) { });
expect(caughtError).toStrictEqual(error);
} expect(handleBuildRequestErrorThrownError).toStrictEqual(error);
}); });
}); });
}); });
@@ -24,7 +24,7 @@ interface JobData {
const cacheDefaults = { const cacheDefaults = {
disabled: false, disabled: false,
customPaths: [], paths: [],
}; };
export async function prepareJobAsync( export async function prepareJobAsync(
+33 -4
View File
@@ -42,6 +42,8 @@ import {
EasBuildFreeTierDisabledAndroidError, EasBuildFreeTierDisabledAndroidError,
EasBuildFreeTierDisabledError, EasBuildFreeTierDisabledError,
EasBuildFreeTierDisabledIOSError, EasBuildFreeTierDisabledIOSError,
EasBuildProjectArchiveUploadError,
EasBuildResourceClassNotAvailableInFreeTierError,
EasBuildTooManyPendingBuildsError, EasBuildTooManyPendingBuildsError,
RequestValidationError, RequestValidationError,
TurtleDeprecatedJobFormatError, TurtleDeprecatedJobFormatError,
@@ -80,9 +82,14 @@ interface Builder<TPlatform extends Platform, Credentials, TJob extends Job> {
export type BuildRequestSender = () => Promise<BuildFragment | undefined>; export type BuildRequestSender = () => Promise<BuildFragment | undefined>;
function resolveBuildParamsInput<T extends Platform>(ctx: BuildContext<T>): BuildParamsInput { function resolveBuildParamsInput<T extends Platform>(
ctx: BuildContext<T>,
metadata: Metadata
): BuildParamsInput {
return { return {
resourceClass: ctx.resourceClass, resourceClass: ctx.resourceClass,
sdkVersion: metadata.sdkVersion,
reactNativeVersion: metadata.reactNativeVersion,
}; };
} }
@@ -142,7 +149,7 @@ export async function prepareBuildRequestForPlatformAsync<
assert(projectArchive); assert(projectArchive);
const metadata = await collectMetadataAsync(ctx); const metadata = await collectMetadataAsync(ctx);
const buildParams = resolveBuildParamsInput(ctx); const buildParams = resolveBuildParamsInput(ctx, metadata);
const job = await builder.prepareJobAsync(ctx, { const job = await builder.prepareJobAsync(ctx, {
projectArchive, projectArchive,
credentials: credentialsResult?.credentials, credentials: credentialsResult?.credentials,
@@ -176,6 +183,8 @@ const SERVER_SIDE_DEFINED_ERRORS: Record<string, typeof EasCommandError> = {
EAS_BUILD_FREE_TIER_DISABLED: EasBuildFreeTierDisabledError, EAS_BUILD_FREE_TIER_DISABLED: EasBuildFreeTierDisabledError,
EAS_BUILD_FREE_TIER_DISABLED_IOS: EasBuildFreeTierDisabledIOSError, EAS_BUILD_FREE_TIER_DISABLED_IOS: EasBuildFreeTierDisabledIOSError,
EAS_BUILD_FREE_TIER_DISABLED_ANDROID: EasBuildFreeTierDisabledAndroidError, EAS_BUILD_FREE_TIER_DISABLED_ANDROID: EasBuildFreeTierDisabledAndroidError,
EAS_BUILD_RESOURCE_CLASS_NOT_AVAILABLE_IN_FREE_TIER:
EasBuildResourceClassNotAvailableInFreeTierError,
VALIDATION_ERROR: RequestValidationError, VALIDATION_ERROR: RequestValidationError,
}; };
@@ -197,8 +206,15 @@ export function handleBuildRequestError(error: any, platform: Platform): never {
`You have already reached the maximum number of pending ${requestedPlatformDisplayNames[platform]} builds for your account. Try again later.` `You have already reached the maximum number of pending ${requestedPlatformDisplayNames[platform]} builds for your account. Try again later.`
); );
} else if (error?.graphQLErrors) { } else if (error?.graphQLErrors) {
const graphQLError = error.graphQLErrors[0];
const requestIdLine = graphQLError?.extensions?.requestId
? `\nRequest ID: ${graphQLError.extensions.requestId}`
: '';
const originalErrorMessage = graphQLError?.message
? `\nError message: ${graphQLError.message}`
: '';
throw new Error( throw new Error(
'Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, report the issue.' `Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, report the issue.${requestIdLine}${originalErrorMessage}`
); );
} }
throw error; throw error;
@@ -254,6 +270,12 @@ async function uploadProjectAsync<TPlatform extends Platform>(
properties: ctx.analyticsEventProperties, properties: ctx.analyticsEventProperties,
} }
); );
} catch (err: any) {
let errMessage = 'Failed to upload the project tarball to EAS Build';
if (err.message) {
errMessage += `\n\nReason: ${err.message}`;
}
throw new EasBuildProjectArchiveUploadError(errMessage);
} finally { } finally {
if (projectTarballPath) { if (projectTarballPath) {
await fs.remove(projectTarballPath); await fs.remove(projectTarballPath);
@@ -483,6 +505,7 @@ async function handleSingleBuildProgressAsync(
} }
break; break;
} }
case BuildStatus.PendingCancel:
case BuildStatus.Canceled: case BuildStatus.Canceled:
spinner.fail('Build canceled'); spinner.fail('Build canceled');
return { refetch: false }; return { refetch: false };
@@ -513,6 +536,7 @@ const statusToDisplayName: Record<BuildStatus, string> = {
[BuildStatus.New]: 'waiting to enter the queue (concurrency limit reached)', [BuildStatus.New]: 'waiting to enter the queue (concurrency limit reached)',
[BuildStatus.InQueue]: 'in queue', [BuildStatus.InQueue]: 'in queue',
[BuildStatus.InProgress]: 'in progress', [BuildStatus.InProgress]: 'in progress',
[BuildStatus.PendingCancel]: 'canceled',
[BuildStatus.Canceled]: 'canceled', [BuildStatus.Canceled]: 'canceled',
[BuildStatus.Finished]: 'finished', [BuildStatus.Finished]: 'finished',
[BuildStatus.Errored]: 'failed', [BuildStatus.Errored]: 'failed',
@@ -531,7 +555,12 @@ async function handleMultipleBuildsProgressAsync(
builds.filter(build => build.status === BuildStatus.Finished).length === buildCount; builds.filter(build => build.status === BuildStatus.Finished).length === buildCount;
const allSettled = const allSettled =
builds.filter(build => builds.filter(build =>
[BuildStatus.Finished, BuildStatus.Errored, BuildStatus.Canceled].includes(build.status) [
BuildStatus.Finished,
BuildStatus.Errored,
BuildStatus.Canceled,
BuildStatus.PendingCancel,
].includes(build.status)
).length === buildCount; ).length === buildCount;
if (allSettled) { if (allSettled) {
+14 -24
View File
@@ -1,9 +1,9 @@
import { Platform, Workflow } from '@expo/eas-build-job'; import { Platform, Workflow } from '@expo/eas-build-job';
import { EasJson, EasJsonAccessor, ResourceClass } from '@expo/eas-json'; import { EasJson, EasJsonAccessor } from '@expo/eas-json';
import chalk from 'chalk'; import chalk from 'chalk';
import fs from 'fs-extra'; import fs from 'fs-extra';
import Log from '../log'; import Log, { learnMore } from '../log';
import { resolveWorkflowAsync } from '../project/workflow'; import { resolveWorkflowAsync } from '../project/workflow';
import { easCliVersion } from '../utils/easCli'; import { easCliVersion } from '../utils/easCli';
import { getVcsClient } from '../vcs'; import { getVcsClient } from '../vcs';
@@ -14,6 +14,10 @@ interface ConfigureParams {
nonInteractive: boolean; nonInteractive: boolean;
} }
export async function easJsonExistsAsync(projectDir: string): Promise<boolean> {
return await fs.pathExists(EasJsonAccessor.formatEasJsonPath(projectDir));
}
/** /**
* Creates eas.json if it does not exist. * Creates eas.json if it does not exist.
* *
@@ -24,7 +28,7 @@ interface ConfigureParams {
export async function ensureProjectConfiguredAsync( export async function ensureProjectConfiguredAsync(
configureParams: ConfigureParams configureParams: ConfigureParams
): Promise<boolean> { ): Promise<boolean> {
if (await fs.pathExists(EasJsonAccessor.formatEasJsonPath(configureParams.projectDir))) { if (await easJsonExistsAsync(configureParams.projectDir)) {
return false; return false;
} }
@@ -53,21 +57,11 @@ const EAS_JSON_MANAGED_DEFAULT: EasJson = {
development: { development: {
developmentClient: true, developmentClient: true,
distribution: 'internal', distribution: 'internal',
ios: {
resourceClass: ResourceClass.M_MEDIUM,
},
}, },
preview: { preview: {
distribution: 'internal', distribution: 'internal',
ios: {
resourceClass: ResourceClass.M_MEDIUM,
},
},
production: {
ios: {
resourceClass: ResourceClass.M_MEDIUM,
},
}, },
production: {},
}, },
submit: { submit: {
production: {}, production: {},
@@ -86,20 +80,12 @@ const EAS_JSON_BARE_DEFAULT: EasJson = {
}, },
ios: { ios: {
buildConfiguration: 'Debug', buildConfiguration: 'Debug',
resourceClass: ResourceClass.M_MEDIUM,
}, },
}, },
preview: { preview: {
distribution: 'internal', distribution: 'internal',
ios: {
resourceClass: ResourceClass.M_MEDIUM,
},
},
production: {
ios: {
resourceClass: ResourceClass.M_MEDIUM,
},
}, },
production: {},
}, },
submit: { submit: {
production: {}, production: {},
@@ -120,5 +106,9 @@ async function createEasJsonAsync(projectDir: string): Promise<void> {
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`); await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
await getVcsClient().trackFileAsync(easJsonPath); await getVcsClient().trackFileAsync(easJsonPath);
Log.withTick(`Generated ${chalk.bold('eas.json')}`); Log.withTick(
`Generated ${chalk.bold('eas.json')}. ${learnMore(
'https://docs.expo.dev/build-reference/eas-json/'
)}`
);
} }
+2
View File
@@ -8,6 +8,7 @@ import { CredentialsContext } from '../credentials/context';
import { Target } from '../credentials/ios/types'; import { Target } from '../credentials/ios/types';
import { BuildResourceClass } from '../graphql/generated'; import { BuildResourceClass } from '../graphql/generated';
import { GradleBuildContext } from '../project/android/gradle'; import { GradleBuildContext } from '../project/android/gradle';
import { CustomBuildConfigMetadata } from '../project/customBuildConfig';
import { XcodeBuildContext } from '../project/ios/scheme'; import { XcodeBuildContext } from '../project/ios/scheme';
import { Actor } from '../user/User'; import { Actor } from '../user/User';
import { LocalBuildOptions } from './local'; import { LocalBuildOptions } from './local';
@@ -51,6 +52,7 @@ export interface BuildContext<T extends Platform> {
graphqlClient: ExpoGraphqlClient; graphqlClient: ExpoGraphqlClient;
analytics: Analytics; analytics: Analytics;
workflow: Workflow; workflow: Workflow;
customBuildConfigMetadata?: CustomBuildConfigMetadata;
android: T extends Platform.ANDROID ? AndroidBuildContext : undefined; android: T extends Platform.ANDROID ? AndroidBuildContext : undefined;
ios: T extends Platform.IOS ? IosBuildContext : undefined; ios: T extends Platform.IOS ? IosBuildContext : undefined;
} }
+7 -5
View File
@@ -9,6 +9,7 @@ import { Analytics, AnalyticsEventProperties, BuildEvent } from '../analytics/An
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField'; import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient'; import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { CredentialsContext } from '../credentials/context'; import { CredentialsContext } from '../credentials/context';
import { CustomBuildConfigMetadata } from '../project/customBuildConfig';
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils'; import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
import { resolveWorkflowAsync } from '../project/workflow'; import { resolveWorkflowAsync } from '../project/workflow';
import { Actor } from '../user/User'; import { Actor } from '../user/User';
@@ -33,7 +34,8 @@ export async function createBuildContextAsync<T extends Platform>({
actor, actor,
graphqlClient, graphqlClient,
analytics, analytics,
getDynamicProjectConfigAsync, getDynamicPrivateProjectConfigAsync,
customBuildConfigMetadata,
}: { }: {
buildProfileName: string; buildProfileName: string;
buildProfile: BuildProfile<T>; buildProfile: BuildProfile<T>;
@@ -49,9 +51,10 @@ export async function createBuildContextAsync<T extends Platform>({
actor: Actor; actor: Actor;
graphqlClient: ExpoGraphqlClient; graphqlClient: ExpoGraphqlClient;
analytics: Analytics; analytics: Analytics;
getDynamicProjectConfigAsync: DynamicConfigContextFn; getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
customBuildConfigMetadata?: CustomBuildConfigMetadata;
}): Promise<BuildContext<T>> { }): Promise<BuildContext<T>> {
const { exp, projectId } = await getDynamicProjectConfigAsync({ env: buildProfile.env }); const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env });
const projectName = exp.slug; const projectName = exp.slug;
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId); const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
const workflow = await resolveWorkflowAsync(projectDir, platform); const workflow = await resolveWorkflowAsync(projectDir, platform);
@@ -90,8 +93,6 @@ export async function createBuildContextAsync<T extends Platform>({
const resourceClass = await resolveBuildResourceClassAsync( const resourceClass = await resolveBuildResourceClassAsync(
buildProfile, buildProfile,
platform, platform,
projectDir,
exp,
resourceClassFlag resourceClassFlag
); );
@@ -118,6 +119,7 @@ export async function createBuildContextAsync<T extends Platform>({
workflow, workflow,
message, message,
runFromCI, runFromCI,
customBuildConfigMetadata,
}; };
if (platform === Platform.ANDROID) { if (platform === Platform.ANDROID) {
const common = commonContext as CommonContext<Platform.ANDROID>; const common = commonContext as CommonContext<Platform.ANDROID>;
+4
View File
@@ -8,8 +8,12 @@ export class EasBuildFreeTierDisabledIOSError extends EasCommandError {}
export class EasBuildFreeTierDisabledAndroidError extends EasCommandError {} export class EasBuildFreeTierDisabledAndroidError extends EasCommandError {}
export class EasBuildResourceClassNotAvailableInFreeTierError extends EasCommandError {}
export class RequestValidationError extends EasCommandError {} export class RequestValidationError extends EasCommandError {}
export class EasBuildDownForMaintenanceError extends EasCommandError {} export class EasBuildDownForMaintenanceError extends EasCommandError {}
export class EasBuildTooManyPendingBuildsError extends EasCommandError {} export class EasBuildTooManyPendingBuildsError extends EasCommandError {}
export class EasBuildProjectArchiveUploadError extends EasCommandError {}
@@ -77,7 +77,7 @@ describe(bumpVersionAsync, () => {
); );
await bumpVersionAsync({ await bumpVersionAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER, bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/repo', projectDir: '/app',
exp: fakeExp, exp: fakeExp,
targets: [ targets: [
{ {
@@ -88,9 +88,9 @@ describe(bumpVersionAsync, () => {
], ],
}); });
const appJSON = await fs.readJSON('/repo/app.json'); const appJSON = await fs.readJSON('/app/app.json');
const infoPlist = (await readPlistAsync( const infoPlist = (await readPlistAsync(
'/repo/ios/myproject/Info.plist' '/app/ios/myproject/Info.plist'
)) as IOSConfig.InfoPlist; )) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.0'); expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('2'); expect(fakeExp.ios?.buildNumber).toBe('2');
@@ -113,7 +113,7 @@ describe(bumpVersionAsync, () => {
); );
await bumpVersionAsync({ await bumpVersionAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER, bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/repo', projectDir: '/app',
exp: fakeExp, exp: fakeExp,
targets: [ targets: [
{ {
@@ -124,9 +124,9 @@ describe(bumpVersionAsync, () => {
], ],
}); });
const appJSON = await fs.readJSON('/repo/app.json'); const appJSON = await fs.readJSON('/app/app.json');
const infoPlist = (await readPlistAsync( const infoPlist = (await readPlistAsync(
'/repo/ios/myproject/Info2.plist' '/app/ios/myproject/Info2.plist'
)) as IOSConfig.InfoPlist; )) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.0'); expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('2'); expect(fakeExp.ios?.buildNumber).toBe('2');
@@ -148,7 +148,7 @@ describe(bumpVersionAsync, () => {
); );
await bumpVersionAsync({ await bumpVersionAsync({
bumpStrategy: BumpStrategy.APP_VERSION, bumpStrategy: BumpStrategy.APP_VERSION,
projectDir: '/repo', projectDir: '/app',
exp: fakeExp, exp: fakeExp,
targets: [ targets: [
{ {
@@ -159,9 +159,9 @@ describe(bumpVersionAsync, () => {
], ],
}); });
const appJSON = await fs.readJSON('/repo/app.json'); const appJSON = await fs.readJSON('/app/app.json');
const infoPlist = (await readPlistAsync( const infoPlist = (await readPlistAsync(
'/repo/ios/myproject/Info.plist' '/app/ios/myproject/Info.plist'
)) as IOSConfig.InfoPlist; )) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.1'); expect(fakeExp.version).toBe('1.0.1');
expect(fakeExp.ios?.buildNumber).toBe('1'); expect(fakeExp.ios?.buildNumber).toBe('1');
@@ -183,7 +183,7 @@ describe(bumpVersionAsync, () => {
); );
await bumpVersionAsync({ await bumpVersionAsync({
bumpStrategy: BumpStrategy.NOOP, bumpStrategy: BumpStrategy.NOOP,
projectDir: '/repo', projectDir: '/app',
exp: fakeExp, exp: fakeExp,
targets: [ targets: [
{ {
@@ -194,9 +194,9 @@ describe(bumpVersionAsync, () => {
], ],
}); });
const appJSON = await fs.readJSON('/repo/app.json'); const appJSON = await fs.readJSON('/app/app.json');
const infoPlist = (await readPlistAsync( const infoPlist = (await readPlistAsync(
'/repo/ios/myproject/Info.plist' '/app/ios/myproject/Info.plist'
)) as IOSConfig.InfoPlist; )) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.0'); expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('1'); expect(fakeExp.ios?.buildNumber).toBe('1');
@@ -214,11 +214,11 @@ describe(bumpVersionInAppJsonAsync, () => {
await bumpVersionInAppJsonAsync({ await bumpVersionInAppJsonAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER, bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/repo', projectDir: '/app',
exp: fakeExp, exp: fakeExp,
}); });
const appJSON = await fs.readJSON('/repo/app.json'); const appJSON = await fs.readJSON('/app/app.json');
expect(fakeExp.version).toBe('1.0.0'); expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('2'); expect(fakeExp.ios?.buildNumber).toBe('2');
expect(appJSON.expo.version).toBe('1.0.0'); expect(appJSON.expo.version).toBe('1.0.0');
@@ -230,11 +230,11 @@ describe(bumpVersionInAppJsonAsync, () => {
await bumpVersionInAppJsonAsync({ await bumpVersionInAppJsonAsync({
bumpStrategy: BumpStrategy.APP_VERSION, bumpStrategy: BumpStrategy.APP_VERSION,
projectDir: '/repo', projectDir: '/app',
exp: fakeExp, exp: fakeExp,
}); });
const appJSON = await fs.readJSON('/repo/app.json'); const appJSON = await fs.readJSON('/app/app.json');
expect(fakeExp.version).toBe('1.0.1'); expect(fakeExp.version).toBe('1.0.1');
expect(fakeExp.ios?.buildNumber).toBe('1'); expect(fakeExp.ios?.buildNumber).toBe('1');
expect(appJSON.expo.version).toBe('1.0.1'); expect(appJSON.expo.version).toBe('1.0.1');
@@ -246,11 +246,11 @@ describe(bumpVersionInAppJsonAsync, () => {
await bumpVersionInAppJsonAsync({ await bumpVersionInAppJsonAsync({
bumpStrategy: BumpStrategy.NOOP, bumpStrategy: BumpStrategy.NOOP,
projectDir: '/repo', projectDir: '/app',
exp: fakeExp, exp: fakeExp,
}); });
const appJSON = await fs.readJSON('/repo/app.json'); const appJSON = await fs.readJSON('/app/app.json');
expect(fakeExp.version).toBe('1.0.0'); expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('1'); expect(fakeExp.ios?.buildNumber).toBe('1');
expect(appJSON.expo.version).toBe('1.0.0'); expect(appJSON.expo.version).toBe('1.0.0');
@@ -262,7 +262,7 @@ describe(readBuildNumberAsync, () => {
describe('bare project', () => { describe('bare project', () => {
it('reads the build number from native code', async () => { it('reads the build number from native code', async () => {
const exp = initBareWorkflowProject(); const exp = initBareWorkflowProject();
const buildNumber = await readBuildNumberAsync('/repo', exp, {}); const buildNumber = await readBuildNumberAsync('/app', exp, {});
expect(buildNumber).toBe('1'); expect(buildNumber).toBe('1');
}); });
}); });
@@ -270,7 +270,7 @@ describe(readBuildNumberAsync, () => {
describe('managed project', () => { describe('managed project', () => {
it('reads the build number from expo config', async () => { it('reads the build number from expo config', async () => {
const exp = initManagedProject(); const exp = initManagedProject();
const buildNumber = await readBuildNumberAsync('/repo', exp, {}); const buildNumber = await readBuildNumberAsync('/app', exp, {});
expect(buildNumber).toBe('1'); expect(buildNumber).toBe('1');
}); });
}); });
@@ -280,24 +280,38 @@ describe(readShortVersionAsync, () => {
describe('bare project', () => { describe('bare project', () => {
it('reads the short version from native code', async () => { it('reads the short version from native code', async () => {
const exp = initBareWorkflowProject(); const exp = initBareWorkflowProject();
const appVersion = await readShortVersionAsync('/repo', exp, {}); const appVersion = await readShortVersionAsync('/app', exp, {});
expect(appVersion).toBe('1.0.0'); expect(appVersion).toBe('1.0.0');
}); });
it('evaluates interpolated build number', async () => { it('evaluates interpolated build number', async () => {
const exp = initBareWorkflowProject({ const exp = initBareWorkflowProject({
appVersion: '$(CURRENT_PROJECT_VERSION)', appVersion: '$(CURRENT_PROJECT_VERSION)',
}); });
const buildNumber = await readShortVersionAsync('/repo', exp, { const buildNumber = await readShortVersionAsync('/app', exp, {
CURRENT_PROJECT_VERSION: '1.0.0', CURRENT_PROJECT_VERSION: '1.0.0',
}); });
expect(buildNumber).toBe('1.0.0'); expect(buildNumber).toBe('1.0.0');
}); });
it('fails when build number is invalid', async () => {
const exp = initBareWorkflowProject({
appVersion: '$(CURRENT_PROJECT_VERSION)',
});
await expect(
readShortVersionAsync('/app', exp, {
CURRENT_PROJECT_VERSION: '0.0.7.1.028',
})
).rejects.toThrowError(
'CFBundleShortVersionString (version field in app.json/app.config.js) must be a period-separated list of three non-negative integers. Current value: 0.0.7.1.028'
);
});
}); });
describe('managed project', () => { describe('managed project', () => {
it('reads the version from app config', async () => { it('reads the version from app config', async () => {
const exp = initBareWorkflowProject(); const exp = initBareWorkflowProject();
const appVersion = await readShortVersionAsync('/repo', exp, {}); const appVersion = await readShortVersionAsync('/app', exp, {});
expect(appVersion).toBe('1.0.0'); expect(appVersion).toBe('1.0.0');
}); });
}); });
@@ -309,66 +323,75 @@ describe(getInfoPlistPath, () => {
{ {
'./ios/testapp/Info.plist': '', './ios/testapp/Info.plist': '',
}, },
'/repo' '/app'
); );
const plistPath = getInfoPlistPath('/repo', {}); const plistPath = getInfoPlistPath('/app', {});
expect(plistPath).toBe('/repo/ios/testapp/Info.plist'); expect(plistPath).toBe('/app/ios/testapp/Info.plist');
}); });
it('returns INFOPLIST_FILE if specified', () => { it('returns INFOPLIST_FILE if specified', () => {
vol.fromJSON( vol.fromJSON(
{ {
'./ios/testapp/Info.plist': '', './ios/testapp/Info.plist': '',
}, },
'/repo' '/app'
); );
const plistPath = getInfoPlistPath('/repo', { INFOPLIST_FILE: './qwert/NotInfo.plist' }); const plistPath = getInfoPlistPath('/app', { INFOPLIST_FILE: './qwert/NotInfo.plist' });
expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist'); expect(plistPath).toBe('/app/ios/qwert/NotInfo.plist');
}); });
it('evaluates SRCROOT in Info.plist', () => { it('evaluates SRCROOT in Info.plist', () => {
vol.fromJSON( vol.fromJSON(
{ {
'./ios/testapp/Info.plist': '', './ios/testapp/Info.plist': '',
}, },
'/repo' '/app'
); );
const plistPath = getInfoPlistPath('/repo', { const plistPath = getInfoPlistPath('/app', {
INFOPLIST_FILE: '$(SRCROOT)/qwert/NotInfo.plist', INFOPLIST_FILE: '$(SRCROOT)/qwert/NotInfo.plist',
}); });
expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist'); expect(plistPath).toBe('/app/ios/qwert/NotInfo.plist');
}); });
it('evaluates BuildSettings in Info.plist', () => { it('evaluates BuildSettings in Info.plist', () => {
vol.fromJSON( vol.fromJSON(
{ {
'./ios/testapp/Info.plist': '', './ios/testapp/Info.plist': '',
}, },
'/repo' '/app'
); );
const plistPath = getInfoPlistPath('/repo', { const plistPath = getInfoPlistPath('/app', {
INFOPLIST_FILE: '$(SRCROOT)/qwert/$(TARGET_NAME).plist', INFOPLIST_FILE: '$(SRCROOT)/qwert/$(TARGET_NAME).plist',
TARGET_NAME: 'NotInfo', TARGET_NAME: 'NotInfo',
}); });
expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist'); expect(plistPath).toBe('/app/ios/qwert/NotInfo.plist');
}); });
}); });
function initBareWorkflowProject({ function initBareWorkflowProject({
appVersion = '1.0.0', appVersion = '1.0.0',
buildNumber = '1', buildNumber = '1',
expoConfig = {},
infoPlistName = 'Info.plist', infoPlistName = 'Info.plist',
}: { appVersion?: string; buildNumber?: string; infoPlistName?: string } = {}): ExpoConfig { }: {
appVersion?: string;
buildNumber?: string;
infoPlistName?: string;
expoConfig?: { version?: string; ios?: object };
} = {}): ExpoConfig {
const fakeExp: ExpoConfig = { const fakeExp: ExpoConfig = {
name: 'myproject', name: 'myproject',
slug: 'myproject', slug: 'myproject',
version: '1.0.0', version: '1.0.0',
ios: { ios: {
buildNumber: '1', buildNumber: '1',
...expoConfig.ios,
}, },
...expoConfig,
}; };
vol.fromJSON( vol.fromJSON(
{ {
'./app.json': JSON.stringify({ './app.json': JSON.stringify({
expo: fakeExp, expo: fakeExp,
}), }),
'./ios/myproject.xcodeproj/project.pbxproj': '',
[`./ios/myproject/${infoPlistName}`]: `<?xml version="1.0" encoding="UTF-8"?> [`./ios/myproject/${infoPlistName}`]: `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
@@ -380,7 +403,7 @@ function initBareWorkflowProject({
</dict> </dict>
</plist>`, </plist>`,
}, },
'/repo' '/app'
); );
return fakeExp; return fakeExp;
@@ -402,7 +425,7 @@ function initManagedProject(): ExpoConfig {
expo: fakeExp, expo: fakeExp,
}), }),
}, },
'/repo' '/app'
); );
return fakeExp; return fakeExp;
+1 -1
View File
@@ -27,7 +27,7 @@ interface JobData {
const cacheDefaults = { const cacheDefaults = {
disabled: false, disabled: false,
customPaths: [], paths: [],
}; };
export async function prepareJobAsync( export async function prepareJobAsync(
+17 -3
View File
@@ -22,6 +22,8 @@ import { readPlistAsync, writePlistAsync } from '../../utils/plist';
import { updateAppJsonConfigAsync } from '../utils/appJson'; import { updateAppJsonConfigAsync } from '../utils/appJson';
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version'; import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
const SHORT_VERSION_REGEX = /^\d+.\d+.\d+$/;
export enum BumpStrategy { export enum BumpStrategy {
APP_VERSION, APP_VERSION,
BUILD_NUMBER, BUILD_NUMBER,
@@ -97,6 +99,14 @@ export async function bumpVersionInAppJsonAsync({
} }
} }
function validateShortVersion(shortVersion: string | undefined): void {
if (shortVersion && !SHORT_VERSION_REGEX.test(shortVersion)) {
throw new Error(
`CFBundleShortVersionString (version field in app.json/app.config.js) must be a period-separated list of three non-negative integers. Current value: ${shortVersion}`
);
}
}
export async function readShortVersionAsync( export async function readShortVersionAsync(
projectDir: string, projectDir: string,
exp: ExpoConfig, exp: ExpoConfig,
@@ -105,11 +115,15 @@ export async function readShortVersionAsync(
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS); const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
if (workflow === Workflow.GENERIC) { if (workflow === Workflow.GENERIC) {
const infoPlist = await readInfoPlistAsync(projectDir, buildSettings); const infoPlist = await readInfoPlistAsync(projectDir, buildSettings);
return (
const shortVersion =
infoPlist.CFBundleShortVersionString && infoPlist.CFBundleShortVersionString &&
evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings) evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings);
);
validateShortVersion(shortVersion);
return shortVersion;
} else { } else {
validateShortVersion(exp.version);
return exp.version; return exp.version;
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@ import semver from 'semver';
import { ora } from '../ora'; import { ora } from '../ora';
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin'; const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
const PLUGIN_PACKAGE_VERSION = '0.0.128'; const PLUGIN_PACKAGE_VERSION = '1.0.24';
export enum LocalBuildMode { export enum LocalBuildMode {
/** /**
+6 -1
View File
@@ -19,6 +19,7 @@ import { getVcsClient } from '../vcs';
import { maybeResolveVersionsAsync as maybeResolveAndroidVersionsAsync } from './android/version'; import { maybeResolveVersionsAsync as maybeResolveAndroidVersionsAsync } from './android/version';
import { BuildContext } from './context'; import { BuildContext } from './context';
import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios/version'; import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios/version';
import { LocalBuildMode } from './local';
export async function collectMetadataAsync<T extends Platform>( export async function collectMetadataAsync<T extends Platform>(
ctx: BuildContext<T> ctx: BuildContext<T>
@@ -47,7 +48,10 @@ export async function collectMetadataAsync<T extends Platform>(
gitCommitMessage: truncateGitCommitMessage( gitCommitMessage: truncateGitCommitMessage(
(await vcsClient.getLastCommitMessageAsync()) ?? undefined (await vcsClient.getLastCommitMessageAsync()) ?? undefined
), ),
isGitWorkingTreeDirty: await vcsClient.hasUncommittedChangesAsync(), isGitWorkingTreeDirty:
ctx.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL
? false
: await vcsClient.hasUncommittedChangesAsync(),
username: getUsername(ctx.exp, ctx.user), username: getUsername(ctx.exp, ctx.user),
message: ctx.message, message: ctx.message,
...(ctx.platform === Platform.IOS && { ...(ctx.platform === Platform.IOS && {
@@ -58,6 +62,7 @@ export async function collectMetadataAsync<T extends Platform>(
runWithNoWaitFlag: ctx.noWait, runWithNoWaitFlag: ctx.noWait,
runFromCI: ctx.runFromCI, runFromCI: ctx.runFromCI,
buildMode: ctx.buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD, buildMode: ctx.buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
customWorkflowName: ctx.customBuildConfigMetadata?.workflowName,
}; };
return sanitizeMetadata(metadata); return sanitizeMetadata(metadata);
} }
+1
View File
@@ -132,6 +132,7 @@ function createBuildToPartialChoiceMaker(
name: build.platform === AppPlatform.Ios ? 'Build number' : 'Version code', name: build.platform === AppPlatform.Ios ? 'Build number' : 'Version code',
value: build.appBuildVersion ? chalk.bold(build.appBuildVersion) : null, value: build.appBuildVersion ? chalk.bold(build.appBuildVersion) : null,
}, },
{ name: 'Profile', value: build.buildProfile ? chalk.bold(build.buildProfile) : null },
]; ];
const filteredDescriptionArray: string[] = descriptionItems const filteredDescriptionArray: string[] = descriptionItems
+75 -10
View File
@@ -31,10 +31,18 @@ import {
appPlatformEmojis, appPlatformEmojis,
toPlatforms, toPlatforms,
} from '../platform'; } from '../platform';
import { validateCustomBuildConfigAsync } from '../project/customBuildConfig'; import {
CustomBuildConfigMetadata,
validateCustomBuildConfigAsync,
} from '../project/customBuildConfig';
import { checkExpoSdkIsSupportedAsync } from '../project/expoSdk'; import { checkExpoSdkIsSupportedAsync } from '../project/expoSdk';
import { validateMetroConfigForManagedWorkflowAsync } from '../project/metroConfig'; import { validateMetroConfigForManagedWorkflowAsync } from '../project/metroConfig';
import { validateAppVersionRuntimePolicySupportAsync } from '../project/projectUtils'; import {
installExpoUpdatesAsync,
isExpoUpdatesInstalledAsDevDependency,
isExpoUpdatesInstalledOrAvailable,
validateAppVersionRuntimePolicySupportAsync,
} from '../project/projectUtils';
import { import {
validateAppConfigForRemoteVersionSource, validateAppConfigForRemoteVersionSource,
validateBuildProfileVersionSettings, validateBuildProfileVersionSettings,
@@ -90,7 +98,7 @@ export async function runBuildAndSubmitAsync(
projectDir: string, projectDir: string,
flags: BuildFlags, flags: BuildFlags,
actor: Actor, actor: Actor,
getDynamicProjectConfigAsync: DynamicConfigContextFn getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn
): Promise<void> { ): Promise<void> {
await getVcsClient().ensureRepoExistsAsync(); await getVcsClient().ensureRepoExistsAsync();
await ensureRepoIsCleanAsync(flags.nonInteractive); await ensureRepoIsCleanAsync(flags.nonInteractive);
@@ -117,9 +125,14 @@ export async function runBuildAndSubmitAsync(
buildProfiles, buildProfiles,
}); });
const customBuildConfigMetadataByPlatform: { [p in AppPlatform]?: CustomBuildConfigMetadata } =
{};
for (const buildProfile of buildProfiles) { for (const buildProfile of buildProfiles) {
validateBuildProfileVersionSettings(buildProfile, easJsonCliConfig); validateBuildProfileVersionSettings(buildProfile, easJsonCliConfig);
await validateCustomBuildConfigAsync(projectDir, buildProfile.profile); const maybeMetadata = await validateCustomBuildConfigAsync(projectDir, buildProfile.profile);
if (maybeMetadata) {
customBuildConfigMetadataByPlatform[toAppPlatform(buildProfile.platform)] = maybeMetadata;
}
} }
const startedBuilds: { const startedBuilds: {
@@ -129,6 +142,7 @@ export async function runBuildAndSubmitAsync(
const buildCtxByPlatform: { [p in AppPlatform]?: BuildContext<Platform> } = {}; const buildCtxByPlatform: { [p in AppPlatform]?: BuildContext<Platform> } = {};
for (const buildProfile of buildProfiles) { for (const buildProfile of buildProfiles) {
const platform = toAppPlatform(buildProfile.platform);
const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({ const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({
projectDir, projectDir,
flags, flags,
@@ -138,12 +152,13 @@ export async function runBuildAndSubmitAsync(
actor, actor,
graphqlClient, graphqlClient,
analytics, analytics,
getDynamicProjectConfigAsync, getDynamicPrivateProjectConfigAsync,
customBuildConfigMetadata: customBuildConfigMetadataByPlatform[platform],
}); });
if (maybeBuild) { if (maybeBuild) {
startedBuilds.push({ build: maybeBuild, buildProfile }); startedBuilds.push({ build: maybeBuild, buildProfile });
} }
buildCtxByPlatform[toAppPlatform(buildProfile.platform)] = buildCtx; buildCtxByPlatform[platform] = buildCtx;
} }
if (flags.localBuildOptions.localBuildMode) { if (flags.localBuildOptions.localBuildMode) {
@@ -208,7 +223,9 @@ export async function runBuildAndSubmitAsync(
} }
const haveAllBuildsFailedOrCanceled = builds.every( const haveAllBuildsFailedOrCanceled = builds.every(
build => build?.status && [BuildStatus.Errored, BuildStatus.Canceled].includes(build?.status) build =>
build?.status &&
[BuildStatus.Errored, BuildStatus.Canceled, BuildStatus.PendingCancel].includes(build?.status)
); );
await maybeDownloadAndRunSimulatorBuildsAsync(builds, flags); await maybeDownloadAndRunSimulatorBuildsAsync(builds, flags);
@@ -245,7 +262,8 @@ async function prepareAndStartBuildAsync({
actor, actor,
graphqlClient, graphqlClient,
analytics, analytics,
getDynamicProjectConfigAsync, getDynamicPrivateProjectConfigAsync,
customBuildConfigMetadata,
}: { }: {
projectDir: string; projectDir: string;
flags: BuildFlags; flags: BuildFlags;
@@ -255,7 +273,8 @@ async function prepareAndStartBuildAsync({
actor: Actor; actor: Actor;
graphqlClient: ExpoGraphqlClient; graphqlClient: ExpoGraphqlClient;
analytics: Analytics; analytics: Analytics;
getDynamicProjectConfigAsync: DynamicConfigContextFn; getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
customBuildConfigMetadata?: CustomBuildConfigMetadata;
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> { }): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
const buildCtx = await createBuildContextAsync({ const buildCtx = await createBuildContextAsync({
buildProfileName: buildProfile.profileName, buildProfileName: buildProfile.profileName,
@@ -272,7 +291,8 @@ async function prepareAndStartBuildAsync({
actor, actor,
graphqlClient, graphqlClient,
analytics, analytics,
getDynamicProjectConfigAsync, getDynamicPrivateProjectConfigAsync,
customBuildConfigMetadata,
}); });
if (moreBuilds) { if (moreBuilds) {
@@ -291,6 +311,14 @@ async function prepareAndStartBuildAsync({
buildCtx.projectId, buildCtx.projectId,
flags.nonInteractive flags.nonInteractive
); );
if (buildProfile.profile.channel) {
await validateExpoUpdatesInstalledAsProjectDependencyAsync({
projectDir,
sdkVersion: buildCtx.exp.sdkVersion,
nonInteractive: flags.nonInteractive,
buildProfile,
});
}
await validateAppVersionRuntimePolicySupportAsync(buildCtx.projectDir, buildCtx.exp); await validateAppVersionRuntimePolicySupportAsync(buildCtx.projectDir, buildCtx.exp);
if (easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE) { if (easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE) {
@@ -410,3 +438,40 @@ async function maybeDownloadAndRunSimulatorBuildsAsync(
} }
} }
} }
async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
projectDir,
buildProfile,
nonInteractive,
sdkVersion,
}: {
projectDir: string;
buildProfile: ProfileData<'build'>;
nonInteractive: boolean;
sdkVersion?: string;
}): Promise<void> {
if (isExpoUpdatesInstalledOrAvailable(projectDir, sdkVersion)) {
return;
}
if (isExpoUpdatesInstalledAsDevDependency(projectDir)) {
Log.warn(
`The build profile "${buildProfile.profileName}" uses the channel "${buildProfile.profile.channel}", but you've added "expo-updates" as a dev dependency. To make channels work for your builds, move "expo-updates" from dev dependencies to the main dependencies in your project.`
);
} else if (nonInteractive) {
Log.warn(
`The build profile "${buildProfile.profileName}" has specified the channel "${buildProfile.profile.channel}", but the "expo-updates" package hasn't been installed. To use channels for your builds, install the "expo-updates" package by running "npx expo install expo-updates".`
);
} else {
Log.warn(
`The build profile "${buildProfile.profileName}" specifies the channel "${buildProfile.profile.channel}", but the "expo-updates" package is missing. To use channels in your builds, install the "expo-updates" package.`
);
const installExpoUpdates = await confirmAsync({
message: `Would you like to install the "expo-updates" package?`,
});
if (installExpoUpdates) {
await installExpoUpdatesAsync(projectDir, { silent: false });
Log.withTick('Installed expo-updates');
}
}
}
+1
View File
@@ -2,6 +2,7 @@ export enum BuildStatus {
NEW = 'new', NEW = 'new',
IN_QUEUE = 'in-queue', IN_QUEUE = 'in-queue',
IN_PROGRESS = 'in-progress', IN_PROGRESS = 'in-progress',
PENDING_CANCEL = 'pending-cancel',
ERRORED = 'errored', ERRORED = 'errored',
FINISHED = 'finished', FINISHED = 'finished',
CANCELED = 'canceled', CANCELED = 'canceled',
@@ -27,6 +27,7 @@ export function formatGraphQLBuild(build: BuildFragment): string {
return chalk.blue('in queue'); return chalk.blue('in queue');
case GraphQLBuildStatus.InProgress: case GraphQLBuildStatus.InProgress:
return chalk.blue('in progress'); return chalk.blue('in progress');
case GraphQLBuildStatus.PendingCancel:
case GraphQLBuildStatus.Canceled: case GraphQLBuildStatus.Canceled:
return chalk.gray('canceled'); return chalk.gray('canceled');
case GraphQLBuildStatus.Finished: case GraphQLBuildStatus.Finished:
@@ -90,6 +91,7 @@ export function formatGraphQLBuild(build: BuildFragment): string {
case GraphQLBuildStatus.InQueue: case GraphQLBuildStatus.InQueue:
case GraphQLBuildStatus.InProgress: case GraphQLBuildStatus.InProgress:
return '<in progress>'; return '<in progress>';
case GraphQLBuildStatus.PendingCancel:
case GraphQLBuildStatus.Canceled: case GraphQLBuildStatus.Canceled:
case GraphQLBuildStatus.Errored: case GraphQLBuildStatus.Errored:
return null; return null;
@@ -80,7 +80,7 @@ function printBuildResult(build: BuildFragment): void {
} }
return; return;
} }
if (build.status === BuildStatus.Canceled) { if ([BuildStatus.Canceled, BuildStatus.PendingCancel].includes(build.status)) {
Log.addNewLineIfNone(); Log.addNewLineIfNone();
Log.error( Log.error(
`${appPlatformEmojis[build.platform]} ${ `${appPlatformEmojis[build.platform]} ${
@@ -1,12 +1,9 @@
import { ExpoConfig } from '@expo/config-types';
import { Platform } from '@expo/eas-build-job'; import { Platform } from '@expo/eas-build-job';
import { BuildProfile, ResourceClass } from '@expo/eas-json'; import { BuildProfile, ResourceClass } from '@expo/eas-json';
import chalk from 'chalk'; import chalk from 'chalk';
import semver from 'semver';
import { BuildResourceClass } from '../../graphql/generated'; import { BuildResourceClass } from '../../graphql/generated';
import Log from '../../log'; import Log, { link } from '../../log';
import { getReactNativeVersionAsync } from '../metadata';
type AndroidResourceClass = Exclude< type AndroidResourceClass = Exclude<
ResourceClass, ResourceClass,
@@ -42,22 +39,20 @@ const androidResourceClassToBuildResourceClassMapping: Record<
export async function resolveBuildResourceClassAsync<T extends Platform>( export async function resolveBuildResourceClassAsync<T extends Platform>(
profile: BuildProfile<T>, profile: BuildProfile<T>,
platform: Platform, platform: Platform,
projectDir: string,
exp: ExpoConfig,
resourceClassFlag?: ResourceClass resourceClassFlag?: ResourceClass
): Promise<BuildResourceClass> { ): Promise<BuildResourceClass> {
const profileResourceClass = profile.resourceClass; const profileResourceClass = profile.resourceClass;
if (profileResourceClass && resourceClassFlag && resourceClassFlag !== profileResourceClass) { if (profileResourceClass && resourceClassFlag && resourceClassFlag !== profileResourceClass) {
Log.warn( Log.warn(
`Build profile specifies the "${profileResourceClass}" resource class but you passed "${resourceClassFlag}" to --resource-class.\nUsing the "${resourceClassFlag}" as the override.` `Build profile specifies the "${profileResourceClass}" resource class but you passed "${resourceClassFlag}" to --resource-class.\nUsing "${resourceClassFlag}" as the override.`
); );
} }
const selectedResourceClass = resourceClassFlag ?? profileResourceClass; const selectedResourceClass = resourceClassFlag ?? profileResourceClass;
return platform === Platform.IOS return platform === Platform.IOS
? await resolveIosResourceClassAsync(exp, projectDir, resourceClassFlag ?? profileResourceClass) ? resolveIosResourceClass(resourceClassFlag, profileResourceClass)
: resolveAndroidResourceClass(selectedResourceClass); : resolveAndroidResourceClass(selectedResourceClass);
} }
@@ -79,13 +74,19 @@ function resolveAndroidResourceClass(selectedResourceClass?: ResourceClass): Bui
return androidResourceClassToBuildResourceClassMapping[resourceClass as AndroidResourceClass]; return androidResourceClassToBuildResourceClassMapping[resourceClass as AndroidResourceClass];
} }
async function resolveIosResourceClassAsync( function resolveIosResourceClass(
exp: ExpoConfig, resourceClassFlag?: ResourceClass,
projectDir: string, profileResourceClass?: ResourceClass
selectedResourceClass?: ResourceClass ): BuildResourceClass {
): Promise<BuildResourceClass> { const resourceClass = resourceClassFlag ?? profileResourceClass ?? ResourceClass.DEFAULT;
const resourceClass =
selectedResourceClass ?? (await resolveIosDefaultRequestedResourceClassAsync(exp, projectDir)); if (resourceClassFlag === ResourceClass.LARGE) {
throw new Error(
`Experimental "large" resource class for Intel iOS workers is no longer available. Remove the specified resource class to use the default, or learn more about all available resource classes: ${link(
'https://docs.expo.dev/build-reference/eas-json/'
)}`
);
}
if ([ResourceClass.M1_EXPERIMENTAL, ResourceClass.M1_MEDIUM].includes(resourceClass)) { if ([ResourceClass.M1_EXPERIMENTAL, ResourceClass.M1_MEDIUM].includes(resourceClass)) {
Log.warn( Log.warn(
@@ -95,29 +96,13 @@ async function resolveIosResourceClassAsync(
); );
} }
if (resourceClass === ResourceClass.M1_LARGE) { if ([ResourceClass.M_LARGE, ResourceClass.M1_LARGE].includes(resourceClass)) {
Log.warn( Log.warn(
`Resource class ${chalk.bold('m1-large')} is deprecated. Use ${chalk.bold( `Resource class ${chalk.bold(resourceClass)} is deprecated. Use ${chalk.bold(
'm-large' 'large'
)} instead.` )} instead.`
); );
} }
return iosResourceClassToBuildResourceClassMapping[resourceClass]; return iosResourceClassToBuildResourceClassMapping[resourceClass];
} }
async function resolveIosDefaultRequestedResourceClassAsync(
exp: ExpoConfig,
projectDir: string
): Promise<ResourceClass> {
const { sdkVersion } = exp;
const reactNativeVersion = await getReactNativeVersionAsync(projectDir);
if (
(sdkVersion && semver.satisfies(sdkVersion, '>=48')) ||
(reactNativeVersion && semver.satisfies(reactNativeVersion, '>=0.71.0'))
) {
return ResourceClass.M_MEDIUM;
} else {
return ResourceClass.DEFAULT;
}
}
+20 -10
View File
@@ -11,16 +11,19 @@ import Log from '../log';
import SessionManager from '../user/SessionManager'; import SessionManager from '../user/SessionManager';
import AnalyticsContextField from './context/AnalyticsContextField'; import AnalyticsContextField from './context/AnalyticsContextField';
import ContextField from './context/ContextField'; import ContextField from './context/ContextField';
import { DynamicProjectConfigContextField } from './context/DynamicProjectConfigContextField'; import {
DynamicPrivateProjectConfigContextField,
DynamicPublicProjectConfigContextField,
} from './context/DynamicProjectConfigContextField';
import LoggedInContextField from './context/LoggedInContextField'; import LoggedInContextField from './context/LoggedInContextField';
import MaybeLoggedInContextField from './context/MaybeLoggedInContextField'; import MaybeLoggedInContextField from './context/MaybeLoggedInContextField';
import { OptionalProjectConfigContextField } from './context/OptionalProjectConfigContextField'; import { OptionalPrivateProjectConfigContextField } from './context/OptionalPrivateProjectConfigContextField';
import ProjectConfigContextField from './context/ProjectConfigContextField'; import { PrivateProjectConfigContextField } from './context/PrivateProjectConfigContextField';
import ProjectDirContextField from './context/ProjectDirContextField'; import ProjectDirContextField from './context/ProjectDirContextField';
import SessionManagementContextField from './context/SessionManagementContextField'; import SessionManagementContextField from './context/SessionManagementContextField';
import { EasCommandError } from './errors'; import { EasCommandError } from './errors';
type ContextInput< export type ContextInput<
T extends { T extends {
[name: string]: any; [name: string]: any;
} = object } = object
@@ -28,7 +31,7 @@ type ContextInput<
[P in keyof T]: ContextField<T[P]>; [P in keyof T]: ContextField<T[P]>;
}; };
type ContextOutput< export type ContextOutput<
T extends { T extends {
[name: string]: any; [name: string]: any;
} = object } = object
@@ -65,7 +68,7 @@ export default abstract class EasCommand extends Command {
* run within a project directory, null otherwise. * run within a project directory, null otherwise.
*/ */
OptionalProjectConfig: { OptionalProjectConfig: {
projectConfig: new OptionalProjectConfigContextField(), privateProjectConfig: new OptionalPrivateProjectConfigContextField(),
}, },
/** /**
* Require this command to be run in a project directory. Return the project directory in the context. * Require this command to be run in a project directory. Return the project directory in the context.
@@ -78,7 +81,9 @@ export default abstract class EasCommand extends Command {
*/ */
DynamicProjectConfig: { DynamicProjectConfig: {
// eslint-disable-next-line async-protect/async-suffix // eslint-disable-next-line async-protect/async-suffix
getDynamicProjectConfigAsync: new DynamicProjectConfigContextField(), getDynamicPublicProjectConfigAsync: new DynamicPublicProjectConfigContextField(),
// eslint-disable-next-line async-protect/async-suffix
getDynamicPrivateProjectConfigAsync: new DynamicPrivateProjectConfigContextField(),
}, },
/** /**
* Require the project to be identified and registered on server. Returns the project config in the context. * Require the project to be identified and registered on server. Returns the project config in the context.
@@ -87,7 +92,7 @@ export default abstract class EasCommand extends Command {
*/ */
ProjectConfig: { ProjectConfig: {
loggedIn: new LoggedInContextField(), loggedIn: new LoggedInContextField(),
projectConfig: new ProjectConfigContextField(), privateProjectConfig: new PrivateProjectConfigContextField(),
}, },
/** /**
* Analytics manager. Returns the analytics manager in the context for use by the command. * Analytics manager. Returns the analytics manager in the context for use by the command.
@@ -188,13 +193,18 @@ export default abstract class EasCommand extends Command {
if (err instanceof EasCommandError) { if (err instanceof EasCommandError) {
Log.error(err.message); Log.error(err.message);
} else if (err instanceof CombinedError && err?.graphQLErrors) { } else if (err instanceof CombinedError && err?.graphQLErrors) {
const cleanMessage = err.message.replace('[GraphQL] ', ''); let cleanMessage = err.message.replace('[GraphQL] ', '');
cleanMessage += err?.graphQLErrors?.[0]?.extensions?.requestId
? `\nRequest ID: ${err.graphQLErrors[0].extensions.requestId}`
: '';
Log.error(cleanMessage); Log.error(cleanMessage);
baseMessage = BASE_GRAPHQL_ERROR_MESSAGE; baseMessage = BASE_GRAPHQL_ERROR_MESSAGE;
} else { } else {
Log.error(err.message); Log.error(err.message);
} }
Log.debug(err); Log.debug(err);
throw new Error(baseMessage); const sanitizedError = new Error(baseMessage);
sanitizedError.stack = err.stack;
throw sanitizedError;
} }
} }
@@ -1,4 +1,6 @@
import { CombinedError } from '@urql/core'; import { CombinedError } from '@urql/core';
import { GraphQLError } from 'graphql/error';
import { v4 as uuidv4 } from 'uuid';
import { AnalyticsWithOrchestration, createAnalyticsAsync } from '../../analytics/AnalyticsManager'; import { AnalyticsWithOrchestration, createAnalyticsAsync } from '../../analytics/AnalyticsManager';
import Log from '../../log'; import Log from '../../log';
@@ -17,6 +19,7 @@ jest.mock('../../analytics/AnalyticsManager', () => {
jest.mock('../../log'); jest.mock('../../log');
let originalProcessArgv: string[]; let originalProcessArgv: string[];
const mockRequestId = uuidv4();
beforeAll(() => { beforeAll(() => {
originalProcessArgv = process.argv; originalProcessArgv = process.argv;
@@ -137,6 +140,38 @@ describe(EasCommand.name, () => {
expect(logDebugSpy).toBeCalledWith(error); expect(logDebugSpy).toBeCalledWith(error);
}); });
it('logs the cleaned message with request ID if present', async () => {
const TestEasCommand = createTestEasCommand();
const logErrorSpy = jest.spyOn(Log, 'error');
const logDebugSpy = jest.spyOn(Log, 'debug');
const runAsyncMock = jest.spyOn(TestEasCommand.prototype as any, 'runAsync');
const graphQLError = new GraphQLError(
'Unexpected GraphQL error message',
null,
null,
null,
null,
null,
{
errorCode: 'UNKNOWN_GRAPHQL_ERROR',
requestId: mockRequestId,
}
);
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
runAsyncMock.mockImplementation(() => {
throw error;
});
try {
await TestEasCommand.run();
} catch {}
expect(logErrorSpy).toBeCalledWith(
`Unexpected GraphQL error message\nRequest ID: ${mockRequestId}`
);
expect(logDebugSpy).toBeCalledWith(error);
});
it('re-throws the error with default base message', async () => { it('re-throws the error with default base message', async () => {
const TestEasCommand = createTestEasCommand(); const TestEasCommand = createTestEasCommand();
const runAsyncMock = jest.spyOn(TestEasCommand.prototype as any, 'runAsync'); const runAsyncMock = jest.spyOn(TestEasCommand.prototype as any, 'runAsync');
@@ -1,29 +1,55 @@
import { ExpoConfig } from '@expo/config-types'; import { ExpoConfig } from '@expo/config-types';
import { ExpoConfigOptions, getExpoConfig } from '../../project/expoConfig'; import {
ExpoConfigOptions,
getPrivateExpoConfig,
getPublicExpoConfig,
} from '../../project/expoConfig';
import ContextField, { ContextOptions } from './ContextField'; import ContextField, { ContextOptions } from './ContextField';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync'; import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync'; import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
export type DynamicConfigContextFn = (options: ExpoConfigOptions) => Promise<{ export type DynamicConfigContextFn = (options?: ExpoConfigOptions) => Promise<{
projectId: string; projectId: string;
exp: ExpoConfig; exp: ExpoConfig;
projectDir: string; projectDir: string;
}>; }>;
export class DynamicProjectConfigContextField extends ContextField<DynamicConfigContextFn> { export class DynamicPublicProjectConfigContextField extends ContextField<DynamicConfigContextFn> {
async getValueAsync({ async getValueAsync({
nonInteractive, nonInteractive,
sessionManager, sessionManager,
}: ContextOptions): Promise<DynamicConfigContextFn> { }: ContextOptions): Promise<DynamicConfigContextFn> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync(); const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
return async (options?: ExpoConfigOptions) => { return async (options?: ExpoConfigOptions) => {
const expBefore = getExpoConfig(projectDir, options); const expBefore = getPublicExpoConfig(projectDir, options);
const projectId = await getProjectIdAsync(sessionManager, expBefore, { const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive, nonInteractive,
env: options?.env, env: options?.env,
}); });
const exp = getExpoConfig(projectDir, options); const exp = getPublicExpoConfig(projectDir, options);
return {
exp,
projectDir,
projectId,
};
};
}
}
export class DynamicPrivateProjectConfigContextField extends ContextField<DynamicConfigContextFn> {
async getValueAsync({
nonInteractive,
sessionManager,
}: ContextOptions): Promise<DynamicConfigContextFn> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
return async (options?: ExpoConfigOptions) => {
const expBefore = getPrivateExpoConfig(projectDir, options);
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
env: options?.env,
});
const exp = getPrivateExpoConfig(projectDir, options);
return { return {
exp, exp,
projectDir, projectDir,
@@ -1,12 +1,12 @@
import { ExpoConfig } from '@expo/config-types'; import { ExpoConfig } from '@expo/config-types';
import { InvalidEasJsonError } from '@expo/eas-json/build/errors'; import { InvalidEasJsonError } from '@expo/eas-json/build/errors';
import { getExpoConfig } from '../../project/expoConfig'; import { getPrivateExpoConfig } from '../../project/expoConfig';
import ContextField, { ContextOptions } from './ContextField'; import ContextField, { ContextOptions } from './ContextField';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync'; import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync'; import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
export class OptionalProjectConfigContextField extends ContextField< export class OptionalPrivateProjectConfigContextField extends ContextField<
| { | {
projectId: string; projectId: string;
exp: ExpoConfig; exp: ExpoConfig;
@@ -35,11 +35,11 @@ export class OptionalProjectConfigContextField extends ContextField<
return undefined; return undefined;
} }
const expBefore = getExpoConfig(projectDir); const expBefore = getPrivateExpoConfig(projectDir);
const projectId = await getProjectIdAsync(sessionManager, expBefore, { const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive, nonInteractive,
}); });
const exp = getExpoConfig(projectDir); const exp = getPrivateExpoConfig(projectDir);
return { return {
exp, exp,
projectDir, projectDir,
@@ -1,11 +1,11 @@
import { ExpoConfig } from '@expo/config-types'; import { ExpoConfig } from '@expo/config-types';
import { getExpoConfig } from '../../project/expoConfig'; import { getPrivateExpoConfig } from '../../project/expoConfig';
import ContextField, { ContextOptions } from './ContextField'; import ContextField, { ContextOptions } from './ContextField';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync'; import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync'; import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
export default class ProjectConfigContextField extends ContextField<{ export class PrivateProjectConfigContextField extends ContextField<{
projectId: string; projectId: string;
exp: ExpoConfig; exp: ExpoConfig;
projectDir: string; projectDir: string;
@@ -16,11 +16,11 @@ export default class ProjectConfigContextField extends ContextField<{
projectDir: string; projectDir: string;
}> { }> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync(); const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
const expBefore = getExpoConfig(projectDir); const expBefore = getPrivateExpoConfig(projectDir);
const projectId = await getProjectIdAsync(sessionManager, expBefore, { const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive, nonInteractive,
}); });
const exp = getExpoConfig(projectDir); const exp = getPrivateExpoConfig(projectDir);
return { return {
projectId, projectId,
@@ -1,4 +1,4 @@
import { getProjectConfigDescription, modifyConfigAsync } from '@expo/config'; import { getProjectConfigDescription } from '@expo/config';
import { ExpoConfig } from '@expo/config-types'; import { ExpoConfig } from '@expo/config-types';
import { Env } from '@expo/eas-build-job'; import { Env } from '@expo/eas-build-job';
import chalk from 'chalk'; import chalk from 'chalk';
@@ -6,7 +6,7 @@ import chalk from 'chalk';
import { AppQuery } from '../../../graphql/queries/AppQuery'; import { AppQuery } from '../../../graphql/queries/AppQuery';
import Log, { learnMore } from '../../../log'; import Log, { learnMore } from '../../../log';
import { ora } from '../../../ora'; import { ora } from '../../../ora';
import { getExpoConfig } from '../../../project/expoConfig'; import { createOrModifyExpoConfigAsync, getPrivateExpoConfig } from '../../../project/expoConfig';
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync'; import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
import { toAppPrivacy } from '../../../project/projectUtils'; import { toAppPrivacy } from '../../../project/projectUtils';
import SessionManager from '../../../user/SessionManager'; import SessionManager from '../../../user/SessionManager';
@@ -24,8 +24,8 @@ export async function saveProjectIdToAppConfigAsync(
projectId: string, projectId: string,
options: { env?: Env } = {} options: { env?: Env } = {}
): Promise<void> { ): Promise<void> {
const exp = getExpoConfig(projectDir, options); const exp = getPrivateExpoConfig(projectDir, options);
const result = await modifyConfigAsync( const result = await createOrModifyExpoConfigAsync(
projectDir, projectDir,
{ {
extra: { ...exp.extra, eas: { ...exp.extra?.eas, projectId } }, extra: { ...exp.extra, eas: { ...exp.extra?.eas, projectId } },
@@ -160,12 +160,12 @@ export async function getProjectIdAsync(
switch (user.__typename) { switch (user.__typename) {
case 'User': case 'User':
return user.username; return user.username;
case 'SSOUser':
return user.username;
case 'Robot': case 'Robot':
throw new Error( throw new Error(
'The "owner" manifest property is required when using robot users. See: https://docs.expo.dev/versions/latest/config/app/#owner' 'The "owner" manifest property is required when using robot users. See: https://docs.expo.dev/versions/latest/config/app/#owner'
); );
case 'SSOUser':
throw new Error('SSO users are not supported yet.');
} }
}; };
+17 -1
View File
@@ -1,3 +1,5 @@
import { Flags } from '@oclif/core';
import EasCommand from '../../commandUtils/EasCommand'; import EasCommand from '../../commandUtils/EasCommand';
import Log from '../../log'; import Log from '../../log';
@@ -5,13 +7,27 @@ export default class AccountLogin extends EasCommand {
static override description = 'log in with your Expo account'; static override description = 'log in with your Expo account';
static override aliases = ['login']; static override aliases = ['login'];
static override flags = {
// can pass either --sso or -s
sso: Flags.boolean({
description: 'Login with SSO',
char: 's',
default: false,
hidden: true,
}),
};
static override contextDefinition = { static override contextDefinition = {
...this.ContextOptions.SessionManagment, ...this.ContextOptions.SessionManagment,
}; };
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
const {
flags: { sso },
} = await this.parse(AccountLogin);
const { sessionManager } = await this.getContextAsync(AccountLogin, { nonInteractive: false }); const { sessionManager } = await this.getContextAsync(AccountLogin, { nonInteractive: false });
await sessionManager.showLoginPromptAsync(); await sessionManager.showLoginPromptAsync({ sso });
Log.log('Logged in'); Log.log('Logged in');
} }
} }
+42 -1
View File
@@ -1,8 +1,10 @@
import chalk from 'chalk'; import chalk from 'chalk';
import nullthrows from 'nullthrows';
import EasCommand from '../../commandUtils/EasCommand'; import EasCommand from '../../commandUtils/EasCommand';
import { Role } from '../../graphql/generated';
import Log from '../../log'; import Log from '../../log';
import { getActorDisplayName } from '../../user/User'; import { Actor, getActorDisplayName } from '../../user/User';
export default class AccountView extends EasCommand { export default class AccountView extends EasCommand {
static override description = 'show the username you are logged in as'; static override description = 'show the username you are logged in as';
@@ -18,9 +20,48 @@ export default class AccountView extends EasCommand {
} = await this.getContextAsync(AccountView, { nonInteractive: true }); } = await this.getContextAsync(AccountView, { nonInteractive: true });
if (actor) { if (actor) {
Log.log(chalk.green(getActorDisplayName(actor))); Log.log(chalk.green(getActorDisplayName(actor)));
// personal account is included, only show if more accounts that personal account
// but do show personal account in list if there are more
const accountExcludingPersonalAccount = actor.accounts.filter(
account => !('username' in actor) || account.name !== actor.username
);
if (accountExcludingPersonalAccount.length > 0) {
Log.newLine();
Log.log('Accounts:');
actor.accounts.forEach(account => {
const roleOnAccount = AccountView.getRoleOnAccount(actor, account);
Log.log(`• ${account.name} (Role: ${AccountView.getLabelForRole(roleOnAccount)})`);
});
}
} else { } else {
Log.warn('Not logged in'); Log.warn('Not logged in');
process.exit(1); process.exit(1);
} }
} }
private static getRoleOnAccount(actor: Actor, account: Actor['accounts'][0]): Role {
if ('username' in actor && account.name === actor.username) {
return Role.Owner;
}
return nullthrows(account.users.find(user => user.actor.id === actor.id)?.role);
}
private static getLabelForRole(role: Role): string {
switch (role) {
case Role.Owner:
return 'Owner';
case Role.Admin:
return 'Admin';
case Role.Developer:
return 'Developer';
case Role.ViewOnly:
return 'Viewer';
case Role.Custom:
case Role.HasAdmin:
case Role.NotAdmin:
return 'Custom';
}
}
} }
@@ -35,7 +35,7 @@ export default class BranchCreate extends EasCommand {
flags: { json: jsonFlag, 'non-interactive': nonInteractive }, flags: { json: jsonFlag, 'non-interactive': nonInteractive },
} = await this.parse(BranchCreate); } = await this.parse(BranchCreate);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchCreate, { } = await this.getContextAsync(BranchCreate, {
nonInteractive, nonInteractive,
@@ -108,7 +108,7 @@ export default class BranchDelete extends EasCommand {
} }
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchDelete, { nonInteractive }); } = await this.getContextAsync(BranchDelete, { nonInteractive });
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId); const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
+1 -1
View File
@@ -20,7 +20,7 @@ export default class BranchList extends EasCommand {
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
const { flags } = await this.parse(BranchList); const { flags } = await this.parse(BranchList);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchList, { } = await this.getContextAsync(BranchList, {
nonInteractive: flags['non-interactive'], nonInteractive: flags['non-interactive'],
@@ -72,7 +72,7 @@ export default class BranchRename extends EasCommand {
flags: { json: jsonFlag, from: currentName, to: newName, 'non-interactive': nonInteractive }, flags: { json: jsonFlag, from: currentName, to: newName, 'non-interactive': nonInteractive },
} = await this.parse(BranchRename); } = await this.parse(BranchRename);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchRename, { } = await this.getContextAsync(BranchRename, {
nonInteractive, nonInteractive,
+1 -1
View File
@@ -38,7 +38,7 @@ export default class BranchView extends EasCommand {
} = await this.parse(BranchView); } = await this.parse(BranchView);
const { 'non-interactive': nonInteractive } = flags; const { 'non-interactive': nonInteractive } = flags;
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchView, { } = await this.getContextAsync(BranchView, {
nonInteractive, nonInteractive,
@@ -147,7 +147,7 @@ export default class BuildCancel extends EasCommand {
flags: { 'non-interactive': nonInteractive }, flags: { 'non-interactive': nonInteractive },
} = await this.parse(BuildCancel); } = await this.parse(BuildCancel);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildCancel, { } = await this.getContextAsync(BuildCancel, {
nonInteractive, nonInteractive,
@@ -174,7 +174,7 @@ export default class BuildCancel extends EasCommand {
const spinner = ora().start('Canceling the build…'); const spinner = ora().start('Canceling the build…');
try { try {
const { status } = await cancelBuildAsync(graphqlClient, buildId); const { status } = await cancelBuildAsync(graphqlClient, buildId);
if (status === BuildStatus.Canceled) { if ([BuildStatus.Canceled, BuildStatus.PendingCancel].includes(status)) {
spinner.succeed('Build canceled'); spinner.succeed('Build canceled');
} else { } else {
spinner.text = 'Build is already completed'; spinner.text = 'Build is already completed';
@@ -7,10 +7,11 @@ import { ensureProjectConfiguredAsync } from '../../build/configure';
import EasCommand from '../../commandUtils/EasCommand'; import EasCommand from '../../commandUtils/EasCommand';
import Log, { learnMore } from '../../log'; import Log, { learnMore } from '../../log';
import { RequestedPlatform } from '../../platform'; import { RequestedPlatform } from '../../platform';
import { isExpoUpdatesInstalled } from '../../project/projectUtils'; import { isExpoUpdatesInstalled, isUsingEASUpdate } from '../../project/projectUtils';
import { resolveWorkflowAsync } from '../../project/workflow'; import { resolveWorkflowAsync } from '../../project/workflow';
import { promptAsync } from '../../prompts'; import { promptAsync } from '../../prompts';
import { syncUpdatesConfigurationAsync as syncAndroidUpdatesConfigurationAsync } from '../../update/android/UpdatesModule'; import { syncUpdatesConfigurationAsync as syncAndroidUpdatesConfigurationAsync } from '../../update/android/UpdatesModule';
import { ensureEASUpdateIsConfiguredInEasJsonAsync } from '../../update/configure';
import { syncUpdatesConfigurationAsync as syncIosUpdatesConfigurationAsync } from '../../update/ios/UpdatesModule'; import { syncUpdatesConfigurationAsync as syncIosUpdatesConfigurationAsync } from '../../update/ios/UpdatesModule';
import { getVcsClient } from '../../vcs'; import { getVcsClient } from '../../vcs';
@@ -33,7 +34,7 @@ export default class BuildConfigure extends EasCommand {
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
const { flags } = await this.parse(BuildConfigure); const { flags } = await this.parse(BuildConfigure);
const { const {
projectConfig: { exp, projectId, projectDir }, privateProjectConfig: { exp, projectId, projectDir },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildConfigure, { } = await this.getContextAsync(BuildConfigure, {
nonInteractive: false, nonInteractive: false,
@@ -57,10 +58,13 @@ export default class BuildConfigure extends EasCommand {
// ensure eas.json exists // ensure eas.json exists
Log.newLine(); Log.newLine();
await ensureProjectConfiguredAsync({ const didCreateEasJson = await ensureProjectConfiguredAsync({
projectDir, projectDir,
nonInteractive: false, nonInteractive: false,
}); });
if (didCreateEasJson && isUsingEASUpdate(exp, projectId)) {
await ensureEASUpdateIsConfiguredInEasJsonAsync(projectDir);
}
// configure expo-updates // configure expo-updates
if (expoUpdatesIsInstalled) { if (expoUpdatesIsInstalled) {
+2 -2
View File
@@ -118,7 +118,7 @@ export default class Build extends EasCommand {
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
getDynamicProjectConfigAsync, getDynamicPrivateProjectConfigAsync,
projectDir, projectDir,
analytics, analytics,
} = await this.getContextAsync(Build, { } = await this.getContextAsync(Build, {
@@ -144,7 +144,7 @@ export default class Build extends EasCommand {
projectDir, projectDir,
flagsWithPlatform, flagsWithPlatform,
actor, actor,
getDynamicProjectConfigAsync getDynamicPrivateProjectConfigAsync
); );
} }
@@ -73,7 +73,7 @@ export default class BuildInspect extends EasCommand {
const { flags } = await this.parse(BuildInspect); const { flags } = await this.parse(BuildInspect);
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
getDynamicProjectConfigAsync, getDynamicPrivateProjectConfigAsync,
projectDir, projectDir,
analytics, analytics,
} = await this.getContextAsync(BuildInspect, { } = await this.getContextAsync(BuildInspect, {
@@ -120,7 +120,7 @@ export default class BuildInspect extends EasCommand {
}, },
}, },
actor, actor,
getDynamicProjectConfigAsync getDynamicPrivateProjectConfigAsync
); );
if (!flags.verbose) { if (!flags.verbose) {
Log.log(chalk.green('Build successful')); Log.log(chalk.green('Build successful'));
@@ -46,7 +46,7 @@ export default class BuildInternal extends EasCommand {
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
getDynamicProjectConfigAsync, getDynamicPrivateProjectConfigAsync,
projectDir, projectDir,
analytics, analytics,
} = await this.getContextAsync(BuildInternal, { } = await this.getContextAsync(BuildInternal, {
@@ -74,7 +74,7 @@ export default class BuildInternal extends EasCommand {
}, },
}, },
actor, actor,
getDynamicProjectConfigAsync getDynamicPrivateProjectConfigAsync
); );
} }
} }
+4 -1
View File
@@ -27,6 +27,7 @@ export default class BuildList extends EasCommand {
BuildStatus.NEW, BuildStatus.NEW,
BuildStatus.IN_QUEUE, BuildStatus.IN_QUEUE,
BuildStatus.IN_PROGRESS, BuildStatus.IN_PROGRESS,
BuildStatus.PENDING_CANCEL,
BuildStatus.ERRORED, BuildStatus.ERRORED,
BuildStatus.FINISHED, BuildStatus.FINISHED,
BuildStatus.CANCELED, BuildStatus.CANCELED,
@@ -68,7 +69,7 @@ export default class BuildList extends EasCommand {
'non-interactive': nonInteractive, 'non-interactive': nonInteractive,
} = flags; } = flags;
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildList, { } = await this.getContextAsync(BuildList, {
nonInteractive, nonInteractive,
@@ -123,6 +124,8 @@ const toGraphQLBuildStatus = (buildStatus?: BuildStatus): GraphQLBuildStatus | u
return GraphQLBuildStatus.InQueue; return GraphQLBuildStatus.InQueue;
} else if (buildStatus === BuildStatus.IN_PROGRESS) { } else if (buildStatus === BuildStatus.IN_PROGRESS) {
return GraphQLBuildStatus.InProgress; return GraphQLBuildStatus.InProgress;
} else if (buildStatus === BuildStatus.PENDING_CANCEL) {
return GraphQLBuildStatus.PendingCancel;
} else if (buildStatus === BuildStatus.ERRORED) { } else if (buildStatus === BuildStatus.ERRORED) {
return GraphQLBuildStatus.Errored; return GraphQLBuildStatus.Errored;
} else if (buildStatus === BuildStatus.FINISHED) { } else if (buildStatus === BuildStatus.FINISHED) {
@@ -101,7 +101,7 @@ export default class BuildResign extends EasCommand {
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
getDynamicProjectConfigAsync, getDynamicPrivateProjectConfigAsync,
projectDir, projectDir,
analytics, analytics,
} = await this.getContextAsync(BuildResign, { } = await this.getContextAsync(BuildResign, {
@@ -130,7 +130,7 @@ export default class BuildResign extends EasCommand {
platform, platform,
flags.profile ?? 'production' flags.profile ?? 'production'
); );
const { exp, projectId } = await getDynamicProjectConfigAsync({ env: buildProfile.env }); const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env });
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId); const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
const build = await this.ensureBuildSelectedAsync( const build = await this.ensureBuildSelectedAsync(
{ graphqlClient, projectId, platform, nonInteractive, limit, offset }, { graphqlClient, projectId, platform, nonInteractive, limit, offset },
+4 -3
View File
@@ -81,7 +81,7 @@ export default class Run extends EasCommand {
const queryOptions = getPaginatedQueryOptions(flags); const queryOptions = getPaginatedQueryOptions(flags);
const { const {
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
projectConfig: { projectId }, privateProjectConfig: { projectId },
} = await this.getContextAsync(Run, { } = await this.getContextAsync(Run, {
nonInteractive: false, nonInteractive: false,
}); });
@@ -210,8 +210,9 @@ async function maybeGetBuildAsync(
}, },
paginatedQueryOptions, paginatedQueryOptions,
selectPromptDisabledFunction: build => !isRunnableOnSimulatorOrEmulator(build), selectPromptDisabledFunction: build => !isRunnableOnSimulatorOrEmulator(build),
selectPromptWarningMessage: selectPromptWarningMessage: `Artifacts for this build have expired and are no longer available, or this is not ${
'Artifacts for this build have expired and are no longer available, or this is not a simulator/emulator build.', flags.selectedPlatform === AppPlatform.Ios ? 'a simulator' : 'an emulator'
} build.`,
}); });
return validateChosenBuild(build, flags.selectedPlatform); return validateChosenBuild(build, flags.selectedPlatform);
} else if (flags.runArchiveFlags.latest) { } else if (flags.runArchiveFlags.latest) {
@@ -0,0 +1,120 @@
import { Platform } from '@expo/eas-build-job';
import { EasJsonAccessor } from '@expo/eas-json';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import EasCommand from '../../../commandUtils/EasCommand';
import { EasNonInteractiveAndJsonFlags } from '../../../commandUtils/flags';
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
import { toAppPlatform } from '../../../graphql/types/AppPlatform';
import Log from '../../../log';
import { selectRequestedPlatformAsync, toPlatforms } from '../../../platform';
import { getApplicationIdentifierAsync } from '../../../project/applicationIdentifier';
import {
ensureVersionSourceIsRemoteAsync,
validateAppConfigForRemoteVersionSource,
} from '../../../project/remoteVersionSource';
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
import { getProfilesAsync } from '../../../utils/profiles';
export default class BuildVersionGetView extends EasCommand {
public static override description = 'get the latest version from EAS servers';
public static override flags = {
platform: Flags.enum({
char: 'p',
options: ['android', 'ios', 'all'],
}),
profile: Flags.string({
char: 'e',
description:
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
helpValue: 'PROFILE_NAME',
}),
...EasNonInteractiveAndJsonFlags,
};
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
...this.ContextOptions.DynamicProjectConfig,
...this.ContextOptions.ProjectDir,
};
public async runAsync(): Promise<void> {
const { flags } = await this.parse(BuildVersionGetView);
if (flags.json) {
enableJsonOutput();
}
const {
loggedIn: { graphqlClient },
getDynamicPrivateProjectConfigAsync,
projectDir,
} = await this.getContextAsync(BuildVersionGetView, {
nonInteractive: true,
});
if (!flags.platform && flags['non-interactive']) {
throw new Error('"--platform" flag is required in non-interactive mode.');
}
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
await ensureVersionSourceIsRemoteAsync(easJsonAccessor, {
nonInteractive: flags['non-interactive'],
});
const platforms = toPlatforms(requestedPlatform);
const buildProfiles = await getProfilesAsync({
type: 'build',
easJsonAccessor,
platforms,
profileName: flags.profile ?? undefined,
});
const results: { [key in Platform]?: string } = {};
for (const { profile, platform } of buildProfiles) {
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
env: profile.env,
});
validateAppConfigForRemoteVersionSource(exp, platform);
const applicationIdentifier = await getApplicationIdentifierAsync({
graphqlClient,
projectDir,
projectId,
exp,
buildProfile: profile,
platform,
});
const remoteVersions = await AppVersionQuery.latestVersionAsync(
graphqlClient,
projectId,
toAppPlatform(platform),
applicationIdentifier
);
if (remoteVersions?.buildVersion) {
results[platform] = remoteVersions?.buildVersion;
}
}
if (flags.json) {
const jsonResults: { versionCode?: string; buildNumber?: string } = {};
if (results.android) {
jsonResults.versionCode = results.android;
}
if (results.ios) {
jsonResults.buildNumber = results.ios;
}
printJsonOnlyOutput(jsonResults);
} else {
if (Object.keys(results).length === 0) {
Log.log('No remote versions are configured for this project.');
return;
}
if (results.android) {
Log.log(`Android versionCode - ${chalk.bold(results.android)}`);
}
if (results.ios) {
Log.log(`iOS buildNumber - ${chalk.bold(results.ios)}`);
}
}
}
}
@@ -22,7 +22,7 @@ import {
import { promptAsync } from '../../../prompts'; import { promptAsync } from '../../../prompts';
export default class BuildVersionSetView extends EasCommand { export default class BuildVersionSetView extends EasCommand {
static override description = 'Update version of an app.'; static override description = 'update version of an app';
static override flags = { static override flags = {
platform: Flags.enum({ platform: Flags.enum({
@@ -47,7 +47,7 @@ export default class BuildVersionSetView extends EasCommand {
const { flags } = await this.parse(BuildVersionSetView); const { flags } = await this.parse(BuildVersionSetView);
const { const {
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
getDynamicProjectConfigAsync, getDynamicPrivateProjectConfigAsync,
projectDir, projectDir,
} = await this.getContextAsync(BuildVersionSetView, { } = await this.getContextAsync(BuildVersionSetView, {
nonInteractive: false, nonInteractive: false,
@@ -55,14 +55,14 @@ export default class BuildVersionSetView extends EasCommand {
const platform = await selectPlatformAsync(flags.platform); const platform = await selectPlatformAsync(flags.platform);
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir); const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
await ensureVersionSourceIsRemoteAsync(easJsonAccessor); await ensureVersionSourceIsRemoteAsync(easJsonAccessor, { nonInteractive: false });
const profile = await EasJsonUtils.getBuildProfileAsync( const profile = await EasJsonUtils.getBuildProfileAsync(
easJsonAccessor, easJsonAccessor,
platform, platform,
flags.profile ?? undefined flags.profile ?? undefined
); );
const { exp, projectId } = await getDynamicProjectConfigAsync({ env: profile.env }); const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: profile.env });
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId); const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
validateAppConfigForRemoteVersionSource(exp, platform); validateAppConfigForRemoteVersionSource(exp, platform);
@@ -39,7 +39,7 @@ interface SyncContext<T extends Platform> {
export default class BuildVersionSyncView extends EasCommand { export default class BuildVersionSyncView extends EasCommand {
public static override description = public static override description =
'Update a version in native code with a value stored on EAS servers'; 'update a version in native code with a value stored on EAS servers';
public static override flags = { public static override flags = {
platform: Flags.enum({ platform: Flags.enum({
@@ -64,7 +64,7 @@ export default class BuildVersionSyncView extends EasCommand {
const { flags } = await this.parse(BuildVersionSyncView); const { flags } = await this.parse(BuildVersionSyncView);
const { const {
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
getDynamicProjectConfigAsync, getDynamicPrivateProjectConfigAsync,
projectDir, projectDir,
} = await this.getContextAsync(BuildVersionSyncView, { } = await this.getContextAsync(BuildVersionSyncView, {
nonInteractive: true, nonInteractive: true,
@@ -72,7 +72,7 @@ export default class BuildVersionSyncView extends EasCommand {
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform); const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir); const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
await ensureVersionSourceIsRemoteAsync(easJsonAccessor); await ensureVersionSourceIsRemoteAsync(easJsonAccessor, { nonInteractive: false });
const platforms = toPlatforms(requestedPlatform); const platforms = toPlatforms(requestedPlatform);
const buildProfiles = await getProfilesAsync({ const buildProfiles = await getProfilesAsync({
@@ -82,7 +82,7 @@ export default class BuildVersionSyncView extends EasCommand {
profileName: flags.profile ?? undefined, profileName: flags.profile ?? undefined,
}); });
for (const profileInfo of buildProfiles) { for (const profileInfo of buildProfiles) {
const { exp, projectId } = await getDynamicProjectConfigAsync({ const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
env: profileInfo.profile.env, env: profileInfo.profile.env,
}); });
+1 -1
View File
@@ -28,7 +28,7 @@ export default class BuildView extends EasCommand {
flags, flags,
} = await this.parse(BuildView); } = await this.parse(BuildView);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildView, { } = await this.getContextAsync(BuildView, {
nonInteractive: true, nonInteractive: true,
@@ -38,7 +38,7 @@ export default class ChannelCreate extends EasCommand {
flags: { json: jsonFlag, 'non-interactive': nonInteractive }, flags: { json: jsonFlag, 'non-interactive': nonInteractive },
} = await this.parse(ChannelCreate); } = await this.parse(ChannelCreate);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelCreate, { } = await this.getContextAsync(ChannelCreate, {
nonInteractive, nonInteractive,
@@ -41,7 +41,7 @@ export default class ChannelDelete extends EasCommand {
flags: { json: jsonFlag, 'non-interactive': nonInteractive }, flags: { json: jsonFlag, 'non-interactive': nonInteractive },
} = await this.parse(ChannelDelete); } = await this.parse(ChannelDelete);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelDelete, { } = await this.getContextAsync(ChannelDelete, {
nonInteractive, nonInteractive,
@@ -75,7 +75,7 @@ export default class ChannelEdit extends EasCommand {
flags: { branch: branchFlag, json, 'non-interactive': nonInteractive }, flags: { branch: branchFlag, json, 'non-interactive': nonInteractive },
} = await this.parse(ChannelEdit); } = await this.parse(ChannelEdit);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelEdit, { } = await this.getContextAsync(ChannelEdit, {
nonInteractive, nonInteractive,
@@ -27,7 +27,7 @@ export default class ChannelList extends EasCommand {
const paginatedQueryOptions = getPaginatedQueryOptions(flags); const paginatedQueryOptions = getPaginatedQueryOptions(flags);
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags; const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelList, { } = await this.getContextAsync(ChannelList, {
nonInteractive, nonInteractive,
@@ -336,7 +336,7 @@ export default class ChannelRollout extends EasCommand {
}, },
} = await this.parse(ChannelRollout); } = await this.parse(ChannelRollout);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelRollout, { } = await this.getContextAsync(ChannelRollout, {
nonInteractive, nonInteractive,
@@ -38,7 +38,7 @@ export default class ChannelView extends EasCommand {
const paginatedQueryOptions = getPaginatedQueryOptions(flags); const paginatedQueryOptions = getPaginatedQueryOptions(flags);
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags; const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelView, { } = await this.getContextAsync(ChannelView, {
nonInteractive, nonInteractive,
+2 -3
View File
@@ -41,7 +41,7 @@ export default class Config extends EasCommand {
enableJsonOutput(); enableJsonOutput();
} }
const { platform: maybePlatform, profile: maybeProfile } = flags; const { platform: maybePlatform, profile: maybeProfile } = flags;
const { getDynamicProjectConfigAsync, projectDir } = await this.getContextAsync(Config, { const { getDynamicPublicProjectConfigAsync, projectDir } = await this.getContextAsync(Config, {
nonInteractive: false, nonInteractive: false,
}); });
@@ -82,9 +82,8 @@ export default class Config extends EasCommand {
Log.log(JSON.stringify(profile, null, 2)); Log.log(JSON.stringify(profile, null, 2));
} }
} else { } else {
const { exp: appConfig } = await getDynamicProjectConfigAsync({ const { exp: appConfig } = await getDynamicPublicProjectConfigAsync({
env: profile.env, env: profile.env,
isPublicConfig: true,
}); });
if (flags.json) { if (flags.json) {
+4 -4
View File
@@ -21,8 +21,8 @@ export default class Credentials extends EasCommand {
const { flags } = await this.parse(Credentials); const { flags } = await this.parse(Credentials);
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
projectConfig, privateProjectConfig,
getDynamicProjectConfigAsync, getDynamicPrivateProjectConfigAsync,
analytics, analytics,
} = await this.getContextAsync(Credentials, { } = await this.getContextAsync(Credentials, {
nonInteractive: false, nonInteractive: false,
@@ -32,8 +32,8 @@ export default class Credentials extends EasCommand {
actor, actor,
graphqlClient, graphqlClient,
analytics, analytics,
projectConfig ?? null, privateProjectConfig ?? null,
getDynamicProjectConfigAsync, getDynamicPrivateProjectConfigAsync,
flags.platform flags.platform
).runAsync(); ).runAsync();
} }
@@ -15,7 +15,7 @@ export default class DeviceCreate extends EasCommand {
// this command is interactive by design // this command is interactive by design
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
projectConfig, privateProjectConfig,
} = await this.getContextAsync(DeviceCreate, { } = await this.getContextAsync(DeviceCreate, {
nonInteractive: false, nonInteractive: false,
}); });
@@ -24,7 +24,7 @@ export default class DeviceCreate extends EasCommand {
appStore: new AppStoreApi(), appStore: new AppStoreApi(),
user: actor, user: actor,
graphqlClient, graphqlClient,
projectId: projectConfig?.projectId, projectId: privateProjectConfig?.projectId,
}); });
const manager = new DeviceManager(ctx); const manager = new DeviceManager(ctx);
await manager.createAsync(); await manager.createAsync();
@@ -43,7 +43,7 @@ export default class DeviceDelete extends EasCommand {
const paginatedQueryOptions = getPaginatedQueryOptions(flags); const paginatedQueryOptions = getPaginatedQueryOptions(flags);
let { 'apple-team-id': appleTeamIdentifier, udid } = flags; let { 'apple-team-id': appleTeamIdentifier, udid } = flags;
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(DeviceDelete, { } = await this.getContextAsync(DeviceDelete, {
nonInteractive: paginatedQueryOptions.nonInteractive, nonInteractive: paginatedQueryOptions.nonInteractive,
@@ -112,11 +112,17 @@ export default class DeviceDelete extends EasCommand {
const removeAppleSpinner = ora('Disabling device on Apple').start(); const removeAppleSpinner = ora('Disabling device on Apple').start();
try { try {
const appleValidatedDevices = await Device.getAsync(context); const appleValidatedDevices = await Device.getAsync(context);
const appleValidatedDevice = appleValidatedDevices.find(d => d.id === device.id); const appleValidatedDevice = appleValidatedDevices.find(
d => d.attributes.udid === device.identifier
);
if (appleValidatedDevice) { if (appleValidatedDevice) {
await appleValidatedDevice.updateAsync({ status: DeviceStatus.DISABLED }); await appleValidatedDevice.updateAsync({ status: DeviceStatus.DISABLED });
}
removeAppleSpinner.succeed('Disabled device on Apple'); removeAppleSpinner.succeed('Disabled device on Apple');
} else {
removeAppleSpinner.warn(
'Device not found on Apple Developer Portal. Expo-registered devices will not appear there until they are chosen for an internal distribution build.'
);
}
} catch (err) { } catch (err) {
removeAppleSpinner.fail(); removeAppleSpinner.fail();
throw err; throw err;
+1 -1
View File
@@ -29,7 +29,7 @@ export default class BuildList extends EasCommand {
const { flags } = await this.parse(BuildList); const { flags } = await this.parse(BuildList);
const paginatedQueryOptions = getPaginatedQueryOptions(flags); const paginatedQueryOptions = getPaginatedQueryOptions(flags);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildList, { } = await this.getContextAsync(BuildList, {
nonInteractive: paginatedQueryOptions.nonInteractive, nonInteractive: paginatedQueryOptions.nonInteractive,
@@ -0,0 +1,168 @@
import { Device } from '@expo/apple-utils';
import { Flags } from '@oclif/core';
import assert from 'assert';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { PaginatedQueryOptions, getPaginatedQueryOptions } from '../../commandUtils/pagination';
import { AppleDeviceMutation } from '../../credentials/ios/api/graphql/mutations/AppleDeviceMutation';
import {
AppleDeviceQuery,
AppleDeviceQueryResult,
} from '../../credentials/ios/api/graphql/queries/AppleDeviceQuery';
import { authenticateAsync, getRequestContext } from '../../credentials/ios/appstore/authenticate';
import {
selectAppleDeviceOnAppleTeamAsync,
selectAppleTeamOnAccountAsync,
} from '../../devices/queries';
import formatDevice from '../../devices/utils/formatDevice';
import { AppleDevice, AppleTeamFragment, Maybe } from '../../graphql/generated';
import Log from '../../log';
import { ora } from '../../ora';
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
export default class DeviceRename extends EasCommand {
static override description = 'rename a registered device';
static override flags = {
'apple-team-id': Flags.string({ description: 'The Apple team ID on which to find the device' }),
udid: Flags.string({ description: 'The Apple device ID to rename' }),
name: Flags.string({ description: 'The new name for the device' }),
...EasNonInteractiveAndJsonFlags,
};
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(DeviceRename);
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
let { 'apple-team-id': appleTeamIdentifier, udid, name } = flags;
const {
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(DeviceRename, {
nonInteractive: paginatedQueryOptions.nonInteractive,
});
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
let appleTeamName;
if (paginatedQueryOptions.json) {
enableJsonOutput();
}
let appleTeam: AppleTeamFragment;
if (!appleTeamIdentifier) {
appleTeam = await selectAppleTeamOnAccountAsync(graphqlClient, {
accountName: account.name,
selectionPromptTitle: `What Apple team would you like to list devices for?`,
paginatedQueryOptions,
});
appleTeamIdentifier = appleTeam.appleTeamIdentifier;
appleTeamName = appleTeam.appleTeamName;
}
assert(appleTeamIdentifier, 'No team identifier is specified');
const chosenDevice = udid
? await AppleDeviceQuery.getByDeviceIdentifierAsync(graphqlClient, account.name, udid)
: await selectAppleDeviceOnAppleTeamAsync(graphqlClient, {
accountName: account.name,
appleTeamIdentifier,
selectionPromptTitle: `Which device would you like to rename?`,
paginatedQueryOptions,
});
const newDeviceName = name ? name : await this.promptForNewDeviceNameAsync(chosenDevice.name);
this.logChosenDevice(chosenDevice, appleTeamName, appleTeamIdentifier, paginatedQueryOptions);
await this.renameDeviceOnExpoAsync(graphqlClient, chosenDevice, newDeviceName!);
await this.renameDeviceOnAppleAsync(chosenDevice, appleTeamIdentifier, newDeviceName!);
}
async promptForNewDeviceNameAsync(
initial: Maybe<string> | undefined
): Promise<string | undefined> {
const { name } = await promptAsync({
type: 'text',
name: 'name',
message: 'New device name:',
initial: initial ?? undefined,
});
return name;
}
async renameDeviceOnExpoAsync(
graphqlClient: ExpoGraphqlClient,
chosenDevice: AppleDevice | AppleDeviceQueryResult,
newDeviceName: string
): Promise<void> {
const removalSpinner = ora(`Renaming Apple device on Expo`).start();
try {
await AppleDeviceMutation.updateAppleDeviceAsync(graphqlClient, chosenDevice.id, {
name: newDeviceName,
});
removalSpinner.succeed('Renamed Apple device on Expo');
} catch (err) {
removalSpinner.fail();
throw err;
}
}
async renameDeviceOnAppleAsync(
device: AppleDevice | AppleDeviceQueryResult,
appleTeamIdentifier: string,
newDeviceName: string
): Promise<void> {
const ctx = await authenticateAsync({ teamId: appleTeamIdentifier });
const context = getRequestContext(ctx);
Log.addNewLineIfNone();
const removeAppleSpinner = ora('Renaming device on Apple').start();
try {
const appleValidatedDevices = await Device.getAsync(context);
const appleValidatedDevice = appleValidatedDevices.find(
d => d.attributes.udid === device.identifier
);
if (appleValidatedDevice) {
await appleValidatedDevice.updateAsync({ name: newDeviceName });
removeAppleSpinner.succeed('Renamed device on Apple');
} else {
removeAppleSpinner.warn(
'Device not found on Apple Developer Portal. Expo-registered devices will not appear there until they are chosen for an internal distribution build.'
);
}
} catch (err) {
removeAppleSpinner.fail();
throw err;
}
}
logChosenDevice(
device: AppleDevice | AppleDeviceQueryResult,
appleTeamName: Maybe<string> | undefined,
appleTeamIdentifier: string,
{ json }: PaginatedQueryOptions
): void {
if (json) {
printJsonOnlyOutput(device);
} else {
Log.addNewLineIfNone();
Log.log(
formatDevice(device, {
appleTeamName,
appleTeamIdentifier: appleTeamIdentifier!,
})
);
Log.newLine();
}
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ If you are not sure what is the UDID of the device you are looking for, run:
throw new Error('Device UDID is missing'); throw new Error('Device UDID is missing');
} }
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(DeviceView, { } = await this.getContextAsync(DeviceView, {
nonInteractive: true, nonInteractive: true,
@@ -35,7 +35,7 @@ export default class MetadataPull extends EasCommand {
const { flags } = await this.parse(MetadataPull); const { flags } = await this.parse(MetadataPull);
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
projectConfig: { exp, projectId, projectDir }, privateProjectConfig: { exp, projectId, projectDir },
analytics, analytics,
} = await this.getContextAsync(MetadataPull, { } = await this.getContextAsync(MetadataPull, {
nonInteractive: false, nonInteractive: false,
@@ -33,7 +33,7 @@ export default class MetadataPush extends EasCommand {
const { flags } = await this.parse(MetadataPush); const { flags } = await this.parse(MetadataPush);
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
projectConfig: { exp, projectId, projectDir }, privateProjectConfig: { exp, projectId, projectDir },
analytics, analytics,
} = await this.getContextAsync(MetadataPush, { } = await this.getContextAsync(MetadataPush, {
nonInteractive: false, nonInteractive: false,
+1 -1
View File
@@ -16,7 +16,7 @@ export default class Open extends EasCommand {
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
// this command is interactive by nature (only really run by humans in a terminal) // this command is interactive by nature (only really run by humans in a terminal)
const { const {
projectConfig: { projectId, exp }, privateProjectConfig: { projectId, exp },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(Open, { } = await this.getContextAsync(Open, {
nonInteractive: false, nonInteractive: false,
@@ -1,4 +1,4 @@
import { AppJSONConfig, PackageJSONConfig, getConfig, modifyConfigAsync } from '@expo/config'; import { AppJSONConfig, PackageJSONConfig, getConfig } from '@expo/config';
import chalk from 'chalk'; import chalk from 'chalk';
import { vol } from 'memfs'; import { vol } from 'memfs';
import { instance, mock } from 'ts-mockito'; import { instance, mock } from 'ts-mockito';
@@ -12,12 +12,17 @@ import FeatureGating from '../../../commandUtils/gating/FeatureGating';
import { jester, jester2 } from '../../../credentials/__tests__/fixtures-constants'; import { jester, jester2 } from '../../../credentials/__tests__/fixtures-constants';
import { AppMutation } from '../../../graphql/mutations/AppMutation'; import { AppMutation } from '../../../graphql/mutations/AppMutation';
import { AppQuery } from '../../../graphql/queries/AppQuery'; import { AppQuery } from '../../../graphql/queries/AppQuery';
import { createOrModifyExpoConfigAsync } from '../../../project/expoConfig';
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync'; import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
import { confirmAsync, promptAsync } from '../../../prompts'; import { confirmAsync, promptAsync } from '../../../prompts';
import ProjectInit from '../init'; import ProjectInit from '../init';
jest.mock('fs'); jest.mock('fs');
jest.mock('@expo/config'); jest.mock('@expo/config');
jest.mock('../../../project/expoConfig', () => ({
...jest.requireActual('../../../project/expoConfig'),
createOrModifyExpoConfigAsync: jest.fn(),
}));
jest.mock('../../../prompts'); jest.mock('../../../prompts');
jest.mock('../../../user/actions'); jest.mock('../../../user/actions');
jest.mock('../../../graphql/mutations/AppMutation'); jest.mock('../../../graphql/mutations/AppMutation');
@@ -143,13 +148,16 @@ describe(ProjectInit.name, () => {
}); });
describe('force', () => { describe('force', () => {
it('does not prompt to overwrite when different', async () => { beforeEach(() => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({ jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234', id: '1234',
slug: 'testing-123', slug: 'testing-123',
fullName: '@jester/testing-123', fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0], ownerAccount: jester.accounts[0],
}); });
});
it('does not prompt to overwrite when different', async () => {
await new ProjectInit(['--id', '12345', '--force'], { await new ProjectInit(['--id', '12345', '--force'], {
root: '/test-project', root: '/test-project',
} as any).run(); } as any).run();
@@ -183,7 +191,7 @@ describe(ProjectInit.name, () => {
}); });
await new ProjectInit(['--id', '1234'], commandOptions).run(); await new ProjectInit(['--id', '1234'], commandOptions).run();
expect(modifyConfigAsync).not.toHaveBeenCalled(); expect(createOrModifyExpoConfigAsync).not.toHaveBeenCalled();
}); });
it('prompts to configure if not consistent', async () => { it('prompts to configure if not consistent', async () => {
@@ -195,14 +203,20 @@ describe(ProjectInit.name, () => {
}); });
jest.mocked(confirmAsync).mockResolvedValue(true); jest.mocked(confirmAsync).mockResolvedValue(true);
jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any }); jest
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await new ProjectInit(['--id', '1234'], commandOptions).run(); await new ProjectInit(['--id', '1234'], commandOptions).run();
expect(confirmAsync).toHaveBeenCalled(); expect(confirmAsync).toHaveBeenCalled();
expect(modifyConfigAsync).toHaveBeenCalledTimes(2); expect(createOrModifyExpoConfigAsync).toHaveBeenCalledTimes(2);
expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { owner: 'jester2' }); expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { slug: 'testing-124' }); owner: 'jester2',
});
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
slug: 'testing-124',
});
}); });
it('overrides if force flag is present and it is not consistent', async () => { it('overrides if force flag is present and it is not consistent', async () => {
@@ -213,14 +227,20 @@ describe(ProjectInit.name, () => {
ownerAccount: jester2.accounts[0], ownerAccount: jester2.accounts[0],
}); });
jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any }); jest
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await new ProjectInit(['--id', '1234', '--force'], commandOptions).run(); await new ProjectInit(['--id', '1234', '--force'], commandOptions).run();
expect(confirmAsync).not.toHaveBeenCalled(); expect(confirmAsync).not.toHaveBeenCalled();
expect(modifyConfigAsync).toHaveBeenCalledTimes(2); expect(createOrModifyExpoConfigAsync).toHaveBeenCalledTimes(2);
expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { owner: 'jester2' }); expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { slug: 'testing-124' }); owner: 'jester2',
});
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
slug: 'testing-124',
});
}); });
it('throws if non-interactive is present and it is not consistent', async () => { it('throws if non-interactive is present and it is not consistent', async () => {
@@ -231,7 +251,9 @@ describe(ProjectInit.name, () => {
ownerAccount: jester2.accounts[0], ownerAccount: jester2.accounts[0],
}); });
jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any }); jest
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await expect( await expect(
new ProjectInit(['--id', '1234', '--non-interactive'], commandOptions).run() new ProjectInit(['--id', '1234', '--non-interactive'], commandOptions).run()
@@ -240,7 +262,7 @@ describe(ProjectInit.name, () => {
); );
expect(confirmAsync).not.toHaveBeenCalled(); expect(confirmAsync).not.toHaveBeenCalled();
expect(modifyConfigAsync).not.toHaveBeenCalled(); expect(createOrModifyExpoConfigAsync).not.toHaveBeenCalled();
}); });
}); });
}); });
@@ -271,7 +293,7 @@ describe(ProjectInit.name, () => {
}); });
await new ProjectInit(['--id', '1234'], commandOptions).run(); await new ProjectInit(['--id', '1234'], commandOptions).run();
expect(modifyConfigAsync).not.toHaveBeenCalled(); expect(createOrModifyExpoConfigAsync).not.toHaveBeenCalled();
}); });
it('prompts to configure if not consistent', async () => { it('prompts to configure if not consistent', async () => {
@@ -285,14 +307,20 @@ describe(ProjectInit.name, () => {
}); });
jest.mocked(confirmAsync).mockResolvedValue(true); jest.mocked(confirmAsync).mockResolvedValue(true);
jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any }); jest
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await new ProjectInit(['--id', '1234'], commandOptions).run(); await new ProjectInit(['--id', '1234'], commandOptions).run();
expect(confirmAsync).toHaveBeenCalled(); expect(confirmAsync).toHaveBeenCalled();
expect(modifyConfigAsync).toHaveBeenCalledTimes(2); expect(createOrModifyExpoConfigAsync).toHaveBeenCalledTimes(2);
expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { owner: 'jester2' }); expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { slug: 'testing-124' }); owner: 'jester2',
});
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
slug: 'testing-124',
});
}); });
it('overrides if force flag is present and it is not consistent', async () => { it('overrides if force flag is present and it is not consistent', async () => {
@@ -305,14 +333,20 @@ describe(ProjectInit.name, () => {
ownerAccount: jester2.accounts[0], ownerAccount: jester2.accounts[0],
}); });
jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any }); jest
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await new ProjectInit(['--id', '1234', '--force'], commandOptions).run(); await new ProjectInit(['--id', '1234', '--force'], commandOptions).run();
expect(confirmAsync).not.toHaveBeenCalled(); expect(confirmAsync).not.toHaveBeenCalled();
expect(modifyConfigAsync).toHaveBeenCalledTimes(2); expect(createOrModifyExpoConfigAsync).toHaveBeenCalledTimes(2);
expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { owner: 'jester2' }); expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { slug: 'testing-124' }); owner: 'jester2',
});
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
slug: 'testing-124',
});
}); });
it('throws if non-interactive is present and it is not consistent', async () => { it('throws if non-interactive is present and it is not consistent', async () => {
@@ -325,7 +359,9 @@ describe(ProjectInit.name, () => {
ownerAccount: jester2.accounts[0], ownerAccount: jester2.accounts[0],
}); });
jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any }); jest
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await expect( await expect(
new ProjectInit(['--id', '1234', '--non-interactive'], commandOptions).run() new ProjectInit(['--id', '1234', '--non-interactive'], commandOptions).run()
@@ -334,7 +370,7 @@ describe(ProjectInit.name, () => {
); );
expect(confirmAsync).not.toHaveBeenCalled(); expect(confirmAsync).not.toHaveBeenCalled();
expect(modifyConfigAsync).not.toHaveBeenCalled(); expect(createOrModifyExpoConfigAsync).not.toHaveBeenCalled();
}); });
}); });
}); });
@@ -367,14 +403,20 @@ describe(ProjectInit.name, () => {
}); });
jest.mocked(confirmAsync).mockResolvedValue(true); jest.mocked(confirmAsync).mockResolvedValue(true);
jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any }); jest
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await new ProjectInit([], commandOptions).run(); await new ProjectInit([], commandOptions).run();
expect(confirmAsync).toHaveBeenCalled(); expect(confirmAsync).toHaveBeenCalled();
expect(modifyConfigAsync).toHaveBeenCalledTimes(2); expect(createOrModifyExpoConfigAsync).toHaveBeenCalledTimes(2);
expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { owner: 'jester2' }); expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { slug: 'testing-124' }); owner: 'jester2',
});
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
slug: 'testing-124',
});
}); });
}); });
@@ -43,7 +43,7 @@ export default class ProjectInfo extends EasCommand {
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ProjectInfo, { } = await this.getContextAsync(ProjectInfo, {
nonInteractive: true, nonInteractive: true,
@@ -1,4 +1,4 @@
import { getProjectConfigDescription, modifyConfigAsync } from '@expo/config'; import { getProjectConfigDescription } from '@expo/config';
import { ExpoConfig } from '@expo/config-types'; import { ExpoConfig } from '@expo/config-types';
import { Flags } from '@oclif/core'; import { Flags } from '@oclif/core';
import chalk from 'chalk'; import chalk from 'chalk';
@@ -13,7 +13,7 @@ import { AppMutation } from '../../graphql/mutations/AppMutation';
import { AppQuery } from '../../graphql/queries/AppQuery'; import { AppQuery } from '../../graphql/queries/AppQuery';
import Log, { link } from '../../log'; import Log, { link } from '../../log';
import { ora } from '../../ora'; import { ora } from '../../ora';
import { getExpoConfig } from '../../project/expoConfig'; import { createOrModifyExpoConfigAsync, getPrivateExpoConfig } from '../../project/expoConfig';
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync'; import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
import { toAppPrivacy } from '../../project/projectUtils'; import { toAppPrivacy } from '../../project/projectUtils';
import { confirmAsync, promptAsync } from '../../prompts'; import { confirmAsync, promptAsync } from '../../prompts';
@@ -60,7 +60,7 @@ export default class ProjectInit extends EasCommand {
projectDir: string, projectDir: string,
modifications: Partial<ExpoConfig> modifications: Partial<ExpoConfig>
): Promise<void> { ): Promise<void> {
const result = await modifyConfigAsync(projectDir, modifications); const result = await createOrModifyExpoConfigAsync(projectDir, modifications);
switch (result.type) { switch (result.type) {
case 'success': case 'success':
break; break;
@@ -98,7 +98,7 @@ export default class ProjectInit extends EasCommand {
projectDir: string, projectDir: string,
{ force, nonInteractive }: InitializeMethodOptions { force, nonInteractive }: InitializeMethodOptions
): Promise<void> { ): Promise<void> {
const exp = getExpoConfig(projectDir); const exp = getPrivateExpoConfig(projectDir);
const appForProjectId = await AppQuery.byIdAsync(graphqlClient, projectId); const appForProjectId = await AppQuery.byIdAsync(graphqlClient, projectId);
const correctOwner = appForProjectId.ownerAccount.name; const correctOwner = appForProjectId.ownerAccount.name;
const correctSlug = appForProjectId.slug; const correctSlug = appForProjectId.slug;
@@ -153,7 +153,7 @@ export default class ProjectInit extends EasCommand {
projectDir: string, projectDir: string,
{ force, nonInteractive }: InitializeMethodOptions { force, nonInteractive }: InitializeMethodOptions
): Promise<void> { ): Promise<void> {
const exp = getExpoConfig(projectDir); const exp = getPrivateExpoConfig(projectDir);
const existingProjectId = exp.extra?.eas?.projectId; const existingProjectId = exp.extra?.eas?.projectId;
if (projectId === existingProjectId) { if (projectId === existingProjectId) {
@@ -209,7 +209,7 @@ export default class ProjectInit extends EasCommand {
actor: Actor, actor: Actor,
projectDir: string projectDir: string
): Promise<string> { ): Promise<string> {
const exp = getExpoConfig(projectDir); const exp = getPrivateExpoConfig(projectDir);
const existingProjectId = exp.extra?.eas?.projectId; const existingProjectId = exp.extra?.eas?.projectId;
if (existingProjectId) { if (existingProjectId) {
@@ -66,7 +66,7 @@ export default class EnvironmentSecretCreate extends EasCommand {
}, },
} = await this.parse(EnvironmentSecretCreate); } = await this.parse(EnvironmentSecretCreate);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvironmentSecretCreate, { } = await this.getContextAsync(EnvironmentSecretCreate, {
nonInteractive, nonInteractive,
@@ -31,7 +31,7 @@ export default class EnvironmentSecretDelete extends EasCommand {
flags: { id, 'non-interactive': nonInteractive }, flags: { id, 'non-interactive': nonInteractive },
} = await this.parse(EnvironmentSecretDelete); } = await this.parse(EnvironmentSecretDelete);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvironmentSecretDelete, { } = await this.getContextAsync(EnvironmentSecretDelete, {
nonInteractive, nonInteractive,
+1 -1
View File
@@ -19,7 +19,7 @@ export default class EnvironmentSecretList extends EasCommand {
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvironmentSecretList, { } = await this.getContextAsync(EnvironmentSecretList, {
nonInteractive: true, nonInteractive: true,
+1 -1
View File
@@ -51,7 +51,7 @@ export default class EnvironmentSecretPush extends EasCommand {
flags: { scope, force, 'env-file': maybeEnvFilePath, 'non-interactive': nonInteractive }, flags: { scope, force, 'env-file': maybeEnvFilePath, 'non-interactive': nonInteractive },
} = await this.parse(EnvironmentSecretPush); } = await this.parse(EnvironmentSecretPush);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvironmentSecretPush, { } = await this.getContextAsync(EnvironmentSecretPush, {
nonInteractive, nonInteractive,
+1 -1
View File
@@ -100,7 +100,7 @@ export default class Submit extends EasCommand {
const { flags: rawFlags } = await this.parse(Submit); const { flags: rawFlags } = await this.parse(Submit);
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
projectConfig: { exp, projectId, projectDir }, privateProjectConfig: { exp, projectId, projectDir },
analytics, analytics,
} = await this.getContextAsync(Submit, { } = await this.getContextAsync(Submit, {
nonInteractive: false, nonInteractive: false,
@@ -1,11 +1,4 @@
import { import { AppJSONConfig, ExpoConfig, PackageJSONConfig, Platform, getConfig } from '@expo/config';
AppJSONConfig,
ExpoConfig,
GetConfigOptions,
PackageJSONConfig,
Platform,
getConfig,
} from '@expo/config';
import { Updates } from '@expo/config-plugins'; import { Updates } from '@expo/config-plugins';
import { vol } from 'memfs'; import { vol } from 'memfs';
import nullthrows from 'nullthrows'; import nullthrows from 'nullthrows';
@@ -14,7 +7,10 @@ import { instance, mock } from 'ts-mockito';
import UpdatePublish from '..'; import UpdatePublish from '..';
import { ensureBranchExistsAsync } from '../../../branch/queries'; import { ensureBranchExistsAsync } from '../../../branch/queries';
import { DynamicProjectConfigContextField } from '../../../commandUtils/context/DynamicProjectConfigContextField'; import {
DynamicPrivateProjectConfigContextField,
DynamicPublicProjectConfigContextField,
} from '../../../commandUtils/context/DynamicProjectConfigContextField';
import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField'; import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient'; import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides'; import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides';
@@ -237,13 +233,19 @@ function mockTestProject({
jest.mocked(getConfig).mockReturnValue(mockManifest as any); jest.mocked(getConfig).mockReturnValue(mockManifest as any);
jest jest
.spyOn(DynamicProjectConfigContextField.prototype, 'getValueAsync') .spyOn(DynamicPrivateProjectConfigContextField.prototype, 'getValueAsync')
.mockResolvedValue(async (options?: GetConfigOptions) => { .mockResolvedValue(async () => {
let exp = { ...mockManifest.exp }; const exp = { ...mockManifest.exp };
return {
// Fake a limited public config exp,
if (options?.isPublicConfig) { projectDir: projectRoot,
exp = { projectId: configuredProjectId,
};
});
jest
.spyOn(DynamicPublicProjectConfigContextField.prototype, 'getValueAsync')
.mockResolvedValue(async () => {
const exp = {
name: mockManifest.exp.name, name: mockManifest.exp.name,
version: mockManifest.exp.version, version: mockManifest.exp.version,
slug: mockManifest.exp.slug, slug: mockManifest.exp.slug,
@@ -251,8 +253,6 @@ function mockTestProject({
owner: mockManifest.exp.owner, owner: mockManifest.exp.owner,
extra: mockManifest.exp.extra, extra: mockManifest.exp.extra,
}; };
}
return { return {
exp, exp,
projectDir: projectRoot, projectDir: projectRoot,
@@ -3,7 +3,7 @@ import { vol } from 'memfs';
import { instance, mock } from 'ts-mockito'; import { instance, mock } from 'ts-mockito';
import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField'; import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField';
import ProjectConfigContextField from '../../../commandUtils/context/ProjectConfigContextField'; import { PrivateProjectConfigContextField } from '../../../commandUtils/context/PrivateProjectConfigContextField';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient'; import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides'; import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides';
import FeatureGating from '../../../commandUtils/gating/FeatureGating'; import FeatureGating from '../../../commandUtils/gating/FeatureGating';
@@ -324,7 +324,7 @@ function mockTestProject({
const graphqlClient = instance(mock<ExpoGraphqlClient>({})); const graphqlClient = instance(mock<ExpoGraphqlClient>({}));
jest.mocked(getConfig).mockReturnValue(mockManifest as any); jest.mocked(getConfig).mockReturnValue(mockManifest as any);
jest.spyOn(ProjectConfigContextField.prototype, 'getValueAsync').mockResolvedValue({ jest.spyOn(PrivateProjectConfigContextField.prototype, 'getValueAsync').mockResolvedValue({
exp: mockManifest.exp, exp: mockManifest.exp,
projectDir: projectRoot, projectDir: projectRoot,
projectId: configuredProjectId, projectId: configuredProjectId,
@@ -1,10 +1,4 @@
import { import { AppJSONConfig, ExpoConfig, PackageJSONConfig, getConfig } from '@expo/config';
AppJSONConfig,
ExpoConfig,
GetConfigOptions,
PackageJSONConfig,
getConfig,
} from '@expo/config';
import { Updates } from '@expo/config-plugins'; import { Updates } from '@expo/config-plugins';
import { vol } from 'memfs'; import { vol } from 'memfs';
import nullthrows from 'nullthrows'; import nullthrows from 'nullthrows';
@@ -12,7 +6,10 @@ import path from 'path';
import { instance, mock } from 'ts-mockito'; import { instance, mock } from 'ts-mockito';
import { ensureBranchExistsAsync } from '../../../branch/queries'; import { ensureBranchExistsAsync } from '../../../branch/queries';
import { DynamicProjectConfigContextField } from '../../../commandUtils/context/DynamicProjectConfigContextField'; import {
DynamicPrivateProjectConfigContextField,
DynamicPublicProjectConfigContextField,
} from '../../../commandUtils/context/DynamicProjectConfigContextField';
import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField'; import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient'; import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides'; import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides';
@@ -217,13 +214,19 @@ function mockTestProject({
jest.mocked(getConfig).mockReturnValue(mockManifest as any); jest.mocked(getConfig).mockReturnValue(mockManifest as any);
jest jest
.spyOn(DynamicProjectConfigContextField.prototype, 'getValueAsync') .spyOn(DynamicPrivateProjectConfigContextField.prototype, 'getValueAsync')
.mockResolvedValue(async (options?: GetConfigOptions) => { .mockResolvedValue(async () => {
let exp = { ...mockManifest.exp }; const exp = { ...mockManifest.exp };
return {
// Fake a limited public config exp,
if (options?.isPublicConfig) { projectDir: projectRoot,
exp = { projectId: configuredProjectId,
};
});
jest
.spyOn(DynamicPublicProjectConfigContextField.prototype, 'getValueAsync')
.mockResolvedValue(async () => {
const exp = {
name: mockManifest.exp.name, name: mockManifest.exp.name,
version: mockManifest.exp.version, version: mockManifest.exp.version,
slug: mockManifest.exp.slug, slug: mockManifest.exp.slug,
@@ -231,8 +234,6 @@ function mockTestProject({
owner: mockManifest.exp.owner, owner: mockManifest.exp.owner,
extra: mockManifest.exp.extra, extra: mockManifest.exp.extra,
}; };
}
return { return {
exp, exp,
projectDir: projectRoot, projectDir: projectRoot,
@@ -1,14 +1,16 @@
import { Flags } from '@oclif/core'; import { Flags } from '@oclif/core';
import chalk from 'chalk'; import chalk from 'chalk';
import { easJsonExistsAsync } from '../../build/configure';
import EasCommand from '../../commandUtils/EasCommand'; import EasCommand from '../../commandUtils/EasCommand';
import { EASNonInteractiveFlag } from '../../commandUtils/flags'; import { EASNonInteractiveFlag } from '../../commandUtils/flags';
import Log from '../../log'; import Log, { learnMore } from '../../log';
import { RequestedPlatform } from '../../platform'; import { RequestedPlatform } from '../../platform';
import { import {
ensureEASUpdateIsConfiguredAsync, ensureEASUpdateIsConfiguredAsync,
ensureEASUpdateIsConfiguredInEasJsonAsync, ensureEASUpdateIsConfiguredInEasJsonAsync,
} from '../../update/configure'; } from '../../update/configure';
import { getVcsClient } from '../../vcs';
export default class UpdateConfigure extends EasCommand { export default class UpdateConfigure extends EasCommand {
static override description = 'configure the project to support EAS Update'; static override description = 'configure the project to support EAS Update';
@@ -32,7 +34,7 @@ export default class UpdateConfigure extends EasCommand {
const { flags } = await this.parse(UpdateConfigure); const { flags } = await this.parse(UpdateConfigure);
const platform = flags.platform as RequestedPlatform; const platform = flags.platform as RequestedPlatform;
const { const {
projectConfig: { projectId, exp, projectDir }, privateProjectConfig: { projectId, exp, projectDir },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdateConfigure, { } = await this.getContextAsync(UpdateConfigure, {
nonInteractive: flags['non-interactive'], nonInteractive: flags['non-interactive'],
@@ -42,6 +44,8 @@ export default class UpdateConfigure extends EasCommand {
'💡 The following process will configure your project to run EAS Update. These changes only apply to your local project files and you can safely revert them at any time.' '💡 The following process will configure your project to run EAS Update. These changes only apply to your local project files and you can safely revert them at any time.'
); );
await getVcsClient().ensureRepoExistsAsync();
await ensureEASUpdateIsConfiguredAsync(graphqlClient, { await ensureEASUpdateIsConfiguredAsync(graphqlClient, {
exp, exp,
projectId, projectId,
@@ -54,21 +58,14 @@ export default class UpdateConfigure extends EasCommand {
Log.addNewLineIfNone(); Log.addNewLineIfNone();
Log.log(`🎉 Your app is configured with EAS Update!`); Log.log(`🎉 Your app is configured with EAS Update!`);
Log.newLine(); Log.newLine();
Log.log(`${chalk.bold('Next steps')}:`); const easJsonExists = await easJsonExistsAsync(projectDir);
Log.newLine(); if (!easJsonExists) {
Log.log('Update a production build:'); Log.log(`- Run ${chalk.bold('eas build:configure')} to complete your installation`);
Log.log(`1. Create a new build. Example: ${chalk.bold('eas build --profile production')}.`); }
Log.log('2. Make changes in your project.');
Log.log(`3. Publish an update. Example: ${chalk.bold('eas update --channel production')}.`);
Log.log('4. Force close and reopen the app at least twice to view the update.');
Log.newLine();
Log.log('Preview an update:');
Log.log( Log.log(
`1. Publish an update to a branch. Example: ${chalk.bold('eas update --branch new-feature')}.` `- ${learnMore('https://docs.expo.dev/eas-update/introduction/', {
); learnMoreMessage: 'Learn more about other capabilities of EAS Update',
Log.log( })}`
'2. In Expo Go or a development build, navigate to Projects > [project name] > Branch > Open.'
); );
} }
} }
+65 -15
View File
@@ -23,6 +23,7 @@ import { RequestedPlatform } from '../../platform';
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils'; import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
import { import {
ExpoCLIExportPlatformFlag, ExpoCLIExportPlatformFlag,
RawAsset,
buildBundlesAsync, buildBundlesAsync,
buildUnsortedUpdateInfoGroupAsync, buildUnsortedUpdateInfoGroupAsync,
collectAssetsAsync, collectAssetsAsync,
@@ -39,13 +40,13 @@ import {
} from '../../project/publish'; } from '../../project/publish';
import { ensureEASUpdateIsConfiguredAsync } from '../../update/configure'; import { ensureEASUpdateIsConfiguredAsync } from '../../update/configure';
import { getUpdateGroupJsonInfo } from '../../update/utils'; import { getUpdateGroupJsonInfo } from '../../update/utils';
import { maybeWarnAboutEasPlanOverageAsync } from '../../utils/billing';
import { import {
checkManifestBodyAgainstUpdateInfoGroup, checkManifestBodyAgainstUpdateInfoGroup,
getCodeSigningInfoAsync, getCodeSigningInfoAsync,
getManifestBodyAsync, getManifestBodyAsync,
signBody, signBody,
} from '../../utils/code-signing'; } from '../../utils/code-signing';
import areSetsEqual from '../../utils/expodash/areSetsEqual';
import uniqBy from '../../utils/expodash/uniqBy'; import uniqBy from '../../utils/expodash/uniqBy';
import formatFields from '../../utils/formatFields'; import formatFields from '../../utils/formatFields';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json'; import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
@@ -60,6 +61,7 @@ type RawUpdateFlags = {
platform: string; platform: string;
'input-dir': string; 'input-dir': string;
'skip-bundler': boolean; 'skip-bundler': boolean;
'clear-cache': boolean;
'private-key-path'?: string; 'private-key-path'?: string;
'non-interactive': boolean; 'non-interactive': boolean;
json: boolean; json: boolean;
@@ -77,6 +79,7 @@ type UpdateFlags = {
updateMessage?: string; updateMessage?: string;
inputDir: string; inputDir: string;
skipBundler: boolean; skipBundler: boolean;
clearCache: boolean;
privateKeyPath?: string; privateKeyPath?: string;
json: boolean; json: boolean;
nonInteractive: boolean; nonInteractive: boolean;
@@ -95,6 +98,7 @@ export default class UpdatePublish extends EasCommand {
required: false, required: false,
}), }),
message: Flags.string({ message: Flags.string({
char: 'm',
description: 'A short message describing the update', description: 'A short message describing the update',
required: false, required: false,
}), }),
@@ -115,6 +119,10 @@ export default class UpdatePublish extends EasCommand {
description: `Skip running Expo CLI to bundle the app before publishing`, description: `Skip running Expo CLI to bundle the app before publishing`,
default: false, default: false,
}), }),
'clear-cache': Flags.boolean({
description: `Clear the bundler cache before publishing`,
default: false,
}),
platform: Flags.enum({ platform: Flags.enum({
char: 'p', char: 'p',
options: [ options: [
@@ -152,6 +160,7 @@ export default class UpdatePublish extends EasCommand {
updateMessage: updateMessageArg, updateMessage: updateMessageArg,
inputDir, inputDir,
skipBundler, skipBundler,
clearCache,
privateKeyPath, privateKeyPath,
json: jsonFlag, json: jsonFlag,
nonInteractive, nonInteractive,
@@ -159,7 +168,8 @@ export default class UpdatePublish extends EasCommand {
} = this.sanitizeFlags(rawFlags); } = this.sanitizeFlags(rawFlags);
const { const {
getDynamicProjectConfigAsync, getDynamicPublicProjectConfigAsync,
getDynamicPrivateProjectConfigAsync,
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdatePublish, { } = await this.getContextAsync(UpdatePublish, {
nonInteractive, nonInteractive,
@@ -173,11 +183,8 @@ export default class UpdatePublish extends EasCommand {
exp: expPossiblyWithoutEasUpdateConfigured, exp: expPossiblyWithoutEasUpdateConfigured,
projectId, projectId,
projectDir, projectDir,
} = await getDynamicProjectConfigAsync({ } = await getDynamicPublicProjectConfigAsync();
isPublicConfig: true,
});
await maybeWarnAboutEasPlanOverageAsync(graphqlClient, projectId);
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]); await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]);
await ensureEASUpdateIsConfiguredAsync(graphqlClient, { await ensureEASUpdateIsConfiguredAsync(graphqlClient, {
@@ -187,10 +194,8 @@ export default class UpdatePublish extends EasCommand {
projectId, projectId,
}); });
const { exp } = await getDynamicProjectConfigAsync({ isPublicConfig: true }); const { exp } = await getDynamicPublicProjectConfigAsync();
const { exp: expPrivate } = await getDynamicProjectConfigAsync({ const { exp: expPrivate } = await getDynamicPrivateProjectConfigAsync();
isPublicConfig: false,
});
const codeSigningInfo = await getCodeSigningInfoAsync(expPrivate, privateKeyPath); const codeSigningInfo = await getCodeSigningInfoAsync(expPrivate, privateKeyPath);
const branchName = await getBranchNameForCommandAsync({ const branchName = await getBranchNameForCommandAsync({
@@ -214,7 +219,7 @@ export default class UpdatePublish extends EasCommand {
if (!skipBundler) { if (!skipBundler) {
const bundleSpinner = ora().start('Exporting...'); const bundleSpinner = ora().start('Exporting...');
try { try {
await buildBundlesAsync({ projectDir, inputDir, exp, platformFlag }); await buildBundlesAsync({ projectDir, inputDir, exp, platformFlag, clearCache });
bundleSpinner.succeed('Exported bundle(s)'); bundleSpinner.succeed('Exported bundle(s)');
} catch (e) { } catch (e) {
bundleSpinner.fail('Export failed'); bundleSpinner.fail('Export failed');
@@ -236,14 +241,58 @@ export default class UpdatePublish extends EasCommand {
const assets = filterExportedPlatformsByFlag(collectedAssets, platformFlag); const assets = filterExportedPlatformsByFlag(collectedAssets, platformFlag);
realizedPlatforms = Object.keys(assets) as PublishPlatform[]; realizedPlatforms = Object.keys(assets) as PublishPlatform[];
const uploadResults = await uploadAssetsAsync( // Timeout mechanism:
// - Start with 60 second timeout. 60 seconds is chosen because the cloud function that processes
// uploaded assets has a timeout of 60 seconds.
// - Each time one or more assets reports as ready, reset the timeout to 60 seconds.
// - Start upload. Internally, uploadAssetsAsync uploads them all and then checks for successful
// processing every (5 + n) seconds with a linear backoff of n + 1 second.
// - At the same time as upload is started, start timeout checker which checks every 1 second to see
// if timeout has been reached. When timeout expires, send a cancellation signal to currently running
// upload function call to instruct it to stop uploading or checking for successful processing.
let lastUploadedStorageKeys = new Set<string>();
let lastAssetUploadResults: {
asset: RawAsset & { storageKey: string };
finished: boolean;
}[] = [];
let timeAtWhichToTimeout = Date.now() + 60 * 1000; // sixty seconds from now
const cancelationToken = { isCanceledOrFinished: false };
const uploadResults = await Promise.race([
uploadAssetsAsync(
graphqlClient, graphqlClient,
assets, assets,
projectId, projectId,
(totalAssets, missingAssets) => { cancelationToken,
assetSpinner.text = `Uploading (${totalAssets - missingAssets}/${totalAssets})`; assetUploadResults => {
} const currentUploadedStorageKeys = new Set(
assetUploadResults.filter(r => r.finished).map(r => r.asset.storageKey)
); );
if (!areSetsEqual(currentUploadedStorageKeys, lastUploadedStorageKeys)) {
timeAtWhichToTimeout = Date.now() + 60 * 1000; // reset timeout to sixty seconds from now
lastUploadedStorageKeys = currentUploadedStorageKeys;
lastAssetUploadResults = assetUploadResults;
}
const totalAssets = assetUploadResults.length;
const missingAssetCount = assetUploadResults.filter(a => !a.finished).length;
assetSpinner.text = `Uploading (${totalAssets - missingAssetCount}/${totalAssets})`;
}
),
(async () => {
while (Date.now() < timeAtWhichToTimeout) {
if (cancelationToken.isCanceledOrFinished) {
break;
}
await new Promise(res => setTimeout(res, 1000)); // wait 1 second
}
cancelationToken.isCanceledOrFinished = true;
const timedOutAssets = lastAssetUploadResults
.filter(r => !r.finished)
.map(r => `\n- ${r.asset.originalPath ?? r.asset.path}`);
throw new Error(`Asset processing timed out for assets: ${timedOutAssets}`);
})(),
]);
uploadedAssetCount = uploadResults.uniqueUploadedAssetCount; uploadedAssetCount = uploadResults.uniqueUploadedAssetCount;
assetLimitPerUpdateGroup = uploadResults.assetLimitPerUpdateGroup; assetLimitPerUpdateGroup = uploadResults.assetLimitPerUpdateGroup;
@@ -490,6 +539,7 @@ export default class UpdatePublish extends EasCommand {
updateMessage, updateMessage,
inputDir: flags['input-dir'], inputDir: flags['input-dir'],
skipBundler: flags['skip-bundler'], skipBundler: flags['skip-bundler'],
clearCache: flags['clear-cache'],
platform: flags.platform as RequestedPlatform, platform: flags.platform as RequestedPlatform,
privateKeyPath: flags['private-key-path'], privateKeyPath: flags['private-key-path'],
nonInteractive, nonInteractive,
+1 -1
View File
@@ -41,7 +41,7 @@ export default class UpdateList extends EasCommand {
const { flags } = await this.parse(UpdateList); const { flags } = await this.parse(UpdateList);
const { branch: branchFlag, all, json: jsonFlag, 'non-interactive': nonInteractive } = flags; const { branch: branchFlag, all, json: jsonFlag, 'non-interactive': nonInteractive } = flags;
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdateList, { } = await this.getContextAsync(UpdateList, {
nonInteractive, nonInteractive,
@@ -72,6 +72,7 @@ export default class UpdateRepublish extends EasCommand {
exclusive: ['branch', 'channel'], exclusive: ['branch', 'channel'],
}), }),
message: Flags.string({ message: Flags.string({
char: 'm',
description: 'Short message describing the republished update', description: 'Short message describing the republished update',
required: false, required: false,
}), }),
@@ -94,7 +95,7 @@ export default class UpdateRepublish extends EasCommand {
const flags = this.sanitizeFlags(rawFlags); const flags = this.sanitizeFlags(rawFlags);
const { const {
projectConfig: { exp, projectId }, privateProjectConfig: { exp, projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdateRepublish, { } = await this.getContextAsync(UpdateRepublish, {
nonInteractive: flags.nonInteractive, nonInteractive: flags.nonInteractive,
@@ -122,7 +122,8 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
} = this.sanitizeFlags(rawFlags); } = this.sanitizeFlags(rawFlags);
const { const {
getDynamicProjectConfigAsync, getDynamicPublicProjectConfigAsync,
getDynamicPrivateProjectConfigAsync,
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdateRollBackToEmbedded, { } = await this.getContextAsync(UpdateRollBackToEmbedded, {
nonInteractive, nonInteractive,
@@ -136,9 +137,7 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
exp: expPossiblyWithoutEasUpdateConfigured, exp: expPossiblyWithoutEasUpdateConfigured,
projectId, projectId,
projectDir, projectDir,
} = await getDynamicProjectConfigAsync({ } = await getDynamicPublicProjectConfigAsync();
isPublicConfig: true,
});
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]); await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]);
@@ -149,10 +148,8 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
projectId, projectId,
}); });
const { exp } = await getDynamicProjectConfigAsync({ isPublicConfig: true }); const { exp } = await getDynamicPublicProjectConfigAsync();
const { exp: expPrivate } = await getDynamicProjectConfigAsync({ const { exp: expPrivate } = await getDynamicPrivateProjectConfigAsync();
isPublicConfig: false,
});
const codeSigningInfo = await getCodeSigningInfoAsync(expPrivate, privateKeyPath); const codeSigningInfo = await getCodeSigningInfoAsync(expPrivate, privateKeyPath);
const branchName = await getBranchNameForCommandAsync({ const branchName = await getBranchNameForCommandAsync({
@@ -33,7 +33,7 @@ export default class WebhookCreate extends EasCommand {
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
const { flags } = await this.parse(WebhookCreate); const { flags } = await this.parse(WebhookCreate);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(WebhookCreate, { } = await this.getContextAsync(WebhookCreate, {
nonInteractive: flags['non-interactive'], nonInteractive: flags['non-interactive'],
@@ -39,7 +39,7 @@ export default class WebhookDelete extends EasCommand {
flags: { 'non-interactive': nonInteractive }, flags: { 'non-interactive': nonInteractive },
} = await this.parse(WebhookDelete); } = await this.parse(WebhookDelete);
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(WebhookDelete, { } = await this.getContextAsync(WebhookDelete, {
nonInteractive, nonInteractive,
@@ -36,7 +36,7 @@ export default class WebhookList extends EasCommand {
} }
const { const {
projectConfig: { projectId }, privateProjectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(WebhookList, { } = await this.getContextAsync(WebhookList, {
nonInteractive: true, nonInteractive: true,
@@ -151,6 +151,7 @@ export function getNewAndroidApiMock(): { [key in keyof typeof AndroidGraphqlCli
updateAndroidAppCredentialsAsync: jest.fn(), updateAndroidAppCredentialsAsync: jest.fn(),
updateAndroidAppBuildCredentialsAsync: jest.fn(), updateAndroidAppBuildCredentialsAsync: jest.fn(),
createAndroidAppBuildCredentialsAsync: jest.fn(), createAndroidAppBuildCredentialsAsync: jest.fn(),
setDefaultAndroidAppBuildCredentialsAsync: jest.fn(),
getDefaultAndroidAppBuildCredentialsAsync: jest.fn(), getDefaultAndroidAppBuildCredentialsAsync: jest.fn(),
getAndroidAppBuildCredentialsByNameAsync: jest.fn(), getAndroidAppBuildCredentialsByNameAsync: jest.fn(),
createOrUpdateAndroidAppBuildCredentialsByNameAsync: jest.fn(), createOrUpdateAndroidAppBuildCredentialsByNameAsync: jest.fn(),
@@ -146,6 +146,16 @@ export async function updateAndroidAppBuildCredentialsAsync(
); );
} }
export async function setDefaultAndroidAppBuildCredentialsAsync(
graphqlClient: ExpoGraphqlClient,
buildCredentials: AndroidAppBuildCredentialsFragment
): Promise<AndroidAppBuildCredentialsFragment> {
return await AndroidAppBuildCredentialsMutation.setDefaultAndroidAppBuildCredentialsAsync(
graphqlClient,
buildCredentials.id
);
}
export async function createAndroidAppBuildCredentialsAsync( export async function createAndroidAppBuildCredentialsAsync(
graphqlClient: ExpoGraphqlClient, graphqlClient: ExpoGraphqlClient,
appLookupParams: AppLookupParams, appLookupParams: AppLookupParams,
@@ -164,14 +174,6 @@ export async function createAndroidAppBuildCredentialsAsync(
graphqlClient, graphqlClient,
appLookupParams appLookupParams
); );
const buildCredentialsList = androidAppCredentials.androidAppBuildCredentialsList;
const existingDefaultBuildCredentials =
buildCredentialsList.find(buildCredentials => buildCredentials.isDefault) ?? null;
if (existingDefaultBuildCredentials && isDefault) {
throw new Error(
'Cannot create new default Android Build Credentials. A set of default credentials exists already.'
);
}
return await AndroidAppBuildCredentialsMutation.createAndroidAppBuildCredentialsAsync( return await AndroidAppBuildCredentialsMutation.createAndroidAppBuildCredentialsAsync(
graphqlClient, graphqlClient,
@@ -5,6 +5,7 @@ import gql from 'graphql-tag';
import { ExpoGraphqlClient } from '../../../../../commandUtils/context/contextUtils/createGraphqlClient'; import { ExpoGraphqlClient } from '../../../../../commandUtils/context/contextUtils/createGraphqlClient';
import { withErrorHandlingAsync } from '../../../../../graphql/client'; import { withErrorHandlingAsync } from '../../../../../graphql/client';
import { import {
AndroidAppBuildCredentials,
AndroidAppBuildCredentialsFragment, AndroidAppBuildCredentialsFragment,
AndroidAppBuildCredentialsInput, AndroidAppBuildCredentialsInput,
CreateAndroidAppBuildCredentialsMutation, CreateAndroidAppBuildCredentialsMutation,
@@ -55,6 +56,37 @@ export const AndroidAppBuildCredentialsMutation = {
); );
return data.androidAppBuildCredentials.createAndroidAppBuildCredentials; return data.androidAppBuildCredentials.createAndroidAppBuildCredentials;
}, },
async setDefaultAndroidAppBuildCredentialsAsync(
graphqlClient: ExpoGraphqlClient,
androidAppBuildCredentialsId: string
): Promise<AndroidAppBuildCredentialsFragment> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<AndroidAppBuildCredentials>(
gql`
mutation AndroidAppBuildCredentialsMutation(
$androidAppBuildCredentialsId: ID!
$isDefault: Boolean!
) {
androidAppBuildCredentials {
setDefault(id: $androidAppBuildCredentialsId, isDefault: $isDefault) {
id
...AndroidAppBuildCredentialsFragment
}
}
}
${print(AndroidAppBuildCredentialsFragmentNode)}
`,
{
androidAppBuildCredentialsId,
isDefault: true,
}
)
.toPromise()
);
assert(data, `GraphQL: 'setDefault' not defined in server response ${JSON.stringify(data)}}`);
return data;
},
async setKeystoreAsync( async setKeystoreAsync(
graphqlClient: ExpoGraphqlClient, graphqlClient: ExpoGraphqlClient,
androidAppBuildCredentialsId: string, androidAppBuildCredentialsId: string,
+2 -14
View File
@@ -6,7 +6,7 @@ import chalk from 'chalk';
import { Analytics } from '../analytics/AnalyticsManager'; import { Analytics } from '../analytics/AnalyticsManager';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient'; import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import Log from '../log'; import Log from '../log';
import { getExpoConfig } from '../project/expoConfig'; import { getPrivateExpoConfig } from '../project/expoConfig';
import { confirmAsync } from '../prompts'; import { confirmAsync } from '../prompts';
import { Actor } from '../user/User'; import { Actor } from '../user/User';
import * as AndroidGraphqlClient from './android/api/GraphqlClient'; import * as AndroidGraphqlClient from './android/api/GraphqlClient';
@@ -56,18 +56,6 @@ export class CredentialsContext {
this.projectInfo = options.projectInfo; this.projectInfo = options.projectInfo;
} }
static getExpoConfigInProject(
projectDir: string,
{ env }: { env?: Env } = {}
): ExpoConfig | null {
try {
return getExpoConfig(projectDir, { env });
} catch {
// ignore error, context might be created outside of expo project
return null;
}
}
get hasProjectContext(): boolean { get hasProjectContext(): boolean {
return !!this.projectInfo; return !!this.projectInfo;
} }
@@ -87,7 +75,7 @@ export class CredentialsContext {
return; return;
} }
// trigger getConfig error // trigger getConfig error
getExpoConfig(this.options.projectDir); getPrivateExpoConfig(this.options.projectDir);
} }
async bestEffortAppStoreAuthenticateAsync(): Promise<void> { async bestEffortAppStoreAuthenticateAsync(): Promise<void> {
@@ -13,6 +13,7 @@ export async function chooseDevicesAsync(
const { devices } = await promptAsync({ const { devices } = await promptAsync({
type: 'multiselect', type: 'multiselect',
name: 'devices', name: 'devices',
selectionFormat: '<num> devices selected',
message: 'Select devices for the ad hoc build:', message: 'Select devices for the ad hoc build:',
hint: '- Space to select. Return to submit', hint: '- Space to select. Return to submit',
choices: allDevices.map(device => ({ choices: allDevices.map(device => ({
Loaded 100 of 174 files, more files were not shown because too many files have changed in this diff. Show more