Compare commits

...
Author SHA1 Message Date
Quinlan Jung 14d491a558 [eas-cli] unify channel graphql query types 2023-07-24 15:54:43 -07:00
Expo CI 51d0555baa update CHANGELOG.md 2023-07-24 17:24:12 +00:00
Szymon Dziedzic 2137176a65 v3.17.0 2023-07-24 19:16:40 +02:00
Szymon Dziedzic 3fda52c8d7 [eas-cli] handle server side defined error for situation when intel-medium is not available and remove intel-medium for eas.schema.json (#1947)
* [eas-cli] handle server side defined error when `intel-medium` resource class is not available

* don't suggest using `intel-medium` in `eas.schema.json`

* update CHANGELOG.md

* update CHANGELOG.md
2023-07-24 19:08:10 +02:00
Quinlan Jung b252b5b582 [eas-cli] utils: amend rollouts definition (#1945)
* [eas-cli] utils: amend rollouts definition

* update CHANGELOG.md
2023-07-24 09:10:07 -07:00
Szymon Dziedzic 2befe410cf [eas-cli] allow for rollout option when configuring submission (#1938)
* [eas-cli] allow for `rollout` option when configuring submission

* update CHANGELOG.md

* add entry to `eas.schema.json`

* improve eas.schema.json

* make rollout default to 1 if not set

* Revert "make rollout default to 1 if not set"

This reverts commit 2ab689b02c.

* generate schema against staging

* update fragment
2023-07-24 14:05:10 +02:00
Quinlan Jung 1463e0de74 [eas-cli] branch mapping utility functions (#1944)
* [eas-cli] branch mapping primitives

* refactor

* extra test

* update CHANGELOG.md
2023-07-20 12:45:40 -07:00
Piotr Szeremeta e963558ef6 [ENG-8956][eas-build] Improve EAS CLI tsconfig.json for local development (#1933)
Changed noUnusedLocals and noUnusedParameters to false for easier local development and experimenting.
These checks do not run during local development. Instead, `yarn build` catches these cases on github.
2023-07-20 16:35:52 +02:00
Quinlan Jung fe57c055b6 [eas-cli] bump urql graphql client (#1936)
* [eas-cli] bump urql graphql client

* update CHANGELOG.md

* fix test
2023-07-19 10:34:04 -07:00
Expo CI 045840111e update CHANGELOG.md 2023-07-18 16:13:46 +00:00
Szymon Dziedzic 9a6068c1f2 v3.16.0 2023-07-18 18:04:52 +02:00
Szymon Dziedzic d06a77bd6d [eas-cli] fix incorrect handling of valid inProgress release status (#1934)
* [eas-cli] fix incorrect handling of valid `inProgress` release status

* update CHANGELOG.md

* add another test
2023-07-18 15:47:45 +02:00
Szymon Dziedzic 7bcf83d141 Revert "add another test"
This reverts commit cd9323e115.
2023-07-18 15:34:09 +02:00
Szymon Dziedzic cd9323e115 add another test 2023-07-18 15:33:15 +02:00
Firas Regaieg 34145c38ca [Build] Print friendly error msg in case account doesn't have required permission (#1867)
* Print friendly error msg (reviewed)

Comment: This is useful when credentials fail, if user doesn't have permissions.

* Print friendly error msg - 2nd review

* Print friendly error msg - 2nd review (reviewed)

* Replace changelog event under main section
2023-07-18 15:28:33 +02:00
Piotr SzeremetaandSzymon Dziedzic 57dd372957 [ENG-9283][eas-cli] Throw error when project tarball exceeds 1GB (#1928)
* [ENG-9283][eas-cli] Throw error when project tarball exceeds 1GB

* update CHANGELOG.md

* Update packages/eas-cli/src/build/build.ts

Co-authored-by: Szymon Dziedzic <[email protected]>

* Update packages/eas-cli/src/build/build.ts

Co-authored-by: Szymon Dziedzic <[email protected]>

* Update CHANGELOG.md

Co-authored-by: Szymon Dziedzic <[email protected]>

---------

Co-authored-by: Szymon Dziedzic <[email protected]>
2023-07-17 14:12:17 +02:00
Will Schurman 5232641217 [eas-cli] Use node server default port selection for SSO login server (#1930) 2023-07-13 09:29:51 -07:00
Cedric van Putten 3c3d3150c9 [eas-cli] Ignore the existing entitlements when ios is gitignored (#1906)
* [eas-cli] Ignore the existing entitlements when ios is gitignored

* chore: add changelog entry
2023-07-13 16:51:39 +02:00
Will Schurman fd322d4189 [eas-cli] Add styling to SSO auth redirect completion page (#1929) 2023-07-12 09:11:03 -07:00
Expo CI 77254f6be1 update CHANGELOG.md 2023-07-11 06:59:32 +00:00
Szymon Dziedzic 44fcbbaf17 v3.15.1 2023-07-11 08:51:55 +02:00
Szymon Dziedzic db841b45ee upgrade eas-cli-local-build-plugin to 1.0.27 2023-07-11 08:51:40 +02:00
Szymon Dziedzic 568669d384 [eas-cli] bump @expo/steps version (#1927) 2023-07-11 08:11:17 +02:00
Quinlan Jung aa50bf11ce [eas-cli] fix runtime logs (#1925)
* [eas-cli] fix runtime logs

* update CHANGELOG.md

* pr feedback
2023-07-10 10:37:46 -07:00
Sundeep Peswani 119133360b [eas-cli] Handle free user limit error messages (#1921)
* [eas-cli] Handle free user limit error messages

* update CHANGELOG.md
2023-07-10 13:18:20 -04:00
Radosław Krzemień 3f0f25dd46 [eas-cli] [ENG-8957] Handle multiple GraphQLErrors in CombinedError (#1924)
* [eas-cli] Handle multiple GraphQLErrors in CombinedError

CombinedError technically can have more than 1 GraphQLError inside it, even though for now we only use it with 1. In such a case it would be good for the eas-cli to be able to handle and properly log/display all the multiple error messages

See: https://linear.app/expo/issue/ENG-8957/improve-error-handling-in-eas-cli-to-support-case-when-combined-error

* [eas-cli] Revert change

Small change made for testing slipped through and got committed

See: https://linear.app/expo/issue/ENG-8957/improve-error-handling-in-eas-cli-to-support-case-when-combined-error

* update CHANGELOG.md
2023-07-10 13:07:10 +02:00
Szymon Dziedzic e821ae95e1 fix changelog (#1926) 2023-07-10 09:20:40 +02:00
Szymon Dziedzic f4e51b4fbc [eas-json] add info about new image to eas.schema.json (#1920) 2023-07-10 08:11:27 +02:00
James Ide 963bea9b03 [ENG-9093][eas-cli] Throw if useClassicUpdates is set when the developer is trying to use EAS Update (#1915)
Why
---
Fail fast with a clear message if the app config contradicts itself or the developer's behavior.

How
---
Throw if useClassicUpdates is set in the app config and the developer is running an EAS Update-related command or if the app config uses an EAS Update URL. If the developer is using a static app config (app.json), offer to modify the config file for them.

Test Plan
---
Added a unit test for ensureEASUpdateIsConfiguredAsync. Manually tested as wlel.
2023-07-05 17:32:20 -07:00
Quinlan Jung 9bf2020b2b [eas-cli] better runtime version error for bare workflow (#1910)
* [eas-cli] better runtime version error for bare workflow

* update CHANGELOG.md

* resolve conflicts
2023-07-03 14:41:27 -07:00
Brent Vatne 50e695b5a3 [eas-cli] Bump dependencies (#1911) 2023-07-03 13:08:42 -07:00
Expo CI f3577deac4 update CHANGELOG.md 2023-06-30 23:13:32 +00:00
Brent Vatne f11c0b4d34 v3.15.0 2023-06-30 16:05:25 -07:00
Szymon Dziedzic 88411e8fc0 [eas-cli] print the list of environment variables that we load in EAS CLI when evaluating app config for a build (#1909) 2023-06-30 16:04:28 -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
Brent Vatne da4b56a58d Fix changelog for 3.9.3 2023-04-18 15:59:05 -07:00
Expo CI 1080600af6 update CHANGELOG.md 2023-04-18 22:47:47 +00:00
Will Schurman dcb6713f12 v3.9.3 2023-04-18 15:38:20 -07:00
Will Schurman 24a1d2d581 [eas-cli] Fix another prompt again in shared code (#1797) 2023-04-18 15:36:36 -07:00
Will Schurman 8af88c2d29 [eas-cli] Fix update message prompt (#1791) 2023-04-13 16:25:22 -07:00
Expo CI d416ac2162 update CHANGELOG.md 2023-04-13 10:11:07 +00:00
Dominik Sokal 7bf0493a4d v3.9.2 2023-04-13 12:01:04 +02:00
Dominik Sokal 91f32cc377 add missing changelog entry 2023-04-13 11:35:52 +02:00
Dominik Sokal cf54f62bac print meaningful error message if extra.eas.projectId is not a string (#1788) 2023-04-13 11:33:04 +02:00
Wojciech Kozyra 3ef3e8447c [eas-cli] always show logs from expo install (#1782)
* Always show logs from expo install

* update CHANGELOG.md
2023-04-12 12:39:03 +02:00
Jon Samp ad64e1afad [eas-cli] Fix rollout percents when running channel:rollout --end (#1781)
* fix rollout percents when running --end

* update CHANGELOG.md
2023-04-11 12:02:21 -05:00
Szymon Dziedzic a0800cf7ef [ENG-7968][eas-cli][eas-json] print deprecation warnings for deprecated eas.json fields (#1768)
* [eas-cli][eas-json] print deprecation warnings for deprecated `eas.json` fields

* fix tests

* update deprecation warning

* extract logic to separate function

* apply suggestions from code review

* fix tests

* update CHANGELOG.md
2023-04-11 13:40:00 +02:00
Expo CI 34436ee674 update CHANGELOG.md 2023-04-10 20:22:07 +00:00
Cedric van Putten 1072b75927 v3.9.1 2023-04-10 21:50:01 +02:00
Cedric van Putten c0c9d2e7df [eas-cli] Update apple utilities to avoid the maintenance error (#1780)
* [eas-cli] Update apple utilities to avoid the maintenance error

* [eas-cli] Add changelog entry

* [eas-cli] Dont change the eas-cli version

* [eas-cli] Fix changelog entry

* [eas-cli] fix it again
2023-04-10 21:48:24 +02:00
Dominik Sokal 19b831667f Update bug_report.yml (#1777) 2023-04-07 14:16:55 +02:00
Szymon Dziedzic 09db7da23b [ENG-7968][eas-cli][eas-json] don't use defaults for cacheDefaultPaths in the EAS CLI (#1769)
* [eas-cli][eas-json] don't use cacheDefaultPaths

* update deps

* update CHANGELOG.md

* update CHANGELOG.md

* fix changelog

* fix changelog again
2023-04-06 15:25:56 +02:00
Expo CI f08dde2469 update CHANGELOG.md 2023-04-06 12:49:28 +00:00
gabrieldonadel 213b5d240c v3.9.0 2023-04-06 09:28:23 -03:00
Gabriel Donadel Dall'AgnolandDominik Sokal 16ebdce913 [eas-cli] Fix expoCommandAsync to support the new cli bin path (#1772)
* [eas-cli] Fix expoCommandAsync to support the new cli bin path

* update CHANGELOG.md

* Apply suggestions from code review

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

---------

Co-authored-by: Dominik Sokal <[email protected]>
2023-04-06 09:22:04 -03:00
Dominik Sokal 121ff7a718 validate custom build config in CLI (#1770) 2023-04-06 10:34:25 +02:00
Wojciech Kozyra 360393f647 [eas-json] Add support for running on server (#1766)
* [eas-json] Add support for running on server

* review feedback

* update CHANGELOG.md
2023-04-06 09:55:04 +02:00
Wojciech Kozyra 82d66054dc [eas-cli] Do not require commits in eas build:internal (#1760)
* [eas-cli] Do not require commits in eas build:internal

* lint fix

* alternative approach

* update CHANGELOG.md
2023-03-30 12:25:10 +02:00
Will Schurman d8849dbf8c [eas-cli] Unhide channel:rollout command and modernize (#1758) 2023-03-28 09:54:45 -07:00
Dominik Sokal 4d6b5f435b [ENG-7981] use the eas-cli npm tag for eas-cli-local-build-plugin updates (#1759)
* use the eas-cli npm tag for eas-cli-local-build-plugin updates

* update CHANGELOG.md
2023-03-28 12:06:16 +02:00
Will Schurman 8fe7dfea29 [eas-cli] Add info about code signing and roll back to embedded in update info (#1755) 2023-03-24 13:42:59 -07:00
Will Schurman 8750155c25 [eas-cli] Add support for roll back to embedded updates (#1754) 2023-03-24 13:40:52 -07:00
Expo CI a0fce1c773 update CHANGELOG.md 2023-03-16 13:32:36 +00:00
Cedric van Putten cad3dca5df v3.8.1 2023-03-16 14:09:02 +01:00
Cedric van Putten 2ef6e79395 [eas-cli] Add missing changelog entry 2023-03-16 14:03:51 +01:00
Cedric van Putten 44270814a7 [eas-cli] Add test case to ensure public config is used as update (#1745)
* [eas-cli] Add test case to ensure public config is used as update

* [eas-cli] Use the public config when creating updates
2023-03-15 23:12:47 +01:00
Radosław Krzemień 95155681db [eas-cli] [ENG-6945] Don't use capitalised base error messages (#1744)
* Capitalize command id

In case the base error message is not provided and the command id starts with lower case character it will be capitalized to unify the behaviour with other cases

See: https://linear.app/expo/issue/ENG-6945/improve-eas-cli-error-logging

* Decapitalize command id

After some discussion it was agreed to decapitalize the base error messages instead, and unify the behaviour this way

See: https://linear.app/expo/issue/ENG-6945/improve-eas-cli-error-logging

* Remove redundant decapitalisation

Seems like `oclif` guarantees the command ids to be lower case in which case the explicit decapitalisation is redundant

See: https://linear.app/expo/issue/ENG-6945/improve-eas-cli-error-logging

* update CHANGELOG.md
2023-03-14 15:42:41 +01:00
Radosław KrzemieńandWojciech Kozyra 53fd599d0a [eas-cli] [ENG-6945] Unify error display method (#1738)
* Unify error display method

Overriden the `Command`'s `catch` method to catch all the errors thrown by the commands, log the error message there the same way as it was previously handled in `src/build/build.ts` and re-throw generic `Error` instance with predefined base error message.
To avoid duplication, old `Log.error()` calls have been removed from `src/build/build.ts`.
Also added tests to see if the `catch()` method works as intended.

* Adjust base error messages

Base error messages have been adjusted after @dsokal suggested that the error may be caused by something different from a request and displaying message about a failed request is misleading

* update CHANGELOG.md

* Add test for unclean error message

Checks if the `[GraphQL] ` prefix gets removed when logging the error message

* Update handling GraphQL errors

Post-review update to how graphQL errors are handled

* Print different message for graphQL errors

When a graphQL error is caught a different base error message is printed

* Print original error to debug

When a graphQL error is caught and the message is cleaned, an original message is printed to debug (or just the same message otherwise)

* Add custom error classes

Created some custom error classes for known server side defined errors

* Log the error, not the message

Changed the debug log to log the error object, not the error message

* Fix

Fixed testing leftover

* Change phrasing

Edited the phrasing in default messages related to EAS Build

* Update if condition

Changed the if condition after review comment

* Create error base class

Created a new base class for the errors expected in build.ts, so they can be handled separately in `catch()`

* Move EasCommand properties

Moved the class properties above the class methods

* Remove helper method

Removed the helper method after review comment

* Change const type

Updated the type of SERVER_SIDE_DEFINED_ERRORS constant

* Change default base error message

Set the default base error message to `<command_name> command failed.`

* Remove comments

Removed leftover comments

* Change optional property

Changed empty string to optional property

* Post-review tweaks

Self-explanatory

* Fix failing test

One of tests started failing after changing the coalescing operators (|| -> ??). This fixes that test

See: https://linear.app/expo/issue/ENG-6945/improve-eas-cli-error-logging

* Remove error class

Removed `GenericGraphQLError` class after @wkozyra95 comment, since atm there's no need to handle it differently than a generic `Error`

See: https://linear.app/expo/issue/ENG-6945/improve-eas-cli-error-logging

* Move `EasCommandError` base class

Moved `EasCommandError` base class after @wkozyra95 comment. The class is not specific to `Build` command and so it has been moved to `commandUtils` directory

See: https://linear.app/expo/issue/ENG-6945/improve-eas-cli-error-logging

* Make message mandatory

Made `message` parameter in `EasCommandError`s mandatory, to remove need for providing a fallback string and making the server the source of truth for the errors messages

See: https://linear.app/expo/issue/ENG-6945/improve-eas-cli-error-logging

* Update packages/eas-cli/src/commandUtils/EasCommand.ts

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

* Move `baseGraphQLErrorMessage` property

Made the property into a constant in a global scope, since atm it is not changed in any of the subclasses

See: https://linear.app/expo/issue/ENG-6945/improve-eas-cli-error-logging

* Suppress ESLint warning

ESLint erroneously complains about the constructor of `EasCommandError` to be useless, which is not true is the constructor of `Error` parent class allows for optional `message` parameter and the constructor of `EasCommandError` does not

See: https://linear.app/expo/issue/ENG-6945/improve-eas-cli-error-logging

* Add error handling tests

Added a bunch of new tests related to the `handleBuildRequestError` function

See: https://linear.app/expo/issue/ENG-6945/improve-eas-cli-error-logging

* Directly export `handleBuildRequestError`

Dropped the `testExports` convention after team discussion

See: https://linear.app/expo/issue/ENG-6945/improve-eas-cli-error-logging

---------

Co-authored-by: Wojciech Kozyra <[email protected]>
2023-03-14 11:26:27 +01:00
dependabot[bot] b51201d010 Bump @sideway/formula from 3.0.0 to 3.0.1 (#1742)
Bumps [@sideway/formula](https://github.com/sideway/formula) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/sideway/formula/releases)
- [Commits](https://github.com/sideway/formula/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: "@sideway/formula"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-13 12:52:53 +01:00
Expo CI f86c079733 update CHANGELOG.md 2023-03-13 11:51:39 +00:00
Szymon Dziedzic 843ec7407b v3.8.0 2023-03-13 12:44:41 +01:00
Szymon Dziedzic 537cd7872a [ENG-7709][eas-cli][eas-json] add m-medium and m-large resource classes (#1739)
* [eas-cli][eas-json] add `m-medium` and `m-large` resource classes

* add deprecation warning for m1-medium resource class

* update deprecation messages

* apply suggested changes

* update CHANGELOG.md
2023-03-13 12:33:48 +01:00
Dominik Sokal 007c40bed4 [gha] send slack notification on failed tests (#1720) 2023-02-24 16:26:23 +01:00
Dominik Sokal ba9f8d8ddd Update CHANGELOG.md 2023-02-24 09:54:39 +01:00
Expo CI e4bd933e81 update CHANGELOG.md 2023-02-24 08:27:04 +00:00
Evan Bacon a6434bf6b8 v3.7.2 2023-02-24 02:18:15 -06:00
Evan Bacon 19bad84109 [eas-cli] bump apple-utils to support hashcash for authentication (#1719)
* bump apple-utils to support hashcash for authentication

* update CHANGELOG.md
2023-02-24 02:16:06 -06:00
Expo CI 5d9f8f9d76 update CHANGELOG.md 2023-02-23 21:08:09 +00:00
Brent Vatne d9d884d211 v3.7.1 2023-02-23 12:59:33 -08:00
Brent Vatne f902bb845b Add changelog entry 2023-02-23 12:59:05 -08:00
Brent Vatne 03c76c1e0e [eas-cli] Bump apple-utils 2023-02-23 12:58:33 -08:00
Expo CI 1d0d329cdb update CHANGELOG.md 2023-02-23 10:48:34 +00:00
Dominik Sokal 7ed1efb7d6 v3.7.0 2023-02-23 11:23:55 +01:00
Dominik Sokal b7bfec0366 [ENG-7565] add basic support for custom builds (#1710)
* custom builds - wip

* upgrade @expo/eas-build-job

* [eas-json] clean up build profile schema + types

* validate if custom build config exists

* don't configure credentials for custom builds

* set buildMode in metadata

* address PR feedback

* regenerate graphql code
2023-02-23 10:16:22 +01:00
Frederick Widjaja a5308f0d37 Support variant detection for package*UniversalApk Gradle Commands (#1708)
* Support variant detection for package*UniversalApk Gradle Commands

* Update CHANGELOG.md
2023-02-23 09:31:54 +01:00
Cedric van Putten 3ad644bb9d [eas-cli] Provide more context about uploaded assets when publishing (#1488) 2023-02-22 17:39:44 -08:00
Szymon Dziedzic 233e5451a8 update changelog 2023-02-20 11:40:47 +01:00
Szymon Dziedzic ce954cdecc v3.6.1 2023-02-20 11:19:14 +01:00
Szymon Dziedzic 59bd1caf98 [ENG-7376][eas-cli] link to build phase in error message (#1699)
* [eas-cli] link to build phase in error message

* add better fallback

* use fallback for all links

* use terminalLink.isSupported

* improvements after code review

* use old fallback

* update CHANGELOG.md
2023-02-17 15:09:54 +00:00
dependabot[bot] 86877fdda3 Bump undici from 5.8.2 to 5.19.1 (#1706)
Bumps [undici](https://github.com/nodejs/undici) from 5.8.2 to 5.19.1.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v5.8.2...v5.19.1)

---
updated-dependencies:
- dependency-name: undici
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-17 09:00:13 +00:00
Will Schurman 9299c9b7cc [eas-cli] Clarify missing owner field error message for Robot users (#1702)
* [eas-cli] Clarify missing owner field error message for Robot users

* update CHANGELOG.md

* Address comment
2023-02-16 18:34:25 +00:00
Szymon Dziedzic 64fb2e5b3a [ci] fix release script (#1704)
* fix release script

* apply suggested changes
2023-02-16 11:19:53 +00:00
Szymon Dziedzic 91fdbac773 [eas-cli] display build message data when picking build in submit, build:run and build:resign commands (#1700)
* [eas-cli] display build message data when picking build in `submit`, `build:run` and `build:resign` commands

* display only 200 first characters of message if it exists

* print only defined values

* add '...' to message if trimmed

* use array instead of object

* update CHANGELOG.md
2023-02-16 10:36:23 +00:00
Szymon Dziedzic 507eadb914 [ci] commit release changelog update using expo bot PAT (#1697) 2023-02-15 16:09:18 +00:00
Wojciech Kozyra f1cde7a291 [eas-cli] Fix README.md (#1701) 2023-02-15 13:01:56 +00:00
Wojciech Kozyra 8b9ad47d16 [eas-cli] Validate platform for local builds earlier (#1698)
* [eas-cli] Validate platform for local builds earlier

* update CHANGELOG.md
2023-02-15 11:28:19 +00:00
Wojciech Kozyra 192984b47d [eas-cli] Disable analytics for environments behind proxy (#1696)
* [eas-cli] Disable analytics for environments behind proxy

* update CHANGELOG.md
2023-02-15 09:46:55 +00:00
Szymon Dziedzic 2c36810ce7 update changelog 2023-02-14 14:31:10 +01:00
Szymon Dziedzic bfa63f95fe v3.6.0 2023-02-14 13:32:03 +01:00
Szymon Dziedzic c414b9f975 [ENG-7389][eas-json] add Xcode 14.2 image to schema (#1693)
* add Xcode 14.2 image to schema

* update CHANGELOG.md
2023-02-14 11:21:07 +00:00
Raul Riera 6946a085cb feat: return the build message when one is available (#1691)
* Return the message when available

* Update CHANGELOG.md

* run generate-graphql-code
2023-02-13 15:56:41 +00:00
Will Schurman 317d2fcbb6 [eas-cli] Warn when project ID but no owner specified and mismatch logged in user (#1667) 2023-02-06 10:39:49 -08:00
Will Schurman dd61b4bb70 [eas-cli] Fix suggested application identifier to match owning account name (#1670) 2023-02-06 10:32:22 -08:00
Jon Samp 80fbf7cf62 Revert "[eas-cli] use appVersion instead of sdkVersion (#1588)" (#1669)
* Revert "[eas-cli] use appVersion instead of sdkVersion (#1588)"

This reverts commit 9ef8483882.

* co main changelog

* update CHANGELOG.md
2023-02-06 11:23:39 -06:00
Expo CI 26b6a17160 update CHANGELOG.md 2023-02-05 20:27:34 +00:00
Brent Vatne 7f4e2eef95 v3.5.2 2023-02-05 12:20:17 -08:00
Brent Vatne 88b683655c [eas-cli] Update subdomain to developer-mdn (#1673) 2023-02-05 12:19:31 -08:00
Will Schurman f3aa3d98d6 [eas-cli] Update test certificates (#1671) 2023-02-03 20:20:59 -08:00
dependabot[bot] f852b9329d Bump http-cache-semantics from 4.1.0 to 4.1.1 (#1668)
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-03 15:50:33 +01:00
Expo CI a2340531b0 update CHANGELOG.md 2023-02-01 12:56:13 +00:00
Szymon Dziedzic 39df3a1364 v3.5.1 2023-02-01 13:49:38 +01:00
Szymon Dziedzic 2f1d454582 [eas-cli] fix issue with the eas.json envs being not available when resolving local config (#1666)
* [eas-cli] fix issue with `eas.json` envs not available when resolving local config

* fix type definitions to prevent it from happening again

* update CHANGELOG.md
2023-02-01 13:44:32 +01:00
Expo CI e2560ea867 update CHANGELOG.md 2023-01-31 13:59:54 +00:00
Wojciech Kozyra 346d136b8b v3.5.0 2023-01-31 14:36:55 +01:00
Wojciech Kozyra dd77312510 [eas-cli] Update build metadata for internal builds (#1651) 2023-01-31 14:28:53 +01:00
Wojciech Kozyra 120a965368 [ENG-7367][eas-cli] Fix using local credentials with enterprise provisioning (#1657) 2023-01-30 12:26:22 +01:00
dependabot[bot] 050336b6fd Bump ua-parser-js from 0.7.31 to 0.7.33 (#1658)
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.31 to 0.7.33.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.31...0.7.33)

---
updated-dependencies:
- dependency-name: ua-parser-js
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-27 12:02:48 +01:00
Szymon Dziedzic 7fc3e3fcdb [ENG-7351][eas-cli] set m1-medium resource class for builds which don't specify resource class for SDK 48 and RN 0.71 (#1655)
* [eas-cli] set `m1-medium` resource class for builds which don't specify resource class for SDK 48 and RN 0.71

* refactor resolveBuildResourceClassAsync function

* apply suggested changes

* drop warning for ios large resource classes

* call getDynamicProjectConfigAsync in create context function

* update CHANGELOG.md
2023-01-26 17:15:04 +01:00
Quinlan Jung 2d117e5b4a [ENG-7328][eas-cli] fix undefined branchMappingLogic (#1653)
* [eas-cli] fix undefined branchMappingLogic

* spaces

* update CHANGELOG.md
2023-01-25 11:28:25 -08:00
Szymon Dziedzic 131377bd2c fix [#1650](https://github.com/expo/eas-cli/pull/1650) changelog entry 2023-01-25 14:10:06 +01:00
Szymon Dziedzic ed61b0c41b [ENG-7094][eas-cli] move image validation to server side (#1650)
* [eas-cli] move image validation to server side

* update error log

* apply suggested changes

* update CHANGELOG.md
2023-01-24 13:54:43 +01:00
Expo CI 9f4d3bfc56 update CHANGELOG.md 2023-01-23 11:26:08 +00:00
Dominik Sokal 306e7b1d71 v3.4.1 2023-01-23 12:13:29 +01:00
Dominik Sokal a2b73844f0 upgrade eas-cli-local-build-plugin to 0.0.128 2023-01-23 12:09:33 +01:00
Dominik Sokal ebbe786cd0 fix local builds (#1649)
* fix local builds

* update CHANGELOG.md
2023-01-23 12:08:01 +01:00
Expo CI 190172f06c update CHANGELOG.md 2023-01-20 12:59:11 +00:00
Dominik Sokal 2232a5ed59 v3.4.0 2023-01-20 13:51:06 +01:00
Dominik Sokal ef3e9079e5 [ENG-7283] add a prompt to switch iOS builds to M1 if queue is long (#1642)
* add a prompt to switch iOS builds to M1 if queue is long

* update CHANGELOG.md
2023-01-20 13:46:46 +01:00
Wojciech Kozyra ec10eff1e6 [eas-cli] Print warning about EAS_NO_VCS option to stderr (#1641)
* [eas-cli] Print warning about EAS_NO_VCS option to stderr

* update CHANGELOG.md
2023-01-20 11:31:50 +01:00
Szymon Dziedzic 3e3ec87622 [ENG-7223][ci] run tests on each changelog workflow automated commit (#1640)
* [ci] add test changelog workflow

* update main changelog workflow

* delete changelog_2 workflow

* use correct command

* select last commit author as automated changelog change author
2023-01-20 11:01:12 +01:00
Wojciech Kozyra f1cf92d63a [eas-cli] Print error message sent from server (#1639) 2023-01-19 14:17:59 +01:00
Brent Vatne 7096daa116 [eas-cli] Switch to using M1 by default when running build:configure (#1637) 2023-01-18 11:35:24 -08:00
Dominik Sokal 6429e70a9a print disabled resource class labels (#1635)
* print disabled resource class labels

* update CHANGELOG.md
2023-01-18 18:26:21 +01:00
Wojciech Kozyra 083381da82 [eas-cli] Automatically promote new cli release to staging on EAS Build (#1624) 2023-01-16 10:08:46 +01:00
Dominik Sokal 1c1af2da86 remove nx and upgrade lerna (https://github.com/lerna/lerna/pull/3495) 2023-01-13 10:40:06 +01:00
Wojciech Kozyra 72ce75e1ee [eas-cli] Base implementation for running builds directly from GitHub (#1568) 2023-01-13 10:01:53 +01:00
Wojciech Kozyra ea553a1025 [eas-cli] Remove workaround for gitignore with trailing backslash (#1622)
* [eas-cli] Remove workaround (fixed upstream)

* update CHANGELOG.md
2023-01-12 15:27:23 +01:00
Expo CI 3a6bcb3f2e update CHANGELOG.md 2023-01-12 11:06:59 +00:00
Dominik Sokal 8b27255ce2 v3.3.2 2023-01-12 11:59:22 +01:00
Szymon Dziedzic 1f0ae27ea0 [eas-cli] improve handling SSOUser type (#1621)
* [eas-cli] improve handling SSOUser type

* update CHANGELOG.md
2023-01-12 11:53:45 +01:00
Dominik Sokal 62d26ca32b add nx to root deps 2023-01-12 11:22:44 +01:00
Dominik Sokal 3aebb54d11 upgrade dependencies (#1617)
* upgrade dependencies

* fixes

* fix types

* update CHANGELOG.md
2023-01-12 10:26:23 +01:00
Cedric van Putten 0bf42e4cd4 [eas-cli] Add missing key information about updates in json outputs (#1619)
* Revert "Revert "[eas-cli] Add missing key information about updates in json outputs (#1611)""

This reverts commit d20db0fff4.

* [eas-cli] Fix missing update fragment in publish mutation

* [docs] Update changelog to reflect the revert and new entry
2023-01-12 10:06:48 +01:00
Expo CI ec4ef42f3c update CHANGELOG.md 2023-01-11 22:25:41 +00:00
James Ide a70222d35d v3.3.1 2023-01-11 14:18:03 -08:00
James Ide d20db0fff4 Revert "[eas-cli] Add missing key information about updates in json outputs (#1611)"
Why
---
This reverts commit e42031eaef. A customer reported an issue with publishing updates.

How
---
Ran `git revert` and then re-generated the GraphQL schemas due to merge conflicts using `yarn generate-graphql-code`.

Test Plan
---
Automated tests. Ran `easd update --auto` in a test project.
2023-01-11 12:43:35 -08:00
Expo CI 00ef6087f8 update CHANGELOG.md 2023-01-11 16:22:58 +00:00
Szymon Dziedzic 9c74c5c6b8 v3.3.0 2023-01-11 16:52:32 +01:00
Szymon Dziedzic 719febc519 [ENG-7158][ENG-7060][eas-cli][eas-json] handle new resource class names (#1616)
* [eas-cli][eas-json] handle new resource classes in EAS CLI

* show warning for large iOS resource classes

* don't deprecate resource class flag in this PR

* apply suggested improvements

* fix markdown description of top level resource class

* fix large resource class warning

* update CHANGELOG.md

* update eas.schema.json

* improve eas.schema.json

* add support for medium resource class and update eas.json

* fix error message for large resource classes

* temporary changes to solve issues with SSOUser being added to schema

* fix tests

* fix error message

* remove unnecessary dot

* update changelog
2023-01-11 16:47:37 +01:00
Dominik Sokal 9d9c99c0f6 [ENG-7198][build] deprecate --resource-class flag (#1615)
* [build] deprecate --resource-class flag

* address PR feedback
2023-01-11 10:18:08 +01:00
Cedric van Putten e42031eaef [eas-cli] Add missing key information about updates in json outputs (#1611)
* [eas-cli] Add missing key information about updates in json outputs

* [eas-cli] Fix linting issue

* [eas-cli] Fix update command tests with new types

* [eas-cli] Emphasize the update JSON output helper provides info for JSON output

* update CHANGELOG.md
2023-01-09 05:44:33 -08:00
Dominik Sokal 9ec43bce84 [ENG-7158] add resource class to eas.json (#1609)
* add resource class to eas.json

* address PR feedback
2023-01-09 14:22:56 +01:00
Expo CI 488c3b3cb8 update CHANGELOG.md 2023-01-09 13:08:30 +00:00
Szymon Dziedzic f5ac3d26a0 v3.2.1 2023-01-09 14:01:21 +01:00
Szymon Dziedzic 6be86d5119 [eas-cli] validate chosen build in eas build:run command (#1614)
* [eas-cli] throw error when there are no emulator/simulator builds in `eas build:run` command

* handle missing build in correct place

* handle same error for latest flag

* add function to sanitize chosen build

* sanitize build selected by id

* improve build santization

* apply suggested improvements

* rename validation function

* update CHANGELOG.md
2023-01-09 14:00:30 +01:00
dependabot[bot] 04025802ac Bump json5 from 1.0.1 to 1.0.2 (#1610)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 11:37:25 +01:00
Expo CI 0a4c8785e6 update CHANGELOG.md 2023-01-09 09:51:36 +00:00
Szymon Dziedzic 1de239b6ba v3.2.0 2023-01-09 10:45:11 +01:00
Simon Blake 4515d29907 Add --json flag to webhook:list command (#1605) 2023-01-04 13:41:10 +01:00
Szymon Dziedzic 7591cac883 [ENG-7069][eas-cli] Use pagianted select prompt for eas build:run command (#1601)
* [eas-cli] Use pagianted select prompt for eas build:run command

* apply suggestions from code review

* apply improvements from code review

* update CHANGELOG.md
2023-01-04 11:32:17 +01:00
Szymon Dziedzic 9765f75a7d [eas-cli] use completedAt timestamp as build finish date instead of updatedAt in eas build:run command (#1604)
* [eas-cli] use `completedAt` timestamp as build finish date instead of `updatedAt` in `eas build:run` command

* update CHANGELOG.md
2023-01-04 11:22:10 +01:00
Szymon Dziedzic 1f1b58db82 [ENG-7070][eas-cli] fix issues with invisible build info in some terminals while using submit command (#1603)
* [eas-cli] fix issues with invisible build info in some terminals while using submit command

* update CHANGELOG.md
2023-01-04 11:18:10 +01:00
Bartosz KaszubowskiandDominik Sokal b0c88a37a9 [eas-cli] ensure that all links are clickable, unify display (#1591)
Co-authored-by: Dominik Sokal <[email protected]>
2023-01-04 11:09:35 +01:00
Jon Samp 5694bb9fdc [eas-cli] Makes eas.json configuration to only run on update:configure (#1598)
* Makes eas.json configuration to only run on update:configure

* update CHANGELOG.md
2023-01-03 22:37:49 -06:00
Szymon Dziedzic 9245657e23 [ENG-7070][eas-cli] Fix issues with invisible build info in some terminals in build:run and build:resign commands (#1602)
* [eas-cli] Fix issues with invisible build info in some terminals

* update CHANGELOG.md
2023-01-03 13:41:25 +01:00
Jon Samp 9ef8483882 [eas-cli] use appVersion instead of sdkVersion (#1588)
* use appVersion instead of sdkVersion

* update CHANGELOG.md
2022-12-29 00:00:23 -06:00
Dominik Sokal b859e11126 update CONTRIBUTING.md + add RELEASING.md (#1583)
* update CONTRIBUTING.md

* address PR feedback
2022-12-21 11:48:55 +01:00
James Ide cee499e0e6 [ui] Rename "queue" to "Free tier queue" (#1582) 2022-12-20 15:17:28 -08:00
Cedric van Putten 8702f165f3 [eas-cli] Add --channel to update republish (#1580)
* [eas-cli] Add test cases for republishing updates

* [eas-cli] Tweak the channel -> branch method errors to reuse in republish

* [eas-cli] Add support for --channel in update:republish

* [eas-cli] Add test cases to prevent using --group, --channel, or --branch together

* update CHANGELOG.md

* [eas-cli] Update the test case for resolving branch from channel
2022-12-19 22:35:14 +01:00
Cedric van Putten a6bea7e234 [eas-cli] Increase the timeout for loading the command config (#1581) 2022-12-19 21:47:13 +01:00
Expo CI 37343dd586 update CHANGELOG.md 2022-12-19 16:24:12 +00:00
Cedric van Putten 058183f202 v3.1.1 2022-12-19 16:57:33 +01:00
Cedric van Putten 7ee50de78e [eas-cli] Fix update --channel and --branch flag validation (#1579)
* [eas-cli] Add initial test cases for the update command

* [eas-cli] Add runtimeVersion option when mocking exports

* [eas-cli] Fix update flag validation for --branch and --channel

* [eas-cli] Add test case for --non-interactive, --channel, and --message

* update CHANGELOG.md

* [eas-cli] Fix linting issue

* [eas-cli] Reuse the input dir from the command

* [eas-cli] Explicitly mention the allowed flag combinations to avoid ambiguity

* [eas-cli] Move --auto to a distinct location in the error message

* [eas-cli] Fix linting issue
2022-12-19 16:36:18 +01:00
Expo CI ca11aa9e41 update CHANGELOG.md 2022-12-17 00:00:52 +00:00
Brent Vatne 64d985fcda v3.1.0 2022-12-16 15:54:02 -08:00
Jon Samp bd2bb4dcf6 [eas-cli] Add --channel flag to eas update command (#1567)
* Add --channel flag to eas update command

* use existing method

* adds undefined to return type

* move to getBranchNameFromChannelNameAsync.ts

* renames the function to singular "update"
2022-12-15 10:56:30 -06:00
Wojciech Kozyra 17db6c4cf1 [eas-cli] Add build:resign command (#1575) 2022-12-15 14:23:10 +01:00
Jon SampandDominik Sokal 5203096aca [eas-cli] Adds channels to build profiles on eas update:configure (#1570)
* Adds eas.json configuration during eas update configure

* updates next steps message

* renames eas updates to eas update

* self feedback

* update CHANGELOG.md

* Update packages/eas-cli/src/commands/update/configure.ts

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

* Update packages/eas-cli/src/commands/update/configure.ts

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

* code style feedback incorp

* use EasJsonAccessor

* Update packages/eas-cli/src/update/configure.ts

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

* lint

Co-authored-by: Dominik Sokal <[email protected]>
2022-12-12 10:59:41 -06:00
Expo CI a53e1f916f update CHANGELOG.md 2022-12-07 10:15:44 +00:00
Szymon Dziedzic 09a4b3d387 v3.0.0 2022-12-07 11:01:22 +01:00
Szymon Dziedzic 24e7de5bfb [eas-cli] Fix eas cli node:assert import error by using imports from assert instead of node:assert (#1569)
* [eas-cli] use import from 'assert' instead from 'node:assert'

* update CHANGELOG.md
2022-12-07 10:57:45 +01:00
Szymon Dziedzic 613fb5b6ea [eas-json] Make not recognized image error handler run only if image is present under valid key with invalid value (#1565)
* [eas-json] Make not recognized image error handler fire only if image is present under valid key with invalid value

* revert changes to error message

* update CHANGELOG.md
2022-12-06 14:08:03 +01:00
Cedric van Putten 4e117075fa refactor(update): remove republish from update command (#1535)
* fix(update): add code signing support to republish and block different branches

* refactor(update): move codesigning info into update fragment

* update CHANGELOG.md

* fix(update): add code signing support to republish and block different branches

* refactor(update): mark group and republish flags as deprecated

* refactor(update): drop republish and group flags

* refactor(update): drop republish flag from command

* refactor(update): remove last republish code from update command

* refactor(update): either use --group or --branch in deprecation warning

* refactor(update): drop old runtime version from update command

* refactor(update): move asset variables closer to populated area

* fix(update): fix republish typo in deprecation warning

* fix(update): correct a merge conflict error related to publish query changes
2022-12-05 13:25:52 +01:00
Cedric van Putten 33f4ae7a05 [eas-cli] Split republish command from update command (#1533)
* refactor(update): split out republish to separate command

* fix(update): add code signing support to republish and block different branches

* test(update): add missing republish command tests

* test(update): add test case without codesigning

* refactor(update): move codesigning info into update fragment

* refactor(update): make branch and group exclusive and simplify code

* update CHANGELOG.md
2022-12-05 04:08:56 -08:00
Expo CI caba28901d update CHANGELOG.md 2022-12-05 12:03:51 +00:00
Szymon Dziedzic 77e02f5759 v2.9.0 2022-12-05 12:55:30 +01:00
Szymon Dziedzic fa2717aada [ENG-6238][eas-cli] prompt developer to download the simulator app after build is finished when in interactive mode (#1554)
* [eas-cli] prompt developer to download the simulator app when in interactive mode

* check if build status is finished

* improvements after code review

* fix iOS simulator select prompt

* fix typo

* improve prompt message

* update CHANGELOG.md
2022-12-05 12:54:08 +01:00
Szymon Dziedzic 322f797cbe [ENG-7007][eas-cli] change Android start intent to MAIN instead of RUN in eas build:run (#1556)
* [eas-cli] change am start intent to MAIN instead of RUN in eas build:run command

* update CHANGELOG.md
2022-12-01 17:35:10 +01:00
Szymon Dziedzic 2a64b099ed [ENG-7006][eas-cli] Indicate in eas build:run when build artifacts has expired (#1547)
* [eas-cli] inidicate in `eas build:run` when build artifacts has expired

* minor changes after code review

* update CHANGELOG.md
2022-12-01 11:25:40 +01:00
Juwan WheatleyandDominik Sokal 81465733e8 [eas-cli] add isGitWorkingTreeDirty to EAS Update publish command (#1550)
* [eas-cli] add isGitWorkingTreeDirty to EAS Update publish command

* update CHANGELOG.md

* Update CHANGELOG.md

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

Co-authored-by: Dominik Sokal <[email protected]>
2022-11-30 15:10:46 -05:00
Szymon Dziedzic 9b22056205 [ENG-6238][eas-cli] add caching for eas build run command (#1542)
* [eas-cli] add caching for `eas build:run`

* cache in tmp directory

* use path from `path` instead of `node:path`

* don't hide eas build:run command

* apply feedback from review

* improvements after code review

* update CHANGELOG.md
2022-11-29 12:12:32 +01:00
Wojciech Kozyra 24841f82e0 [eas-cli] Consistent prompt for application identifier for both platforms (#1543) 2022-11-29 11:38:47 +01:00
Expo CI a8fae470d0 update CHANGELOG.md 2022-11-28 11:25:27 +00:00
Dominik Sokal 72283cdda2 v2.8.0 2022-11-28 12:13:38 +01:00
Dominik Sokal 9874ff26b3 upgrade eas-cli-local-build-plugin to 0.0.120 2022-11-28 12:13:16 +01:00
Dominik Sokal 85d64b7ad3 [ENG-6491] remove internal expo id from device:list (#1541)
* remove internal expo id from device:list

* update CHANGELOG.md
2022-11-28 09:34:36 +01:00
Szymon Dziedzic 2dba2705b7 [ENG-6238][eas-cli] add build:run support for Android (#1485)
* [eas-cli] add build:run support for Android

* add assertions about android executables

* use spawnAsync instead of spawn

* minor refactor

* minor improvements to prompts

* improve download function name

* refactor code after the review

* apply suggestions from the review

* fix commit message formatting

* use progress tracker instead of progress bar

* improve formatting of build choices

* fix typo in function name

* requested improvements

* test using path.join for fast-lob

* fix windows glob issues

* fix glob on windows

* allow users to run ios builds only on apple device

* use path.join instead of interpolation

* address feedback

* auto select android for non mac users

* minor improvements

* improve error messages

* update CHANGELOG.md
2022-11-25 13:57:19 +01:00
Cedric van Putten d1bec6e00a [eas-cli] Only create a channel with branch name when branch is created (#1507)
* fix(update): only create a channel with branch name when branch is created

* refactor(update): rename branchIsCreated to createdBranch

* update CHANGELOG.md
2022-11-23 18:28:08 +01:00
Szymon Dziedzic 597e012ca3 [ENG-6941][eas-json] add better error message for when invalid image is set in the eas.json (#1531)
* [eas-json] add custom error message handler for invalid image validation error

* update CHANGELOG.md
2022-11-21 15:52:30 +01:00
Wojciech Kozyra 8632b8ada7 [eas-cli] Upload project sources and submit archives to GCS (#1524) 2022-11-17 11:57:41 +01:00
Wojciech Kozyra b288879838 [eas-cli] Add SDK version to tracking context (#1529) 2022-11-17 11:34:24 +01:00
Dominik Sokal 8beb8f46da change wording: "Submission Service" -> "EAS Submit" (#1527)
* change wording: "Submission Service" -> "EAS Submit"

* update CHANGELOG.md
2022-11-17 09:53:44 +01:00
Expo CI 9120bf5777 update CHANGELOG.md 2022-11-16 14:58:36 +00:00
Szymon Dziedzic 01619d4182 v2.7.1 2022-11-16 15:49:27 +01:00
Szymon Dziedzic aa4ea4ae2a update eas-build-job version in yarn.lock 2022-11-16 15:48:54 +01:00
Szymon Dziedzic c236c2ffb5 upgrade eas-cli-local-build-plugin to 0.0.119 2022-11-16 15:45:52 +01:00
Szymon Dziedzic b5a3a513de [ENG-6892][eas-json] add Xcode 14.1 image (#1511)
* [eas-json] add Xcode 14.1 image

* bump build tools version

* update CHANGELOG.md
2022-11-16 15:30:15 +01:00
Wojciech Kozyra 6058183c45 [eas-cli] Show what prompt failed when running in non interactive shell. (#1523) 2022-11-16 09:57:22 +01:00
Wojciech Kozyra a042d27573 [eas-cli] Pass env variables when resolving config in credentials manager. (#1520) 2022-11-15 11:19:26 +01:00
Dominik Sokal 88b21e476d [scripts] run yarn install before running scripts (#1519) 2022-11-14 10:34:02 +01:00
Expo CI 3d497d4f05 update CHANGELOG.md 2022-11-11 17:26:16 +00:00
Brent Vatne 9b668e3629 v2.7.0 2022-11-11 09:16:07 -08:00
Brent Vatne a670a2a3fc upgrade eas-cli-local-build-plugin to 0.0.118 2022-11-11 09:15:44 -08:00
Brent Vatne 53a407fa78 [eas-cli] Fix test 2022-11-11 09:12:30 -08:00
Brent Vatne 5ab1f637a5 [eas-cli] Remove dependency on expo package from eas init (#1517) 2022-11-11 09:01:17 -08:00
Szymon Dziedzic f59c945347 [ENG-6162][eas-cli] Change choice formatting when selecting build to submit (#1502)
* [eas-cli] change choice formatting when selecting build to submit

* fix typo

* use description with one line choice

* use eas update channel vs old release channel

* update CHANGELOG.md
2022-11-10 17:49:41 +01:00
Evan Bacon 83e50ab546 feat: support uploading different bundle combos (#1461)
* feat: support uploading web bundles

Expo Router defaults to shipping with universal Metro support (ios, android, web) which means eas update breaks by default if it asserts that web is not supported.

This PR also fixes the inability to upload single platforms.

* ensure platform filtering works

* Update publish.ts

* add tests

* update CHANGELOG.md

* style xits from PR feedback

* attempt to fix

* fix

* Update index.ts

* Update CHANGELOG.md

* Update publish-test.ts

* Update publish-test.ts
2022-11-09 15:49:28 -06:00
Dominik Sokal 9a415fe743 update CHANGELOG.md 2022-11-07 10:34:56 +01:00
Mackenzie Salisbury e722b6601c [eas-json] fix ordering of distribution enum values (#1504) 2022-11-07 10:19:56 +01:00
Dominik Sokal 170e5145f0 update CHANGELOG.md 2022-11-03 09:15:37 +01:00
Evan Bacon 405c532f13 use version check for disabling global CLI (#1474) 2022-11-02 21:22:29 -05:00
Juwan Wheatley 5bed1c3a01 [eas-cli][update] Attach Git Commit to EAS Update (#1499)
* [eas-cli][update] Attach Git Commit to EAS Update

* update changelog

* include commit in update summary log
2022-11-02 19:06:24 -04:00
Cedric van PuttenandEvan Bacon 8d0d34c497 [eas-cli] Improve configuration setup for EAS update (#1479)
* refactor(update): auto configure EAS updates when running eas update

* fix(update): make sure eas update does what it needs to do

* refactor(update): drop obsolete eas update validation errors

* refactor(update): drop duplicate truncate update message

* refactor(update): drop duplicate install expo-update message

* fix(update): use the old truncated update message limit

* refactor(update): ensure the latest changes are integrated in the PR

* fix(update): use the EAS update configured expo config

* chore(update): make the EAS Update outage a bit more readable

* fix(update): remove _internal from merged manifest if defined

* fix(update): make sure people can run `eas update:configure` without project id

* update CHANGELOG.md

* refactor(update): skip setting the sdkVersion when configuring

* fix(update): only merge nested objects when configuring

* fix(update): refetch the config whenever projects are auto-setup

* refactor(update): simplify the config object merging

* refactor(update): change logging based on review input

Co-authored-by: Evan Bacon <[email protected]>

* refactor(update): drop fyi .md extension from logging

Co-authored-by: Evan Bacon <[email protected]>

* refactor(update): alawys reload the project config after ensuring eas updates

* fix(update): linting issue caused by logging changes

* refactor(update): drop unused exports from updates configure code

* refactor(update): split merge config to separate method for testing

* fix(update): show full install log when debugging is enabled

* fix(update): add back the missing <remove this key> instruction

Co-authored-by: Evan Bacon <[email protected]>
2022-11-02 23:18:50 +01:00
Dominik Sokal 6270bc122b [ENG-6619] validate icon PNGs before running build (#1477)
* validate icon PNGs before running build

* address PR feedback

* address PR feedback

* address more PR feedback
2022-11-02 18:05:55 +01:00
Cedric van PuttenandWill Schurman 57f1128450 [eas-cli] Extract entity utilities from update command class (#1478)
* refactor(update): remove default non-interactive value to rely on falsy values

Co-authored-by: Will Schurman <[email protected]>

* refactor(updates): make non interactive optional to rely on falsy values

* Revert "refactor(update): remove default non-interactive value to rely on falsy values"

This reverts commit 23ac36d36e35b7a87724229234d41dcac8cf02db.

* Revert "refactor(updates): make non interactive optional to rely on falsy values"

This reverts commit 94ae7cc039c94f28b74a5cfd9857f79e5a11e82c.

* refactor(update): move ensure methods outside the update command

* fix(update): add back log about channel pointing to branch

* refactor(branch): reuse query from shared utilsin `branch:create`

* refactor(channel): reuse query from shared utilsin `channel:create`

Co-authored-by: Will Schurman <[email protected]>
2022-10-31 18:47:56 +01:00
Cedric van PuttenandWill Schurman cb09906b2e [eas-cli] Add flag sanitization in updates command (#1473)
* [eas-cli] Add flag sanitization in updates command

* refactor(update): remove default non-interactive value to rely on falsy values

Co-authored-by: Will Schurman <[email protected]>

* refactor(updates): make non interactive optional to rely on falsy values

* Revert "refactor(update): remove default non-interactive value to rely on falsy values"

This reverts commit 23ac36d36e35b7a87724229234d41dcac8cf02db.

* Revert "refactor(updates): make non interactive optional to rely on falsy values"

This reverts commit 94ae7cc039c94f28b74a5cfd9857f79e5a11e82c.

Co-authored-by: Will Schurman <[email protected]>
2022-10-31 18:18:44 +01:00
Bartosz Kaszubowski fa704c43b3 [eas-cli] Update: improve DX on first run, correct error message (#1427) 2022-10-27 17:01:27 +02:00
Dominik Sokal eede0c3dd7 fix CHANGELOG.md 2022-10-27 12:07:44 +02:00
Expo CI 1758a4e5e5 update CHANGELOG.md 2022-10-27 08:06:51 +00:00
Dominik Sokal 20ebf24a20 v2.6.0 2022-10-27 10:00:11 +02:00
Juwan WheatleyandWill Schurman 427ececb0b [eas-cli] Warn about outdated build deployment when configuring EAS Update (#1467)
* [eas-cli] Warn about outdated build deployment when configuring EAS Update

* update changelog with PR number

* print warning no matter what

* Update packages/eas-cli/src/commands/update/configure.ts

Co-authored-by: Will Schurman <[email protected]>

Co-authored-by: Will Schurman <[email protected]>
2022-10-26 17:18:50 -04:00
Cedric van Putten f65670e18f [eas-cli] Drop remaining cli tables (#1481)
* refactor: Replace `branch:view` and `update:list` table with formatted fields

* refactor: Replace `channel:view` and `channel:list` table with formatted fields

* refactor: Replace `update:list --all` table with formatted fields

* refactor: drop cli-table3 dependency

* update CHANGELOG.md
2022-10-26 11:55:51 -07:00
Dominik Sokal 41a0a4554d upgrade dependencies (#1480)
* upgrade dependencies

* regenerate graphql code

* fix types

* revert axios upgrade

* fix tests

* upgrade oclif deps

* fix more tests

* update CHANGELOG.md
2022-10-26 14:09:33 +02:00
Will Schurman c1da053f8c [eas-cli] Add feature gate support (#1475) 2022-10-25 08:55:14 -07:00
Szymon Dziedzic 57da02af9d [ENG-6238][eas-cli] Add eas build:run command and implement running iOS simulator builds directly from EAS CLI (#1447)
* create eas run command

* improve run command

* downaload and install iOS sim app

* add logic to select best avaliable simulator

* add not implemented android command

* use expo cli implementation of detecting if simulator is running

* support both archive and app paths in path argument

* better validate path argument

* refactor iOS code

* assert that Xcode and xcrun are installed

* rename ios and android run files

* assert simulator app is installed

* apply suggestions from code review

* improve select sim build prompt

* hide command

* minor fixes

* apply improvements suggested in code review

* update CHANGELOG.md
2022-10-25 12:02:38 +02:00
Brent Vatne b9c505bbdc Update CODEOWNERS 2022-10-24 11:28:02 -07:00
Brent Vatne 633e6a6721 Update CODEOWNERS 2022-10-24 11:19:59 -07:00
Cedric van PuttenandDominik Sokal f7ddb4ba8f [eas-cli] Add metadata validation command with complex rules (#1416)
* refactor: update ajv, add human errors, and prepare lint command

* feature: add custom validation rules

* refactor: allow metadata context to be created without auth state

* refactor: update validation errors with new custom issues

* refactor: update existing code to use new features

* feature: add custom apple validation rules

* feature: implement validation command with json option

* chore: clean up outdated files

* refactor: update config schema type to improve human error message

* [eas-cli] Fix typo in ajv version

* [eas-cli] Use built-in context API to get project dir

* [eas-cli] Remove commented code

* [eas-cli] Load project without enforcing project id in metadata:lint

* [eas-cli] Guard against faulty config objects

* update CHANGELOG.md

* docs(cli): fix keyword length typo

* refactor(cli): use optional chaining over ternary

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

* refactor(cli): drop non-null assertion in config resolver

* refactor(cli): drop implicit coercion in expressions

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

* refactor(cli): only initialize ajv when used

* refactor(cli): simplify error logging

* refactor(cli): move coascle to expodash as filter

* fix(cli): use proper empty check on readable errors

* refactor(cli): support non interactive in metadata lint

* refactor(cli): remove metadata context from lint command

* refactor(cli): split up metadata context into different methods

* refactor(cli): use profile instead of metadata path in config resolve methods

* refactor(cli): rename context to auth and simplify usage

* refactor(cli): move json schema files to top-level imports

* refactor(cli): use predicates in expodash filter instead of filter functions

* test(cli): update config tests to use mocked profile isntead

* test(cli): fix issue in filter predicate test

* [eas-cli] Reimplement the analytics manager

Co-authored-by: Dominik Sokal <[email protected]>
2022-10-24 18:09:11 +02:00
Cedric van Putten 91eb2c467b [eas-cli] Replace branch:list table with formatted fields (#1466)
* [eas-cli] Replace channel:view table with formatted fields

* update CHANGELOG.md

* [eas-cli] Add getBranchDescription method like update group
2022-10-24 15:57:41 +02:00
Cedric van Putten f237a7e074 [eas-cli] Replace device:create input summary table with format fields (#1465)
* [eas-cli] Replace device:create input summary table with format fields

* update CHANGELOG.md

* [eas-cli] Add device class to format device
2022-10-24 15:17:57 +02:00
Cedric van PuttenandDominik Sokal 242c3cffb6 [eas-cli] Replace secret:list table with format fields (#1464)
* [eas-cli] Replace secret:list table with format fields

* update CHANGELOG.md

* [eas-cli] Replace created at with update in secrets list

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

* Revert updated at

Co-authored-by: Dominik Sokal <[email protected]>
2022-10-24 14:59:55 +02:00
Dominik Sokal eee54af6ee Update CHANGELOG.md 2022-10-24 12:28:44 +02:00
Expo CI 86f86dbe86 update CHANGELOG.md 2022-10-24 10:26:53 +00:00
335 changed files with 30338 additions and 8817 deletions

No files matched your search

+3
View File
@@ -1,2 +1,5 @@
* text eol=lf
*.png binary
*.jpg binary
**/yarn.lock linguist-generated
+1 -1
View File
@@ -37,7 +37,7 @@ body:
required: true
attributes:
label: Environment
description: Run `npx expo-env-info` and `expo doctor`, and paste the output here.
description: Run `npx expo-env-info` and `npx expo-doctor`, and paste the output here.
- type: textarea
validations:
required: false
+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]) -->
# 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.
<!-- 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
@@ -0,0 +1,4 @@
{
"printWidth": 100,
"singleQuote": true
}
@@ -0,0 +1,53 @@
name: notify-slack-on-fail-or-recover
inputs:
channel:
required: true
type: string
slack_webhook:
required: true
type: string
runs:
using: 'composite'
steps:
- name: Get previous workflow run status
uses: actions/github-script@v6
id: run-status
with:
script: |
const script = require('.github/internal-actions/notify-slack-on-fail-or-recover/main.js');
return await script({github, context, core});
- name: Commit subject
shell: bash
run: |
subject="$(git show --format=%s --quiet | sed 's/"/\\"/g')"
echo "${{ fromJSON(steps.run-status.outputs.result).previous }}"
echo "SUBJECT=$subject" >> "$GITHUB_ENV"
- name: Send Slack Success Notification
if: fromJSON(steps.run-status.outputs.result).previous == 'failure' && fromJSON(steps.run-status.outputs.result).current == 'success'
uses: rtCamp/action-slack-notify@v2
env:
SLACK_CHANNEL: ${{ inputs.channel }}
SLACK_COLOR: good
SLACK_ICON: https://github.com/expo-bot.png?size=48
SLACK_TITLE: 'Fixed, was failing'
SLACK_MESSAGE: ${{ env.SUBJECT }}
SLACK_USERNAME: 'EAS CLI CI'
SLACK_WEBHOOK: ${{ inputs.slack_webhook }}
SLACK_FOOTER: ''
- name: Send Slack Failure Notification
if: fromJSON(steps.run-status.outputs.result).current == 'failure'
uses: rtCamp/action-slack-notify@v2
env:
SLACK_CHANNEL: ${{ inputs.channel }}
SLACK_COLOR: danger
SLACK_ICON: https://github.com/expo-bot.png?size=48
SLACK_TITLE: 'Failed'
SLACK_MESSAGE: ${{ env.SUBJECT }}
SLACK_USERNAME: 'EAS CLI CI'
SLACK_WEBHOOK: ${{ inputs.slack_webhook }}
SLACK_FOOTER: ''
@@ -0,0 +1,155 @@
function updateOctokitInstance(github) {
updateGithubRestActions(github);
addConvenienceMethods(github);
}
function addConvenienceMethods(github) {
Object.assign(github, {
// checks if a run is a failure from the perspective of sending Slack
// notifications. Works for both runs and run attempts, which should be the
// same object shapes.
// When a run has not been completed, it fetches the jobs for the workflow
// to check if the job is "failing" instead of "failed".
async isFailedRun(run) {
console.log(`checking run: ${run.id}`);
console.log(`status: ${run.status}`);
console.log(`conclusion: ${run.conclusion}`);
console.log(`run_attempt: ${run.run_attempt}`);
if (run.status === 'completed') {
console.log('completed -- checking conclusion');
return run.conclusion === 'failure' || run.conclusion === 'timed_out';
}
const owner = run.repository.owner.login;
const repo = run.repository.name;
const jobsResp = await github.rest.actions.listJobsForWorkflowRun({
owner,
repo,
run_id: run.id,
});
console.log('checking jobs');
const jobs = jobsResp.data;
for (const job of jobs.jobs) {
if (job.conclusion === 'failure' || job.conclusion === 'timed_out') {
console.log('job failed');
return true;
} else if (job.status === 'completed') {
continue;
}
for (const step of job.steps) {
if (step.conclusion === 'failure') {
console.log('job step failed');
return true;
}
}
}
return false;
},
});
}
// github-script's Octokit/REST plugins are out of date, so we have to
// add our own custom endpoint method
// see https://octokit.github.io/rest.js/v18#custom-endpoint-methods for
// documentation
function updateGithubRestActions(github) {
Object.assign(github.rest.actions, {
getWorkflowRunAttempt: github.request.defaults({
method: 'GET',
url: '/repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}',
params: {
owner: {
required: true,
type: 'string',
},
repo: {
required: true,
type: 'string',
},
run_id: {
required: true,
type: 'number',
},
attempt_number: {
required: true,
type: 'number',
},
},
}),
});
}
// Returns { current: 'failure' } | { current: 'success', previous: 'success' | 'failure' } to indicate the failure status
// of the current and previous runs
module.exports = async ({ github }) => {
const { GITHUB_REPOSITORY, GITHUB_RUN_ID, GITHUB_REF_NAME } = process.env;
const [owner, repo] = GITHUB_REPOSITORY.split('/');
updateOctokitInstance(github);
const workflowRunResp = await github.rest.actions.getWorkflowRun({
owner,
repo,
run_id: GITHUB_RUN_ID,
});
const currentRun = workflowRunResp.data;
const listWorkflowRunsResp = await github.rest.actions.listWorkflowRuns({
owner,
repo,
workflow_id: currentRun.workflow_id,
branch: GITHUB_REF_NAME,
});
const { workflow_runs } = listWorkflowRunsResp.data;
if (workflow_runs.length === 1 && workflow_runs[0].run_attempt === 1) {
return false;
}
const previousRuns = workflow_runs.filter(
(run) => run.status === 'completed' && run.conclusion !== 'cancelled'
);
if (await github.isFailedRun(currentRun)) {
return { current: 'failure', previous: null };
}
if (currentRun.run_attempt === 1 && previousRuns.length > 0) {
return {
current: 'success',
previous: (await github.isFailedRun(previousRuns[0])) ? 'failure' : 'success',
};
}
for (let i = currentRun.run_attempt - 1; i > 0; i--) {
const previousAttemptResp = await github.rest.actions.getWorkflowRunAttempt({
owner,
repo,
run_id: currentRun.id,
attempt_number: i,
});
const previousAttempt = previousAttemptResp.data;
if (
previousAttempt.conclusion === 'success' ||
previousAttempt.conclusion === 'failure' ||
previousAttempt.conclusion === 'timed_out'
) {
return {
current: 'success',
previous: (await github.isFailedRun(previousAttempt)) ? 'failure' : 'success',
};
}
}
if (previousRuns.length > 0) {
return {
current: 'success',
previous: (await github.isFailedRun(previousRuns[0])) ? 'failure' : 'success',
};
}
return { current: 'success', previous: 'success' };
};
@@ -0,0 +1,12 @@
{
"name": "notify-slack-on-fail-or-recover",
"version": "1.0.0",
"description": "A GitHub Action to notify Expo Slack of build failures and recovery.",
"main": "main.js",
"author": "Expo",
"license": "UNLICENSED",
"private": true,
"devDependencies": {
"prettier": "^2.5.1"
}
}
@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
[email protected]:
version "2.5.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
+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
+30 -39
View File
@@ -1,46 +1,37 @@
on:
issue_comment:
types: [created]
name: CHANGELOG.md PR check
name: Changelog
on:
pull_request:
types:
- opened
- synchronize
- labeled
- unlabeled
jobs:
pr-commented:
pr-changelog-entry:
name: Check CHANGELOG.md updated
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
- uses: actions/setup-node@v2
- name: Check if CHANGELOG.md was updated
id: changelog-updated
uses: tj-actions/changed-files@v35
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
files: CHANGELOG.md
- 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:
script: |
core.setFailed('Please add a changelog entry!')
- uses: mshick/[email protected]
if: always()
with:
message-id: changelog-entry-check
refresh-message-position: true
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!' }}
message-failure: |
❌ 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.
🤖 To use the bot, simply comment on this PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`.
⏩ 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.
+21 -11
View File
@@ -29,17 +29,17 @@ jobs:
with:
node-version: 18
- run: yarn install --frozen-lockfile --check-files
- run: yarn build
- run: yarn build-ci
- name: Build tarballs
working-directory: ${{ github.workspace }}/packages/eas-cli
run: |
yarn pretarball-ci
yarn oclif pack:tarballs --no-xz --targets linux-x64,linux-arm
- id: x64
run: echo "::set-output name=filename::$(ls eas-*-x64.tar.gz)"
run: echo "filename=$(ls eas-*-x64.tar.gz)" >> $GITHUB_OUTPUT
working-directory: ${{ github.workspace }}/packages/eas-cli/dist
- id: arm
run: echo "::set-output name=filename::$(ls eas-*-arm.tar.gz)"
run: echo "filename=$(ls eas-*-arm.tar.gz)" >> $GITHUB_OUTPUT
working-directory: ${{ github.workspace }}/packages/eas-cli/dist
- name: Upload linux-x64 tarball
uses: actions/upload-release-asset@v1
@@ -70,14 +70,14 @@ jobs:
with:
node-version: 18
- run: yarn install --frozen-lockfile --check-files
- run: yarn build
- run: yarn build-ci
- name: Build tarballs
working-directory: ${{ github.workspace }}/packages/eas-cli
run: |
yarn pretarball-ci
yarn oclif pack:tarballs --no-xz --targets darwin-x64
- id: x64
run: echo "::set-output name=filename::$(ls eas-*-x64.tar.gz)"
run: echo "filename=$(ls eas-*-x64.tar.gz)" >> $GITHUB_OUTPUT
working-directory: ${{ github.workspace }}/packages/eas-cli/dist
- name: Upload darwin-x64 tarball
uses: actions/upload-release-asset@v1
@@ -99,16 +99,16 @@ jobs:
node-version: 18
- run: yarn install --frozen-lockfile --check-files
- run: sudo apt-get install nsis
- run: yarn build
- run: yarn build-ci
- run: |
yarn pretarball-ci
yarn oclif pack:win
working-directory: ${{ github.workspace }}/packages/eas-cli
- id: x64
run: echo "::set-output name=filename::$(ls eas-*-x64.exe)"
run: echo "filename=$(ls eas-*-x64.exe)" >> $GITHUB_OUTPUT
working-directory: ${{ github.workspace }}/packages/eas-cli/dist/win32
- id: x86
run: echo "::set-output name=filename::$(ls eas-*-x86.exe)"
run: echo "filename=$(ls eas-*-x86.exe)" >> $GITHUB_OUTPUT
working-directory: ${{ github.workspace }}/packages/eas-cli/dist/win32
- name: Upload x64 installer
uses: actions/upload-release-asset@v1
@@ -142,17 +142,27 @@ jobs:
- name: Install dependencies
run: yarn install --frozen-lockfile --check-files
- name: Build
run: yarn build
run: yarn build-ci
- name: Publish packages to npm
run: yarn lerna publish from-package --yes --no-verify-access
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
- name: Add latest-eas-build-staging tag
run: |
VERSION=$(cat lerna.json | jq -r .version)
echo //registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN > ~/.npmrc
npm dist-tag add eas-cli@$VERSION latest-eas-build-staging
echo "Run \"npm dist-tag add eas-cli@$VERSION latest-eas-build\" to promote it to production."
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
release-changelog:
name: Update changelog and publish release
runs-on: ubuntu-latest
needs: publish-to-npm
steps:
- uses: actions/checkout@v2
with:
token: ${{ secrets.EXPO_BOT_PAT }}
- uses: actions/setup-node@v2
with:
node-version: 18
@@ -189,9 +199,9 @@ jobs:
env:
SLACK_CHANNEL: eas-cli
SLACK_COLOR: ${{ job.status }}
SLACK_ICON: https://github.com/expo-ci.png?size=48
SLACK_ICON: https://github.com/expo-bot.png?size=48
SLACK_TITLE: 'Released eas-cli@${{ env.EAS_CLI_VERSION }}'
SLACK_MESSAGE: ${{ env.SLACK_CHANGELOG }}
SLACK_USERNAME: 'Expo CI'
SLACK_USERNAME: 'EAS CLI CI'
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_FOOTER: ''
+18 -2
View File
@@ -1,4 +1,5 @@
name: Run tests
on:
push:
branches: [main]
@@ -25,17 +26,32 @@ jobs:
- run: yarn typecheck
- run: yarn test
if: ${{ !matrix.coverage }}
env:
NODE_OPTIONS: --max-old-space-size=8192
- run: yarn test --coverage
if: ${{ matrix.coverage }}
env:
# Oclif seems to cause OOM errors when testing with coverage enabled.
# 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 install --frozen-lockfile --check-files
working-directory: ./scripts
- run: yarn lint-changelog
- uses: codecov/codecov-action@v1
if: ${{ matrix.coverage }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
- run: yarn lint-changelog
notify-slack:
runs-on: ubuntu-latest
needs: test
name: Notify Slack
if: ${{ github.ref == 'refs/heads/main' && always() }}
steps:
- uses: actions/checkout@v2
- name: Notify Slack
uses: ./.github/internal-actions/notify-slack-on-fail-or-recover
with:
channel: eas-cli
slack_webhook: ${{ secrets.SLACK_WEBHOOK }}
+509 -2
View File
@@ -10,10 +10,516 @@ This is the log of notable changes to EAS CLI and related packages.
### 🐛 Bug fixes
- Revert using local CLI for `expo export` ([#1460](https://github.com/expo/eas-cli/pull/1460)). ([#1472](https://github.com/expo/eas-cli/pull/1472) by [@dsokal](https://github.com/dsokal))
### 🧹 Chores
## [3.17.0](https://github.com/expo/eas-cli/releases/tag/v3.17.0) - 2023-07-24
### 🎉 New features
- Add `rollout` option for configuring Android submissions. ([#1938](https://github.com/expo/eas-cli/pull/1938) by [@szdziedzic](https://github.com/szdziedzic))
### 🧹 Chores
- Added branch mapping utility functions. ([#1944](https://github.com/expo/eas-cli/pull/1944) by [@quinlanj](https://github.com/quinlanj))
- Amend branch mapping utility functions. ([#1945](https://github.com/expo/eas-cli/pull/1945) by [@quinlanj](https://github.com/quinlanj))
- Handle error thrown when `intel-medium` resource class is not available as server-side defined error. ([#1947](https://github.com/expo/eas-cli/pull/1947) by [@szdziedzic](https://github.com/szdziedzic))
- Remove `intel-medium` from `eas.schema.json`, so it's not suggested as a valid value by our VSCode plugin. ([#1947](https://github.com/expo/eas-cli/pull/1947) by [@szdziedzic](https://github.com/szdziedzic))
## [3.16.0](https://github.com/expo/eas-cli/releases/tag/v3.16.0) - 2023-07-18
### 🎉 New features
- Add styling to SSO auth redirect completion page. ([#1929](https://github.com/expo/eas-cli/pull/1929) by [@wschurman](https://github.com/wschurman))
- Ignore entitlements from native template when `/ios` is gitignored. ([#1906](https://github.com/expo/eas-cli/pull/1906) by [@byCedric](https://github.com/byCedric))
- Use node server default port selection for SSO login server. ([#1930](https://github.com/expo/eas-cli/pull/1930) by [@wschurman](https://github.com/wschurman))
### 🐛 Bug fixes
- Fix incorrect handling of valid `inProgress` Android submission release status. ([#1934](https://github.com/expo/eas-cli/pull/1934) by [@szdziedzic](https://github.com/szdziedzic))
### 🧹 Chores
- Print friendly error msg in case account doesn't have required permission. ([#1867](https://github.com/expo/eas-cli/pull/1867) by [@firasrg](https://github.com/firasrg))
- Limit project file upload size to 2GB. ([#1928](https://github.com/expo/eas-cli/pull/1928) by [@khamilowicz](https://github.com/khamilowicz))
- Bump urql graphql client major version. ([#1936](https://github.com/expo/eas-cli/pull/1936) by [@quinlanj](https://github.com/quinlanj))
## [3.15.1](https://github.com/expo/eas-cli/releases/tag/v3.15.1) - 2023-07-11
### 🐛 Bug fixes
- Ensure useClassicUpdates is not set when using EAS Update commands. ([#1915](https://github.com/expo/eas-cli/pull/1915) by [@ide](https://github.com/ide))
- Handle multiple GraphQLErrors when receiving a CombinedError. ([#1924](https://github.com/expo/eas-cli/pull/1924) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
### 🧹 Chores
- Bump Expo package dependencies. ([#1911](https://github.com/expo/eas-cli/pull/1911) by [@brentvatne](https://github.com/brentvatne))
- Better bare workflow runtimeVersion error. ([#1910](https://github.com/expo/eas-cli/pull/1910) by [@quinlanj](https://github.com/quinlanj))
- Fix runtime version print logs. ([#1925](https://github.com/expo/eas-cli/pull/1925) by [@quinlanj](https://github.com/quinlanj))
- Add info about new `macos-ventura-13.4-xcode-14.3.1` image to `eas.schema.json`. ([#1920](https://github.com/expo/eas-cli/pull/1920) by [@szdziedzic](https://github.com/szdziedzic))
## [3.15.0](https://github.com/expo/eas-cli/releases/tag/v3.15.0) - 2023-06-30
### 🎉 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))
### 🧹 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))
- Adds support for 2 new server-side errors related to build limits. ([#1921](https://github.com/expo/eas-cli/pull/1921) by [@sundeeppeswani](https://github.com/sundeeppeswani))
## [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
### 🐛 Bug fixes
- Fix prompts in `eas update`. ([#1797](https://github.com/expo/eas-cli/pull/1797) by [@wschurman](https://github.com/wschurman))
## [3.9.2](https://github.com/expo/eas-cli/releases/tag/v3.9.2) - 2023-04-13
### 🐛 Bug fixes
- Fixes the rollout percentages when ending a rollout. ([#1781](https://github.com/expo/eas-cli/pull/1781) by [@jonsamp](https://github.com/jonsamp))
- Print meaningful error message if `extra.eas.projectId` is not a string. ([#1788](https://github.com/expo/eas-cli/pull/1788) by [@dsokal](https://github.com/dsokal))
### 🧹 Chores
- Show output of `expo install expo-updates`. ([#1782](https://github.com/expo/eas-cli/pull/1782) by [@wkozyra95](https://github.com/wkozyra95))
- Print deprecation warnings for deprecated `eas.json` fields. ([#1768](https://github.com/expo/eas-cli/pull/1768) by [@szdziedzic](https://github.com/szdziedzic))
## [3.9.1](https://github.com/expo/eas-cli/releases/tag/v3.9.1) - 2023-04-10
### 🐛 Bug fixes
- Update apple utilities to work around the maintenance error. ([#1780](https://github.com/expo/eas-cli/pull/1780) by [@byCedric](https://github.com/byCedric))
### 🧹 Chores
- Don't use defaults for the `cache.cacheDefaultPaths` build profile field. ([#1769](https://github.com/expo/eas-cli/pull/1769) by [@szdziedzic](https://github.com/szdziedzic))
## [3.9.0](https://github.com/expo/eas-cli/releases/tag/v3.9.0) - 2023-04-06
### 🎉 New features
- Add support for roll back to embedded updates. ([#1754](https://github.com/expo/eas-cli/pull/1754), [#1755](https://github.com/expo/eas-cli/pull/1755) by [@wschurman](https://github.com/wschurman))
### 🐛 Bug fixes
- Update `expoCommandAsync` to support the new Expo CLI bin path. ([#1772](https://github.com/expo/eas-cli/pull/1772) by [@gabrieldonadel](https://github.com/gabrieldonadel))
### 🧹 Chores
- Use the `eas-cli` npm tag for checking for the local build plugin updates. ([#1759](https://github.com/expo/eas-cli/pull/1759) by [@dsokal](https://github.com/dsokal))
- Ignore `requireCommit` option in `eas build:internal`. ([#1760](https://github.com/expo/eas-cli/pull/1760) by [@wkozyra95](https://github.com/wkozyra95))
- Support parsing eas.json from a string. ([#1766](https://github.com/expo/eas-cli/pull/1766) by [@wkozyra95](https://github.com/wkozyra95))
## [3.8.1](https://github.com/expo/eas-cli/releases/tag/v3.8.1) - 2023-03-16
### 🐛 Bug fixes
- Ensure public config is used as update. ([#1745](https://github.com/expo/eas-cli/pull/1745) by [@byCedric](https://github.com/byCedric))
### 🧹 Chores
- Unify how the command errors are displayed. ([#1738](https://github.com/expo/eas-cli/pull/1738) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
- Unify the case of base error messages for command failures. ([#1744](https://github.com/expo/eas-cli/pull/1744) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
## [3.8.0](https://github.com/expo/eas-cli/releases/tag/v3.8.0) - 2023-03-13
### 🎉 New features
- Add new `m-medium` and `m-large` resource classes. ([#1739](https://github.com/expo/eas-cli/pull/1739) by [@szdziedzic](https://github.com/szdziedzic))
## [3.7.2](https://github.com/expo/eas-cli/releases/tag/v3.7.2) - 2023-02-24
### 🐛 Bug fixes
- Implement Apple's proprietary Hashcash algorithm for signing authentication requests. ([#1719](https://github.com/expo/eas-cli/pull/1719) by [@EvanBacon](https://github.com/EvanBacon))
## [3.7.1](https://github.com/expo/eas-cli/releases/tag/v3.7.1) - 2023-02-23
### 🐛 Bug fixes
- Bump `@expo/apple-utils` to fix Apple developer auth. ([03c76c1](https://github.com/expo/eas-cli/commit/03c76c1e0efb9ec55e40b45e33c80b3479f920c3) by [@brentvatne](https://github.com/brentvatne))
## [3.7.0](https://github.com/expo/eas-cli/releases/tag/v3.7.0) - 2023-02-23
### 🎉 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))
## [3.6.1](https://github.com/expo/eas-cli/releases/tag/v3.6.1) - 2023-02-20
### 🐛 Bug fixes
- Disable analytics when running behind a proxy. ([#1696](https://github.com/expo/eas-cli/pull/1696) by [@wkozyra95](https://github.com/wkozyra95))
- Clarify missing owner field error message for Robot users. ([#1702](https://github.com/expo/eas-cli/pull/1702) by [@wschurman](https://github.com/wschurman))
### 🧹 Chores
- Validate the platform for local builds earlier. ([#1698](https://github.com/expo/eas-cli/pull/1698) by [@wkozyra95](https://github.com/wkozyra95))
- Display build message when picking build in `eas submit`, `eas build:resign`, and `eas build:run` commands. ([#1700](https://github.com/expo/eas-cli/pull/1700) by [@szdziedzic](https://github.com/szdziedzic))
- Add a link directly to a build phase logs in the EAS CLI build error message. ([#1699](https://github.com/expo/eas-cli/pull/1699) by [@szdziedzic](https://github.com/szdziedzic))
## [3.6.0](https://github.com/expo/eas-cli/releases/tag/v3.6.0) - 2023-02-14
### 🎉 New features
- Use `sdkVersion` as default runtime version policy when running `eas update:configure`. ([#1669](https://github.com/expo/eas-cli/pull/1669) by [@jonsamp](https://github.com/jonsamp))
- Warn when project ID but no owner specified and mismatch logged in user. ([#1667](https://github.com/expo/eas-cli/pull/1667) by [@wschurman](https://github.com/wschurman))
- Return the build `message` if one is available. ([#1691](https://github.com/expo/eas-cli/pull/1691) by [@raulriera](https://github.com/raulriera))
### 🐛 Bug fixes
- Fix suggested application identifier to match owning account name. ([#1670](https://github.com/expo/eas-cli/pull/1670) by [@wschurman](https://github.com/wschurman))
### 🧹 Chores
- Add Xcode 14.2 image to the VSCode schema. ([#1693](https://github.com/expo/eas-cli/pull/1693) by [@szdziedzic](https://github.com/szdziedzic))
## [3.5.2](https://github.com/expo/eas-cli/releases/tag/v3.5.2) - 2023-02-05
### 🐛 Bug fixes
- Use the new developer-mdn.apple.com subdomain instead of developer.apple.com. ([#1673](https://github.com/expo/eas-cli/pull/1673) by [@brentvatne](https://github.com/brentvatne))
## [3.5.1](https://github.com/expo/eas-cli/releases/tag/v3.5.1) - 2023-02-01
### 🐛 Bug fixes
- Fix the issue with the `eas.json` envs being not available when resolving dynamic config. ([#1666](https://github.com/expo/eas-cli/pull/1666) by [@szdziedzic](https://github.com/szdziedzic))
## [3.5.0](https://github.com/expo/eas-cli/releases/tag/v3.5.0) - 2023-01-31
### 🎉 New features
- Set `m1-medium` resource class for SDK version `>=48` and RN version `>=0.71` builds with unspecified resource class. ([#1655](https://github.com/expo/eas-cli/pull/1655) by [@szdziedzic](https://github.com/szdziedzic))
### 🐛 Bug fixes
- Fix undefined branchMappingLogic. ([#1653](https://github.com/expo/eas-cli/pull/1653) by [@quinlanj](https://github.com/quinlanj))
- Fix using local credentials for internal distribution builds with universal provisioning. ([#1657](https://github.com/expo/eas-cli/pull/1657) by [@wkozyra95](https://github.com/wkozyra95))
### 🧹 Chores
- Move image validation to the server side and better handle server validation errors in the eas-cli. ([#1650](https://github.com/expo/eas-cli/pull/1650) by [@szdziedzic](https://github.com/szdziedzic))
- Update metadata on the worker when using git-based builds. ([#1651](https://github.com/expo/eas-cli/pull/1651) by [@wkozyra95](https://github.com/wkozyra95))
## [3.4.1](https://github.com/expo/eas-cli/releases/tag/v3.4.1) - 2023-01-23
### 🐛 Bug fixes
- Fix incorrect exit code for successful local builds. ([#1649](https://github.com/expo/eas-cli/pull/1649) by [@dsokal](https://github.com/dsokal))
## [3.4.0](https://github.com/expo/eas-cli/releases/tag/v3.4.0) - 2023-01-20
### 🎉 New features
- Add `--json` flag to `eas config` command. ([#1568](https://github.com/expo/eas-cli/pull/1568) by [@wkozyra95](https://github.com/wkozyra95))
- Add M1 resource class configuration to default eas.json when running eas build:configure on a new project. ([#1637](https://github.com/expo/eas-cli/pull/1637) by [@brentvatne](https://github.com/brentvatne))
- Add prompt to switch iOS builds to M1 if the build queue is long. ([#1642](https://github.com/expo/eas-cli/pull/1642) by [@dsokal](https://github.com/dsokal))
### 🐛 Bug fixes
- Fix running commands with `--json` flag when EAS_NO_VCS is set. ([#1641](https://github.com/expo/eas-cli/pull/1641) by [@wkozyra95](https://github.com/wkozyra95))
### 🧹 Chores
- Remove unnecessary workaround for trailing backslash in .gitignore. ([#1622](https://github.com/expo/eas-cli/pull/1622) by [@wkozyra95](https://github.com/wkozyra95))
- Internal command that will be run on EAS worker when building from GitHub. ([#1568](https://github.com/expo/eas-cli/pull/1568) by [@wkozyra95](https://github.com/wkozyra95))
- Make the disabled tier error message more descriptive. ([#1635](https://github.com/expo/eas-cli/pull/1635) by [@dsokal](https://github.com/dsokal))
- Control some of the EAS Build error messages server-side. ([#1639](https://github.com/expo/eas-cli/pull/1639) by [@wkozyra95](https://github.com/wkozyra95))
## [3.3.2](https://github.com/expo/eas-cli/releases/tag/v3.3.2) - 2023-01-12
### 🐛 Bug fixes
- Add missing key information about updates in `eas update --json` and `eas update:view --json`. ([#1619](https://github.com/expo/eas-cli/pull/1619) by [@byCedric](https://github.com/byCedric))
### 🧹 Chores
- Upgrade dependencies. ([#1617](https://github.com/expo/eas-cli/pull/1617) by [@dsokal](https://github.com/dsokal))
- Improve handling `SSOUser` type. ([#1621](https://github.com/expo/eas-cli/pull/1621) by [@szdziedzic](https://github.com/szdziedzic))
## [3.3.1](https://github.com/expo/eas-cli/releases/tag/v3.3.1) - 2023-01-11
### 🐛 Bug fixes
- Revert missing key information about updates in `eas update --json` and `eas update:view --json`. ([d20db0f](https://github.com/expo/eas-cli/commit/d20db0fff42a6f4512fea426a66cb6f168e37f7c) by [@ide](https://github.com/ide))
## [3.3.0](https://github.com/expo/eas-cli/releases/tag/v3.3.0) - 2023-01-11
### 🎉 New features
- Add ability to select resource class for a build in build profile. ([#1609](https://github.com/expo/eas-cli/pull/1609) by [@dsokal](https://github.com/dsokal))
- Deprecate `--resource-class` flag. ([#1615](https://github.com/expo/eas-cli/pull/1615) by [@dsokal](https://github.com/dsokal))
- Add new resource classes and update already available. ([#1616](https://github.com/expo/eas-cli/pull/1616) by [@szdziedzic](https://github.com/szdziedzic))
### 🐛 Bug fixes
- Add missing key information about updates in `eas update --json` and `eas update:view --json`. ([#1611](https://github.com/expo/eas-cli/pull/1611) by [@byCedric](https://github.com/byCedric))
## [3.2.1](https://github.com/expo/eas-cli/releases/tag/v3.2.1) - 2023-01-09
### 🐛 Bug fixes
- Validate chosen build in the `eas build:run` command. ([#1614](https://github.com/expo/eas-cli/pull/1614) by [@szdziedzic](https://github.com/szdziedzic))
## [3.2.0](https://github.com/expo/eas-cli/releases/tag/v3.2.0) - 2023-01-09
### 🎉 New features
- Add `--channel` option to `update:republish`. ([#1580](https://github.com/expo/eas-cli/pull/1580) by [@byCedric](https://github.com/byCedric))
- Use `appVersion` as default runtime version policy when running `eas update:configure`. ([#1588](https://github.com/expo/eas-cli/pull/1588) by [@jonsamp](https://github.com/jonsamp))
- Support `--json` flag in webhook list command. ([#1605](https://github.com/expo/eas-cli/pull/1605) by [@sheddy7](https://github.com/sheddy7))
### 🐛 Bug fixes
- Makes eas.json configuration to only run on `update:configure`. ([#1598](https://github.com/expo/eas-cli/pull/1598) by [@jonsamp](https://github.com/jonsamp))
- Fix issue with invisible build info in some terminals in the `eas build:run` and `eas build:resign` commands. ([#1602](https://github.com/expo/eas-cli/pull/1602) by [@szdziedzic](https://github.com/szdziedzic))
- Fix issues with invisible build info in some terminals while using the `eas submit` command. ([#1603](https://github.com/expo/eas-cli/pull/1603) by [@szdziedzic](https://github.com/szdziedzic))
- Use the `completedAt` timestamp as the build finish date instead of the `updatedAt` timestamp in the `eas build:run` command. ([#1604](https://github.com/expo/eas-cli/pull/1604) by [@szdziedzic](https://github.com/szdziedzic))
### 🧹 Chores
- Make all URLs in logs clickable in terminals supporting hyperlinks. ([#1591](https://github.com/expo/eas-cli/pull/1591) by [@Simek](https://github.com/Simek))
- Use paginated select prompt for the `eas build:run` command. ([#1601](https://github.com/expo/eas-cli/pull/1601) by [@szdziedzic](https://github.com/szdziedzic))
## [3.1.1](https://github.com/expo/eas-cli/releases/tag/v3.1.1) - 2022-12-19
### 🐛 Bug fixes
- Fix update `--channel` and `--branch` flag validation. ([#1579](https://github.com/expo/eas-cli/pull/1579) by [@byCedric](https://github.com/byCedric))
## [3.1.0](https://github.com/expo/eas-cli/releases/tag/v3.1.0) - 2022-12-17
### 🎉 New features
- Adds `--channel` flag to `eas update`. ([#1567](https://github.com/expo/eas-cli/pull/1567) by [@jonsamp](https://github.com/jonsamp))
- Add channel configurations to **eas.json** during `eas update:configure`. ([#1570](https://github.com/expo/eas-cli/pull/1570) by [@jonsamp](https://github.com/jonsamp))
- Add `build:resign` command. ([#1575](https://github.com/expo/eas-cli/pull/1575) by [@wkozyra95](https://github.com/wkozyra95))
## [3.0.0](https://github.com/expo/eas-cli/releases/tag/v3.0.0) - 2022-12-07
### 🛠 Breaking changes
- Move update republish to separate command. ([#1533](https://github.com/expo/eas-cli/pull/1533) by [@byCedric](https://github.com/byCedric))
### 🐛 Bug fixes
- Make the not recognized image error handler run only if the image is present under a valid key and has an invalid value. ([#1565](https://github.com/expo/eas-cli/pull/1565) by [@szdziedzic](https://github.com/szdziedzic))
- Fix the `node:assert` import error by using imports from `assert` instead of from `node:assert`. ([#1569](https://github.com/expo/eas-cli/pull/1569) by [@szdziedzic](https://github.com/szdziedzic))
### 🧹 Chores
- Remove old republish code from the update command. ([#1535](https://github.com/expo/eas-cli/pull/1535) by [@byCedric](https://github.com/byCedric))
## [2.9.0](https://github.com/expo/eas-cli/releases/tag/v2.9.0) - 2022-12-05
### 🎉 New features
- Add caching for `eas build:run` command. ([#1542](https://github.com/expo/eas-cli/pull/1542) by [@szdziedzic](https://github.com/szdziedzic))
- Add `isGitWorkingTreeDirty` to EAS Update records. ([#1550](https://github.com/expo/eas-cli/pull/1550) by [@FiberJW](https://github.com/FiberJW))
- Prompt developer to download the app after the simulator build is finished. ([#1554](https://github.com/expo/eas-cli/pull/1554) by [@szdziedzic](https://github.com/szdziedzic))
### 🐛 Bug fixes
- Ask for the missing application identifier consistently in `eas build:version:sync` command. ([#1543](https://github.com/expo/eas-cli/pull/1543) by [@wkozyra95](https://github.com/wkozyra95))
- Disable selecting builds whose artifacts have expired in the `eas build:run` command. ([#1547](https://github.com/expo/eas-cli/pull/1547) by [@szdziedzic](https://github.com/szdziedzic))
- Change intent with which the `eas build:run` command opens an Android app to `android.intent.action.MAIN`. ([#1556](https://github.com/expo/eas-cli/pull/1556) by [@szdziedzic](https://github.com/szdziedzic))
## [2.8.0](https://github.com/expo/eas-cli/releases/tag/v2.8.0) - 2022-11-28
### 🎉 New features
- Add `eas build:run` support for Android. ([#1485](https://github.com/expo/eas-cli/pull/1485) by [@szdziedzic](https://github.com/szdziedzic))
### 🐛 Bug fixes
- Only create a new channel when update branch is new. ([#1507](https://github.com/expo/eas-cli/pull/1507) by [@byCedric](https://github.com/byCedric))
### 🧹 Chores
- Better wording in eas submit. ([#1527](https://github.com/expo/eas-cli/pull/1527) by [@dsokal](https://github.com/dsokal))
- Upload project sources for EAS Build and archives for EAS Submit to GCS. ([#1524](https://github.com/expo/eas-cli/pull/1524) by [@wkozyra95](https://github.com/wkozyra95))
- Add a better error message for when an invalid `image` is set in `eas-json`. ([#1531](https://github.com/expo/eas-cli/pull/1531) by [@szdziedzic](https://github.com/szdziedzic))
- Remove internal expo id from device formatter. ([#1541](https://github.com/expo/eas-cli/pull/1541) by [@dsokal](https://github.com/dsokal))
## [2.7.1](https://github.com/expo/eas-cli/releases/tag/v2.7.1) - 2022-11-16
### 🐛 Bug fixes
- Use envs from the build profile to resolve credentials in `eas credentials`. ([#1520](https://github.com/expo/eas-cli/pull/1520) by [@wkozyra95](https://github.com/wkozyra95))
### 🧹 Chores
- Print prompt message when failing because of the non-interactive shell. ([#1523](https://github.com/expo/eas-cli/pull/1523) by [@wkozyra95](https://github.com/wkozyra95))
- Add SDK version to the analytics build events. ([#1529](https://github.com/expo/eas-cli/pull/1529) by [@wkozyra95](https://github.com/wkozyra95))
- Add Xcode 14.1 image to `eas-json`. ([#1511](https://github.com/expo/eas-cli/pull/1511) by [@szdziedzic](https://github.com/szdziedzic))
## [2.7.0](https://github.com/expo/eas-cli/releases/tag/v2.7.0) - 2022-11-11
### 🎉 New features
- Support uploading different platform combinations in `eas update`. ([#1461](https://github.com/expo/eas-cli/pull/1461) by [@EvanBacon](https://github.com/EvanBacon))
- Add Git commit to EAS Update record. ([#1499](https://github.com/expo/eas-cli/pull/1499) by [@fiberjw](https://github.com/fiberjw))
- Use local CLI for `expo export` in SDK +46. ([#1474](https://github.com/expo/eas-cli/pull/1474) by [@EvanBacon](https://github.com/EvanBacon))
- Validate icon PNGs before running Android build. ([#1477](https://github.com/expo/eas-cli/pull/1477) by [@dsokal](https://github.com/dsokal))
### 🐛 Bug fixes
- Fixed the values of the distribution enum on the build profile schema receiving the incorrect descriptions. ([#1504](https://github.com/expo/eas-cli/pull/1504) by [@macksal](https://github.com/macksal))
- Remove hard dependency on expo package being installed to init a project. ([#1517](https://github.com/expo/eas-cli/pull/1517) by [@brentvatne](https://github.com/brentvatne))
### 🧹 Chores
- Remove friction from the initial EAS update setup. ([#1479](https://github.com/expo/eas-cli/pull/1479) by [@byCedric](https://github.com/byCedric))
- Improve choice formatting when selecting a build to submit in the `eas submit` command. ([#1502](https://github.com/expo/eas-cli/pull/1502) by [@szdziedzic](https://github.com/szdziedzic))
## [2.6.0](https://github.com/expo/eas-cli/releases/tag/v2.6.0) - 2022-10-27
### 🎉 New features
- Add custom metadata validation command with more complex rules. ([#1416](https://github.com/expo/eas-cli/pull/1416) by [@byCedric](https://github.com/byCedric))
- Add `eas build:run` command which runs iOS simulator builds from the CLI. ([#1447](https://github.com/expo/eas-cli/pull/1447) by [@szdziedzic](https://github.com/szdziedzic))
- Add feature gate support. ([#1475](https://github.com/expo/eas-cli/pull/1475) by [@wschurman](https://github.com/wschurman))
- Warn about outdated build deployment when configuring EAS Update. ([#1467](https://github.com/expo/eas-cli/pull/1467) by [@fiberjw](https://github.com/fiberjw))
### 🧹 Chores
- Replace `secret:list` table with formatted fields. ([#1464](https://github.com/expo/eas-cli/pull/1464) by [@byCedric](https://github.com/byCedric))
- Replace `device:create` input table with formatted fields. ([#1465](https://github.com/expo/eas-cli/pull/1465) by [@byCedric](https://github.com/byCedric))
- Replace `channel:view` table with formatted fields. ([#1466](https://github.com/expo/eas-cli/pull/1466) by [@byCedric](https://github.com/byCedric))
- Upgrade dependencies. ([#1480](https://github.com/expo/eas-cli/pull/1480) by [@dsokal](https://github.com/dsokal))
- Replace all remaining tables with formatted fields. ([#1481](https://github.com/expo/eas-cli/pull/1481) by [@byCedric](https://github.com/byCedric))
## [2.5.1](https://github.com/expo/eas-cli/releases/tag/v2.5.1) - 2022-10-24
### 🐛 Bug fixes
- Revert using local CLI for `expo export` ([#1460](https://github.com/expo/eas-cli/pull/1460)). ([#1472](https://github.com/expo/eas-cli/pull/1472) by [@dsokal](https://github.com/dsokal))
## [2.5.0](https://github.com/expo/eas-cli/releases/tag/v2.5.0) - 2022-10-24
### 🎉 New features
@@ -24,6 +530,7 @@ This is the log of notable changes to EAS CLI and related packages.
### 🐛 Bug fixes
- Initialize analytics consistently and move analytics into context. ([#1444](https://github.com/expo/eas-cli/pull/1444) by [@wschurman](https://github.com/wschurman))
- - Skip using `--non-interactive` and `--experimental-bundle` flags when local Expo CLI is installed. ([#1460](https://github.com/expo/eas-cli/pull/1460) by [@EvanBacon](https://github.com/EvanBacon))
### 🧹 Chores
@@ -239,7 +746,7 @@ This is the log of notable changes to EAS CLI and related packages.
- Add new option to `eas device:create` - allow importing devices from Apple Developer Portal. ([#1236](https://github.com/expo/eas-cli/pull/1236) by [@dsokal](https://github.com/dsokal))
- Add support for attaching messages to builds. ([#1237](https://github.com/expo/eas-cli/pull/1237) by [@dsokal](https://github.com/dsokal))
- Introduce interactive pagination for `branch` commands. ([#1213](https://github.com/expo/eas-cli/pull/1213) by [@kgc00](https://github.com/kgc00))
- Introduce interactive pagination for `branch` commands. ([#1213](https://github.com/expo/eas-cli/pull/1213) by [@kgc00](https://github.com/kgc00))
## [0.56.0](https://github.com/expo/eas-cli/releases/tag/v0.56.0) - 2022-07-28
+8 -8
View File
@@ -1,16 +1,16 @@
* @dsokal @wkozyra95
* @szdziedzic @khamilowicz
packages/eas-cli/src/build @dsokal @wkozyra95
packages/eas-cli/src/commands/build @dsokal @wkozyra95
packages/eas-cli/src/build @szdziedzic @khamilowicz
packages/eas-cli/src/commands/build @szdziedzic @khamilowicz
packages/eas-cli/src/submit @dsokal
packages/eas-cli/src/commands/submit.ts @dsokal
packages/eas-cli/src/submit @szdziedzic @khamilowicz
packages/eas-cli/src/commands/submit.ts @szdziedzic @khamilowicz
packages/eas-cli/src/update @kgc00 @wschurman
packages/eas-cli/src/commands/update @kgc00 @wschurman
packages/eas-cli/src/update @EvanBacon @byCedric @wschurman
packages/eas-cli/src/commands/update @EvanBacon @byCedric @wschurman
packages/eas-cli/schema @bycedric
packages/eas-cli/src/metadata @bycedric
packages/eas-cli/src/commands/metadata @bycedric
packages/eas-json @dsokal @wkozyra95
packages/eas-json @szdziedzic @khamilowicz
+47 -3
View File
@@ -1,4 +1,48 @@
## Releasing a new version
# Development
1. Run `yarn release` in the repository root folder. The next version is chosen automatically based on the changelog entries. If you want to use different version, pass the version string as a positional argument to the command, e.g. `yarn release 1.2.3`.
2. That's it! GitHub Actions is going to take care of the rest. Watch the #eas-cli Slack channel for a successful release notification.
Set up an alias for the EAS CLI so you can try it in projects all around your computer. The project is compiled on the fly so you don't need to run a build (watch) command.
> The only exception is when you just cloned the repository or there have been changes in `packages/eas-json`. In that case, you'll have to run `yarn build` in the root.
Open your `.zshrc` or other config file and add:
```
alias easd="/PATH/TO/eas-cli/bin/run"
```
Then use it with `easd` like `easd build`.
If you're making changes to `packages/eas-json` or prefer/need to work with production code, start the build command in watch mode with:
```
yarn start
```
## Format
- Be sure to update the [`CHANGELOG.md`](./CHANGELOG.md) with changes for every PR. There is a changelog bot that can generate the proper entry for you. The instructions on how to use it are in the PR description placeholder.
- End `async` functions with `Async` like `runAsync`. This is just how we format functions at Expo.
- Utilize the unified `Log` module instead of `console.log`.
## Using local and staging API
For development against local API:
- Set up the API server as described at https://github.com/expo/universe/tree/main/server/www.
- Set the `EXPO_LOCAL=1` env variable either on every command run (e.g. `EXPO_LOCAL=1 easd ...`) or more permamently with `export EXPO_LOCAL=1` (works in a current shell session).
For development against staging API:
- Set the `EXPO_STAGING=1` env variable either on every command run (e.g. `EXPO_STAGING=1 easd ...`) or more permamently with `export EXPO_STAGING=1` (works in a current shell session).
## Working on local builds (`eas build --local`)
See https://github.com/expo/eas-build/blob/main/DEVELOPMENT.md for how to set up your environment when making changes to [`eas-cli-local-build-plugin`](https://github.com/expo/eas-build/tree/main/packages/local-build-plugin) and/or [`build-tools`](https://github.com/expo/eas-build/tree/main/packages/build-tools).
## Testing
Run `yarn test` either in the repository root or in a package directory that you're working on.
## Releasing
See [RELEASING.md](./RELEASING.md).
+18
View File
@@ -0,0 +1,18 @@
# Releasing EAS CLI
1. Run `yarn release` in the repository root folder. The next version is chosen automatically based on the changelog entries. If you want to use different version, pass the version string as a positional argument to the command, e.g. `yarn release 1.2.3`.
2. That's it! GitHub Actions is going to take care of the rest. Watch the #eas-cli Slack channel for a successful release notification.
## Choosing the next version
As stated above, the next EAS CLI version is chosen automatically based on the existing changelog entries under the unreleased section in **CHANGELOG.md**.
The algorithm works as follows:
- If there are any entries in the "🛠 Breaking changes" section, bump the MAJOR version.
- Otherwise, if there are any entries in the "🎉 New features", bump the MINOR version.
- Otherwise, bump the PATCH version.
## Updating EAS CLI version on EAS Build
The release process promotes the new version to staging. After verifying it works there, you can promote it to production with `npm dist-tag add eas-cli@{version} latest-eas-build`.
+2 -1
View File
@@ -1,5 +1,6 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "2.5.1"
"version": "3.17.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
+16 -14
View File
@@ -1,36 +1,38 @@
{
"name": "eas-cli-root",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"build": "lerna run build",
"build-ci": "lerna run build-ci",
"build:tarballs:linux": "yarn build-for-size-check",
"build-for-size-check": "yarn build && yarn workspace eas-cli pretarball-ci && CLI_SIZE_CHECK=1 yarn workspace eas-cli oclif pack:tarballs --no-xz --targets linux-x64",
"typecheck": "lerna run typecheck",
"start": "lerna run watch --parallel",
"start": "lerna run watch --stream --parallel",
"watch": "yarn start",
"eas": "packages/eas-cli/bin/run",
"lint": "eslint .",
"lint-changelog": "./scripts/bin/run lint-changelog",
"release": "./scripts/bin/run release",
"release": "lerna run rebuild && ./scripts/bin/run release",
"test": "jest",
"clean": "lerna run clean && rm -rf node_modules coverage"
},
"devDependencies": {
"@types/jest": "28.1.6",
"@types/node": "18.6.5",
"eslint": "8.21.0",
"eslint-config-universe": "11.1.0",
"eslint-plugin-async-protect": "2.1.0",
"@types/jest": "29.2.5",
"@types/node": "18.11.18",
"eslint": "8.31.0",
"eslint-config-universe": "11.1.1",
"eslint-plugin-async-protect": "3.0.0",
"eslint-plugin-node": "11.1.0",
"jest": "28.1.3",
"jest-watch-typeahead": "2.0.0",
"lerna": "4.0.0",
"oclif": "3.1.2",
"prettier": "2.7.1",
"ts-jest": "28.0.7",
"typescript": "4.7.4"
"jest": "29.3.1",
"jest-watch-typeahead": "2.2.1",
"lerna": "6.4.1",
"oclif": "3.4.3",
"prettier": "2.8.2",
"ts-jest": "29.0.3",
"typescript": "4.9.4"
},
"volta": {
"node": "18.6.0",
+269 -75
View File
@@ -70,25 +70,31 @@ eas --help COMMAND
* [`eas build:configure`](#eas-buildconfigure)
* [`eas build:inspect`](#eas-buildinspect)
* [`eas build:list`](#eas-buildlist)
* [`eas build:resign`](#eas-buildresign)
* [`eas build:run`](#eas-buildrun)
* [`eas build:submit`](#eas-buildsubmit)
* [`eas build:version:get`](#eas-buildversionget)
* [`eas build:version:set`](#eas-buildversionset)
* [`eas build:version:sync`](#eas-buildversionsync)
* [`eas build:view [BUILD_ID]`](#eas-buildview-build_id)
* [`eas channel:create [NAME]`](#eas-channelcreate-name)
* [`eas channel:edit [NAME]`](#eas-channeledit-name)
* [`eas channel:list`](#eas-channellist)
* [`eas channel:rollout [CHANNEL]`](#eas-channelrollout-channel)
* [`eas channel:view [NAME]`](#eas-channelview-name)
* [`eas config`](#eas-config)
* [`eas credentials`](#eas-credentials)
* [`eas device:create`](#eas-devicecreate)
* [`eas device:delete`](#eas-devicedelete)
* [`eas device:list`](#eas-devicelist)
* [`eas device:rename`](#eas-devicerename)
* [`eas device:view [UDID]`](#eas-deviceview-udid)
* [`eas diagnostics`](#eas-diagnostics)
* [`eas help [COMMAND]`](#eas-help-command)
* [`eas init`](#eas-init)
* [`eas login`](#eas-login)
* [`eas logout`](#eas-logout)
* [`eas metadata:lint`](#eas-metadatalint)
* [`eas metadata:pull`](#eas-metadatapull)
* [`eas metadata:push`](#eas-metadatapush)
* [`eas open`](#eas-open)
@@ -97,11 +103,13 @@ eas --help COMMAND
* [`eas secret:create`](#eas-secretcreate)
* [`eas secret:delete`](#eas-secretdelete)
* [`eas secret:list`](#eas-secretlist)
* [`eas secret:push`](#eas-secretpush)
* [`eas submit`](#eas-submit)
* [`eas update`](#eas-update)
* [`eas update:configure`](#eas-updateconfigure)
* [`eas update:delete GROUPID`](#eas-updatedelete-groupid)
* [`eas update:list`](#eas-updatelist)
* [`eas update:republish`](#eas-updaterepublish)
* [`eas update:view GROUPID`](#eas-updateview-groupid)
* [`eas webhook:create`](#eas-webhookcreate)
* [`eas webhook:delete [ID]`](#eas-webhookdelete-id)
@@ -125,7 +133,7 @@ ALIASES
$ eas login
```
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/account/login.ts)_
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -142,7 +150,7 @@ ALIASES
$ eas logout
```
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/account/logout.ts)_
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view`
@@ -159,7 +167,7 @@ ALIASES
$ eas whoami
```
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/account/view.ts)_
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -173,7 +181,7 @@ DESCRIPTION
display or change analytics settings
```
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/analytics.ts)_
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]`
@@ -202,7 +210,7 @@ EXAMPLES
$ eas autocomplete --refresh-cache
```
_See code: [@expo/plugin-autocomplete](https://github.com/expo/plugin-autocomplete/blob/v1.4.0/src/commands/autocomplete/index.ts)_
_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v1.3.10/src/commands/autocomplete/index.ts)_
## `eas branch:create [NAME]`
@@ -223,7 +231,7 @@ DESCRIPTION
create a branch
```
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/branch/create.ts)_
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -244,7 +252,7 @@ DESCRIPTION
delete a branch
```
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/branch/delete.ts)_
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -264,7 +272,7 @@ DESCRIPTION
list all branches
```
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/branch/list.ts)_
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -284,7 +292,7 @@ DESCRIPTION
rename a branch
```
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/branch/rename.ts)_
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -307,7 +315,7 @@ DESCRIPTION
view a branch
```
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/branch/view.ts)_
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build`
@@ -337,7 +345,7 @@ DESCRIPTION
start a build
```
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/build/index.ts)_
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -354,7 +362,7 @@ DESCRIPTION
cancel a build
```
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/build/cancel.ts)_
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -371,7 +379,7 @@ DESCRIPTION
configure the project to support EAS Build
```
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/build/configure.ts)_
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:inspect`
@@ -406,7 +414,7 @@ DESCRIPTION
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/v2.5.1/packages/eas-cli/src/commands/build/inspect.ts)_
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -414,10 +422,11 @@ list all builds for your project
```
USAGE
$ eas build:list [--platform all|android|ios] [--status new|in-queue|in-progress|errored|finished|canceled]
[--distribution store|internal|simulator] [--channel <value>] [--appVersion <value>] [--appBuildVersion <value>]
[--sdkVersion <value>] [--runtimeVersion <value>] [--appIdentifier <value>] [--buildProfile <value>]
[--gitCommitHash <value>] [--offset <value>] [--limit <value>] [--json --non-interactive]
$ eas build:list [--platform all|android|ios] [--status
new|in-queue|in-progress|pending-cancel|errored|finished|canceled] [--distribution store|internal|simulator]
[--channel <value>] [--appVersion <value>] [--appBuildVersion <value>] [--sdkVersion <value>] [--runtimeVersion
<value>] [--appIdentifier <value>] [--buildProfile <value>] [--gitCommitHash <value>] [--offset <value>] [--limit
<value>] [--json --non-interactive]
FLAGS
--appBuildVersion=<value>
@@ -427,23 +436,73 @@ FLAGS
--channel=<value>
--distribution=(store|internal|simulator)
--gitCommitHash=<value>
--json Enable JSON output, non-JSON messages will be printed
to stderr.
--limit=<value> The number of items to fetch each query. Defaults to 10
and is capped at 50.
--non-interactive Run the command in non-interactive mode.
--offset=<value> Start queries from specified index. Use for paginating
results. Defaults to 0.
--json Enable JSON output, non-JSON messages
will be printed to stderr.
--limit=<value> The number of items to fetch each query.
Defaults to 10 and is capped at 50.
--non-interactive Run the command in non-interactive mode.
--offset=<value> Start queries from specified index. Use
for paginating results. Defaults to 0.
--platform=(all|android|ios)
--runtimeVersion=<value>
--sdkVersion=<value>
--status=(new|in-queue|in-progress|errored|finished|canceled)
--status=(new|in-queue|in-progress|pending-cancel|errored|finished|canceled)
DESCRIPTION
list all builds for your project
```
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/build/list.ts)_
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:resign`
re-sign a build archive
```
USAGE
$ eas build:resign [-p android|ios] [-e <value>] [--wait] [--id <value>] [--offset <value>] [--limit <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)
--id=<value> ID of the build to re-sign.
--json Enable JSON output, non-JSON messages will be printed to stderr.
--limit=<value> The number of items to fetch each query. Defaults to 50 and is capped at 100.
--non-interactive Run the command in non-interactive mode.
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
--[no-]wait Wait for build(s) to complete.
DESCRIPTION
re-sign a build archive
```
_See code: [src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/build/resign.ts)_
## `eas build:run`
run simulator/emulator builds from eas-cli
```
USAGE
$ eas build:run [--latest | --id <value> | --path <value> | --url <value>] [-p android|ios] [--offset
<value>] [--limit <value>]
FLAGS
-p, --platform=(android|ios)
--id=<value> ID of the simulator/emulator build to run
--latest Run the latest simulator/emulator build for specified platform
--limit=<value> The number of items to fetch each query. Defaults to 50 and is capped at 100.
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
--path=<value> Path to the simulator/emulator build archive or app
--url=<value> Simulator/Emulator build archive url
DESCRIPTION
run simulator/emulator builds from eas-cli
```
_See code: [src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/build/run.ts)_
## `eas build:submit`
@@ -463,7 +522,7 @@ FLAGS
--non-interactive Run command in non-interactive mode
--path=<value> Path to the .apk/.aab/.ipa file
--url=<value> App archive url
--verbose Always print logs from Submission Service
--verbose Always print logs from EAS Submit
--[no-]wait Wait for submission to complete
DESCRIPTION
@@ -473,9 +532,30 @@ ALIASES
$ 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.17.0/packages/eas-cli/src/commands/build/version/get.ts)_
## `eas build:version:set`
Update version of an app.
update version of an app
```
USAGE
@@ -487,14 +567,14 @@ FLAGS
-p, --platform=(android|ios)
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/v2.5.1/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.17.0/packages/eas-cli/src/commands/build/version/set.ts)_
## `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
@@ -506,10 +586,10 @@ FLAGS
-p, --platform=(android|ios|all)
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/v2.5.1/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.17.0/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]`
@@ -526,7 +606,7 @@ DESCRIPTION
view a build for your project
```
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/build/view.ts)_
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -547,7 +627,7 @@ DESCRIPTION
create a channel
```
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/channel/create.ts)_
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:edit [NAME]`
@@ -569,7 +649,7 @@ DESCRIPTION
point a channel at a new branch
```
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/channel/edit.ts)_
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list`
@@ -589,7 +669,31 @@ DESCRIPTION
list all channels
```
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/channel/list.ts)_
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:rollout [CHANNEL]`
Roll a new branch out on a channel incrementally.
```
USAGE
$ eas channel:rollout [CHANNEL] [--branch <value>] [--percent <value>] [--end] [--json --non-interactive]
ARGUMENTS
CHANNEL channel on which the rollout should be done
FLAGS
--branch=<value> branch to rollout
--end end the rollout
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
--percent=<value> percent of users to send to the new branch
DESCRIPTION
Roll a new branch out on a channel incrementally.
```
_See code: [src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/channel/rollout.ts)_
## `eas channel:view [NAME]`
@@ -612,7 +716,7 @@ DESCRIPTION
view a channel
```
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/channel/view.ts)_
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -620,18 +724,20 @@ display project configuration (app.json + eas.json)
```
USAGE
$ eas config [-p android|ios] [-e <value>]
$ eas config [-p android|ios] [-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)
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
DESCRIPTION
display project configuration (app.json + eas.json)
```
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/config.ts)_
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/config.ts)_
## `eas credentials`
@@ -648,7 +754,7 @@ DESCRIPTION
manage credentials
```
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/credentials.ts)_
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/credentials.ts)_
## `eas device:create`
@@ -662,7 +768,7 @@ DESCRIPTION
register new Apple Devices to use for internal distribution
```
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/device/create.ts)_
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -682,7 +788,7 @@ DESCRIPTION
remove a registered device from your account
```
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/device/delete.ts)_
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -703,7 +809,28 @@ DESCRIPTION
list all registered devices for your account
```
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/device/list.ts)_
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v3.17.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.17.0/packages/eas-cli/src/commands/device/rename.ts)_
## `eas device:view [UDID]`
@@ -717,7 +844,7 @@ DESCRIPTION
view a device for your project
```
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/device/view.ts)_
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -731,7 +858,7 @@ DESCRIPTION
display environment info
```
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/diagnostics.ts)_
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas help [COMMAND]`
@@ -751,7 +878,7 @@ DESCRIPTION
display help for eas-cli
```
_See code: [@expo/plugin-help](https://github.com/expo/oclif-plugin-help/blob/v5.3.0/src/commands/help.ts)_
_See code: [@expo/plugin-help](https://github.com/expo/oclif-plugin-help/blob/v5.1.22/src/commands/help.ts)_
## `eas init`
@@ -803,6 +930,24 @@ ALIASES
$ eas logout
```
## `eas metadata:lint`
validate the local store configuration
```
USAGE
$ eas metadata:lint [--json] [--profile <value>]
FLAGS
--json Enable JSON output, non-JSON messages will be printed to stderr
--profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.
DESCRIPTION
validate the local store configuration
```
_See code: [src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/metadata/lint.ts)_
## `eas metadata:pull`
generate the local store configuration from the app stores
@@ -818,7 +963,7 @@ DESCRIPTION
generate the local store configuration from the app stores
```
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/metadata/pull.ts)_
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push`
@@ -835,7 +980,7 @@ DESCRIPTION
sync the local store configuration to the app stores
```
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/metadata/push.ts)_
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas open`
@@ -849,7 +994,7 @@ DESCRIPTION
open the project page in a web browser
```
_See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/open.ts)_
_See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/open.ts)_
## `eas project:info`
@@ -863,7 +1008,7 @@ DESCRIPTION
information about the current project
```
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/project/info.ts)_
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -885,7 +1030,7 @@ ALIASES
$ eas init
```
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/project/init.ts)_
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/project/init.ts)_
## `eas secret:create`
@@ -908,7 +1053,7 @@ DESCRIPTION
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/v2.5.1/packages/eas-cli/src/commands/secret/create.ts)_
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/secret/create.ts)_
## `eas secret:delete`
@@ -926,7 +1071,7 @@ DESCRIPTION
delete an environment secret by ID
```
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/secret/delete.ts)_
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/secret/delete.ts)_
## `eas secret:list`
@@ -940,7 +1085,27 @@ DESCRIPTION
list environment secrets available for your current app
```
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/secret/list.ts)_
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/secret/list.ts)_
## `eas secret:push`
read environment secrets from env file and store on the server
```
USAGE
$ eas secret:push [--scope account|project] [--env-file <value>] [--force] [--non-interactive]
FLAGS
--env-file=<value> Env file with secrets
--force Delete and recreate existing secrets
--non-interactive Run the command in non-interactive mode.
--scope=(account|project) [default: project] Scope for the secrets
DESCRIPTION
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.17.0/packages/eas-cli/src/commands/secret/push.ts)_
## `eas submit`
@@ -960,7 +1125,7 @@ FLAGS
--non-interactive Run command in non-interactive mode
--path=<value> Path to the .apk/.aab/.ipa file
--url=<value> App archive url
--verbose Always print logs from Submission Service
--verbose Always print logs from EAS Submit
--[no-]wait Wait for submission to complete
DESCRIPTION
@@ -970,7 +1135,7 @@ ALIASES
$ eas build:submit
```
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/submit.ts)_
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/submit.ts)_
## `eas update`
@@ -978,29 +1143,32 @@ publish an update group
```
USAGE
$ eas update [--branch <value>] [--message <value>] [--republish | --input-dir <value> | --skip-bundler]
[--group <value> | | ] [-p android|ios|all] [--auto] [--private-key-path <value>] [--json --non-interactive]
$ eas update [--branch <value>] [--channel <value>] [-m <value>] [--republish | --input-dir <value> |
--skip-bundler] [--group <value> | | ] [--clear-cache] [-p android|ios|all] [--auto] [--private-key-path <value>]
[--json --non-interactive]
FLAGS
-m, --message=<value> A short message describing the update
-p, --platform=(android|ios|all) [default: all]
--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
--group=<value> Update group to republish
--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)
--input-dir=<value> [default: dist] Location of the bundle
--json Enable JSON output, non-JSON messages will be printed to stderr.
--message=<value> A short message describing the update
--non-interactive Run the command in non-interactive mode.
--private-key-path=<value> File containing the PEM-encoded private key corresponding to the certificate in
expo-updates' configuration. Defaults to a file named "private-key.pem" in the
certificate's directory.
--republish Republish an update group
--republish Republish an update group (deprecated, see republish command)
--skip-bundler Skip running Expo CLI to bundle the app before publishing
DESCRIPTION
publish an update group
```
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/update/index.ts)_
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -1008,16 +1176,17 @@ configure the project to support EAS Update
```
USAGE
$ eas update:configure [-p android|ios|all]
$ eas update:configure [-p android|ios|all] [--non-interactive]
FLAGS
-p, --platform=(android|ios|all) [default: all] Platform to configure
--non-interactive Run the command in non-interactive mode.
DESCRIPTION
configure the project to support EAS Update
```
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/update/configure.ts)_
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -1038,7 +1207,7 @@ DESCRIPTION
delete all the updates in an update group
```
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/update/delete.ts)_
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:list`
@@ -1060,7 +1229,31 @@ DESCRIPTION
view the recent updates
```
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/update/list.ts)_
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:republish`
roll back to an existing update
```
USAGE
$ eas update:republish [--channel <value> | --branch <value> | --group <value>] [-m <value>] [-p android|ios|all]
[--json --non-interactive]
FLAGS
-m, --message=<value> Short message describing the republished update
-p, --platform=(android|ios|all) [default: all]
--branch=<value> Branch name to select an update to republish from
--channel=<value> Channel name to select an update to republish from
--group=<value> Update group ID to republish
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
DESCRIPTION
roll back to an existing update
```
_See code: [src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/update/republish.ts)_
## `eas update:view GROUPID`
@@ -1080,7 +1273,7 @@ DESCRIPTION
update group details
```
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/update/view.ts)_
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/update/view.ts)_
## `eas webhook:create`
@@ -1101,7 +1294,7 @@ DESCRIPTION
create a webhook
```
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/webhook/create.ts)_
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -1121,7 +1314,7 @@ DESCRIPTION
delete a webhook
```
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/webhook/delete.ts)_
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -1129,16 +1322,17 @@ list webhooks
```
USAGE
$ eas webhook:list [--event BUILD|SUBMIT]
$ eas webhook:list [--event BUILD|SUBMIT] [--json]
FLAGS
--event=(BUILD|SUBMIT) Event type that triggers the webhook
--json Enable JSON output, non-JSON messages will be printed to stderr.
DESCRIPTION
list webhooks
```
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/webhook/list.ts)_
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -1160,7 +1354,7 @@ DESCRIPTION
update a webhook
```
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/webhook/update.ts)_
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -1177,7 +1371,7 @@ DESCRIPTION
view a webhook
```
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v2.5.1/packages/eas-cli/src/commands/webhook/view.ts)_
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v3.17.0/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
+2
View File
@@ -5,6 +5,8 @@ documents:
- 'src/credentials/ios/api/graphql/**/!(*.d).{ts,tsx}'
- 'src/credentials/android/api/graphql/**/!(*.d).{ts,tsx}'
- 'src/commands/**/*.ts'
- 'src/branch/**/*.ts'
- 'src/channel/**/*.ts'
generates:
src/graphql/generated.ts:
plugins:
+9635 -2618
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.EAS_NO_VCS = '1';
process.env.EAS_PROJECT_ROOT = '/app';
process.env.FORCE_COLOR = '1';
};
+55 -46
View File
@@ -1,114 +1,121 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "2.5.1",
"version": "3.17.0",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
},
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@expo/apple-utils": "0.0.0-alpha.34",
"@expo/code-signing-certificates": "0.0.2",
"@expo/config": "7.0.1",
"@expo/config-plugins": "5.0.1",
"@expo/config-types": "46.0.2",
"@expo/eas-build-job": "0.2.94",
"@expo/eas-json": "2.2.0",
"@expo/json-file": "8.2.36",
"@expo/apple-utils": "1.3.1",
"@expo/code-signing-certificates": "0.0.5",
"@expo/config": "8.1.2",
"@expo/config-plugins": "7.2.4",
"@expo/config-types": "49.0.0",
"@expo/eas-build-job": "1.0.13",
"@expo/eas-json": "3.17.0",
"@expo/json-file": "8.2.37",
"@expo/multipart-body-parser": "1.1.0",
"@expo/package-manager": "0.0.56",
"@expo/osascript": "2.0.33",
"@expo/package-manager": "0.0.57",
"@expo/pkcs12": "0.0.8",
"@expo/plist": "0.0.18",
"@expo/plugin-autocomplete": "1.4.0",
"@expo/plugin-help": "5.3.0",
"@expo/plugin-warn-if-update-available": "2.3.0",
"@expo/prebuild-config": "5.0.3",
"@expo/plist": "0.0.20",
"@expo/plugin-help": "5.1.22",
"@expo/plugin-warn-if-update-available": "2.5.0",
"@expo/prebuild-config": "6.2.5",
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.0",
"@expo/steps": "1.0.25",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "1.13.10",
"@urql/core": "2.6.1",
"@urql/exchange-retry": "0.3.3",
"ajv": "^6.12.6",
"@oclif/core": "1.23.2",
"@oclif/plugin-autocomplete": "1.3.10",
"@segment/ajv-human-errors": "^2.1.2",
"@urql/core": "4.0.11",
"@urql/exchange-retry": "1.2.0",
"ajv": "8.11.0",
"ajv-formats": "2.1.1",
"better-opn": "3.0.2",
"chalk": "4.1.2",
"cli-progress": "3.11.2",
"cli-table3": "0.6.2",
"dateformat": "4.6.3",
"dotenv": "16.0.3",
"env-paths": "2.2.0",
"envinfo": "7.8.1",
"fast-deep-equal": "3.1.3",
"fast-glob": "3.2.11",
"fast-glob": "3.2.12",
"figures": "3.2.0",
"form-data": "4.0.0",
"fs-extra": "10.1.0",
"getenv": "1.0.0",
"gradle-to-js": "2.0.1",
"graphql": "16.5.0",
"graphql": "16.6.0",
"graphql-tag": "2.12.6",
"https-proxy-agent": "5.0.1",
"ignore": "5.2.0",
"ignore": "5.2.4",
"indent-string": "4.0.0",
"jks-js": "1.0.2",
"joi": "17.6.0",
"jks-js": "1.1.0",
"joi": "17.7.0",
"keychain": "1.3.0",
"log-symbols": "4.1.0",
"mime": "3.0.0",
"minimatch": "5.1.0",
"minimatch": "5.1.2",
"nanoid": "3.3.4",
"node-fetch": "2.6.7",
"node-forge": "1.3.1",
"nullthrows": "1.1.1",
"ora": "5.1.0",
"pkg-dir": "4.2.0",
"pngjs": "6.0.0",
"promise-limit": "2.7.0",
"promise-retry": "2.0.1",
"prompts": "2.4.2",
"qrcode-terminal": "0.12.0",
"resolve-from": "5.0.0",
"semver": "7.3.7",
"semver": "7.3.8",
"slash": "3.0.0",
"tar": "6.1.11",
"tar": "6.1.13",
"terminal-link": "2.1.1",
"tslib": "2.4.0",
"tslib": "2.4.1",
"turndown": "7.1.1",
"untildify": "4.0.0",
"uuid": "8.3.2",
"uuid": "9.0.0",
"wrap-ansi": "7.0.0"
},
"devDependencies": {
"@graphql-codegen/cli": "2.11.5",
"@graphql-codegen/introspection": "2.2.1",
"@graphql-codegen/typescript": "2.7.3",
"@graphql-codegen/typescript-operations": "2.5.3",
"@graphql-codegen/cli": "2.16.3",
"@graphql-codegen/introspection": "2.2.3",
"@graphql-codegen/typescript": "2.8.7",
"@graphql-codegen/typescript-operations": "2.5.12",
"@types/cli-progress": "3.11.0",
"@types/dateformat": "3.0.1",
"@types/envinfo": "7.8.1",
"@types/express": "4.17.15",
"@types/fs-extra": "9.0.13",
"@types/getenv": "^1.0.0",
"@types/mime": "2.0.3",
"@types/mime": "3.0.1",
"@types/node-fetch": "2.6.2",
"@types/node-forge": "1.0.4",
"@types/node-forge": "1.3.1",
"@types/pngjs": "6.0.1",
"@types/promise-retry": "1.1.3",
"@types/prompts": "2.0.14",
"@types/semver": "7.3.10",
"@types/tar": "6.1.2",
"@types/prompts": "2.4.2",
"@types/semver": "7.3.13",
"@types/tar": "6.1.3",
"@types/tough-cookie": "4.0.2",
"@types/uuid": "8.3.4",
"@types/uuid": "9.0.0",
"@types/wrap-ansi": "3.0.0",
"axios": "0.27.2",
"eslint-plugin-graphql": "4.0.0",
"memfs": "3.4.7",
"express": "4.18.2",
"memfs": "3.4.13",
"mockdate": "3.0.5",
"nock": "13.2.9",
"rimraf": "3.0.2",
"ts-deepmerge": "2.0.5",
"ts-deepmerge": "4.0.0",
"ts-mockito": "2.6.1",
"ts-node": "10.9.1",
"typescript": "4.7.4"
"typescript": "4.9.4"
},
"engines": {
"node": ">=14.0.0"
@@ -130,7 +137,7 @@
"bin": "eas",
"commands": "./build/commands",
"plugins": [
"@expo/plugin-autocomplete",
"@oclif/plugin-autocomplete",
"@expo/plugin-help",
"@expo/plugin-warn-if-update-available"
],
@@ -196,11 +203,13 @@
"repository": "expo/eas-cli",
"scripts": {
"postpack": "rimraf oclif.manifest.json",
"prepack": "rimraf build && yarn build && node ./scripts/prepack.js",
"prepack": "yarn rebuild && node ./scripts/prepack.js",
"rebuild": "rimraf build && yarn build-ci",
"pretarball-ci": "./scripts/pretarball-ci.sh",
"build": "tsc --project tsconfig.build.json",
"build-ci": "tsc --project tsconfig.build-ci.json",
"watch": "yarn build --watch --preserveWatchOutput",
"typecheck": "tsc",
"typecheck": "tsc --project tsconfig.build-ci.json",
"test": "jest",
"version": "yarn oclif readme && node scripts/patch-readme && git add README.md",
"generate-graphql-code": "graphql-codegen --config graphql-codegen.yml",
+3 -3
View File
@@ -437,8 +437,8 @@
"$ref": "#/definitions/apple/AppleKidsAge"
}
],
"description": "When parents visit the Kids category on the App Store, they expect the apps they find will protect their children’s data, provide only age-appropriate content, and require a parental gate in order to link out of the app, request permissions, or present purchasing opportunities. It’s critical that no personally identifiable information or device information be transmitted to third parties, and that advertisements are human-reviewed for age appropriateness in order to be displayed.\n@see https://developer.apple.com/news/?id=091202019a",
"markdownDescription": "When parents visit the Kids category on the App Store, they expect the apps they find will protect their children’s data, provide only age-appropriate content, and require a parental gate in order to link out of the app, request permissions, or present purchasing opportunities. It’s critical that no personally identifiable information or device information be transmitted to third parties, and that advertisements are human-reviewed for age appropriateness in order to be displayed.\n\n[Learn more](https://developer.apple.com/news/?id=091202019a)"
"description": "When parents visit the Kids category on the App Store, they expect the apps they find will protect their children’s data, provide only age-appropriate content, and require a parental gate in order to link out of the app, request permissions, or present purchasing opportunities. It’s critical that no personally identifiable information or device information be transmitted to third parties, and that advertisements are human-reviewed for age appropriateness in order to be displayed.\n@see https://developer-mdn.apple.com/news/?id=091202019a",
"markdownDescription": "When parents visit the Kids category on the App Store, they expect the apps they find will protect their children’s data, provide only age-appropriate content, and require a parental gate in order to link out of the app, request permissions, or present purchasing opportunities. It’s critical that no personally identifiable information or device information be transmitted to third parties, and that advertisements are human-reviewed for age appropriateness in order to be displayed.\n\n[Learn more](https://developer-mdn.apple.com/news/?id=091202019a)"
},
"seventeenPlus": {
"type": "boolean",
@@ -778,7 +778,7 @@
},
"properties": {
"configVersion": {
"const": 0,
"enum": [0],
"description": "The EAS metadata store configuration version"
},
"apple": {
@@ -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;
}
};
@@ -1,17 +0,0 @@
import { fixS3Url } from '../uploads';
describe(fixS3Url, () => {
it('fixes broken links', () => {
const input =
'https://submission-service-archives.s3.amazonaws.com/production%2Fdc98ca84-1473-4cb3-ae81-8c7b291cb27e%2F4424aa95-b985-4e2f-8755-9507b1037c1c';
const expectedOutput =
'https://submission-service-archives.s3.amazonaws.com/production/dc98ca84-1473-4cb3-ae81-8c7b291cb27e/4424aa95-b985-4e2f-8755-9507b1037c1c';
expect(fixS3Url(input)).toBe(expectedOutput);
});
it('does not change correct urls', () => {
const correctUrl =
'https://submission-service-archives.s3.amazonaws.com/production/dc98ca84-1473-4cb3-ae81-8c7b291cb27e/4424aa95-b985-4e2f-8755-9507b1037c1c';
expect(fixS3Url(correctUrl)).toBe(correctUrl);
});
});
@@ -133,7 +133,9 @@ export async function createAnalyticsAsync(): Promise<AnalyticsWithOrchestration
await UserSettings.setAsync(USER_SETTINGS_KEY_ANALYTICS_ENABLED, false);
}
const analyticsEnabled = await UserSettings.getAsync(USER_SETTINGS_KEY_ANALYTICS_ENABLED, true);
const analyticsEnabled =
!process.env.https_proxy && // disable analytics if running behind proxy
(await UserSettings.getAsync(USER_SETTINGS_KEY_ANALYTICS_ENABLED, true));
if (!analyticsEnabled) {
return new NoOpAnalytics();
}
+80 -31
View File
@@ -1,17 +1,25 @@
import chalk from 'chalk';
import CliTable from 'cli-table3';
import gql from 'graphql-tag';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { PaginatedQueryOptions } from '../commandUtils/pagination';
import { UpdateBranchFragment } from '../graphql/generated';
import { withErrorHandlingAsync } from '../graphql/client';
import {
CreateUpdateBranchForAppMutation,
CreateUpdateBranchForAppMutationVariables,
UpdateBranch,
UpdateBranchFragment,
} from '../graphql/generated';
import { BranchQuery } from '../graphql/queries/BranchQuery';
import Log from '../log';
import { UPDATE_COLUMNS, formatUpdateMessage, getPlatformsForGroup } from '../update/utils';
import { formatBranch, getBranchDescription } from '../update/utils';
import { printJsonOnlyOutput } from '../utils/json';
import {
SelectPromptEntry,
paginatedQueryWithConfirmPromptAsync,
paginatedQueryWithSelectPromptAsync,
} from '../utils/queries';
import { BranchNotFoundError } from './utils';
export const BRANCHES_LIMIT = 50;
@@ -24,7 +32,7 @@ export async function selectBranchOnAppAsync(
paginatedQueryOptions,
}: {
projectId: string;
displayTextForListItem: (queryItem: UpdateBranchFragment) => string;
displayTextForListItem: (queryItem: UpdateBranchFragment) => SelectPromptEntry;
promptTitle: string;
paginatedQueryOptions: PaginatedQueryOptions;
}
@@ -41,7 +49,7 @@ export async function selectBranchOnAppAsync(
promptOptions: {
title: promptTitle,
getIdentifierForQueryItem: updateBranchFragment => updateBranchFragment.id,
createDisplayTextForSelectionPromptListItem: displayTextForListItem,
makePartialChoiceObject: displayTextForListItem,
},
});
if (!selectedBranch) {
@@ -102,34 +110,75 @@ function renderPageOfBranches(
if (json) {
printJsonOnlyOutput(currentPage);
} else {
const table = new CliTable({
head: ['Branch', ...UPDATE_COLUMNS],
wordWrap: true,
});
table.push(
...currentPage.map(branch => {
if (branch.updates.length === 0) {
return [branch.name, 'N/A', 'N/A', 'N/A', 'N/A'];
}
const latestUpdateOnBranch = branch.updates[0];
return [
branch.name,
formatUpdateMessage(latestUpdateOnBranch),
latestUpdateOnBranch.runtimeVersion,
latestUpdateOnBranch.group,
getPlatformsForGroup({
group: latestUpdateOnBranch.group,
updates: branch.updates,
}),
];
})
);
Log.addNewLineIfNone();
Log.log(chalk.bold('Branches:'));
Log.addNewLineIfNone();
Log.log(table.toString());
Log.log(
currentPage
.map(branch => formatBranch(getBranchDescription(branch)))
.join(`\n\n${chalk.dim('———')}\n\n`)
);
}
}
export async function createUpdateBranchOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{ appId, name }: CreateUpdateBranchForAppMutationVariables
): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
const result = await withErrorHandlingAsync(
graphqlClient
.mutation<CreateUpdateBranchForAppMutation, CreateUpdateBranchForAppMutationVariables>(
gql`
mutation CreateUpdateBranchForApp($appId: ID!, $name: String!) {
updateBranch {
createUpdateBranchForApp(appId: $appId, name: $name) {
id
name
}
}
}
`,
{
appId,
name,
}
)
.toPromise()
);
const newBranch = result.updateBranch.createUpdateBranchForApp;
if (!newBranch) {
throw new Error(`Could not create branch ${name}.`);
}
return newBranch;
}
export async function ensureBranchExistsAsync(
graphqlClient: ExpoGraphqlClient,
{
appId,
branchName,
}: {
appId: string;
branchName: string;
}
): Promise<{ branchId: string; createdBranch: boolean }> {
try {
const updateBranch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
appId,
name: branchName,
});
const { id } = updateBranch;
return { branchId: id, createdBranch: false };
} catch (error) {
if (error instanceof BranchNotFoundError) {
const newUpdateBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
appId,
name: branchName,
});
return { branchId: newUpdateBranch.id, createdBranch: true };
} else {
throw error;
}
}
}
@@ -3,7 +3,13 @@ import { v4 as uuid } from 'uuid';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { selectBuildToCancelAsync } from '../../commands/build/cancel';
import { AppPlatform, BuildFragment, BuildPriority, BuildStatus } from '../../graphql/generated';
import {
AppPlatform,
BuildFragment,
BuildPriority,
BuildResourceClass,
BuildStatus,
} from '../../graphql/generated';
import { BuildQuery } from '../../graphql/queries/BuildQuery';
import { confirmAsync, selectAsync } from '../../prompts';
@@ -85,5 +91,6 @@ function createMockBuildFragment({
},
},
status: BuildStatus.InQueue,
resourceClass: BuildResourceClass.AndroidMedium,
};
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,597 @@
import { Platform } from '@expo/eas-build-job';
import { CombinedError } from '@urql/core';
import { GraphQLError } from 'graphql/error';
import { v4 as uuidv4 } from 'uuid';
import { getError } from '../../__tests__/commands/utils';
import { EasCommandError } from '../../commandUtils/errors';
import Build from '../../commands/build';
import Log, { link } from '../../log';
import { handleBuildRequestError } from '../build';
import {
EasBuildDownForMaintenanceError,
EasBuildFreeTierDisabledAndroidError,
EasBuildFreeTierDisabledError,
EasBuildFreeTierDisabledIOSError,
EasBuildFreeTierIosLimitExceededError,
EasBuildFreeTierLimitExceededError,
EasBuildResourceClassNotAvailableInFreeTierError,
EasBuildTooManyPendingBuildsError,
RequestValidationError,
TurtleDeprecatedJobFormatError,
} from '../errors';
beforeEach(() => {
jest.resetAllMocks();
});
const EXPECTED_STRINGIFIED_GRAPHQL_ERROR_JSON = `[
{
"name": "GraphQLError",
"extensions": {},
"message": "Error 1"
},
{
"name": "GraphQLError",
"extensions": {},
"message": "Error 2"
},
{
"name": "GraphQLError",
"extensions": {},
"message": "Error 3"
}
]`;
const EXPECTED_EAS_BUILD_DOWN_MESSAGE = `EAS Build is down for maintenance. Try again later. Check ${link(
'https://status.expo.dev/'
)} for updates.`;
const EXPECTED_MAX_BUILD_COUNT_MESSAGE_ANDROID = `You have already reached the maximum number of pending Android builds for your account. Try again later.`;
const EXPECTED_MAX_BUILD_COUNT_MESSAGE_IOS = `You have already reached the maximum number of pending iOS builds for your account. Try again later.`;
const EXPECTED_GENERIC_MESSAGE =
'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 => {
return new GraphQLError(message, null, null, null, null, null, {
errorCode,
requestId: mockRequestId,
});
};
const assertReThrownError = (
caughtError: CombinedError | Error,
expectedType: typeof EasCommandError,
expectedMessage: string = 'Error 1'
): void => {
expect(caughtError).toBeInstanceOf(expectedType);
expect((caughtError as Error).message).toEqual(expectedMessage);
};
describe(Build.name, () => {
describe('handle build request errors', () => {
describe('logs graphQL errors to debug if present', () => {
it('does it with Android', async () => {
const platform = Platform.ANDROID;
const logDebugSpy = jest.spyOn(Log, 'debug');
const graphQLErrors = ['Error 1', 'Error 2', 'Error 3'];
const error = new CombinedError({ graphQLErrors });
try {
handleBuildRequestError(error, platform);
} catch {}
expect(logDebugSpy).toBeCalledWith(EXPECTED_STRINGIFIED_GRAPHQL_ERROR_JSON);
});
it('does it with iOS', async () => {
const platform = Platform.IOS;
const logDebugSpy = jest.spyOn(Log, 'debug');
const graphQLErrors = ['Error 1', 'Error 2', 'Error 3'];
const error = new CombinedError({ graphQLErrors });
try {
handleBuildRequestError(error, platform);
} catch {}
expect(logDebugSpy).toBeCalledWith(EXPECTED_STRINGIFIED_GRAPHQL_ERROR_JSON);
});
});
describe('logs nothing to debug if graphQLErrors not present', () => {
it('does it with Android', async () => {
const platform = Platform.ANDROID;
const logDebugSpy = jest.spyOn(Log, 'debug');
const error = new Error('Generic error');
try {
handleBuildRequestError(error, platform);
} catch {}
expect(logDebugSpy).toBeCalledWith(undefined);
});
it('does it with iOS', async () => {
const platform = Platform.IOS;
const logDebugSpy = jest.spyOn(Log, 'debug');
const error = new Error('Generic error');
try {
handleBuildRequestError(error, platform);
} catch {}
expect(logDebugSpy).toBeCalledWith(undefined);
});
});
describe('error is server-side defined error', () => {
describe('for TURTLE_DEPRECATED_JOB_FORMAT', () => {
describe('with Android', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'TURTLE_DEPRECATED_JOB_FORMAT');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<TurtleDeprecatedJobFormatError>(
() => {
handleBuildRequestError(error, platform);
}
);
assertReThrownError(
handleBuildRequestErrorThrownError,
TurtleDeprecatedJobFormatError,
'Error 1'
);
});
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 graphQLError = getGraphQLError('Error 1', 'TURTLE_DEPRECATED_JOB_FORMAT');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<TurtleDeprecatedJobFormatError>(
() => {
handleBuildRequestError(error, platform);
}
);
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('with Android', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildFreeTierDisabledError>(
() => {
handleBuildRequestError(error, platform);
}
);
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildFreeTierDisabledError,
'Error 1'
);
});
});
describe('with iOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildFreeTierDisabledError>(
() => {
handleBuildRequestError(error, platform);
}
);
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildFreeTierDisabledError,
'Error 1'
);
});
});
});
describe('for EAS_BUILD_FREE_TIER_DISABLED_IOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED_IOS');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
try {
handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, EasBuildFreeTierDisabledIOSError, 'Error 1');
}
});
});
describe('for EAS_BUILD_FREE_TIER_DISABLED_ANDROID', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED_ANDROID');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildFreeTierDisabledAndroidError>(
() => {
handleBuildRequestError(error, platform);
}
);
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildFreeTierDisabledAndroidError,
'Error 1'
);
});
});
describe('for EAS_BUILD_FREE_TIER_LIMIT_EXCEEDED', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_LIMIT_EXCEEDED');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildFreeTierLimitExceededError>(
() => {
handleBuildRequestError(error, platform);
}
);
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildFreeTierLimitExceededError,
'Error 1'
);
});
});
describe('for EAS_BUILD_FREE_TIER_IOS_LIMIT_EXCEEDED', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_IOS_LIMIT_EXCEEDED');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError =
getError<EasBuildFreeTierIosLimitExceededError>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildFreeTierIosLimitExceededError,
'Error 1'
);
});
});
describe('for VALIDATION_ERROR', () => {
describe('with Android', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'VALIDATION_ERROR');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<RequestValidationError>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(
handleBuildRequestErrorThrownError,
RequestValidationError,
'Error 1'
);
});
});
describe('with iOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'VALIDATION_ERROR');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<RequestValidationError>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(
handleBuildRequestErrorThrownError,
RequestValidationError,
'Error 1'
);
});
});
});
});
describe('error is other expected graphQL-related error', () => {
describe('for EAS_BUILD_DOWN_FOR_MAINTENANCE', () => {
describe('with Android', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_DOWN_FOR_MAINTENANCE');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildDownForMaintenanceError>(
() => {
handleBuildRequestError(error, platform);
}
);
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildDownForMaintenanceError,
EXPECTED_EAS_BUILD_DOWN_MESSAGE
);
});
});
describe('with iOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_DOWN_FOR_MAINTENANCE');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildDownForMaintenanceError>(
() => {
handleBuildRequestError(error, platform);
}
);
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildDownForMaintenanceError,
EXPECTED_EAS_BUILD_DOWN_MESSAGE
);
});
});
});
describe('for EAS_BUILD_TOO_MANY_PENDING_BUILDS', () => {
describe('with Android', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_TOO_MANY_PENDING_BUILDS');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildTooManyPendingBuildsError>(
() => {
handleBuildRequestError(error, platform);
}
);
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildTooManyPendingBuildsError,
EXPECTED_MAX_BUILD_COUNT_MESSAGE_ANDROID
);
});
});
describe('with iOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_TOO_MANY_PENDING_BUILDS');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildTooManyPendingBuildsError>(
() => {
handleBuildRequestError(error, platform);
}
);
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildTooManyPendingBuildsError,
EXPECTED_MAX_BUILD_COUNT_MESSAGE_IOS
);
});
});
});
});
describe('error is unexpected graphQL-related error', () => {
describe('with Android', () => {
it('throws base Error class with custom message', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const expectedMessage = `${EXPECTED_GENERIC_MESSAGE}\nRequest ID: ${mockRequestId}\nError message: Error 1`;
const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(handleBuildRequestErrorThrownError, Error, expectedMessage);
});
it('throws base Error class with message including all error messages if combined error has multiple GraphQL errors', async () => {
const platform = Platform.ANDROID;
const graphQLErrors = [
getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR'),
getGraphQLError('Error 2', 'OTHER_GRAPHQL_ERROR'),
getGraphQLError('Error 3', 'YET_ANOTHER_GRAPHQL_ERROR'),
];
const error = new CombinedError({ graphQLErrors });
const expectedMessage =
`${EXPECTED_GENERIC_MESSAGE}\n` +
`Request ID: ${mockRequestId}\n` +
'Error message: Error 1\n' +
`Request ID: ${mockRequestId}\n` +
'Error message: Error 2\n' +
`Request ID: ${mockRequestId}\n` +
'Error message: Error 3';
const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform);
});
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 graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR');
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const expectedMessage = `${EXPECTED_GENERIC_MESSAGE}\nRequest ID: ${mockRequestId}\nError message: Error 1`;
const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(handleBuildRequestErrorThrownError, Error, expectedMessage);
});
it('throws base Error class with message including all error messages if combined error has multiple GraphQL errors', async () => {
const platform = Platform.IOS;
const graphQLErrors = [
getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR'),
getGraphQLError('Error 2', 'OTHER_GRAPHQL_ERROR'),
getGraphQLError('Error 3', 'YET_ANOTHER_GRAPHQL_ERROR'),
];
const error = new CombinedError({ graphQLErrors });
const expectedMessage =
`${EXPECTED_GENERIC_MESSAGE}\n` +
`Request ID: ${mockRequestId}\n` +
'Error message: Error 1\n' +
`Request ID: ${mockRequestId}\n` +
'Error message: Error 2\n' +
`Request ID: ${mockRequestId}\n` +
'Error message: Error 3';
const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform);
});
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('with Android', () => {
it('throws base Error class with propagated message', async () => {
const platform = Platform.ANDROID;
const error = new Error('Non-graphQL-related error');
const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform);
});
expect(handleBuildRequestErrorThrownError).toStrictEqual(error);
});
});
describe('with iOS', () => {
it('throws base Error class with propagated message', async () => {
const platform = Platform.IOS;
const error = new Error('Non-graphQL-related error');
const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform);
});
expect(handleBuildRequestErrorThrownError).toStrictEqual(error);
});
});
});
});
});
@@ -0,0 +1,142 @@
import { Platform, Workflow } from '@expo/eas-build-job';
import { ExitError } from '@oclif/core/lib/errors';
import path from 'path';
import { instance, mock, when } from 'ts-mockito';
import { CommonContext } from '../context';
import { validatePNGsForManagedProjectAsync } from '../validate';
const fixturesPath = path.join(__dirname, 'fixtures');
describe(validatePNGsForManagedProjectAsync, () => {
it('does not validate PNGs for generic projects', async () => {
const ctxMock = mock<CommonContext<Platform.ANDROID>>();
when(ctxMock.workflow).thenReturn(Workflow.GENERIC);
const ctx = instance(ctxMock);
await expect(validatePNGsForManagedProjectAsync(ctx)).resolves.not.toThrow();
});
it('does not validate PNGs for projects with SDK >= 47', async () => {
const ctxMock = mock<CommonContext<Platform.ANDROID>>();
when(ctxMock.workflow).thenReturn(Workflow.GENERIC);
when(ctxMock.exp).thenReturn({
name: 'blah',
slug: 'blah',
android: { adaptiveIcon: { foregroundImage: 'assets/icon.jpg' } },
sdkVersion: '47.0.0',
});
const ctx = instance(ctxMock);
await expect(validatePNGsForManagedProjectAsync(ctx)).resolves.not.toThrow();
});
describe(Platform.ANDROID, () => {
it('exits if foregroundImage is not a file with .png extension', async () => {
const ctxMock = mock<CommonContext<Platform.ANDROID>>();
when(ctxMock.workflow).thenReturn(Workflow.MANAGED);
when(ctxMock.platform).thenReturn(Platform.ANDROID);
when(ctxMock.exp).thenReturn({
name: 'blah',
slug: 'blah',
android: { adaptiveIcon: { foregroundImage: 'assets/icon.jpg' } },
sdkVersion: '46.0.0',
});
const ctx = instance(ctxMock);
await expect(validatePNGsForManagedProjectAsync(ctx)).rejects.toThrow(ExitError);
});
it('exits if foregroundImage is not a png file', async () => {
const ctxMock = mock<CommonContext<Platform.ANDROID>>();
when(ctxMock.workflow).thenReturn(Workflow.MANAGED);
when(ctxMock.platform).thenReturn(Platform.ANDROID);
when(ctxMock.exp).thenReturn({
name: 'blah',
slug: 'blah',
android: { adaptiveIcon: { foregroundImage: path.join(fixturesPath, 'icon-jpg.png') } },
sdkVersion: '46.0.0',
});
const ctx = instance(ctxMock);
await expect(validatePNGsForManagedProjectAsync(ctx)).rejects.toThrow(ExitError);
});
it('does not throw if foregroundImage is a png file', async () => {
const ctxMock = mock<CommonContext<Platform.ANDROID>>();
when(ctxMock.workflow).thenReturn(Workflow.MANAGED);
when(ctxMock.platform).thenReturn(Platform.ANDROID);
when(ctxMock.exp).thenReturn({
name: 'blah',
slug: 'blah',
android: { adaptiveIcon: { foregroundImage: path.join(fixturesPath, 'icon-alpha.png') } },
sdkVersion: '46.0.0',
});
const ctx = instance(ctxMock);
await expect(validatePNGsForManagedProjectAsync(ctx)).resolves.not.toThrow();
});
});
// Validating iOS PNGs is currently disabled
// See https://github.com/expo/eas-cli/pull/1477 for context
xdescribe(Platform.IOS, () => {
it('exits if icon is not a file with .png extension', async () => {
const ctxMock = mock<CommonContext<Platform.IOS>>();
when(ctxMock.workflow).thenReturn(Workflow.MANAGED);
when(ctxMock.platform).thenReturn(Platform.IOS);
when(ctxMock.exp).thenReturn({
name: 'blah',
slug: 'blah',
ios: { icon: path.join(fixturesPath, 'assets/icon.jpg') },
sdkVersion: '46.0.0',
});
const ctx = instance(ctxMock);
await expect(validatePNGsForManagedProjectAsync(ctx)).rejects.toThrow(ExitError);
});
it('exits if icon is not a png file', async () => {
const ctxMock = mock<CommonContext<Platform.IOS>>();
when(ctxMock.workflow).thenReturn(Workflow.MANAGED);
when(ctxMock.platform).thenReturn(Platform.IOS);
when(ctxMock.exp).thenReturn({
name: 'blah',
slug: 'blah',
ios: { icon: path.join(fixturesPath, 'icon-jpg.png') },
sdkVersion: '46.0.0',
});
const ctx = instance(ctxMock);
await expect(validatePNGsForManagedProjectAsync(ctx)).rejects.toThrow(ExitError);
});
it('exits if icon has alpha channel (transparency)', async () => {
const ctxMock = mock<CommonContext<Platform.IOS>>();
when(ctxMock.workflow).thenReturn(Workflow.MANAGED);
when(ctxMock.platform).thenReturn(Platform.IOS);
when(ctxMock.exp).thenReturn({
name: 'blah',
slug: 'blah',
ios: { icon: path.join(fixturesPath, 'icon-alpha.png') },
sdkVersion: '46.0.0',
});
const ctx = instance(ctxMock);
await expect(validatePNGsForManagedProjectAsync(ctx)).rejects.toThrow(ExitError);
});
it('does not throw if icon is a png file and does not have alpha channel', async () => {
const ctxMock = mock<CommonContext<Platform.IOS>>();
when(ctxMock.workflow).thenReturn(Workflow.MANAGED);
when(ctxMock.platform).thenReturn(Platform.IOS);
when(ctxMock.exp).thenReturn({
name: 'blah',
slug: 'blah',
ios: { icon: path.join(fixturesPath, 'icon-no-alpha.png') },
sdkVersion: '46.0.0',
});
const ctx = instance(ctxMock);
await expect(validatePNGsForManagedProjectAsync(ctx)).resolves.not.toThrow();
});
});
});
+9 -3
View File
@@ -24,7 +24,11 @@ import {
import { AndroidBuildContext, BuildContext, CommonContext } from '../context';
import { transformMetadata } from '../graphql';
import { logCredentialsSource } from '../utils/credentials';
import { checkGoogleServicesFileAsync, checkNodeEnvVariable } from '../validate';
import {
checkGoogleServicesFileAsync,
checkNodeEnvVariable,
validatePNGsForManagedProjectAsync,
} from '../validate';
import { transformJob } from './graphql';
import { prepareJobAsync } from './prepareJob';
import { syncProjectConfigurationAsync } from './syncProjectConfiguration';
@@ -53,11 +57,12 @@ This means that it will most likely produce an AAB and you will not be able to i
checkNodeEnvVariable(ctx);
await checkGoogleServicesFileAsync(ctx);
await validatePNGsForManagedProjectAsync(ctx);
const gradleContext = await resolveGradleBuildContextAsync(ctx.projectDir, buildProfile);
if (ctx.workflow === Workflow.MANAGED) {
await ensureApplicationIdIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp, ctx.user);
await ensureApplicationIdIsDefinedForManagedProjectAsync(ctx);
}
const applicationId = await getApplicationIdAsync(ctx.projectDir, ctx.exp, gradleContext);
@@ -119,7 +124,8 @@ export async function prepareAndroidBuildAsync(
}
function shouldProvideCredentials(ctx: BuildContext<Platform.ANDROID>): boolean {
return !ctx.buildProfile.withoutCredentials;
const isCustomBuild = ctx.buildProfile.config !== undefined;
return !ctx.buildProfile.withoutCredentials && !isCustomBuild;
}
async function ensureAndroidCredentialsAsync(
@@ -1,11 +1,17 @@
import { Android } from '@expo/eas-build-job';
import { AndroidBuildType, AndroidJobInput } from '../../graphql/generated';
import { transformProjectArchive, transformWorkflow } from '../graphql';
import {
transformBuildMode,
transformBuildTrigger,
transformProjectArchive,
transformWorkflow,
} from '../graphql';
export function transformJob(job: Android.Job): AndroidJobInput {
return {
type: transformWorkflow(job.type),
triggeredBy: transformBuildTrigger(job.triggeredBy),
projectArchive: transformProjectArchive(job.projectArchive),
projectRootDirectory: job.projectRootDirectory,
releaseChannel: job.releaseChannel,
@@ -21,6 +27,8 @@ export function transformJob(job: Android.Job): AndroidJobInput {
buildType: job.buildType && transformBuildType(job.buildType),
developmentClient: job.developmentClient,
experimental: job.experimental,
mode: transformBuildMode(job.mode),
customBuildConfig: job.customBuildConfig,
};
}
@@ -1,9 +1,18 @@
import { Android, ArchiveSource, Job, Platform, sanitizeJob } from '@expo/eas-build-job';
import {
Android,
ArchiveSource,
BuildMode,
BuildTrigger,
Job,
Platform,
sanitizeJob,
} from '@expo/eas-build-job';
import { BuildProfile } from '@expo/eas-json';
import path from 'path';
import slash from 'slash';
import { AndroidCredentials } from '../../credentials/android/AndroidCredentialsProvider';
import { getCustomBuildConfigPath } from '../../project/customBuildConfig';
import { getUsername } from '../../project/projectUtils';
import { getVcsClient } from '../../vcs';
import { BuildContext } from '../context';
@@ -15,8 +24,7 @@ interface JobData {
const cacheDefaults = {
disabled: false,
customPaths: [],
cacheDefaultPaths: true,
paths: [],
};
export async function prepareJobAsync(
@@ -41,11 +49,15 @@ export async function prepareJobAsync(
}
: {};
let buildType = ctx.buildProfile.buildType;
if (!buildType && !buildProfile.gradleCommand && ctx.buildProfile.distribution === 'internal') {
let buildType = buildProfile.buildType;
if (!buildType && !buildProfile.gradleCommand && buildProfile.distribution === 'internal') {
buildType = Android.BuildType.APK;
}
const maybeCustomBuildConfigPath = buildProfile.config
? getCustomBuildConfigPath(buildProfile.config)
: undefined;
const job: Android.Job = {
type: ctx.workflow,
platform: Platform.ANDROID,
@@ -67,8 +79,8 @@ export async function prepareJobAsync(
secrets: {
...buildCredentials,
},
releaseChannel: ctx.buildProfile.releaseChannel,
updates: { channel: ctx.buildProfile.channel },
releaseChannel: buildProfile.releaseChannel,
updates: { channel: buildProfile.channel },
developmentClient: buildProfile.developmentClient,
gradleCommand: buildProfile.gradleCommand,
applicationArchivePath: buildProfile.applicationArchivePath ?? buildProfile.artifactPath,
@@ -81,8 +93,15 @@ export async function prepareJobAsync(
},
}),
experimental: {
prebuildCommand: ctx.buildProfile.prebuildCommand,
prebuildCommand: buildProfile.prebuildCommand,
},
mode: buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
triggeredBy: BuildTrigger.EAS_CLI,
...(maybeCustomBuildConfigPath && {
customBuildConfig: {
path: maybeCustomBuildConfigPath,
},
}),
};
return sanitizeJob(job);
+213 -70
View File
@@ -1,23 +1,27 @@
import { ArchiveSource, ArchiveSourceType, Job, Metadata, Platform } from '@expo/eas-build-job';
import { CredentialsSource } from '@expo/eas-json';
import { CredentialsSource, EasJsonAccessor, EasJsonUtils, ResourceClass } from '@expo/eas-json';
import assert from 'assert';
import chalk from 'chalk';
import cliProgress from 'cli-progress';
import fs from 'fs-extra';
import { GraphQLError } from 'graphql/error';
import nullthrows from 'nullthrows';
import { BuildEvent } from '../analytics/AnalyticsManager';
import { withAnalyticsAsync } from '../analytics/common';
import { getExpoWebsiteBaseUrl } from '../api';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { EasCommandError } from '../commandUtils/errors';
import {
AppPlatform,
BuildFragment,
BuildParamsInput,
BuildPriority,
BuildResourceClass,
BuildStatus,
UploadSessionType,
} from '../graphql/generated';
import { BuildResult } from '../graphql/mutations/BuildMutation';
import { BuildMutation, BuildResult } from '../graphql/mutations/BuildMutation';
import { BuildQuery } from '../graphql/queries/BuildQuery';
import Log, { learnMore, link } from '../log';
import { Ora, ora } from '../ora';
@@ -26,13 +30,30 @@ import {
appPlatformEmojis,
requestedPlatformDisplayNames,
} from '../platform';
import { uploadFileAtPathToS3Async } from '../uploads';
import { confirmAsync } from '../prompts';
import { uploadFileAtPathToGCSAsync } from '../uploads';
import { formatBytes } from '../utils/files';
import { printJsonOnlyOutput } from '../utils/json';
import { createProgressTracker } from '../utils/progress';
import { sleepAsync } from '../utils/promise';
import { getVcsClient } from '../vcs';
import { BuildContext } from './context';
import { runLocalBuildAsync } from './local';
import {
EasBuildDownForMaintenanceError,
EasBuildFreeTierDisabledAndroidError,
EasBuildFreeTierDisabledError,
EasBuildFreeTierDisabledIOSError,
EasBuildFreeTierIosLimitExceededError,
EasBuildFreeTierLimitExceededError,
EasBuildLegacyResourceClassNotAvailableError,
EasBuildProjectArchiveUploadError,
EasBuildResourceClassNotAvailableInFreeTierError,
EasBuildTooManyPendingBuildsError,
RequestValidationError,
TurtleDeprecatedJobFormatError,
} from './errors';
import { transformMetadata } from './graphql';
import { LocalBuildMode, runLocalBuildAsync } from './local';
import { collectMetadataAsync } from './metadata';
import { printDeprecationWarnings } from './utils/printBuildInfo';
import { makeProjectTarballAsync, reviewAndCommitChangesAsync } from './utils/repository';
@@ -65,9 +86,14 @@ interface Builder<TPlatform extends Platform, Credentials, TJob extends Job> {
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 {
resourceClass: ctx.resourceClass,
sdkVersion: metadata.sdkVersion,
reactNativeVersion: metadata.reactNativeVersion,
};
}
@@ -107,85 +133,99 @@ export async function prepareBuildRequestForPlatformAsync<
);
}
const projectArchive = ctx.localBuildOptions.enable
? ({
type: ArchiveSourceType.PATH,
path: (await makeProjectTarballAsync()).path,
} as const)
: ({
type: ArchiveSourceType.S3,
bucketKey: await uploadProjectAsync(ctx),
} as const);
let projectArchive: ArchiveSource | undefined;
if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN) {
projectArchive = {
type: ArchiveSourceType.PATH,
path: (await makeProjectTarballAsync()).path,
};
} else if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL) {
projectArchive = {
type: ArchiveSourceType.PATH,
path: process.cwd(),
};
} else if (!ctx.localBuildOptions.localBuildMode) {
projectArchive = {
type: ArchiveSourceType.GCS,
bucketKey: await uploadProjectAsync(ctx),
};
}
assert(projectArchive);
const metadata = await collectMetadataAsync(ctx);
const buildParams = resolveBuildParamsInput(ctx);
const buildParams = resolveBuildParamsInput(ctx, metadata);
const job = await builder.prepareJobAsync(ctx, {
projectArchive,
credentials: credentialsResult?.credentials,
});
return async () => {
if (ctx.localBuildOptions.enable) {
if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN) {
await runLocalBuildAsync(job, metadata, ctx.localBuildOptions);
return undefined;
} else {
} else if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL) {
await BuildMutation.updateBuildMetadataAsync(ctx.graphqlClient, {
buildId: nullthrows(process.env.EAS_BUILD_ID),
metadata: transformMetadata(metadata),
});
printJsonOnlyOutput({ job, metadata });
return undefined;
} else if (!ctx.localBuildOptions.localBuildMode) {
try {
return await sendBuildRequestAsync(builder, job, metadata, buildParams);
} catch (error: any) {
handleBuildRequestError(error, job.platform);
}
} else {
throw new Error('Unknown localBuildMode.');
}
};
}
function handleBuildRequestError(error: any, platform: Platform): never {
if (error?.graphQLErrors?.[0]?.extensions?.errorCode === 'TURTLE_DEPRECATED_JOB_FORMAT') {
Log.error('EAS Build API has changed. Upgrade to the latest eas-cli version.');
throw new Error('Build request failed.');
} else if (
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_DOWN_FOR_MAINTENANCE'
) {
Log.error(
const SERVER_SIDE_DEFINED_ERRORS: Record<string, typeof EasCommandError> = {
TURTLE_DEPRECATED_JOB_FORMAT: TurtleDeprecatedJobFormatError,
EAS_BUILD_FREE_TIER_DISABLED: EasBuildFreeTierDisabledError,
EAS_BUILD_FREE_TIER_DISABLED_IOS: EasBuildFreeTierDisabledIOSError,
EAS_BUILD_FREE_TIER_DISABLED_ANDROID: EasBuildFreeTierDisabledAndroidError,
EAS_BUILD_FREE_TIER_LIMIT_EXCEEDED: EasBuildFreeTierLimitExceededError,
EAS_BUILD_FREE_TIER_IOS_LIMIT_EXCEEDED: EasBuildFreeTierIosLimitExceededError,
EAS_BUILD_RESOURCE_CLASS_NOT_AVAILABLE_IN_FREE_TIER:
EasBuildResourceClassNotAvailableInFreeTierError,
EAS_BUILD_LEGACY_RESOURCE_CLASS_NOT_AVAILABLE: EasBuildLegacyResourceClassNotAvailableError,
VALIDATION_ERROR: RequestValidationError,
};
export function handleBuildRequestError(error: any, platform: Platform): never {
Log.debug(JSON.stringify(error.graphQLErrors, null, 2));
const graphQLErrorCode: string = error?.graphQLErrors?.[0]?.extensions?.errorCode;
if (graphQLErrorCode in SERVER_SIDE_DEFINED_ERRORS) {
const ErrorClass: typeof EasCommandError = SERVER_SIDE_DEFINED_ERRORS[graphQLErrorCode];
throw new ErrorClass(error?.graphQLErrors?.[0]?.message);
} else if (graphQLErrorCode === 'EAS_BUILD_DOWN_FOR_MAINTENANCE') {
throw new EasBuildDownForMaintenanceError(
`EAS Build is down for maintenance. Try again later. Check ${link(
'https://status.expo.dev/'
)} for updates.`
);
throw new Error('Build request failed.');
} else if (error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_FREE_TIER_DISABLED') {
Log.error(
`EAS Build free tier is temporarily disabled and we are not accepting any new builds. Try again later. ${learnMore(
'https://expo.fyi/eas-build-queues'
)}`
);
throw new Error('Build request failed.');
} else if (
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_FREE_TIER_DISABLED_IOS'
) {
Log.error(
`EAS Build free tier is temporarily disabled for iOS and we are not accepting any new builds. Try again later. ${learnMore(
'https://expo.fyi/eas-build-queues'
)}`
);
throw new Error('Build request failed.');
} else if (
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_FREE_TIER_DISABLED_ANDROID'
) {
Log.error(
`EAS Build free tier is temporarily disabled for Android and we are not accepting any new builds. Try again later. ${learnMore(
'https://expo.fyi/eas-build-queues'
)}`
);
throw new Error('Build request failed.');
} else if (
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_TOO_MANY_PENDING_BUILDS'
) {
Log.error(
} else if (graphQLErrorCode === 'EAS_BUILD_TOO_MANY_PENDING_BUILDS') {
throw new EasBuildTooManyPendingBuildsError(
`You have already reached the maximum number of pending ${requestedPlatformDisplayNames[platform]} builds for your account. Try again later.`
);
throw new Error('Build request failed.');
} else if (error?.graphQLErrors) {
Log.error(
'Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, report the issue.'
const errorMessage = error.graphQLErrors
.map((graphQLError: GraphQLError) => {
const requestIdLine = graphQLError?.extensions?.requestId
? `\nRequest ID: ${graphQLError.extensions.requestId}`
: '';
const errorMessageLine = graphQLError?.message
? `\nError message: ${graphQLError.message}`
: '';
return `${requestIdLine}${errorMessageLine}`;
})
.join('');
throw new Error(
`Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, report the issue.${errorMessage}`
);
}
throw error;
@@ -217,11 +257,15 @@ async function uploadProjectAsync<TPlatform extends Platform>(
);
}
if (projectTarball.size > 2 * 1024 * 1024 * 1024) {
throw new Error('Project archive is too big. Maximum allowed size is 2GB.');
}
projectTarballPath = projectTarball.path;
const { bucketKey } = await uploadFileAtPathToS3Async(
const bucketKey = await uploadFileAtPathToGCSAsync(
ctx.graphqlClient,
UploadSessionType.EasBuildProjectSources,
UploadSessionType.EasBuildGcsProjectSources,
projectTarball.path,
createProgressTracker({
total: projectTarball.size,
@@ -241,6 +285,12 @@ async function uploadProjectAsync<TPlatform extends Platform>(
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 {
if (projectTarballPath) {
await fs.remove(projectTarballPath);
@@ -281,11 +331,16 @@ async function sendBuildRequestAsync<TPlatform extends Platform, Credentials, TJ
);
}
type MaybeBuildFragment = BuildFragment | null;
export type MaybeBuildFragment = BuildFragment | null;
export async function waitForBuildEndAsync(
graphqlClient: ExpoGraphqlClient,
{ buildIds, accountName }: { buildIds: string[]; accountName: string },
{
buildIds,
accountName,
projectDir,
nonInteractive,
}: { buildIds: string[]; accountName: string; projectDir: string; nonInteractive: boolean },
{ intervalSec = 10 } = {}
): Promise<MaybeBuildFragment[]> {
let spinner;
@@ -302,7 +357,10 @@ export async function waitForBuildEndAsync(
const builds = await getBuildsSafelyAsync(graphqlClient, buildIds);
const { refetch } =
builds.length === 1
? await handleSingleBuildProgressAsync({ build: builds[0], accountName }, { spinner })
? await handleSingleBuildProgressAsync(
{ build: builds[0], accountName, projectDir, nonInteractive },
{ spinner }
)
: await handleMultipleBuildsProgressAsync({ builds }, { spinner, originalSpinnerText });
if (!refetch) {
return builds;
@@ -337,7 +395,17 @@ const queueProgressBar = new cliProgress.SingleBar(
);
async function handleSingleBuildProgressAsync(
{ build, accountName }: { build: MaybeBuildFragment; accountName: string },
{
build,
accountName,
projectDir,
nonInteractive,
}: {
build: MaybeBuildFragment;
accountName: string;
projectDir: string;
nonInteractive: boolean;
},
{ spinner }: { spinner: Ora }
): Promise<BuildProgressResult> {
if (build === null) {
@@ -362,7 +430,7 @@ async function handleSingleBuildProgressAsync(
spinner.succeed('Build finished');
return { refetch: false };
case BuildStatus.New:
spinner.text = `Build is waiting to enter the queue. Check your concurrency limit at ${chalk.underline(
spinner.text = `Build is waiting to enter the queue. Check your concurrency limit at ${link(
formatAccountSubscriptionsUrl(accountName)
)}.`;
break;
@@ -376,18 +444,68 @@ async function handleSingleBuildProgressAsync(
if (
!queueProgressBarStarted &&
typeof build.initialQueuePosition === 'number' &&
typeof build.queuePosition === 'number'
typeof build.queuePosition === 'number' &&
typeof build.estimatedWaitTimeLeftSeconds === 'number'
) {
spinner.stopAndPersist();
if (build.priority !== BuildPriority.High) {
Log.newLine();
Log.log('Start builds sooner in the priority queue.');
Log.log(
`Sign up for EAS Production or Enterprise at ${chalk.underline(
`Sign up for EAS Production or Enterprise at ${link(
formatAccountSubscriptionsUrl(accountName)
)}`
);
}
if (
build.platform === AppPlatform.Ios &&
[BuildResourceClass.IosIntelLarge, BuildResourceClass.IosIntelMedium].includes(
build.resourceClass
)
) {
let askToSwitchToM1 = false;
if (
build.priority === BuildPriority.High &&
build.estimatedWaitTimeLeftSeconds >= /* 10 minutes */ 10 * 60
) {
Log.newLine();
Log.warn(
`Warning: Priority queue wait time for legacy iOS Intel workers is longer than usual (more than 10 minutes).`
);
Log.warn(`We recommend switching to the new, faster M1 workers for iOS builds.`);
Log.warn(
learnMore('https://blog.expo.dev/m1-workers-on-eas-build-dcaa2c1333ad', {
learnMoreMessage: 'Learn more on switching to M1 workers.',
})
);
askToSwitchToM1 = true;
} else if (
build.priority !== BuildPriority.High &&
build.estimatedWaitTimeLeftSeconds >= /* 120 minutes */ 120 * 60
) {
Log.newLine();
Log.warn(
`Warning: Free tier queue wait time for legacy iOS Intel workers is longer than usual.`
);
Log.warn(`We recommend switching to the new, faster M1 workers for iOS builds.`);
Log.warn(
learnMore('https://blog.expo.dev/m1-workers-on-eas-build-dcaa2c1333ad', {
learnMoreMessage: 'Learn more on switching to M1 workers.',
})
);
askToSwitchToM1 = true;
}
if (!nonInteractive && askToSwitchToM1) {
const shouldSwitchToM1 = await confirmAsync({
message: `Switch iOS builds to M1 workers (modifies build profiles in eas.json)?`,
});
if (shouldSwitchToM1) {
await updateIosBuildProfilesToUseM1WorkersAsync(projectDir);
}
}
}
Log.newLine();
Log.log(`Waiting in ${priorityToQueueDisplayName[build.priority]}`);
queueProgressBar.start(
@@ -402,6 +520,7 @@ async function handleSingleBuildProgressAsync(
}
break;
}
case BuildStatus.PendingCancel:
case BuildStatus.Canceled:
spinner.fail('Build canceled');
return { refetch: false };
@@ -423,8 +542,8 @@ async function handleSingleBuildProgressAsync(
}
const priorityToQueueDisplayName: Record<BuildPriority, string> = {
[BuildPriority.Normal]: 'queue',
[BuildPriority.NormalPlus]: 'queue',
[BuildPriority.Normal]: 'Free tier queue',
[BuildPriority.NormalPlus]: 'Free tier queue',
[BuildPriority.High]: 'priority queue',
};
@@ -432,6 +551,7 @@ const statusToDisplayName: Record<BuildStatus, string> = {
[BuildStatus.New]: 'waiting to enter the queue (concurrency limit reached)',
[BuildStatus.InQueue]: 'in queue',
[BuildStatus.InProgress]: 'in progress',
[BuildStatus.PendingCancel]: 'canceled',
[BuildStatus.Canceled]: 'canceled',
[BuildStatus.Finished]: 'finished',
[BuildStatus.Errored]: 'failed',
@@ -450,7 +570,12 @@ async function handleMultipleBuildsProgressAsync(
builds.filter(build => build.status === BuildStatus.Finished).length === buildCount;
const allSettled =
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;
if (allSettled) {
@@ -529,3 +654,21 @@ function formatAccountSubscriptionsUrl(accountName: string): string {
getExpoWebsiteBaseUrl()
).toString();
}
async function updateIosBuildProfilesToUseM1WorkersAsync(projectDir: string): Promise<void> {
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
await easJsonAccessor.readRawJsonAsync();
const profileNames = await EasJsonUtils.getBuildProfileNamesAsync(easJsonAccessor);
easJsonAccessor.patch(easJsonRawObject => {
for (const profileName of profileNames) {
easJsonRawObject.build[profileName].ios = {
...easJsonRawObject.build[profileName].ios,
resourceClass: ResourceClass.M_MEDIUM,
};
}
return easJsonRawObject;
});
await easJsonAccessor.writeAsync();
Log.withTick('Updated eas.json. Your next builds will run on M1 workers.');
}
+11 -3
View File
@@ -3,7 +3,7 @@ import { EasJson, EasJsonAccessor } from '@expo/eas-json';
import chalk from 'chalk';
import fs from 'fs-extra';
import Log from '../log';
import Log, { learnMore } from '../log';
import { resolveWorkflowAsync } from '../project/workflow';
import { easCliVersion } from '../utils/easCli';
import { getVcsClient } from '../vcs';
@@ -14,6 +14,10 @@ interface ConfigureParams {
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.
*
@@ -24,7 +28,7 @@ interface ConfigureParams {
export async function ensureProjectConfiguredAsync(
configureParams: ConfigureParams
): Promise<boolean> {
if (await fs.pathExists(EasJsonAccessor.formatEasJsonPath(configureParams.projectDir))) {
if (await easJsonExistsAsync(configureParams.projectDir)) {
return false;
}
@@ -102,5 +106,9 @@ async function createEasJsonAsync(projectDir: string): Promise<void> {
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
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 { BuildResourceClass } from '../graphql/generated';
import { GradleBuildContext } from '../project/android/gradle';
import { CustomBuildConfigMetadata } from '../project/customBuildConfig';
import { XcodeBuildContext } from '../project/ios/scheme';
import { Actor } from '../user/User';
import { LocalBuildOptions } from './local';
@@ -51,6 +52,7 @@ export interface BuildContext<T extends Platform> {
graphqlClient: ExpoGraphqlClient;
analytics: Analytics;
workflow: Workflow;
customBuildConfigMetadata?: CustomBuildConfigMetadata;
android: T extends Platform.ANDROID ? AndroidBuildContext : undefined;
ios: T extends Platform.IOS ? IosBuildContext : undefined;
}
+18 -8
View File
@@ -1,5 +1,5 @@
import { Platform } from '@expo/eas-build-job';
import { BuildProfile, EasJson } from '@expo/eas-json';
import { BuildProfile, EasJson, ResourceClass } from '@expo/eas-json';
import JsonFile from '@expo/json-file';
import getenv from 'getenv';
import resolveFrom from 'resolve-from';
@@ -9,7 +9,7 @@ import { Analytics, AnalyticsEventProperties, BuildEvent } from '../analytics/An
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { CredentialsContext } from '../credentials/context';
import { BuildResourceClass } from '../graphql/generated';
import { CustomBuildConfigMetadata } from '../project/customBuildConfig';
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
import { resolveWorkflowAsync } from '../project/workflow';
import { Actor } from '../user/User';
@@ -17,6 +17,7 @@ import { createAndroidContextAsync } from './android/build';
import { BuildContext, CommonContext } from './context';
import { createIosContextAsync } from './ios/build';
import { LocalBuildOptions } from './local';
import { resolveBuildResourceClassAsync } from './utils/resourceClass';
export async function createBuildContextAsync<T extends Platform>({
buildProfileName,
@@ -28,12 +29,13 @@ export async function createBuildContextAsync<T extends Platform>({
noWait,
platform,
projectDir,
resourceClass,
resourceClassFlag,
message,
actor,
graphqlClient,
analytics,
getDynamicProjectConfigAsync,
getDynamicPrivateProjectConfigAsync,
customBuildConfigMetadata,
}: {
buildProfileName: string;
buildProfile: BuildProfile<T>;
@@ -44,15 +46,15 @@ export async function createBuildContextAsync<T extends Platform>({
noWait: boolean;
platform: T;
projectDir: string;
resourceClass: BuildResourceClass;
resourceClassFlag?: ResourceClass;
message?: string;
actor: Actor;
graphqlClient: ExpoGraphqlClient;
analytics: Analytics;
getDynamicProjectConfigAsync: DynamicConfigContextFn;
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
customBuildConfigMetadata?: CustomBuildConfigMetadata;
}): Promise<BuildContext<T>> {
const { exp, projectId } = await getDynamicProjectConfigAsync({ env: buildProfile.env });
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env });
const projectName = exp.slug;
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
const workflow = await resolveWorkflowAsync(projectDir, platform);
@@ -78,6 +80,7 @@ export async function createBuildContextAsync<T extends Platform>({
const analyticsEventProperties = {
tracking_id: uuidv4(),
platform,
...(exp.sdkVersion && { sdk_version: exp.sdkVersion }),
...(accountId && { account_id: accountId }),
project_id: projectId,
project_type: workflow,
@@ -87,6 +90,12 @@ export async function createBuildContextAsync<T extends Platform>({
};
analytics.logEvent(BuildEvent.BUILD_COMMAND, analyticsEventProperties);
const resourceClass = await resolveBuildResourceClassAsync(
buildProfile,
platform,
resourceClassFlag
);
const commonContext: CommonContext<T> = {
accountName: account.name,
buildProfile,
@@ -110,6 +119,7 @@ export async function createBuildContextAsync<T extends Platform>({
workflow,
message,
runFromCI,
customBuildConfigMetadata,
};
if (platform === Platform.ANDROID) {
const common = commonContext as CommonContext<Platform.ANDROID>;
+25
View File
@@ -0,0 +1,25 @@
import { EasCommandError } from '../commandUtils/errors';
export class TurtleDeprecatedJobFormatError extends EasCommandError {}
export class EasBuildFreeTierDisabledError extends EasCommandError {}
export class EasBuildFreeTierDisabledIOSError extends EasCommandError {}
export class EasBuildFreeTierDisabledAndroidError extends EasCommandError {}
export class EasBuildFreeTierLimitExceededError extends EasCommandError {}
export class EasBuildFreeTierIosLimitExceededError extends EasCommandError {}
export class EasBuildResourceClassNotAvailableInFreeTierError extends EasCommandError {}
export class EasBuildLegacyResourceClassNotAvailableError extends EasCommandError {}
export class RequestValidationError extends EasCommandError {}
export class EasBuildDownForMaintenanceError extends EasCommandError {}
export class EasBuildTooManyPendingBuildsError extends EasCommandError {}
export class EasBuildProjectArchiveUploadError extends EasCommandError {}
+37 -1
View File
@@ -1,11 +1,19 @@
import { ArchiveSource, ArchiveSourceType, Metadata, Workflow } from '@expo/eas-build-job';
import {
ArchiveSource,
ArchiveSourceType,
BuildTrigger,
Metadata,
Workflow,
} from '@expo/eas-build-job';
import {
BuildCredentialsSource,
BuildIosEnterpriseProvisioning,
BuildMetadataInput,
BuildMode,
BuildWorkflow,
DistributionType,
BuildTrigger as GraphQLBuildTrigger,
ProjectArchiveSourceInput,
ProjectArchiveSourceType,
} from '../graphql/generated';
@@ -16,6 +24,11 @@ export function transformProjectArchive(archiveSource: ArchiveSource): ProjectAr
type: ProjectArchiveSourceType.S3,
bucketKey: archiveSource.bucketKey,
};
} else if (archiveSource.type === ArchiveSourceType.GCS) {
return {
type: ProjectArchiveSourceType.Gcs,
bucketKey: archiveSource.bucketKey,
};
} else if (archiveSource.type === ArchiveSourceType.URL) {
return {
type: ProjectArchiveSourceType.Url,
@@ -29,6 +42,7 @@ export function transformProjectArchive(archiveSource: ArchiveSource): ProjectAr
export function transformMetadata(metadata: Metadata): BuildMetadataInput {
return {
...metadata,
buildMode: metadata.buildMode && transformBuildMode(metadata.buildMode),
credentialsSource:
metadata.credentialsSource && transformCredentialsSource(metadata.credentialsSource),
distribution: metadata.distribution && transformDistribution(metadata.distribution),
@@ -76,3 +90,25 @@ export function transformIosEnterpriseProvisioning(
return BuildIosEnterpriseProvisioning.Universal;
}
}
// TODO: check what in metadata
export function transformBuildMode(buildMode: string): BuildMode {
if (buildMode === 'build') {
return BuildMode.Build;
} else if (buildMode === 'resign') {
return BuildMode.Resign;
} else if (buildMode === 'custom') {
return BuildMode.Custom;
} else {
throw new Error(`Unsupported build mode: ${buildMode}`);
}
}
export function transformBuildTrigger(buildTrigger: BuildTrigger): GraphQLBuildTrigger {
if (buildTrigger === BuildTrigger.EAS_CLI) {
return GraphQLBuildTrigger.EasCli;
} else if (buildTrigger === BuildTrigger.GIT_BASED_INTEGRATION) {
return GraphQLBuildTrigger.GitBasedIntegration;
}
throw new Error('Unknown build trigger');
}
@@ -77,7 +77,7 @@ describe(bumpVersionAsync, () => {
);
await bumpVersionAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/repo',
projectDir: '/app',
exp: fakeExp,
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(
'/repo/ios/myproject/Info.plist'
'/app/ios/myproject/Info.plist'
)) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('2');
@@ -113,7 +113,7 @@ describe(bumpVersionAsync, () => {
);
await bumpVersionAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/repo',
projectDir: '/app',
exp: fakeExp,
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(
'/repo/ios/myproject/Info2.plist'
'/app/ios/myproject/Info2.plist'
)) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('2');
@@ -148,7 +148,7 @@ describe(bumpVersionAsync, () => {
);
await bumpVersionAsync({
bumpStrategy: BumpStrategy.APP_VERSION,
projectDir: '/repo',
projectDir: '/app',
exp: fakeExp,
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(
'/repo/ios/myproject/Info.plist'
'/app/ios/myproject/Info.plist'
)) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.1');
expect(fakeExp.ios?.buildNumber).toBe('1');
@@ -183,7 +183,7 @@ describe(bumpVersionAsync, () => {
);
await bumpVersionAsync({
bumpStrategy: BumpStrategy.NOOP,
projectDir: '/repo',
projectDir: '/app',
exp: fakeExp,
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(
'/repo/ios/myproject/Info.plist'
'/app/ios/myproject/Info.plist'
)) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('1');
@@ -214,11 +214,11 @@ describe(bumpVersionInAppJsonAsync, () => {
await bumpVersionInAppJsonAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/repo',
projectDir: '/app',
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.ios?.buildNumber).toBe('2');
expect(appJSON.expo.version).toBe('1.0.0');
@@ -230,11 +230,11 @@ describe(bumpVersionInAppJsonAsync, () => {
await bumpVersionInAppJsonAsync({
bumpStrategy: BumpStrategy.APP_VERSION,
projectDir: '/repo',
projectDir: '/app',
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.ios?.buildNumber).toBe('1');
expect(appJSON.expo.version).toBe('1.0.1');
@@ -246,11 +246,11 @@ describe(bumpVersionInAppJsonAsync, () => {
await bumpVersionInAppJsonAsync({
bumpStrategy: BumpStrategy.NOOP,
projectDir: '/repo',
projectDir: '/app',
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.ios?.buildNumber).toBe('1');
expect(appJSON.expo.version).toBe('1.0.0');
@@ -262,7 +262,7 @@ describe(readBuildNumberAsync, () => {
describe('bare project', () => {
it('reads the build number from native code', async () => {
const exp = initBareWorkflowProject();
const buildNumber = await readBuildNumberAsync('/repo', exp, {});
const buildNumber = await readBuildNumberAsync('/app', exp, {});
expect(buildNumber).toBe('1');
});
});
@@ -270,7 +270,7 @@ describe(readBuildNumberAsync, () => {
describe('managed project', () => {
it('reads the build number from expo config', async () => {
const exp = initManagedProject();
const buildNumber = await readBuildNumberAsync('/repo', exp, {});
const buildNumber = await readBuildNumberAsync('/app', exp, {});
expect(buildNumber).toBe('1');
});
});
@@ -280,24 +280,38 @@ describe(readShortVersionAsync, () => {
describe('bare project', () => {
it('reads the short version from native code', async () => {
const exp = initBareWorkflowProject();
const appVersion = await readShortVersionAsync('/repo', exp, {});
const appVersion = await readShortVersionAsync('/app', exp, {});
expect(appVersion).toBe('1.0.0');
});
it('evaluates interpolated build number', async () => {
const exp = initBareWorkflowProject({
appVersion: '$(CURRENT_PROJECT_VERSION)',
});
const buildNumber = await readShortVersionAsync('/repo', exp, {
const buildNumber = await readShortVersionAsync('/app', exp, {
CURRENT_PROJECT_VERSION: '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', () => {
it('reads the version from app config', async () => {
const exp = initBareWorkflowProject();
const appVersion = await readShortVersionAsync('/repo', exp, {});
const appVersion = await readShortVersionAsync('/app', exp, {});
expect(appVersion).toBe('1.0.0');
});
});
@@ -309,66 +323,75 @@ describe(getInfoPlistPath, () => {
{
'./ios/testapp/Info.plist': '',
},
'/repo'
'/app'
);
const plistPath = getInfoPlistPath('/repo', {});
expect(plistPath).toBe('/repo/ios/testapp/Info.plist');
const plistPath = getInfoPlistPath('/app', {});
expect(plistPath).toBe('/app/ios/testapp/Info.plist');
});
it('returns INFOPLIST_FILE if specified', () => {
vol.fromJSON(
{
'./ios/testapp/Info.plist': '',
},
'/repo'
'/app'
);
const plistPath = getInfoPlistPath('/repo', { INFOPLIST_FILE: './qwert/NotInfo.plist' });
expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist');
const plistPath = getInfoPlistPath('/app', { INFOPLIST_FILE: './qwert/NotInfo.plist' });
expect(plistPath).toBe('/app/ios/qwert/NotInfo.plist');
});
it('evaluates SRCROOT in Info.plist', () => {
vol.fromJSON(
{
'./ios/testapp/Info.plist': '',
},
'/repo'
'/app'
);
const plistPath = getInfoPlistPath('/repo', {
const plistPath = getInfoPlistPath('/app', {
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', () => {
vol.fromJSON(
{
'./ios/testapp/Info.plist': '',
},
'/repo'
'/app'
);
const plistPath = getInfoPlistPath('/repo', {
const plistPath = getInfoPlistPath('/app', {
INFOPLIST_FILE: '$(SRCROOT)/qwert/$(TARGET_NAME).plist',
TARGET_NAME: 'NotInfo',
});
expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist');
expect(plistPath).toBe('/app/ios/qwert/NotInfo.plist');
});
});
function initBareWorkflowProject({
appVersion = '1.0.0',
buildNumber = '1',
expoConfig = {},
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 = {
name: 'myproject',
slug: 'myproject',
version: '1.0.0',
ios: {
buildNumber: '1',
...expoConfig.ios,
},
...expoConfig,
};
vol.fromJSON(
{
'./app.json': JSON.stringify({
expo: fakeExp,
}),
'./ios/myproject.xcodeproj/project.pbxproj': '',
[`./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">
<plist version="1.0">
@@ -380,7 +403,7 @@ function initBareWorkflowProject({
</dict>
</plist>`,
},
'/repo'
'/app'
);
return fakeExp;
@@ -402,7 +425,7 @@ function initManagedProject(): ExpoConfig {
expo: fakeExp,
}),
},
'/repo'
'/app'
);
return fakeExp;
+7 -2
View File
@@ -10,7 +10,11 @@ import { findApplicationTarget, resolveTargetsAsync } from '../../project/ios/ta
import { BuildRequestSender, JobData, prepareBuildRequestForPlatformAsync } from '../build';
import { BuildContext, CommonContext, IosBuildContext } from '../context';
import { transformMetadata } from '../graphql';
import { checkGoogleServicesFileAsync, checkNodeEnvVariable } from '../validate';
import {
checkGoogleServicesFileAsync,
checkNodeEnvVariable,
validatePNGsForManagedProjectAsync,
} from '../validate';
import { ensureIosCredentialsAsync } from './credentials';
import { transformJob } from './graphql';
import { prepareJobAsync } from './prepareJob';
@@ -23,11 +27,12 @@ export async function createIosContextAsync(
const { buildProfile } = ctx;
if (ctx.workflow === Workflow.MANAGED) {
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp, ctx.user);
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx);
}
checkNodeEnvVariable(ctx);
await checkGoogleServicesFileAsync(ctx);
await validatePNGsForManagedProjectAsync(ctx);
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
{
+25 -1
View File
@@ -1,5 +1,7 @@
import { Platform } from '@expo/eas-build-job';
import { BuildProfile } from '@expo/eas-json';
import { CredentialsContext } from '../../credentials/context';
import IosCredentialsProvider from '../../credentials/ios/IosCredentialsProvider';
import { getAppFromContextAsync } from '../../credentials/ios/actions/BuildCredentialsUtils';
import { IosCredentials, Target } from '../../credentials/ios/types';
@@ -31,6 +33,28 @@ export async function ensureIosCredentialsAsync(
};
}
export async function ensureIosCredentialsForBuildResignAsync(
credentialsCtx: CredentialsContext,
targets: Target[],
buildProfile: BuildProfile<Platform.IOS>
): Promise<CredentialsResult<IosCredentials>> {
const provider = new IosCredentialsProvider(credentialsCtx, {
app: await getAppFromContextAsync(credentialsCtx),
targets,
distribution: 'internal',
enterpriseProvisioning: buildProfile.enterpriseProvisioning,
});
const { credentialsSource } = buildProfile;
logCredentialsSource(credentialsSource, Platform.IOS);
return {
credentials: await provider.getCredentialsAsync(credentialsSource),
source: credentialsSource,
};
}
function shouldProvideCredentials(buildCtx: BuildContext<Platform.IOS>): boolean {
return !buildCtx.buildProfile.simulator;
const isCustomBuild = buildCtx.buildProfile.config !== undefined;
return !buildCtx.buildProfile.simulator && !isCustomBuild;
}
+12 -4
View File
@@ -2,16 +2,22 @@ import { Ios } from '@expo/eas-build-job';
import nullthrows from 'nullthrows';
import { IosJobInput, IosJobSecretsInput } from '../../graphql/generated';
import { transformProjectArchive, transformWorkflow } from '../graphql';
import {
transformBuildMode,
transformBuildTrigger,
transformProjectArchive,
transformWorkflow,
} from '../graphql';
export function transformJob(job: Ios.Job): IosJobInput {
return {
type: transformWorkflow(job.type),
triggeredBy: transformBuildTrigger(job.triggeredBy),
projectArchive: transformProjectArchive(job.projectArchive),
projectRootDirectory: job.projectRootDirectory,
projectRootDirectory: nullthrows(job.projectRootDirectory),
releaseChannel: job.releaseChannel,
updates: job.updates,
secrets: transformIosSecrets(job.secrets),
secrets: job.secrets ? transformIosSecrets(job.secrets) : undefined,
builderEnvironment: job.builderEnvironment,
cache: job.cache,
version: job.version?.buildNumber ? { buildNumber: job.version.buildNumber } : undefined,
@@ -23,10 +29,12 @@ export function transformJob(job: Ios.Job): IosJobInput {
developmentClient: job.developmentClient,
simulator: job.simulator,
experimental: job.experimental,
mode: transformBuildMode(job.mode),
customBuildConfig: job.customBuildConfig,
};
}
function transformIosSecrets(secrets: {
export function transformIosSecrets(secrets: {
buildCredentials?: Ios.BuildCredentials;
}): IosJobSecretsInput {
const buildCredentials: IosJobSecretsInput['buildCredentials'] = [];
+61 -23
View File
@@ -1,8 +1,20 @@
import { ArchiveSource, Ios, Job, Platform, sanitizeJob } from '@expo/eas-build-job';
import {
ArchiveSource,
BuildMode,
BuildTrigger,
Ios,
Job,
Platform,
sanitizeJob,
} from '@expo/eas-build-job';
import { BuildProfile } from '@expo/eas-json';
import nullthrows from 'nullthrows';
import path from 'path';
import slash from 'slash';
import { IosCredentials, TargetCredentials } from '../../credentials/ios/types';
import { IosJobSecretsInput } from '../../graphql/generated';
import { getCustomBuildConfigPath } from '../../project/customBuildConfig';
import { getUsername } from '../../project/projectUtils';
import { getVcsClient } from '../../vcs';
import { BuildContext } from '../context';
@@ -15,18 +27,18 @@ interface JobData {
const cacheDefaults = {
disabled: false,
customPaths: [],
cacheDefaultPaths: true,
paths: [],
};
export async function prepareJobAsync(
ctx: BuildContext<Platform.IOS>,
jobData: JobData
): Promise<Job> {
const username = getUsername(ctx.exp, ctx.user);
const buildProfile: BuildProfile<Platform.IOS> = ctx.buildProfile;
const projectRootDirectory =
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
const username = getUsername(ctx.exp, ctx.user);
const buildCredentials: Ios.Job['secrets']['buildCredentials'] = {};
const buildCredentials: Ios.BuildSecrets['buildCredentials'] = {};
if (jobData.credentials) {
const targetNames = Object.keys(jobData.credentials);
for (const targetName of targetNames) {
@@ -34,38 +46,41 @@ export async function prepareJobAsync(
}
}
const maybeCustomBuildConfigPath = buildProfile.config
? getCustomBuildConfigPath(buildProfile.config)
: undefined;
const job: Ios.Job = {
type: ctx.workflow,
platform: Platform.IOS,
projectArchive: jobData.projectArchive,
projectRootDirectory,
builderEnvironment: {
image: ctx.buildProfile.image,
node: ctx.buildProfile.node,
yarn: ctx.buildProfile.yarn,
bundler: ctx.buildProfile.bundler,
cocoapods: ctx.buildProfile.cocoapods,
fastlane: ctx.buildProfile.fastlane,
expoCli: ctx.buildProfile.expoCli,
env: ctx.buildProfile.env,
image: buildProfile.image,
node: buildProfile.node,
yarn: buildProfile.yarn,
bundler: buildProfile.bundler,
cocoapods: buildProfile.cocoapods,
fastlane: buildProfile.fastlane,
expoCli: buildProfile.expoCli,
env: buildProfile.env,
},
cache: {
...cacheDefaults,
...ctx.buildProfile.cache,
...buildProfile.cache,
clear: ctx.clearCache,
},
secrets: {
buildCredentials,
},
releaseChannel: ctx.buildProfile.releaseChannel,
updates: { channel: ctx.buildProfile.channel },
developmentClient: ctx.buildProfile.developmentClient,
simulator: ctx.buildProfile.simulator,
releaseChannel: buildProfile.releaseChannel,
updates: { channel: buildProfile.channel },
developmentClient: buildProfile.developmentClient,
simulator: buildProfile.simulator,
scheme: jobData.buildScheme,
buildConfiguration: ctx.buildProfile.buildConfiguration,
applicationArchivePath:
ctx.buildProfile.applicationArchivePath ?? ctx.buildProfile.artifactPath,
buildArtifactPaths: ctx.buildProfile.buildArtifactPaths,
buildConfiguration: buildProfile.buildConfiguration,
applicationArchivePath: buildProfile.applicationArchivePath ?? buildProfile.artifactPath,
buildArtifactPaths: buildProfile.buildArtifactPaths,
username,
...(ctx.ios.buildNumberOverride && {
version: {
@@ -73,12 +88,35 @@ export async function prepareJobAsync(
},
}),
experimental: {
prebuildCommand: ctx.buildProfile.prebuildCommand,
prebuildCommand: buildProfile.prebuildCommand,
},
mode: buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
triggeredBy: BuildTrigger.EAS_CLI,
...(maybeCustomBuildConfigPath && {
customBuildConfig: {
path: maybeCustomBuildConfigPath,
},
}),
};
return sanitizeJob(job);
}
export function prepareCredentialsToResign(credentials: IosCredentials): IosJobSecretsInput {
const buildCredentials: IosJobSecretsInput['buildCredentials'] = [];
for (const targetName of Object.keys(credentials ?? {})) {
buildCredentials.push({
targetName,
provisioningProfileBase64: nullthrows(credentials?.[targetName].provisioningProfile),
distributionCertificate: {
dataBase64: nullthrows(credentials?.[targetName].distributionCertificate.certificateP12),
password: nullthrows(credentials?.[targetName].distributionCertificate.certificatePassword),
},
});
}
return { buildCredentials };
}
function prepareTargetCredentials(targetCredentials: TargetCredentials): Ios.TargetCredentials {
return {
provisioningProfileBase64: targetCredentials.provisioningProfile,
+17 -3
View File
@@ -22,6 +22,8 @@ import { readPlistAsync, writePlistAsync } from '../../utils/plist';
import { updateAppJsonConfigAsync } from '../utils/appJson';
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
const SHORT_VERSION_REGEX = /^\d+.\d+.\d+$/;
export enum BumpStrategy {
APP_VERSION,
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(
projectDir: string,
exp: ExpoConfig,
@@ -105,11 +115,15 @@ export async function readShortVersionAsync(
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
if (workflow === Workflow.GENERIC) {
const infoPlist = await readInfoPlistAsync(projectDir, buildSettings);
return (
const shortVersion =
infoPlist.CFBundleShortVersionString &&
evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings)
);
evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings);
validateShortVersion(shortVersion);
return shortVersion;
} else {
validateShortVersion(exp.version);
return exp.version;
}
}
+20 -2
View File
@@ -6,10 +6,28 @@ import semver from 'semver';
import { ora } from '../ora';
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
const PLUGIN_PACKAGE_VERSION = '0.0.115';
const PLUGIN_PACKAGE_VERSION = '1.0.27';
export enum LocalBuildMode {
/**
* Local build that users can run on their own machines. Instead
* of sending build request to EAS Servers it's passing it as an argument
* to local-build-plugin, that will run the build locally.
*
* Triggered when running `eas build --local`.
*/
LOCAL_BUILD_PLUGIN = 'local-build-plugin',
/**
* Type of local build that is not accessible to users directly. When
* cloud build is triggered by git based integration, we are running
* in this mode. Instead of sending build request to EAS Servers it's
* printing it to the stdout as JSON, so EAS Build worker can read it.
*/
INTERNAL = 'internal',
}
export interface LocalBuildOptions {
enable: boolean;
localBuildMode?: LocalBuildMode;
skipCleanup?: boolean;
skipNativeBuild?: boolean;
artifactsDir?: string;
+11 -7
View File
@@ -1,5 +1,5 @@
import { Updates } from '@expo/config-plugins';
import { Metadata, Platform, sanitizeMetadata } from '@expo/eas-build-job';
import { BuildMode, Metadata, Platform, sanitizeMetadata } from '@expo/eas-build-job';
import { IosEnterpriseProvisioning } from '@expo/eas-json';
import fs from 'fs-extra';
import resolveFrom from 'resolve-from';
@@ -19,6 +19,7 @@ import { getVcsClient } from '../vcs';
import { maybeResolveVersionsAsync as maybeResolveAndroidVersionsAsync } from './android/version';
import { BuildContext } from './context';
import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios/version';
import { LocalBuildMode } from './local';
export async function collectMetadataAsync<T extends Platform>(
ctx: BuildContext<T>
@@ -37,7 +38,7 @@ export async function collectMetadataAsync<T extends Platform>(
credentialsSource: ctx.buildProfile.credentialsSource,
sdkVersion: ctx.exp.sdkVersion,
runtimeVersion: Updates.getRuntimeVersionNullable(ctx.exp, ctx.platform) ?? undefined,
reactNativeVersion: await getReactNativeVersionAsync(ctx),
reactNativeVersion: await getReactNativeVersionAsync(ctx.projectDir),
...channelOrReleaseChannel,
distribution,
appName: ctx.exp.name,
@@ -47,7 +48,10 @@ export async function collectMetadataAsync<T extends Platform>(
gitCommitMessage: truncateGitCommitMessage(
(await vcsClient.getLastCommitMessageAsync()) ?? undefined
),
isGitWorkingTreeDirty: await vcsClient.hasUncommittedChangesAsync(),
isGitWorkingTreeDirty:
ctx.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL
? false
: await vcsClient.hasUncommittedChangesAsync(),
username: getUsername(ctx.exp, ctx.user),
message: ctx.message,
...(ctx.platform === Platform.IOS && {
@@ -57,6 +61,8 @@ export async function collectMetadataAsync<T extends Platform>(
}),
runWithNoWaitFlag: ctx.noWait,
runFromCI: ctx.runFromCI,
buildMode: ctx.buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
customWorkflowName: ctx.customBuildConfigMetadata?.workflowName,
};
return sanitizeMetadata(metadata);
}
@@ -155,11 +161,9 @@ async function getNativeChannelAsync<T extends Platform>(
return undefined;
}
async function getReactNativeVersionAsync(
ctx: BuildContext<Platform>
): Promise<string | undefined> {
export async function getReactNativeVersionAsync(projectDir: string): Promise<string | undefined> {
try {
const reactNativePackageJsonPath = resolveFrom(ctx.projectDir, 'react-native/package.json');
const reactNativePackageJsonPath = resolveFrom(projectDir, 'react-native/package.json');
return (await fs.readJson(reactNativePackageJsonPath)).version;
} catch (err) {
Log.debug('Failed to resolve react-native version:');
+122 -2
View File
@@ -2,11 +2,15 @@ import chalk from 'chalk';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { PaginatedQueryOptions } from '../commandUtils/pagination';
import { BuildFilter, BuildFragment } from '../graphql/generated';
import { AppPlatform, BuildFilter, BuildFragment } from '../graphql/generated';
import { BuildQuery } from '../graphql/queries/BuildQuery';
import Log from '../log';
import { fromNow } from '../utils/date';
import { printJsonOnlyOutput } from '../utils/json';
import { paginatedQueryWithConfirmPromptAsync } from '../utils/queries';
import {
paginatedQueryWithConfirmPromptAsync,
paginatedQueryWithSelectPromptAsync,
} from '../utils/queries';
import { formatGraphQLBuild } from './utils/formatBuild';
export const BUILDS_LIMIT = 50;
@@ -53,6 +57,122 @@ export async function listAndRenderBuildsOnAppAsync(
}
}
export async function listAndSelectBuildOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{
projectId,
title,
filter,
paginatedQueryOptions,
selectPromptDisabledFunction,
selectPromptWarningMessage,
}: {
projectId: string;
title: string;
filter?: BuildFilter;
paginatedQueryOptions: PaginatedQueryOptions;
selectPromptDisabledFunction?: (build: BuildFragment) => boolean;
selectPromptWarningMessage?: string;
}
): Promise<BuildFragment | null> {
if (paginatedQueryOptions.nonInteractive) {
throw new Error('Unable to select a build in non-interactive mode.');
} else {
const selectedBuild = await paginatedQueryWithSelectPromptAsync({
limit: paginatedQueryOptions.limit ?? BUILDS_LIMIT,
offset: paginatedQueryOptions.offset,
queryToPerform: (limit, offset) =>
BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
appId: projectId,
limit,
offset,
filter,
}),
promptOptions: {
title,
getIdentifierForQueryItem: build => build.id,
makePartialChoiceObject: createBuildToPartialChoiceMaker(selectPromptDisabledFunction),
selectPromptWarningMessage,
},
});
return selectedBuild ?? null;
}
}
function createBuildToPartialChoiceMaker(
selectPromptDisabledFunction?: (build: BuildFragment) => boolean
) {
return (
build: BuildFragment
): {
title: string;
description: string;
disabled?: boolean;
} => {
const splitCommitMessage = build.gitCommitMessage?.split('\n');
const formattedCommitData =
build.gitCommitHash && splitCommitMessage && splitCommitMessage.length > 0
? `${build.gitCommitHash.slice(0, 7)} "${chalk.bold(
splitCommitMessage[0] + (splitCommitMessage.length > 1 ? '…' : '')
)}"`
: null;
const descriptionItems: { name: string; value: string | null }[] = [
{ name: 'Version', value: build.appVersion ? chalk.bold(build.appVersion) : null },
{ name: 'Commit', value: formattedCommitData },
{
name: 'Message',
value: build.message
? chalk.bold(
build.message.length > 200 ? `${build.message.slice(0, 200)}...` : build.message
)
: null,
},
{
name: build.platform === AppPlatform.Ios ? 'Build number' : 'Version code',
value: build.appBuildVersion ? chalk.bold(build.appBuildVersion) : null,
},
{ name: 'Profile', value: build.buildProfile ? chalk.bold(build.buildProfile) : null },
];
const filteredDescriptionArray: string[] = descriptionItems
.filter(item => item.value)
.map(item => `${chalk.bold(item.name)}: ${item.value}`);
return {
title: `${chalk.bold(`ID:`)} ${build.id} (${chalk.bold(
`${fromNow(new Date(build.completedAt))} ago`
)})`,
description: filteredDescriptionArray.length > 0 ? filteredDescriptionArray.join('\n') : '',
disabled: selectPromptDisabledFunction?.(build),
};
};
}
export async function getLatestBuildAsync(
graphqlClient: ExpoGraphqlClient,
{
projectId,
filter,
}: {
projectId: string;
filter?: BuildFilter;
}
): Promise<BuildFragment | null> {
const builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
appId: projectId,
limit: 1,
offset: 0,
filter,
});
if (builds.length === 0) {
return null;
}
return builds[0];
}
function renderPageOfBuilds({
builds,
projectDisplayName,
+132 -59
View File
@@ -5,8 +5,10 @@ import {
EasJson,
EasJsonAccessor,
EasJsonUtils,
ResourceClass,
SubmitProfile,
} from '@expo/eas-json';
import assert from 'assert';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
@@ -16,27 +18,38 @@ import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGr
import {
AppPlatform,
BuildFragment,
BuildResourceClass,
BuildStatus,
BuildWithSubmissionsFragment,
SubmissionFragment,
} from '../graphql/generated';
import { BuildQuery } from '../graphql/queries/BuildQuery';
import { toAppPlatform, toPlatform } from '../graphql/types/AppPlatform';
import Log from '../log';
import Log, { learnMore } from '../log';
import {
RequestedPlatform,
appPlatformDisplayNames,
appPlatformEmojis,
toPlatforms,
} from '../platform';
import {
CustomBuildConfigMetadata,
validateCustomBuildConfigAsync,
} from '../project/customBuildConfig';
import { checkExpoSdkIsSupportedAsync } from '../project/expoSdk';
import { validateMetroConfigForManagedWorkflowAsync } from '../project/metroConfig';
import { validateAppVersionRuntimePolicySupportAsync } from '../project/projectUtils';
import {
installExpoUpdatesAsync,
isExpoUpdatesInstalledAsDevDependency,
isExpoUpdatesInstalledOrAvailable,
validateAppVersionRuntimePolicySupportAsync,
} from '../project/projectUtils';
import {
validateAppConfigForRemoteVersionSource,
validateBuildProfileVersionSettings,
} from '../project/remoteVersionSource';
import { confirmAsync } from '../prompts';
import { runAsync } from '../run/run';
import { isRunnableOnSimulatorOrEmulator } from '../run/utils';
import { createSubmissionContextAsync } from '../submit/context';
import {
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish,
@@ -46,17 +59,18 @@ import {
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
import { validateBuildProfileConfigMatchesProjectConfigAsync } from '../update/utils';
import { Actor } from '../user/User';
import { downloadAndMaybeExtractAppAsync } from '../utils/download';
import { truthy } from '../utils/expodash/filter';
import { printJsonOnlyOutput } from '../utils/json';
import { ProfileData, getProfilesAsync } from '../utils/profiles';
import { getVcsClient } from '../vcs';
import { prepareAndroidBuildAsync } from './android/build';
import { BuildRequestSender, waitForBuildEndAsync } from './build';
import { BuildRequestSender, MaybeBuildFragment, waitForBuildEndAsync } from './build';
import { ensureProjectConfiguredAsync } from './configure';
import { BuildContext } from './context';
import { createBuildContextAsync } from './createContext';
import { prepareIosBuildAsync } from './ios/build';
import { LocalBuildOptions } from './local';
import { UserInputResourceClass } from './types';
import { ensureExpoDevClientInstalledForDevClientBuildsAsync } from './utils/devClient';
import { printBuildResults, printLogsUrls } from './utils/printBuildInfo';
import { ensureRepoIsCleanAsync } from './utils/repository';
@@ -74,54 +88,17 @@ export interface BuildFlags {
autoSubmit: boolean;
submitProfile?: string;
localBuildOptions: LocalBuildOptions;
userInputResourceClass?: UserInputResourceClass;
resourceClass?: ResourceClass;
message?: string;
}
const iosUserInputResourceClassToBuildResourceClassMapping: Record<
UserInputResourceClass,
BuildResourceClass
> = {
[UserInputResourceClass.DEFAULT]: BuildResourceClass.IosDefault,
[UserInputResourceClass.LARGE]: BuildResourceClass.IosLarge,
[UserInputResourceClass.M1_EXPERIMENTAL]: BuildResourceClass.IosM1Large,
};
const androidUserInputResourceClassToBuildResourceClassMapping: Record<
Exclude<UserInputResourceClass, UserInputResourceClass.M1_EXPERIMENTAL>,
BuildResourceClass
> = {
[UserInputResourceClass.DEFAULT]: BuildResourceClass.AndroidDefault,
[UserInputResourceClass.LARGE]: BuildResourceClass.AndroidLarge,
};
function resolveResourceClass(
platform: Platform,
resourceClassInput: UserInputResourceClass
): BuildResourceClass {
if (platform !== Platform.IOS && resourceClassInput === UserInputResourceClass.M1_EXPERIMENTAL) {
throw new Error(
`Resource class ${UserInputResourceClass.M1_EXPERIMENTAL} is only available for iOS builds`
);
}
return platform === Platform.ANDROID
? androidUserInputResourceClassToBuildResourceClassMapping[
resourceClassInput as Exclude<
UserInputResourceClass,
UserInputResourceClass.M1_EXPERIMENTAL
>
]
: iosUserInputResourceClassToBuildResourceClassMapping[resourceClassInput];
}
export async function runBuildAndSubmitAsync(
graphqlClient: ExpoGraphqlClient,
analytics: Analytics,
projectDir: string,
flags: BuildFlags,
actor: Actor,
getDynamicProjectConfigAsync: DynamicConfigContextFn
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn
): Promise<void> {
await getVcsClient().ensureRepoExistsAsync();
await ensureRepoIsCleanAsync(flags.nonInteractive);
@@ -130,7 +107,7 @@ export async function runBuildAndSubmitAsync(
projectDir,
nonInteractive: flags.nonInteractive,
});
const easJsonAccessor = new EasJsonAccessor(projectDir);
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
const easJsonCliConfig: EasJson['cli'] =
(await EasJsonUtils.getCliConfigAsync(easJsonAccessor)) ?? {};
@@ -141,6 +118,13 @@ export async function runBuildAndSubmitAsync(
platforms,
profileName: flags.profile ?? undefined,
});
Log.log(
`Loaded "env" configuration for the "${buildProfiles[0].profileName}" profile: ${
buildProfiles[0].profile.env
? Object.keys(buildProfiles[0].profile.env).join(', ')
: 'no environment variables specified'
}. ${learnMore('https://docs.expo.dev/build-reference/variables/')}`
);
await ensureExpoDevClientInstalledForDevClientBuildsAsync({
projectDir,
@@ -148,8 +132,14 @@ export async function runBuildAndSubmitAsync(
buildProfiles,
});
const customBuildConfigMetadataByPlatform: { [p in AppPlatform]?: CustomBuildConfigMetadata } =
{};
for (const buildProfile of buildProfiles) {
validateBuildProfileVersionSettings(buildProfile, easJsonCliConfig);
const maybeMetadata = await validateCustomBuildConfigAsync(projectDir, buildProfile.profile);
if (maybeMetadata) {
customBuildConfigMetadataByPlatform[toAppPlatform(buildProfile.platform)] = maybeMetadata;
}
}
const startedBuilds: {
@@ -159,28 +149,26 @@ export async function runBuildAndSubmitAsync(
const buildCtxByPlatform: { [p in AppPlatform]?: BuildContext<Platform> } = {};
for (const buildProfile of buildProfiles) {
const platform = toAppPlatform(buildProfile.platform);
const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({
projectDir,
flags,
moreBuilds: platforms.length > 1,
buildProfile,
resourceClass: resolveResourceClass(
buildProfile.platform,
flags.userInputResourceClass ?? UserInputResourceClass.DEFAULT
),
easJsonCliConfig,
actor,
graphqlClient,
analytics,
getDynamicProjectConfigAsync,
getDynamicPrivateProjectConfigAsync,
customBuildConfigMetadata: customBuildConfigMetadataByPlatform[platform],
});
if (maybeBuild) {
startedBuilds.push({ build: maybeBuild, buildProfile });
}
buildCtxByPlatform[toAppPlatform(buildProfile.platform)] = buildCtx;
buildCtxByPlatform[platform] = buildCtx;
}
if (flags.localBuildOptions.enable) {
if (flags.localBuildOptions.localBuildMode) {
return;
}
@@ -234,14 +222,21 @@ export async function runBuildAndSubmitAsync(
const builds = await waitForBuildEndAsync(graphqlClient, {
buildIds: startedBuilds.map(({ build }) => build.id),
accountName,
projectDir,
nonInteractive: flags.nonInteractive,
});
if (!flags.json) {
printBuildResults(builds);
}
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);
if (haveAllBuildsFailedOrCanceled || !flags.autoSubmit) {
if (flags.json) {
printJsonOnlyOutput(builds);
@@ -270,27 +265,27 @@ async function prepareAndStartBuildAsync({
flags,
moreBuilds,
buildProfile,
resourceClass,
easJsonCliConfig,
actor,
graphqlClient,
analytics,
getDynamicProjectConfigAsync,
getDynamicPrivateProjectConfigAsync,
customBuildConfigMetadata,
}: {
projectDir: string;
flags: BuildFlags;
moreBuilds: boolean;
buildProfile: ProfileData<'build'>;
resourceClass: BuildResourceClass;
easJsonCliConfig: EasJson['cli'];
actor: Actor;
graphqlClient: ExpoGraphqlClient;
analytics: Analytics;
getDynamicProjectConfigAsync: DynamicConfigContextFn;
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
customBuildConfigMetadata?: CustomBuildConfigMetadata;
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
const buildCtx = await createBuildContextAsync({
buildProfileName: buildProfile.profileName,
resourceClass,
resourceClassFlag: flags.resourceClass,
clearCache: flags.clearCache,
buildProfile: buildProfile.profile,
nonInteractive: flags.nonInteractive,
@@ -303,7 +298,8 @@ async function prepareAndStartBuildAsync({
actor,
graphqlClient,
analytics,
getDynamicProjectConfigAsync,
getDynamicPrivateProjectConfigAsync,
customBuildConfigMetadata,
});
if (moreBuilds) {
@@ -322,6 +318,14 @@ async function prepareAndStartBuildAsync({
buildCtx.projectId,
flags.nonInteractive
);
if (buildProfile.profile.channel) {
await validateExpoUpdatesInstalledAsProjectDependencyAsync({
projectDir,
sdkVersion: buildCtx.exp.sdkVersion,
nonInteractive: flags.nonInteractive,
buildProfile,
});
}
await validateAppVersionRuntimePolicySupportAsync(buildCtx.projectDir, buildCtx.exp);
if (easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE) {
@@ -409,3 +413,72 @@ function exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds: (BuildFragment | nul
process.exit(1);
}
}
async function downloadAndRunAsync(build: BuildFragment): Promise<void> {
assert(build.artifacts?.applicationArchiveUrl);
const buildPath = await downloadAndMaybeExtractAppAsync(
build.artifacts.applicationArchiveUrl,
build.platform
);
await runAsync(buildPath, build.platform);
}
async function maybeDownloadAndRunSimulatorBuildsAsync(
builds: MaybeBuildFragment[],
flags: BuildFlags
): Promise<void> {
const simBuilds = builds.filter(truthy).filter(isRunnableOnSimulatorOrEmulator);
if (simBuilds.length > 0 && !flags.autoSubmit && !flags.nonInteractive) {
for (const simBuild of simBuilds) {
if (simBuild.platform === AppPlatform.Android || process.platform === 'darwin') {
Log.newLine();
const confirm = await confirmAsync({
message: `Install and run the ${
simBuild.platform === AppPlatform.Android ? 'Android' : 'iOS'
} build on ${simBuild.platform === AppPlatform.Android ? 'an emulator' : 'a simulator'}?`,
});
if (confirm) {
await downloadAndRunAsync(simBuild);
}
}
}
}
}
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 -10
View File
@@ -2,6 +2,7 @@ export enum BuildStatus {
NEW = 'new',
IN_QUEUE = 'in-queue',
IN_PROGRESS = 'in-progress',
PENDING_CANCEL = 'pending-cancel',
ERRORED = 'errored',
FINISHED = 'finished',
CANCELED = 'canceled',
@@ -12,13 +13,3 @@ export enum BuildDistributionType {
INTERNAL = 'internal',
SIMULATOR = 'simulator',
}
export enum UserInputResourceClass {
DEFAULT = 'default',
LARGE = 'large',
/**
* @experimental
* This resource class is not yet ready to be used in production. For testing purposes only. Might be deprecated / deleted at any time.
*/
M1_EXPERIMENTAL = 'm1-experimental',
}
@@ -27,6 +27,7 @@ export function formatGraphQLBuild(build: BuildFragment): string {
return chalk.blue('in queue');
case GraphQLBuildStatus.InProgress:
return chalk.blue('in progress');
case GraphQLBuildStatus.PendingCancel:
case GraphQLBuildStatus.Canceled:
return chalk.gray('canceled');
case GraphQLBuildStatus.Finished:
@@ -38,6 +39,10 @@ export function formatGraphQLBuild(build: BuildFragment): string {
}
},
},
{
label: 'Message',
value: build.message,
},
{
label: 'Distribution',
value: build.distribution?.toLowerCase(),
@@ -86,6 +91,7 @@ export function formatGraphQLBuild(build: BuildFragment): string {
case GraphQLBuildStatus.InQueue:
case GraphQLBuildStatus.InProgress:
return '<in progress>';
case GraphQLBuildStatus.PendingCancel:
case GraphQLBuildStatus.Canceled:
case GraphQLBuildStatus.Errored:
return null;
@@ -1,5 +1,4 @@
import assert from 'assert';
import chalk from 'chalk';
import indentString from 'indent-string';
import qrcodeTerminal from 'qrcode-terminal';
@@ -12,27 +11,52 @@ import {
EasBuildDeprecationInfo,
EasBuildDeprecationInfoType,
} from '../../graphql/generated';
import Log, { learnMore } from '../../log';
import Log, { learnMore, link } from '../../log';
import { appPlatformDisplayNames, appPlatformEmojis } from '../../platform';
import { getBuildLogsUrl, getInternalDistributionInstallUrl } from './url';
function terminalLinkFallback(url: string, text: string): string {
return `${text} (${url})`;
}
const errorCodeToErrorMessageOverride: Record<string, (build: BuildFragment) => string> = {
EAS_BUILD_UNKNOWN_FASTLANE_ERROR: build =>
`The ${link(getBuildLogsUrl(build, 'run-fastlane'), {
text: '"Run fastlane"',
fallback: terminalLinkFallback(getBuildLogsUrl(build, 'run-fastlane'), '"Run fastlane"'),
})} step failed with an unknown error. Refer to the ${link(
getBuildLogsUrl(build, 'xcode-logs'),
{
text: '"Xcode logs"',
fallback: terminalLinkFallback(getBuildLogsUrl(build, 'xcode-logs'), '"Xcode logs"'),
}
)} phase for additional, more detailed logs`,
EAS_BUILD_UNKNOWN_GRADLE_ERROR: build =>
`Gradle build failed with unknown error. See logs for the ${link(
getBuildLogsUrl(build, 'run-gradlew'),
{
text: '"Run gradlew"',
fallback: terminalLinkFallback(getBuildLogsUrl(build, 'run-gradlew'), '"Run gradlew"'),
}
)} phase for more information.`,
};
export function printLogsUrls(builds: BuildFragment[]): void {
if (builds.length === 1) {
Log.log(`Build details: ${chalk.underline(getBuildLogsUrl(builds[0]))}`);
Log.log(`Build details: ${link(getBuildLogsUrl(builds[0]))}`);
} else {
builds.forEach(build => {
const logsUrl = getBuildLogsUrl(build);
Log.log(
`${appPlatformEmojis[build.platform]} ${
appPlatformDisplayNames[build.platform]
} build details: ${chalk.underline(logsUrl)}`
} build details: ${link(logsUrl)}`
);
});
}
}
export function printBuildResults(builds: (BuildFragment | null)[]): void {
Log.newLine();
if (builds.length === 1) {
const [build] = builds;
assert(build, 'Build should be defined');
@@ -43,8 +67,8 @@ export function printBuildResults(builds: (BuildFragment | null)[]): void {
}
function printBuildResult(build: BuildFragment): void {
Log.addNewLineIfNone();
if (build.status === BuildStatus.Errored) {
Log.addNewLineIfNone();
const userError = build.error;
Log.error(
`${appPlatformEmojis[build.platform]} ${
@@ -52,11 +76,12 @@ function printBuildResult(build: BuildFragment): void {
} build failed${userError ? ':' : ''}`
);
if (userError) {
printUserError(userError);
printUserError(userError, build);
}
return;
}
if (build.status === BuildStatus.Canceled) {
if ([BuildStatus.Canceled, BuildStatus.PendingCancel].includes(build.status)) {
Log.addNewLineIfNone();
Log.error(
`${appPlatformEmojis[build.platform]} ${
appPlatformDisplayNames[build.platform]
@@ -66,6 +91,7 @@ function printBuildResult(build: BuildFragment): void {
}
if (build.distribution === DistributionType.Internal) {
Log.addNewLineIfNone();
const logsUrl = getBuildLogsUrl(build);
// It's tricky to install the .apk file directly on Android so let's fallback
// to the build details page and let people press the button to download there
@@ -79,13 +105,18 @@ function printBuildResult(build: BuildFragment): void {
appPlatformDisplayNames[build.platform]
} devices (or scan the QR code) to install the app:`
);
Log.log(`${chalk.underline(logsUrl)}`);
Log.log(`${link(logsUrl)}`);
} else {
// TODO: it looks like buildUrl could possibly be undefined, based on the code below.
// we should account for this case better if it is possible
const url = build.artifacts?.buildUrl ?? '';
Log.log(`${appPlatformEmojis[build.platform]} ${appPlatformDisplayNames[build.platform]} app:`);
Log.log(`${chalk.underline(url)}`);
const url = build.artifacts?.buildUrl;
if (url) {
Log.addNewLineIfNone();
Log.log(
`${appPlatformEmojis[build.platform]} ${appPlatformDisplayNames[build.platform]} app:`
);
Log.log(link(url));
}
}
}
@@ -106,9 +137,22 @@ export function printDeprecationWarnings(deprecationInfo?: EasBuildDeprecationIn
}
}
export function printUserError(error: BuildError): void {
Log.error(error.message);
if (error.docsUrl) {
Log.error(learnMore(error.docsUrl, { dim: false }));
export function printUserError(error: BuildError, build: BuildFragment): void {
const maybeErrorMessageOverride = maybeGetErrorMessageOverride(error, build);
if (maybeErrorMessageOverride) {
Log.error(maybeErrorMessageOverride);
} else {
Log.error(error.message);
if (error.docsUrl) {
Log.error(learnMore(error.docsUrl, { dim: false }));
}
}
}
function maybeGetErrorMessageOverride(error: BuildError, build: BuildFragment): string | null {
if (!(error.errorCode in errorCodeToErrorMessageOverride)) {
return null;
}
return errorCodeToErrorMessageOverride[error.errorCode](build);
}
@@ -0,0 +1,108 @@
import { Platform } from '@expo/eas-build-job';
import { BuildProfile, ResourceClass } from '@expo/eas-json';
import chalk from 'chalk';
import { BuildResourceClass } from '../../graphql/generated';
import Log, { link } from '../../log';
type AndroidResourceClass = Exclude<
ResourceClass,
| ResourceClass.M1_EXPERIMENTAL
| ResourceClass.M1_MEDIUM
| ResourceClass.M1_LARGE
| ResourceClass.INTEL_MEDIUM
| ResourceClass.M_MEDIUM
| ResourceClass.M_LARGE
>;
const iosResourceClassToBuildResourceClassMapping: Record<ResourceClass, BuildResourceClass> = {
[ResourceClass.DEFAULT]: BuildResourceClass.IosDefault,
[ResourceClass.LARGE]: BuildResourceClass.IosLarge,
[ResourceClass.M1_EXPERIMENTAL]: BuildResourceClass.IosMMedium,
[ResourceClass.M1_MEDIUM]: BuildResourceClass.IosMMedium,
[ResourceClass.M1_LARGE]: BuildResourceClass.IosMLarge,
[ResourceClass.INTEL_MEDIUM]: BuildResourceClass.IosIntelMedium,
[ResourceClass.MEDIUM]: BuildResourceClass.IosMedium,
[ResourceClass.M_MEDIUM]: BuildResourceClass.IosMMedium,
[ResourceClass.M_LARGE]: BuildResourceClass.IosMLarge,
};
const androidResourceClassToBuildResourceClassMapping: Record<
AndroidResourceClass,
BuildResourceClass
> = {
[ResourceClass.DEFAULT]: BuildResourceClass.AndroidDefault,
[ResourceClass.LARGE]: BuildResourceClass.AndroidLarge,
[ResourceClass.MEDIUM]: BuildResourceClass.AndroidMedium,
};
export async function resolveBuildResourceClassAsync<T extends Platform>(
profile: BuildProfile<T>,
platform: Platform,
resourceClassFlag?: ResourceClass
): Promise<BuildResourceClass> {
const profileResourceClass = profile.resourceClass;
if (profileResourceClass && resourceClassFlag && resourceClassFlag !== profileResourceClass) {
Log.warn(
`Build profile specifies the "${profileResourceClass}" resource class but you passed "${resourceClassFlag}" to --resource-class.\nUsing "${resourceClassFlag}" as the override.`
);
}
const selectedResourceClass = resourceClassFlag ?? profileResourceClass;
return platform === Platform.IOS
? resolveIosResourceClass(resourceClassFlag, profileResourceClass)
: resolveAndroidResourceClass(selectedResourceClass);
}
function resolveAndroidResourceClass(selectedResourceClass?: ResourceClass): BuildResourceClass {
if (
selectedResourceClass &&
[
ResourceClass.M1_EXPERIMENTAL,
ResourceClass.M1_MEDIUM,
ResourceClass.M1_LARGE,
ResourceClass.INTEL_MEDIUM,
].includes(selectedResourceClass)
) {
throw new Error(`Resource class ${selectedResourceClass} is only available for iOS builds`);
}
const resourceClass = selectedResourceClass ?? ResourceClass.DEFAULT;
return androidResourceClassToBuildResourceClassMapping[resourceClass as AndroidResourceClass];
}
function resolveIosResourceClass(
resourceClassFlag?: ResourceClass,
profileResourceClass?: ResourceClass
): BuildResourceClass {
const resourceClass = resourceClassFlag ?? profileResourceClass ?? ResourceClass.DEFAULT;
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)) {
Log.warn(
`Resource class ${chalk.bold(resourceClass)} is deprecated. Use ${chalk.bold(
'm-medium'
)} instead.`
);
}
if ([ResourceClass.M_LARGE, ResourceClass.M1_LARGE].includes(resourceClass)) {
Log.warn(
`Resource class ${chalk.bold(resourceClass)} is deprecated. Use ${chalk.bold(
'large'
)} instead.`
);
}
return iosResourceClassToBuildResourceClassMapping[resourceClass];
}
+4 -2
View File
@@ -10,12 +10,14 @@ export function getProjectDashboardUrl(accountName: string, projectName: string)
).toString();
}
export function getBuildLogsUrl(build: BuildFragment): string {
export function getBuildLogsUrl(build: BuildFragment, hash?: string): string {
const { project } = build;
const url =
project.__typename !== 'App'
? `/builds/${build.id}`
: `/accounts/${project.ownerAccount.name}/projects/${project.slug}/builds/${build.id}`;
: `/accounts/${project.ownerAccount.name}/projects/${project.slug}/builds/${build.id}${
hash ? `#${hash}` : ''
}`;
return new URL(url, getExpoWebsiteBaseUrl()).toString();
}
+137
View File
@@ -1,8 +1,11 @@
import { Platform, Workflow } from '@expo/eas-build-job';
import { Errors } from '@oclif/core';
import fs from 'fs-extra';
import path from 'path';
import semver from 'semver';
import Log, { learnMore } from '../log';
import { isPNGAsync } from '../utils/image';
import { getVcsClient } from '../vcs';
import { CommonContext } from './context';
@@ -50,3 +53,137 @@ export async function checkGoogleServicesFileAsync<T extends Platform>(
function isInsideDirectory(file: string, directory: string): boolean {
return file.startsWith(directory);
}
export async function validatePNGsForManagedProjectAsync<T extends Platform>(
ctx: CommonContext<T>
): Promise<void> {
if (ctx.workflow !== Workflow.MANAGED) {
return;
}
// don't run PNG checks on SDK 47 and newer
// see https://github.com/expo/eas-cli/pull/1477#issuecomment-1293914917
if (!ctx.exp.sdkVersion || semver.satisfies(ctx.exp.sdkVersion, '>= 47.0.0')) {
return;
}
if (ctx.platform === Platform.ANDROID) {
await validateAndroidPNGsAsync(ctx as CommonContext<Platform.ANDROID>);
}
// Validating iOS PNGs is currently disabled
// See https://github.com/expo/eas-cli/pull/1477 for context
//
// else {
// await validateIosPNGsAsync(ctx as CommonContext<Platform.IOS>);
// }
}
type ConfigPng = { configPath: string; pngPath: string | undefined };
async function validateAndroidPNGsAsync(ctx: CommonContext<Platform.ANDROID>): Promise<void> {
const pngs: ConfigPng[] = [
{
configPath: 'exp.icon',
pngPath: ctx.exp.icon,
},
{
configPath: 'exp.android.icon',
pngPath: ctx.exp.android?.icon,
},
{
configPath: 'exp.android.adaptiveIcon.foregroundImage',
pngPath: ctx.exp.android?.adaptiveIcon?.foregroundImage,
},
{
configPath: 'exp.android.adaptiveIcon.backgroundImage',
pngPath: ctx.exp.android?.adaptiveIcon?.backgroundImage,
},
{
configPath: 'exp.splash.image',
pngPath: ctx.exp.splash?.image,
},
{
configPath: 'exp.notification.icon',
pngPath: ctx.exp.notification?.icon,
},
];
await validatePNGsAsync(pngs);
}
// Validating iOS PNGs is currently disabled
// See https://github.com/expo/eas-cli/pull/1477 for context
//
// async function validateIosPNGsAsync(ctx: CommonContext<Platform.IOS>): Promise<void> {
// const pngs: ConfigPng[] = [
// {
// configPath: 'exp.icon',
// pngPath: ctx.exp.icon,
// },
// {
// configPath: 'exp.ios.icon',
// pngPath: ctx.exp.ios?.icon,
// },
// {
// configPath: 'exp.splash.image',
// pngPath: ctx.exp.splash?.image,
// },
// {
// configPath: 'exp.notification.icon',
// pngPath: ctx.exp.notification?.icon,
// },
// ];
// await validatePNGsAsync(pngs);
// const icon = ctx.exp.ios?.icon ?? ctx.exp.icon;
// if (!icon) {
// return;
// }
// const iconConfigPath = `expo${ctx.exp.ios?.icon ? '.ios' : ''}.icon`;
// try {
// await ensurePNGIsNotTransparentAsync(icon);
// } catch (err: any) {
// if (err instanceof ImageTransparencyError) {
// Log.error(
// `Your iOS app icon (${iconConfigPath}) can't have transparency if you wish to upload your app to the Apple App Store.`
// );
// Log.error(learnMore('https://expo.fyi/remove-alpha-channel', { dim: false }));
// Errors.exit(1);
// } else {
// throw err;
// }
// }
// }
async function validatePNGsAsync(configPngs: ConfigPng[]): Promise<void> {
const validationPromises = configPngs.map(configPng => validatePNGAsync(configPng));
const validationResults = await Promise.allSettled(validationPromises);
const failedValidations = validationResults.filter(
(result): result is PromiseRejectedResult => result.status === 'rejected'
);
if (failedValidations.length === 0) {
return;
}
Log.error('PNG images validation failed:');
for (const { reason } of failedValidations) {
const error: Error = reason;
Log.error(`- ${error.message}`);
}
Errors.exit(1);
}
async function validatePNGAsync({ configPath, pngPath }: ConfigPng): Promise<void> {
if (!pngPath) {
return;
}
if (!pngPath.endsWith('.png')) {
throw new Error(`"${configPath}" is not a PNG file`);
}
if (!(await isPNGAsync(pngPath))) {
throw new Error(`"${configPath}" is not valid PNG`);
}
}
@@ -0,0 +1,85 @@
import assert from 'assert';
// TODO(quin): move this into a common package with www
export type BranchMappingOperator =
| '=='
| '!='
| '<'
| '>'
| '<='
| '>='
| 'in'
| 'regex'
| 'hash_lt'
| 'hash_lte'
| 'hash_gt'
| 'hash_gte';
export type BranchMappingObject = {
clientKey: string;
branchMappingOperator: BranchMappingOperator;
operand: number | string | string[];
};
export type BranchMappingAlwaysTrue = 'true';
export type BranchMappingStatement =
| ['and' | 'or', ...BranchMappingNode[]]
| ['not', BranchMappingNode];
export type BranchMappingNode =
| BranchMappingAlwaysTrue
| BranchMappingObject
| BranchMappingStatement;
export type BranchMapping = {
version: number;
data: {
branchId: string;
branchMappingLogic: BranchMappingNode;
}[];
};
export function getNodesFromStatement(statement: BranchMappingStatement): BranchMappingNode[] {
return statement.slice(1) as BranchMappingNode[];
}
export function isAndStatement(
statement: BranchMappingStatement
): statement is BranchMappingStatement {
return statement[0] === 'and';
}
export function isStatement(node: BranchMappingNode): node is BranchMappingStatement {
return Array.isArray(node);
}
export function isNodeObject(node: BranchMappingNode): node is BranchMappingObject {
return typeof node === 'object' && !isStatement(node);
}
export function andStatement(nodes: BranchMappingNode[]): ['and', ...BranchMappingNode[]] {
return ['and', ...nodes];
}
export function isAlwaysTrue(node: BranchMappingNode): boolean {
return node === 'true';
}
export function alwaysTrue(): BranchMappingAlwaysTrue {
return 'true';
}
export function equalsOperator(): BranchMappingOperator {
return '==';
}
export function hashLtOperator(): BranchMappingOperator {
return 'hash_lt';
}
export function assertNodeObject(node: BranchMappingNode): asserts node is BranchMappingObject {
assert(
isNodeObject(node),
'Branch mapping node must be an object. Received: ' + JSON.stringify(node)
);
}
export function assertNumber(operand: string | number | string[]): asserts operand is number {
assert(typeof operand === 'number', 'Expected a number. Received: ' + JSON.stringify(operand));
}
+5
View File
@@ -0,0 +1,5 @@
export class ChannelNotFoundError extends Error {
constructor(message?: string) {
super(message ?? 'Channel not found.');
}
}
+72 -13
View File
@@ -1,8 +1,12 @@
import chalk from 'chalk';
import gql from 'graphql-tag';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { PaginatedQueryOptions } from '../commandUtils/pagination';
import { withErrorHandlingAsync } from '../graphql/client';
import {
CreateUpdateChannelOnAppMutation,
CreateUpdateChannelOnAppMutationVariables,
ViewBranchesOnUpdateChannelQueryVariables,
ViewUpdateChannelsOnAppQueryVariables,
} from '../graphql/generated';
@@ -42,7 +46,7 @@ export async function selectChannelOnAppAsync(
queryChannelsOnAppAsync(graphqlClient, { appId: projectId, limit, offset }),
promptOptions: {
title: selectionPromptTitle,
createDisplayTextForSelectionPromptListItem: updateChannel => updateChannel.name,
makePartialChoiceObject: updateChannel => ({ title: updateChannel.name }),
getIdentifierForQueryItem: updateChannel => updateChannel.id,
},
});
@@ -153,14 +157,13 @@ function renderPageOfChannels(
printJsonOnlyOutput({ currentPage });
} else {
for (const channel of currentPage) {
renderChannelHeaderContent({ channelName: channel.name, channelId: channel.id });
Log.addNewLineIfNone();
Log.log(
formatFields([
{ label: 'Name', value: channel.name },
{ label: 'ID', value: channel.id },
])
);
logChannelDetails(channel);
if (currentPage.indexOf(channel) < currentPage.length - 1) {
Log.log(`\n${chalk.dim('———')}\n`);
}
}
}
}
@@ -174,13 +177,8 @@ function renderPageOfBranchesOnChannel(
if (json) {
printJsonOnlyOutput({ currentPage: channelWithNewBranches });
} else {
// The channel details contain both the branch and latest update group
Log.addNewLineIfNone();
Log.log(
formatFields([
{ label: 'Name', value: channel.name },
{ label: 'ID', value: channel.id },
])
);
logChannelDetails(channelWithNewBranches);
}
}
@@ -203,3 +201,64 @@ function renderChannelHeaderContent({
Log.addNewLineIfNone();
Log.log(chalk`{bold Branches pointed at this channel and their most recent update group:}`);
}
export async function createChannelOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{
appId,
branchId,
channelName,
}: {
appId: string;
branchId: string;
channelName: string;
}
): Promise<CreateUpdateChannelOnAppMutation> {
// Point the new channel at a branch with its same name.
const branchMapping = JSON.stringify({
data: [{ branchId, branchMappingLogic: 'true' }],
version: 0,
});
return await withErrorHandlingAsync(
graphqlClient
.mutation<CreateUpdateChannelOnAppMutation, CreateUpdateChannelOnAppMutationVariables>(
gql`
mutation CreateUpdateChannelOnApp($appId: ID!, $name: String!, $branchMapping: String!) {
updateChannel {
createUpdateChannelForApp(appId: $appId, name: $name, branchMapping: $branchMapping) {
id
name
branchMapping
}
}
}
`,
{
appId,
name: channelName,
branchMapping,
}
)
.toPromise()
);
}
export async function ensureChannelExistsAsync(
graphqlClient: ExpoGraphqlClient,
{ appId, branchId, channelName }: { appId: string; branchId: string; channelName: string }
): Promise<void> {
try {
await createChannelOnAppAsync(graphqlClient, {
appId,
channelName,
branchId,
});
} catch (e: any) {
const isIgnorableError =
e.graphQLErrors?.length === 1 &&
e.graphQLErrors[0].extensions.errorCode === 'CHANNEL_ALREADY_EXISTS';
if (!isIgnorableError) {
throw e;
}
}
}
+64 -43
View File
@@ -1,21 +1,14 @@
import assert from 'assert';
import CliTable from 'cli-table3';
import chalk from 'chalk';
import { UpdateChannelObject } from '../graphql/queries/ChannelQuery';
import { UpdateBranchObject, UpdateChannelObject } from '../graphql/queries/ChannelQuery';
import Log from '../log';
import { UPDATE_COLUMNS, getUpdateGroupDescriptionsWithBranch } from '../update/utils';
export type BranchMapping = {
version: number;
data: {
branchId: string;
branchMappingLogic: {
operand: number;
clientKey: string;
branchMappingOperator: string;
} & string;
}[];
};
import {
FormattedBranchDescription,
formatBranch,
getUpdateGroupDescriptionsWithBranch,
} from '../update/utils';
import { BranchMapping, assertNodeObject, assertNumber, isNodeObject } from './branch-mapping';
/**
* Get the branch mapping and determine whether it is a rollout.
@@ -42,8 +35,12 @@ export function getBranchMapping(branchMappingString?: string): {
}
const isRollout = branchMapping.data.length === 2;
const rolloutPercent = branchMapping.data[0].branchMappingLogic.operand;
const branchMappingNode = branchMapping.data[0]?.branchMappingLogic;
let rolloutPercent: number | undefined;
if (isNodeObject(branchMappingNode)) {
assertNumber(branchMappingNode.operand);
rolloutPercent = branchMappingNode.operand;
}
switch (branchMapping.data.length) {
case 0:
break;
@@ -53,10 +50,11 @@ export function getBranchMapping(branchMappingString?: string): {
}
break;
case 2:
if (branchMapping.data[0].branchMappingLogic.clientKey !== 'rolloutToken') {
assertNodeObject(branchMappingNode);
if (branchMappingNode.clientKey !== 'rolloutToken') {
throw new Error('Client key of initial branch mapping must be "rolloutToken"');
}
if (branchMapping.data[0].branchMappingLogic.branchMappingOperator !== 'hash_lt') {
if (branchMappingNode.branchMappingOperator !== 'hash_lt') {
throw new Error('Branch mapping operator of initial branch mapping must be "hash_lt"');
}
if (rolloutPercent == null) {
@@ -75,38 +73,61 @@ export function getBranchMapping(branchMappingString?: string): {
export function logChannelDetails(channel: UpdateChannelObject): void {
const { branchMapping, isRollout, rolloutPercent } = getBranchMapping(channel.branchMapping);
const updateChannelsTable = new CliTable({
head: ['branch', ...(isRollout ? ['rollout percent'] : []), ...UPDATE_COLUMNS],
wordWrap: true,
});
if (branchMapping.data.length > 2) {
throw new Error('Branch Mapping data must have length less than or equal to 2.');
}
const rolloutBranchIds = branchMapping.data.map(data => data.branchId);
for (const currentBranch of channel.updateBranches) {
const updateGroupDescriptions = getUpdateGroupDescriptionsWithBranch(
currentBranch.updateGroups
const branchDescription = channel.updateBranches.flatMap(branch => {
const updateGroupWithBranchDescriptions = getUpdateGroupDescriptionsWithBranch(
branch.updateGroups
);
const isRolloutBranch = isRollout && rolloutBranchIds.includes(currentBranch.id);
const isBaseBranch = rolloutBranchIds.length > 0 && rolloutBranchIds[0] === currentBranch.id;
const isRolloutBranch = isRollout && rolloutBranchIds.includes(branch.id);
const isBaseBranch = rolloutBranchIds.length > 0 && rolloutBranchIds[0] === branch.id;
let rolloutPercentNumber: number | undefined = undefined;
if (isRolloutBranch) {
rolloutPercentNumber = isBaseBranch ? rolloutPercent! * 100 : (1 - rolloutPercent!) * 100;
}
updateGroupDescriptions.forEach(({ branch, message, runtimeVersion, group, platforms }) => {
updateChannelsTable.push([
return updateGroupWithBranchDescriptions.map(
({ branch, ...updateGroup }): FormattedBranchDescription => ({
branch,
...(isRolloutBranch
? [isBaseBranch ? `${rolloutPercent! * 100}%` : `${(1 - rolloutPercent!) * 100}%`]
: []),
message,
runtimeVersion,
group,
platforms,
]);
});
branchRolloutPercentage: rolloutPercentNumber,
update: updateGroup,
})
);
});
if (branchDescription.length === 0) {
Log.log(chalk.dim('No branches are pointed to this channel.'));
} else {
Log.log(
branchDescription
.map(description => formatBranch(description))
.join(`\n\n${chalk.dim('———')}\n\n`)
);
}
Log.log(updateChannelsTable.toString());
}
function getUpdateBranchNullable(
channel: UpdateChannelObject,
branchId: string
): UpdateBranchObject | null {
const updateBranches = channel.updateBranches;
const updateBranch = updateBranches.find(branch => branch.id === branchId);
return updateBranch ?? null;
}
export function getUpdateBranch(
channel: UpdateChannelObject,
branchId: string
): UpdateBranchObject {
const updateBranch = getUpdateBranchNullable(channel, branchId);
if (!updateBranch) {
throw new Error(
`Could not find branch with id "${branchId}" in branch-mapping of channel "${channel.name}"`
);
}
return updateBranch;
}
+63 -18
View File
@@ -1,4 +1,6 @@
import { Command } from '@oclif/core';
import { CombinedError } from '@urql/core';
import { GraphQLError } from 'graphql/error';
import nullthrows from 'nullthrows';
import {
@@ -6,18 +8,23 @@ import {
CommandEvent,
createAnalyticsAsync,
} from '../analytics/AnalyticsManager';
import Log from '../log';
import SessionManager from '../user/SessionManager';
import AnalyticsContextField from './context/AnalyticsContextField';
import ContextField from './context/ContextField';
import { DynamicProjectConfigContextField } from './context/DynamicProjectConfigContextField';
import {
DynamicPrivateProjectConfigContextField,
DynamicPublicProjectConfigContextField,
} from './context/DynamicProjectConfigContextField';
import LoggedInContextField from './context/LoggedInContextField';
import MaybeLoggedInContextField from './context/MaybeLoggedInContextField';
import { OptionalProjectConfigContextField } from './context/OptionalProjectConfigContextField';
import ProjectConfigContextField from './context/ProjectConfigContextField';
import { OptionalPrivateProjectConfigContextField } from './context/OptionalPrivateProjectConfigContextField';
import { PrivateProjectConfigContextField } from './context/PrivateProjectConfigContextField';
import ProjectDirContextField from './context/ProjectDirContextField';
import SessionManagementContextField from './context/SessionManagementContextField';
import { EasCommandError } from './errors';
type ContextInput<
export type ContextInput<
T extends {
[name: string]: any;
} = object
@@ -25,7 +32,7 @@ type ContextInput<
[P in keyof T]: ContextField<T[P]>;
};
type ContextOutput<
export type ContextOutput<
T extends {
[name: string]: any;
} = object
@@ -33,6 +40,8 @@ type ContextOutput<
[P in keyof T]: T[P];
};
const BASE_GRAPHQL_ERROR_MESSAGE: string = 'GraphQL request failed.';
export default abstract class EasCommand extends Command {
protected static readonly ContextOptions = {
/**
@@ -60,7 +69,7 @@ export default abstract class EasCommand extends Command {
* run within a project directory, null otherwise.
*/
OptionalProjectConfig: {
projectConfig: new OptionalProjectConfigContextField(),
privateProjectConfig: new OptionalPrivateProjectConfigContextField(),
},
/**
* Require this command to be run in a project directory. Return the project directory in the context.
@@ -73,7 +82,9 @@ export default abstract class EasCommand extends Command {
*/
DynamicProjectConfig: {
// 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.
@@ -82,7 +93,7 @@ export default abstract class EasCommand extends Command {
*/
ProjectConfig: {
loggedIn: new LoggedInContextField(),
projectConfig: new ProjectConfigContextField(),
privateProjectConfig: new PrivateProjectConfigContextField(),
},
/**
* Analytics manager. Returns the analytics manager in the context for use by the command.
@@ -103,6 +114,18 @@ export default abstract class EasCommand extends Command {
*/
static contextDefinition: ContextInput = {};
/**
* The user session manager. Responsible for coordinating all user session related state.
* If needed in a subclass, use the SessionManager ContextOption.
*/
private sessionManagerInternal?: SessionManager;
/**
* The analytics manager. Used for logging analytics.
* It is set up here to ensure a consistent setup.
*/
private analyticsInternal?: AnalyticsWithOrchestration;
/**
* Execute the context in the contextDefinition to satisfy command prerequisites.
*/
@@ -132,20 +155,10 @@ export default abstract class EasCommand extends Command {
return Object.fromEntries(contextValuePairs);
}
/**
* The user session manager. Responsible for coordinating all user session related state.
* If needed in a subclass, use the SessionManager ContextOption.
*/
private sessionManagerInternal?: SessionManager;
private get sessionManager(): SessionManager {
return nullthrows(this.sessionManagerInternal);
}
/**
* The analytics manager. Used for logging analytics.
* It is set up here to ensure a consistent set up.
*/
private analyticsInternal?: AnalyticsWithOrchestration;
private get analytics(): AnalyticsWithOrchestration {
return nullthrows(this.analyticsInternal);
}
@@ -175,4 +188,36 @@ export default abstract class EasCommand extends Command {
await this.analytics.flushAsync();
return super.finally(err);
}
protected override catch(err: Error): Promise<any> {
let baseMessage = `${this.id} command failed.`;
if (err instanceof EasCommandError) {
Log.error(err.message);
} else if (err instanceof CombinedError && err?.graphQLErrors) {
const cleanMessage = err?.graphQLErrors
.map((graphQLError: GraphQLError) => {
const messageLine = graphQLError.message.replace('[GraphQL] ', '');
const requestIdLine = graphQLError.extensions?.requestId
? `\nRequest ID: ${err.graphQLErrors[0].extensions.requestId}`
: '';
const defaultMsg = `${messageLine}${requestIdLine}`;
if (graphQLError.extensions?.errorCode === 'UNAUTHORIZED_ERROR') {
return `You don't have the required permissions to perform this operation.\n\n${defaultMsg}`;
}
return defaultMsg;
})
.join('\n');
Log.error(cleanMessage);
baseMessage = BASE_GRAPHQL_ERROR_MESSAGE;
} else {
Log.error(err.message);
}
Log.debug(err);
const sanitizedError = new Error(baseMessage);
sanitizedError.stack = err.stack;
throw sanitizedError;
}
}
@@ -1,4 +1,9 @@
import { CombinedError } from '@urql/core';
import { GraphQLError } from 'graphql/error';
import { v4 as uuidv4 } from 'uuid';
import { AnalyticsWithOrchestration, createAnalyticsAsync } from '../../analytics/AnalyticsManager';
import Log from '../../log';
import SessionManager from '../../user/SessionManager';
import EasCommand from '../EasCommand';
@@ -11,8 +16,10 @@ jest.mock('../../analytics/AnalyticsManager', () => {
createAnalyticsAsync: jest.fn(),
};
});
jest.mock('../../log');
let originalProcessArgv: string[];
const mockRequestId = uuidv4();
beforeAll(() => {
originalProcessArgv = process.argv;
@@ -40,7 +47,7 @@ const createTestEasCommand = (): typeof EasCommand => {
async runAsync(): Promise<void> {}
}
TestEasCommand.id = 'TestEasCommand'; // normally oclif will assign ids, but b/c this is located outside the commands folder it will not
TestEasCommand.id = 'testEasCommand'; // normally oclif will assign ids, but b/c this is located outside the commands folder it will not
return TestEasCommand;
};
@@ -59,7 +66,7 @@ describe(EasCommand.name, () => {
const sessionManagerSpy = jest.spyOn(SessionManager.prototype, 'getUserAsync');
expect(sessionManagerSpy).toBeCalledTimes(1);
}, 15_000);
}, 30_000);
it('initializes analytics', async () => {
const TestEasCommand = createTestEasCommand();
@@ -96,5 +103,169 @@ describe(EasCommand.name, () => {
expect(analytics.flushAsync).toHaveBeenCalled();
});
describe('catch', () => {
it('logs the message', 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 error = new Error('Unexpected, internal error message');
runAsyncMock.mockImplementation(() => {
throw error;
});
try {
await TestEasCommand.run();
} catch {}
expect(logErrorSpy).toBeCalledWith('Unexpected, internal error message');
expect(logDebugSpy).toBeCalledWith(error);
});
it('logs the cleaned message if needed', 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 graphQLErrors = ['Unexpected GraphQL error message'];
const error = new CombinedError({ graphQLErrors });
runAsyncMock.mockImplementation(() => {
throw error;
});
try {
await TestEasCommand.run();
} catch {}
expect(logErrorSpy).toBeCalledWith('Unexpected GraphQL error message');
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('logs the cleaned messages with request IDs if multiple GraphQL errors 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 graphQLErrors = [
new GraphQLError('Unexpected GraphQL error message', null, null, null, null, null, {
errorCode: 'UNKNOWN_GRAPHQL_ERROR',
requestId: mockRequestId,
}),
new GraphQLError('Other GraphQL error message', null, null, null, null, null, {
errorCode: 'OTHER_GRAPHQL_ERROR',
requestId: mockRequestId,
}),
new GraphQLError('Yet another GraphQL error message', null, null, null, null, null, {
errorCode: 'YET_ANOTHER_GRAPHQL_ERROR',
requestId: mockRequestId,
}),
];
const error = new CombinedError({ graphQLErrors });
runAsyncMock.mockImplementation(() => {
throw error;
});
try {
await TestEasCommand.run();
} catch {}
expect(logErrorSpy).toBeCalledWith(
'Unexpected GraphQL error message\n' +
`Request ID: ${mockRequestId}\n` +
'Other GraphQL error message\n' +
`Request ID: ${mockRequestId}\n` +
'Yet another GraphQL error message\n' +
`Request ID: ${mockRequestId}`
);
expect(logDebugSpy).toBeCalledWith(error);
});
it('re-throws the error with default base message', async () => {
const TestEasCommand = createTestEasCommand();
const runAsyncMock = jest.spyOn(TestEasCommand.prototype as any, 'runAsync');
runAsyncMock.mockImplementation(() => {
throw new Error('Error message');
});
try {
await TestEasCommand.run();
} catch (caughtError) {
expect(caughtError).toBeInstanceOf(Error);
expect((caughtError as Error).message).toEqual('testEasCommand command failed.');
}
});
it('re-throws the error with a different default base message in case of CombinedError', async () => {
const TestEasCommand = createTestEasCommand();
const runAsyncMock = jest.spyOn(TestEasCommand.prototype as any, 'runAsync');
runAsyncMock.mockImplementation(() => {
const graphQLErrors = ['Unexpected GraphQL error message'];
throw new CombinedError({ graphQLErrors });
});
try {
await TestEasCommand.run();
} catch (caughtError) {
expect(caughtError).toBeInstanceOf(Error);
expect((caughtError as Error).message).toEqual('GraphQL request failed.');
}
});
it('re-throws the error with a different default base message in case of CombinedError with multiple GraphQLErrors', async () => {
const TestEasCommand = createTestEasCommand();
const runAsyncMock = jest.spyOn(TestEasCommand.prototype as any, 'runAsync');
runAsyncMock.mockImplementation(() => {
const graphQLErrors = [
new GraphQLError('Unexpected GraphQL error message', null, null, null, null, null, {
errorCode: 'UNEXPECTED_GRAPHQL_ERROR',
requestId: mockRequestId,
}),
new GraphQLError('Other GraphQL error message', null, null, null, null, null, {
errorCode: 'OTHER_GRAPHQL_ERROR',
requestId: mockRequestId,
}),
new GraphQLError('Yet another GraphQL error message', null, null, null, null, null, {
errorCode: 'YET_ANOTHER_GRAPHQL_ERROR',
requestId: mockRequestId,
}),
];
throw new CombinedError({ graphQLErrors });
});
try {
await TestEasCommand.run();
} catch (caughtError) {
expect(caughtError).toBeInstanceOf(Error);
expect((caughtError as Error).message).toEqual('GraphQL request failed.');
}
});
});
});
});
@@ -1,6 +1,10 @@
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 { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
@@ -11,19 +15,41 @@ export type DynamicConfigContextFn = (options?: ExpoConfigOptions) => Promise<{
projectDir: string;
}>;
export class DynamicProjectConfigContextField extends ContextField<DynamicConfigContextFn> {
export class DynamicPublicProjectConfigContextField extends ContextField<DynamicConfigContextFn> {
async getValueAsync({
nonInteractive,
sessionManager,
}: ContextOptions): Promise<DynamicConfigContextFn> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
return async (options?: ExpoConfigOptions) => {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
const expBefore = getExpoConfig(projectDir, options);
const expBefore = getPublicExpoConfig(projectDir, options);
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
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 {
exp,
projectDir,
@@ -1,20 +1,30 @@
import { Actor } from '../../user/User';
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
import FeatureGating from '../gating/FeatureGating';
import ContextField, { ContextOptions } from './ContextField';
import { ExpoGraphqlClient, createGraphqlClient } from './contextUtils/createGraphqlClient';
export default class LoggedInContextField extends ContextField<{
actor: Actor;
featureGating: FeatureGating;
graphqlClient: ExpoGraphqlClient;
}> {
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<{
actor: Actor;
featureGating: FeatureGating;
graphqlClient: ExpoGraphqlClient;
}> {
const { actor, authenticationInfo } = await sessionManager.ensureLoggedInAsync({
nonInteractive,
});
const featureGateServerValues: { [key: string]: boolean } = actor?.featureGates ?? {};
const graphqlClient = createGraphqlClient(authenticationInfo);
return { actor, graphqlClient };
return {
actor,
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
graphqlClient,
};
}
}
@@ -1,13 +1,17 @@
import { Actor } from '../../user/User';
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
import FeatureGating from '../gating/FeatureGating';
import ContextField, { ContextOptions } from './ContextField';
import { ExpoGraphqlClient, createGraphqlClient } from './contextUtils/createGraphqlClient';
export default class MaybeLoggedInContextField extends ContextField<{
actor: Actor | null;
featureGating: FeatureGating;
graphqlClient: ExpoGraphqlClient;
}> {
async getValueAsync({ sessionManager }: ContextOptions): Promise<{
actor: Actor | null;
featureGating: FeatureGating;
graphqlClient: ExpoGraphqlClient;
}> {
const authenticationInfo = {
@@ -17,7 +21,12 @@ export default class MaybeLoggedInContextField extends ContextField<{
const graphqlClient = createGraphqlClient(authenticationInfo);
const actor = await sessionManager.getUserAsync();
const featureGateServerValues: { [key: string]: boolean } = actor?.featureGates ?? {};
return { actor: actor ?? null, graphqlClient };
return {
actor: actor ?? null,
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
graphqlClient,
};
}
}
@@ -1,12 +1,12 @@
import { ExpoConfig } from '@expo/config-types';
import { InvalidEasJsonError } from '@expo/eas-json/build/errors';
import { getExpoConfig } from '../../project/expoConfig';
import { getPrivateExpoConfig } from '../../project/expoConfig';
import ContextField, { ContextOptions } from './ContextField';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
export class OptionalProjectConfigContextField extends ContextField<
export class OptionalPrivateProjectConfigContextField extends ContextField<
| {
projectId: string;
exp: ExpoConfig;
@@ -35,11 +35,11 @@ export class OptionalProjectConfigContextField extends ContextField<
return undefined;
}
const expBefore = getExpoConfig(projectDir);
const expBefore = getPrivateExpoConfig(projectDir);
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
});
const exp = getExpoConfig(projectDir);
const exp = getPrivateExpoConfig(projectDir);
return {
exp,
projectDir,
@@ -1,11 +1,11 @@
import { ExpoConfig } from '@expo/config-types';
import { getExpoConfig } from '../../project/expoConfig';
import { getPrivateExpoConfig } from '../../project/expoConfig';
import ContextField, { ContextOptions } from './ContextField';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
export default class ProjectConfigContextField extends ContextField<{
export class PrivateProjectConfigContextField extends ContextField<{
projectId: string;
exp: ExpoConfig;
projectDir: string;
@@ -16,11 +16,11 @@ export default class ProjectConfigContextField extends ContextField<{
projectDir: string;
}> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
const expBefore = getExpoConfig(projectDir);
const expBefore = getPrivateExpoConfig(projectDir);
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
});
const exp = getExpoConfig(projectDir);
const exp = getPrivateExpoConfig(projectDir);
return {
projectId,
@@ -1,4 +1,4 @@
import { getConfig, modifyConfigAsync } from '@expo/config';
import { getConfig, getConfigFilePaths, modifyConfigAsync } from '@expo/config';
import { vol } from 'memfs';
import { anything, instance, mock, when } from 'ts-mockito';
@@ -15,7 +15,6 @@ jest.mock('fs');
jest.mock('../../../../graphql/queries/AppQuery');
jest.mock('../../contextUtils/findProjectDirAndVerifyProjectSetupAsync');
jest.mock('../../../../user/User');
jest.mock('../../../../ora', () => ({
ora: () => ({
start: () => ({ succeed: () => {}, fail: () => {} }),
@@ -27,6 +26,10 @@ describe(getProjectIdAsync, () => {
let sessionManager: SessionManager;
beforeEach(() => {
jest
.mocked(getConfigFilePaths)
.mockReturnValue({ staticConfigPath: null, dynamicConfigPath: null });
const sessionManagerMock = mock<SessionManager>();
when(sessionManagerMock.ensureLoggedInAsync(anything())).thenResolve({
actor: {
@@ -51,6 +54,7 @@ describe(getProjectIdAsync, () => {
},
],
isExpoAdmin: false,
featureGates: {},
},
authenticationInfo: { accessToken: 'fake', sessionSecret: null },
});
@@ -106,7 +110,79 @@ describe(getProjectIdAsync, () => {
{ nonInteractive: false }
)
).rejects.toThrow(
`Project config: Project identified by "extra.eas.projectId" (notnotbrent) is not owned by owner specified in the "owner" field (wat). ${learnMore(
`Project config: Owner of project identified by "extra.eas.projectId" (notnotbrent) does not match owner specified in the "owner" field (wat). ${learnMore(
'https://expo.fyi/eas-project-id'
)}`
);
});
it('throws when the owner is not specified and is different than logged in user', async () => {
jest.mocked(getConfig).mockReturnValue({
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
} as any);
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
fullName: '@totallybrent/test',
slug: 'test',
ownerAccount: { name: 'totallybrent' } as any,
});
await expect(
getProjectIdAsync(
sessionManager,
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
{ nonInteractive: false }
)
).rejects.toThrow(
`Project config: Owner of project identified by "extra.eas.projectId" (totallybrent) does not match the logged in user (notnotbrent) and the "owner" field is not specified. To ensure all libraries work correctly, "owner": "totallybrent" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
'https://expo.fyi/eas-project-id'
)}`
);
});
it('throws when the owner is not specified and is different than logged in user which is a robot', async () => {
const sessionManagerMock = mock<SessionManager>();
when(sessionManagerMock.ensureLoggedInAsync(anything())).thenResolve({
actor: {
__typename: 'Robot',
id: 'robot_id',
accounts: [
{
id: 'account_id_1',
name: 'notnotbrent',
users: [{ role: Role.Admin, actor: { id: 'robot_id' } }],
},
{
id: 'account_id_2',
name: 'dominik',
users: [{ role: Role.ViewOnly, actor: { id: 'robot_id' } }],
},
],
isExpoAdmin: false,
featureGates: {},
},
authenticationInfo: { accessToken: 'fake', sessionSecret: null },
});
const sessionManagerRobot = instance(sessionManagerMock);
jest.mocked(getConfig).mockReturnValue({
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
} as any);
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
fullName: '@totallybrent/test',
slug: 'test',
ownerAccount: { name: 'totallybrent' } as any,
});
await expect(
getProjectIdAsync(
sessionManagerRobot,
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
{ nonInteractive: false }
)
).rejects.toThrow(
`Project config: Owner of project identified by "extra.eas.projectId" (totallybrent) must be specified in "owner" field when using a robot access token. To ensure all libraries work correctly, "owner": "totallybrent" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
'https://expo.fyi/eas-project-id'
)}`
);
@@ -157,9 +233,13 @@ describe(getProjectIdAsync, () => {
expect(projectId).toEqual('2345');
expect(modifyConfigAsync).toHaveBeenCalledTimes(1);
expect(modifyConfigAsync).toHaveBeenCalledWith('/app', {
extra: { eas: { projectId: '2345' } },
});
expect(modifyConfigAsync).toHaveBeenCalledWith(
'/app',
{
extra: { eas: { projectId: '2345' } },
},
{ skipSDKVersionRequirement: true }
);
});
it('throws if writing the ID back to the config fails', async () => {
@@ -176,4 +256,26 @@ describe(getProjectIdAsync, () => {
getProjectIdAsync(sessionManager, { name: 'test', slug: 'test' }, { nonInteractive: false })
).rejects.toThrow();
});
it('throws if extra.eas.projectId is not a string', async () => {
jest.mocked(getConfig).mockReturnValue({
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: 1234 } } },
} as any);
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234',
fullName: '@notnotbrent/test',
slug: 'test',
ownerAccount: { name: 'notnotbrent' } as any,
});
await expect(
getProjectIdAsync(
sessionManager,
{ name: 'test', slug: 'wat', extra: { eas: { projectId: 1234 } } },
{ nonInteractive: false }
)
).rejects.toThrow(
`Project config: "extra.eas.projectId" must be a string, found number. If you're not sure how to set it up on your own, remove the property entirely and it will be automatically configured on the next EAS CLI run.`
);
});
});
@@ -1,12 +1,12 @@
import {
AnyVariables,
Client,
OperationContext,
OperationResult,
PromisifiedSource,
OperationResultSource,
TypedDocumentNode,
cacheExchange,
createClient as createUrqlClient,
dedupExchange,
fetchExchange,
} from '@urql/core';
import { retryExchange } from '@urql/exchange-retry';
@@ -17,11 +17,11 @@ import { getExpoApiBaseUrl } from '../../../api';
import { httpsProxyAgent } from '../../../fetch';
export interface ExpoGraphqlClient extends Client {
query<Data = any, Variables extends object = object>(
query<Data = any, Variables extends AnyVariables = AnyVariables>(
query: DocumentNode | TypedDocumentNode<Data, Variables> | string,
variables: Variables | undefined,
variables: Variables,
context: Partial<OperationContext> & { additionalTypenames: string[] }
): PromisifiedSource<OperationResult<Data, Variables>>;
): OperationResultSource<OperationResult<Data, Variables>>;
}
export function createGraphqlClient(authInfo: {
@@ -31,7 +31,6 @@ export function createGraphqlClient(authInfo: {
return createUrqlClient({
url: getExpoApiBaseUrl() + '/graphql',
exchanges: [
dedupExchange,
cacheExchange,
retryExchange({
maxDelayMs: 4000,
@@ -12,7 +12,7 @@ import { getVcsClient, setVcsClient } from '../../../vcs';
import GitClient from '../../../vcs/clients/git';
async function applyCliConfigAsync(projectDir: string): Promise<void> {
const easJsonAccessor = new EasJsonAccessor(projectDir);
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
const config = await EasJsonUtils.getCliConfigAsync(easJsonAccessor);
if (config?.version && !semver.satisfies(easCliVersion, config.version)) {
throw new Error(
@@ -1,4 +1,4 @@
import { getProjectConfigDescription, modifyConfigAsync } from '@expo/config';
import { getProjectConfigDescription } from '@expo/config';
import { ExpoConfig } from '@expo/config-types';
import { Env } from '@expo/eas-build-job';
import chalk from 'chalk';
@@ -6,11 +6,11 @@ import chalk from 'chalk';
import { AppQuery } from '../../../graphql/queries/AppQuery';
import Log, { learnMore } from '../../../log';
import { ora } from '../../../ora';
import { getExpoConfig } from '../../../project/expoConfig';
import { createOrModifyExpoConfigAsync, getPrivateExpoConfig } from '../../../project/expoConfig';
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
import { toAppPrivacy } from '../../../project/projectUtils';
import SessionManager from '../../../user/SessionManager';
import { Actor } from '../../../user/User';
import { Actor, getActorUsername } from '../../../user/User';
import { createGraphqlClient } from './createGraphqlClient';
import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync';
@@ -24,10 +24,14 @@ export async function saveProjectIdToAppConfigAsync(
projectId: string,
options: { env?: Env } = {}
): Promise<void> {
const exp = getExpoConfig(projectDir, options);
const result = await modifyConfigAsync(projectDir, {
extra: { ...exp.extra, eas: { ...exp.extra?.eas, projectId } },
});
const exp = getPrivateExpoConfig(projectDir, options);
const result = await createOrModifyExpoConfigAsync(
projectDir,
{
extra: { ...exp.extra, eas: { ...exp.extra?.eas, projectId } },
},
{ skipSDKVersionRequirement: true }
);
switch (result.type) {
case 'success':
@@ -85,18 +89,50 @@ export async function getProjectIdAsync(
const localProjectId = exp.extra?.eas?.projectId;
if (localProjectId) {
if (typeof localProjectId !== 'string') {
throw new Error(
`Project config: "extra.eas.projectId" must be a string, found ${typeof localProjectId}. If you're not sure how to set it up on your own, remove the property entirely and it will be automatically configured on the next EAS CLI run.`
);
}
// check that the local project ID matches account and slug
const appForProjectId = await AppQuery.byIdAsync(graphqlClient, localProjectId);
if (exp.owner && exp.owner !== appForProjectId.ownerAccount.name) {
throw new Error(
`Project config: Project identified by "extra.eas.projectId" (${
`Project config: Owner of project identified by "extra.eas.projectId" (${
appForProjectId.ownerAccount.name
}) is not owned by owner specified in the "owner" field (${exp.owner}). ${learnMore(
}) does not match owner specified in the "owner" field (${exp.owner}). ${learnMore(
'https://expo.fyi/eas-project-id'
)}`
);
}
const actorUsername = getActorUsername(actor);
if (!exp.owner && appForProjectId.ownerAccount.name !== actorUsername) {
if (actorUsername) {
throw new Error(
`Project config: Owner of project identified by "extra.eas.projectId" (${
appForProjectId.ownerAccount.name
}) does not match the logged in user (${actorUsername}) and the "owner" field is not specified. To ensure all libraries work correctly, "owner": "${
appForProjectId.ownerAccount.name
}" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
'https://expo.fyi/eas-project-id'
)}`
);
} else {
// robot caller
throw new Error(
`Project config: Owner of project identified by "extra.eas.projectId" (${
appForProjectId.ownerAccount.name
}) must be specified in "owner" field when using a robot access token. To ensure all libraries work correctly, "owner": "${
appForProjectId.ownerAccount.name
}" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
'https://expo.fyi/eas-project-id'
)}`
);
}
}
if (exp.slug && exp.slug !== appForProjectId.slug) {
throw new Error(
`Project config: Slug for project identified by "extra.eas.projectId" (${
@@ -124,6 +160,8 @@ export async function getProjectIdAsync(
switch (user.__typename) {
case 'User':
return user.username;
case 'SSOUser':
return user.username;
case 'Robot':
throw new Error(
'The "owner" manifest property is required when using robot users. See: https://docs.expo.dev/versions/latest/config/app/#owner'
@@ -0,0 +1,7 @@
export class EasCommandError extends Error {
// constructor is not useless, since the constructor for Error allows for optional `message`
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor(message: string) {
super(message);
}
}
@@ -0,0 +1,40 @@
import nullthrows from 'nullthrows';
import env from '../../env';
import { FeatureGateKey } from './FeatureGateKey';
function parseFeatureGateEnvVariableValue(value: string | undefined): string[] {
if (!value) {
return [];
}
return value.split(',').map(v => v.trim());
}
export default class FeatureGateEnvOverrides {
private readonly map = new Map<string, boolean>();
constructor() {
const overrideEnableGateKeys = new Set(parseFeatureGateEnvVariableValue(env.featureGateEnable));
const overrideDisableGateKeys = new Set(
parseFeatureGateEnvVariableValue(env.featureGateDisable)
);
for (const overrideEnableKey of overrideEnableGateKeys) {
if (overrideDisableGateKeys.has(overrideEnableKey)) {
continue;
}
this.map.set(overrideEnableKey, true);
}
for (const overrideDisableGateKey of overrideDisableGateKeys) {
this.map.set(overrideDisableGateKey, false);
}
}
public isOverridden(key: FeatureGateKey): boolean {
return this.map.has(key) ?? false;
}
public getOverride(key: FeatureGateKey): boolean {
return nullthrows(this.map.get(key));
}
}
@@ -0,0 +1,8 @@
export enum FeatureGateKey {
// for tests
TEST = 'test',
}
export const featureGateDefaultValueWhenNoServerValue: Record<FeatureGateKey, boolean> = {
[FeatureGateKey.TEST]: true,
};
@@ -0,0 +1,15 @@
import { FeatureGateKey } from './FeatureGateKey';
export const setOverride = (_key: FeatureGateKey, _enabled: boolean): void => {
throw new Error('Must use mocked FeatureGateTestOverrides');
};
export const removeOverride = (_key: FeatureGateKey): void => {
throw new Error('Must use mocked FeatureGateTestOverrides');
};
export const isOverridden = (_key: FeatureGateKey): boolean => false;
export const getOverride = (_key: FeatureGateKey): boolean => {
throw new Error('Must use mocked FeatureGateTestOverrides');
};
@@ -0,0 +1,69 @@
import FeatureGateEnvOverrides from './FeatureGateEnvOverrides';
import { FeatureGateKey, featureGateDefaultValueWhenNoServerValue } from './FeatureGateKey';
import * as FeatureGateTestOverrides from './FeatureGateTestOverrides';
export default class FeatureGating {
constructor(
private readonly serverValues: { [key: string]: boolean },
private readonly envOverrides: FeatureGateEnvOverrides
) {}
public isEnabled(experimentKey: FeatureGateKey): boolean {
if (process.env.NODE_ENV === 'test' && FeatureGateTestOverrides.isOverridden(experimentKey)) {
return FeatureGateTestOverrides.getOverride(experimentKey);
}
if (this.envOverrides.isOverridden(experimentKey)) {
return this.envOverrides.getOverride(experimentKey);
}
if (experimentKey in this.serverValues) {
return this.serverValues[experimentKey];
}
return featureGateDefaultValueWhenNoServerValue[experimentKey];
}
/**
* Test gate override methods
*/
public static overrideKeyForScope(
key: FeatureGateKey,
enabled: boolean,
scope: () => void
): void {
if (process.env.NODE_ENV !== 'test') {
throw new Error(`Cannot overrideKeyForScope outside of test environment`);
}
FeatureGateTestOverrides.setOverride(key, enabled);
try {
scope();
} finally {
FeatureGateTestOverrides.removeOverride(key);
}
}
public static async overrideKeyForScopeAsync(
key: FeatureGateKey,
enabled: boolean,
scope: () => Promise<void>
): Promise<void> {
if (process.env.NODE_ENV !== 'test') {
throw new Error(`Cannot overrideKeyForScopeAsync outside of test environment`);
}
FeatureGateTestOverrides.setOverride(key, enabled);
try {
await scope();
} finally {
FeatureGateTestOverrides.removeOverride(key);
}
}
public static overrideKeyForEachInTest(key: FeatureGateKey, enabled: boolean): void {
beforeEach(() => FeatureGateTestOverrides.setOverride(key, enabled));
afterEach(() => FeatureGateTestOverrides.removeOverride(key));
}
}
@@ -0,0 +1,20 @@
import { FeatureGateKey } from '../FeatureGateKey';
const map = new Map();
export const setOverride = (key: FeatureGateKey, enabled: boolean): void => {
map.set(key, enabled);
};
export const removeOverride = (key: FeatureGateKey): void => {
map.delete(key);
};
export const isOverridden = (key: FeatureGateKey): boolean => map.has(key);
export const getOverride = (key: FeatureGateKey): boolean => {
if (map.has(key)) {
return map.get(key);
}
throw new Error(`Key ${key} not overridden`);
};
@@ -0,0 +1,92 @@
import env from '../../../env';
import FeatureGateEnvOverrides from '../FeatureGateEnvOverrides';
import { FeatureGateKey } from '../FeatureGateKey';
import FeatureGating from '../FeatureGating';
jest.mock('../FeatureGateTestOverrides');
describe(FeatureGating, () => {
it('supports scoped overriding', () => {
const featureGating = new FeatureGating({}, new FeatureGateEnvOverrides());
FeatureGating.overrideKeyForScope(FeatureGateKey.TEST, false, () => {
expect(featureGating.isEnabled(FeatureGateKey.TEST)).toBe(false);
});
expect(featureGating.isEnabled(FeatureGateKey.TEST)).toBe(true);
});
it('supports async scoped overriding', async () => {
const featureGating = new FeatureGating({}, new FeatureGateEnvOverrides());
await FeatureGating.overrideKeyForScopeAsync(FeatureGateKey.TEST, false, async () => {
expect(featureGating.isEnabled(FeatureGateKey.TEST)).toBe(false);
});
expect(featureGating.isEnabled(FeatureGateKey.TEST)).toBe(true);
});
it('uses value from the server, falls back to defined value', () => {
const featureGating = new FeatureGating(
{ [FeatureGateKey.TEST]: true },
new FeatureGateEnvOverrides()
);
expect(featureGating.isEnabled(FeatureGateKey.TEST)).toBe(true);
const featureGating2 = new FeatureGating(
{ [FeatureGateKey.TEST]: false },
new FeatureGateEnvOverrides()
);
expect(featureGating2.isEnabled(FeatureGateKey.TEST)).toBe(false);
const featureGating3 = new FeatureGating({}, new FeatureGateEnvOverrides());
expect(featureGating3.isEnabled(FeatureGateKey.TEST)).toBe(true);
});
describe('environment variable overriding', () => {
afterAll(() => {
env.featureGateEnable = undefined;
env.featureGateDisable = undefined;
});
it('supports env overrides affirmative', () => {
env.featureGateEnable = [FeatureGateKey.TEST].join(',');
env.featureGateDisable = [].join(',');
const featureGating = new FeatureGating(
{ [FeatureGateKey.TEST]: false },
new FeatureGateEnvOverrides()
);
expect(featureGating.isEnabled(FeatureGateKey.TEST)).toBe(true);
});
it('supports env overrides negative', () => {
env.featureGateEnable = [].join(',');
env.featureGateDisable = [FeatureGateKey.TEST].join(',');
const featureGating = new FeatureGating(
{ [FeatureGateKey.TEST]: true },
new FeatureGateEnvOverrides()
);
expect(featureGating.isEnabled(FeatureGateKey.TEST)).toBe(false);
});
});
});
describe('jest scoped overriding', () => {
FeatureGating.overrideKeyForEachInTest(FeatureGateKey.TEST, false);
it('supports jest scoped overriding', () => {
const featureGating = new FeatureGating(
{ [FeatureGateKey.TEST]: true },
new FeatureGateEnvOverrides()
);
expect(featureGating.isEnabled(FeatureGateKey.TEST)).toBe(false);
});
});
describe('removal of jest scoped overriding', () => {
it('jest scoped overriding is only for describe scope above', () => {
const featureGating = new FeatureGating(
{ [FeatureGateKey.TEST]: true },
new FeatureGateEnvOverrides()
);
expect(featureGating.isEnabled(FeatureGateKey.TEST)).toBe(true);
});
});
@@ -1,4 +1,5 @@
import { Flags } from '@oclif/core';
import { OptionFlag } from '@oclif/core/lib/interfaces';
import { EasNonInteractiveAndJsonFlags } from './flags';
@@ -37,7 +38,7 @@ export const getLimitFlagWithCustomValues = ({
}: {
defaultTo: number;
limit: number;
}): any =>
}): OptionFlag<number | undefined> =>
Flags.integer({
description: `The number of items to fetch each query. Defaults to ${defaultTo} and is capped at ${limit}.`,
// eslint-disable-next-line async-protect/async-suffix
+17 -1
View File
@@ -1,3 +1,5 @@
import { Flags } from '@oclif/core';
import EasCommand from '../../commandUtils/EasCommand';
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 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 = {
...this.ContextOptions.SessionManagment,
};
async runAsync(): Promise<void> {
const {
flags: { sso },
} = await this.parse(AccountLogin);
const { sessionManager } = await this.getContextAsync(AccountLogin, { nonInteractive: false });
await sessionManager.showLoginPromptAsync();
await sessionManager.showLoginPromptAsync({ sso });
Log.log('Logged in');
}
}
+42 -1
View File
@@ -1,8 +1,10 @@
import chalk from 'chalk';
import nullthrows from 'nullthrows';
import EasCommand from '../../commandUtils/EasCommand';
import { Role } from '../../graphql/generated';
import Log from '../../log';
import { getActorDisplayName } from '../../user/User';
import { Actor, getActorDisplayName } from '../../user/User';
export default class AccountView extends EasCommand {
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 });
if (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 {
Log.warn('Not logged in');
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';
}
}
}
+2 -40
View File
@@ -1,52 +1,14 @@
import chalk from 'chalk';
import gql from 'graphql-tag';
import { createUpdateBranchOnAppAsync } from '../../branch/queries';
import { getDefaultBranchNameAsync } from '../../branch/utils';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { withErrorHandlingAsync } from '../../graphql/client';
import {
CreateUpdateBranchForAppMutation,
CreateUpdateBranchForAppMutationVariables,
UpdateBranch,
} from '../../graphql/generated';
import Log from '../../log';
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
export async function createUpdateBranchOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{ appId, name }: CreateUpdateBranchForAppMutationVariables
): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
const result = await withErrorHandlingAsync(
graphqlClient
.mutation<CreateUpdateBranchForAppMutation, CreateUpdateBranchForAppMutationVariables>(
gql`
mutation createUpdateBranchForApp($appId: ID!, $name: String!) {
updateBranch {
createUpdateBranchForApp(appId: $appId, name: $name) {
id
name
}
}
}
`,
{
appId,
name,
}
)
.toPromise()
);
const newBranch = result.updateBranch.createUpdateBranchForApp;
if (!newBranch) {
throw new Error(`Could not create branch ${name}.`);
}
return newBranch;
}
export default class BranchCreate extends EasCommand {
static override description = 'create a branch';
@@ -73,7 +35,7 @@ export default class BranchCreate extends EasCommand {
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
} = await this.parse(BranchCreate);
const {
projectConfig: { projectId },
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchCreate, {
nonInteractive,
@@ -108,7 +108,7 @@ export default class BranchDelete extends EasCommand {
}
const {
projectConfig: { projectId },
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchDelete, { nonInteractive });
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
@@ -120,7 +120,7 @@ export default class BranchDelete extends EasCommand {
}
({ name: branchName } = await selectBranchOnAppAsync(graphqlClient, {
projectId,
displayTextForListItem: updateBranch => updateBranch.name,
displayTextForListItem: updateBranch => ({ title: updateBranch.name }),
promptTitle: 'Which branch would you like to delete?',
paginatedQueryOptions,
}));
+1 -1
View File
@@ -20,7 +20,7 @@ export default class BranchList extends EasCommand {
async runAsync(): Promise<void> {
const { flags } = await this.parse(BranchList);
const {
projectConfig: { projectId },
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchList, {
nonInteractive: flags['non-interactive'],
@@ -72,7 +72,7 @@ export default class BranchRename extends EasCommand {
flags: { json: jsonFlag, from: currentName, to: newName, 'non-interactive': nonInteractive },
} = await this.parse(BranchRename);
const {
projectConfig: { projectId },
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchRename, {
nonInteractive,
+4 -2
View File
@@ -38,7 +38,7 @@ export default class BranchView extends EasCommand {
} = await this.parse(BranchView);
const { 'non-interactive': nonInteractive } = flags;
const {
projectConfig: { projectId },
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchView, {
nonInteractive,
@@ -57,7 +57,9 @@ export default class BranchView extends EasCommand {
({ name: branchName } = await selectBranchOnAppAsync(graphqlClient, {
projectId,
promptTitle: 'Which branch would you like to view?',
displayTextForListItem: updateBranch => updateBranch.name,
displayTextForListItem: updateBranch => ({
title: updateBranch.name,
}),
// discard limit and offset because this query is not their intended target
paginatedQueryOptions: {
json: paginatedQueryOptions.json,
@@ -147,7 +147,7 @@ export default class BuildCancel extends EasCommand {
flags: { 'non-interactive': nonInteractive },
} = await this.parse(BuildCancel);
const {
projectConfig: { projectId },
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildCancel, {
nonInteractive,
@@ -174,7 +174,7 @@ export default class BuildCancel extends EasCommand {
const spinner = ora().start('Canceling the build…');
try {
const { status } = await cancelBuildAsync(graphqlClient, buildId);
if (status === BuildStatus.Canceled) {
if ([BuildStatus.Canceled, BuildStatus.PendingCancel].includes(status)) {
spinner.succeed('Build canceled');
} else {
spinner.text = 'Build is already completed';
@@ -7,10 +7,14 @@ import { ensureProjectConfiguredAsync } from '../../build/configure';
import EasCommand from '../../commandUtils/EasCommand';
import Log, { learnMore } from '../../log';
import { RequestedPlatform } from '../../platform';
import { isExpoUpdatesInstalled } from '../../project/projectUtils';
import { isExpoUpdatesInstalled, isUsingEASUpdate } from '../../project/projectUtils';
import { resolveWorkflowAsync } from '../../project/workflow';
import { promptAsync } from '../../prompts';
import { syncUpdatesConfigurationAsync as syncAndroidUpdatesConfigurationAsync } from '../../update/android/UpdatesModule';
import {
ensureEASUpdateIsConfiguredInEasJsonAsync,
ensureUseClassicUpdatesIsRemovedAsync,
} from '../../update/configure';
import { syncUpdatesConfigurationAsync as syncIosUpdatesConfigurationAsync } from '../../update/ios/UpdatesModule';
import { getVcsClient } from '../../vcs';
@@ -33,7 +37,7 @@ export default class BuildConfigure extends EasCommand {
async runAsync(): Promise<void> {
const { flags } = await this.parse(BuildConfigure);
const {
projectConfig: { exp, projectId, projectDir },
privateProjectConfig: { exp, projectId, projectDir },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildConfigure, {
nonInteractive: false,
@@ -57,10 +61,19 @@ export default class BuildConfigure extends EasCommand {
// ensure eas.json exists
Log.newLine();
await ensureProjectConfiguredAsync({
const didCreateEasJson = await ensureProjectConfiguredAsync({
projectDir,
nonInteractive: false,
});
if (didCreateEasJson && isUsingEASUpdate(exp, projectId)) {
if (exp.updates?.useClassicUpdates) {
// NOTE: this method modifies the Expo config; be sure to use this function's return value
// if the config object is used later in the future
await ensureUseClassicUpdatesIsRemovedAsync({ exp, projectDir });
}
await ensureEASUpdateIsConfiguredInEasJsonAsync(projectDir);
}
// configure expo-updates
if (expoUpdatesIsInstalled) {
+25 -26
View File
@@ -1,13 +1,13 @@
import { Platform } from '@expo/eas-build-job';
import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
import { EasJsonAccessor, EasJsonUtils, ResourceClass } from '@expo/eas-json';
import { Errors, Flags } from '@oclif/core';
import chalk from 'chalk';
import figures from 'figures';
import fs from 'fs-extra';
import path from 'path';
import { LocalBuildMode } from '../../build/local';
import { BuildFlags, runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
import { UserInputResourceClass } from '../../build/types';
import EasCommand from '../../commandUtils/EasCommand';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { StatuspageServiceName } from '../../graphql/generated';
@@ -31,7 +31,7 @@ interface RawBuildFlags {
json: boolean;
'auto-submit': boolean;
'auto-submit-with-profile'?: string;
'resource-class'?: UserInputResourceClass;
'resource-class'?: ResourceClass;
message?: string;
}
@@ -85,8 +85,13 @@ export default class Build extends EasCommand {
exclusive: ['auto-submit'],
}),
'resource-class': Flags.enum({
options: Object.values(UserInputResourceClass),
options: Object.values(ResourceClass),
hidden: true,
deprecated: {
message: chalk.yellow(
'The --resource-class flag has been deprecated. Define the resource class in eas.json.\nLearn more: https://docs.expo.dev/build-reference/eas-json/'
),
},
description: 'The instance type that will be used to run this build [experimental]',
}),
message: Flags.string({
@@ -113,7 +118,7 @@ export default class Build extends EasCommand {
const {
loggedIn: { actor, graphqlClient },
getDynamicProjectConfigAsync,
getDynamicPrivateProjectConfigAsync,
projectDir,
analytics,
} = await this.getContextAsync(Build, {
@@ -122,7 +127,7 @@ export default class Build extends EasCommand {
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
if (!flags.localBuildOptions.enable) {
if (!flags.localBuildOptions.localBuildMode) {
await maybeWarnAboutEasOutagesAsync(
graphqlClient,
flags.autoSubmit
@@ -139,7 +144,7 @@ export default class Build extends EasCommand {
projectDir,
flagsWithPlatform,
actor,
getDynamicProjectConfigAsync
getDynamicPrivateProjectConfigAsync
);
}
@@ -156,17 +161,6 @@ export default class Build extends EasCommand {
if (flags.json && !nonInteractive) {
Errors.error('--json is allowed only when building in non-interactive mode', { exit: 1 });
}
if (
flags['resource-class'] === UserInputResourceClass.M1_EXPERIMENTAL &&
flags.platform !== Platform.IOS
) {
Errors.error(
`Resource class ${UserInputResourceClass.M1_EXPERIMENTAL} is only available for iOS builds`,
{
exit: 1,
}
);
}
const requestedPlatform =
flags.platform &&
@@ -201,19 +195,17 @@ export default class Build extends EasCommand {
nonInteractive,
localBuildOptions: flags['local']
? {
enable: true,
localBuildMode: LocalBuildMode.LOCAL_BUILD_PLUGIN,
verbose: true,
artifactPath: flags.output && path.resolve(process.cwd(), flags.output),
}
: {
enable: false,
},
: {},
wait: flags['wait'],
clearCache: flags['clear-cache'],
json: flags['json'],
autoSubmit: flags['auto-submit'] || flags['auto-submit-with-profile'] !== undefined,
submitProfile: flags['auto-submit-with-profile'] ?? profile,
userInputResourceClass: flags['resource-class'] ?? UserInputResourceClass.DEFAULT,
resourceClass: flags['resource-class'],
message,
};
}
@@ -223,7 +215,7 @@ export default class Build extends EasCommand {
): Promise<BuildFlags> {
const requestedPlatform = await selectRequestedPlatformAsync(flags.requestedPlatform);
if (flags.localBuildOptions.enable) {
if (flags.localBuildOptions.localBuildMode) {
if (flags.autoSubmit) {
// TODO: implement this
Errors.error('Auto-submits are not yet supported when building locally', { exit: 1 });
@@ -235,6 +227,13 @@ export default class Build extends EasCommand {
});
} else if (process.platform !== 'darwin' && requestedPlatform === RequestedPlatform.Ios) {
Errors.error('Unsupported platform, macOS is required to build apps for iOS', { exit: 1 });
} else if (
!['linux', 'darwin'].includes(process.platform) &&
requestedPlatform === RequestedPlatform.Android
) {
Errors.error('Unsupported platform, macOS or Linux is required to build apps for Android', {
exit: 1,
});
}
}
@@ -245,14 +244,14 @@ export default class Build extends EasCommand {
}
}
async function handleDeprecatedEasJsonAsync(
export async function handleDeprecatedEasJsonAsync(
projectDir: string,
nonInteractive: boolean
): Promise<void> {
if (!(await fs.pathExists(EasJsonAccessor.formatEasJsonPath(projectDir)))) {
return;
}
const easJsonAccessor = new EasJsonAccessor(projectDir);
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
const profileNames = await EasJsonUtils.getBuildProfileNamesAsync(easJsonAccessor);
const platformAndProfileNames: [Platform, string][] = profileNames.flatMap(profileName => [
[Platform.ANDROID, profileName],
@@ -4,6 +4,7 @@ import fs from 'fs-extra';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { LocalBuildMode } from '../../build/local';
import { runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
import EasCommand from '../../commandUtils/EasCommand';
import Log from '../../log';
@@ -72,7 +73,7 @@ export default class BuildInspect extends EasCommand {
const { flags } = await this.parse(BuildInspect);
const {
loggedIn: { actor, graphqlClient },
getDynamicProjectConfigAsync,
getDynamicPrivateProjectConfigAsync,
projectDir,
analytics,
} = await this.getContextAsync(BuildInspect, {
@@ -110,7 +111,7 @@ export default class BuildInspect extends EasCommand {
requestedPlatform: flags.platform,
profile: flags.profile,
localBuildOptions: {
enable: true,
localBuildMode: LocalBuildMode.LOCAL_BUILD_PLUGIN,
...(flags.stage === InspectStage.PRE_BUILD ? { skipNativeBuild: true } : {}),
...(flags.stage === InspectStage.POST_BUILD ? { skipCleanup: true } : {}),
verbose: flags.verbose,
@@ -119,7 +120,7 @@ export default class BuildInspect extends EasCommand {
},
},
actor,
getDynamicProjectConfigAsync
getDynamicPrivateProjectConfigAsync
);
if (!flags.verbose) {
Log.log(chalk.green('Build successful'));
@@ -0,0 +1,80 @@
import { Flags } from '@oclif/core';
import { handleDeprecatedEasJsonAsync } from '.';
import { LocalBuildMode } from '../../build/local';
import { runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
import EasCommand from '../../commandUtils/EasCommand';
import { RequestedPlatform } from '../../platform';
import { enableJsonOutput } from '../../utils/json';
import { setVcsClient } from '../../vcs';
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
/**
* This command will be run on the EAS Build workers, when building
* directly from git. This command resolves credentials and other
* build configuration, that normally would be included in the
* job and metadata objects, and prints them to stdout.
*/
export default class BuildInternal extends EasCommand {
static override hidden = true;
static override flags = {
platform: Flags.enum({
char: 'p',
options: ['android', 'ios'],
required: true,
}),
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',
}),
};
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
...this.ContextOptions.DynamicProjectConfig,
...this.ContextOptions.ProjectDir,
...this.ContextOptions.Analytics,
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(BuildInternal);
// This command is always run with implicit --non-interactive and --json options
enableJsonOutput();
const {
loggedIn: { actor, graphqlClient },
getDynamicPrivateProjectConfigAsync,
projectDir,
analytics,
} = await this.getContextAsync(BuildInternal, {
nonInteractive: true,
});
setVcsClient(new GitNoCommitClient());
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
await runBuildAndSubmitAsync(
graphqlClient,
analytics,
projectDir,
{
requestedPlatform: flags.platform as RequestedPlatform,
profile: flags.profile,
nonInteractive: true,
wait: false,
clearCache: false,
json: true,
autoSubmit: false,
localBuildOptions: {
localBuildMode: LocalBuildMode.INTERNAL,
},
},
actor,
getDynamicPrivateProjectConfigAsync
);
}
}
+8 -21
View File
@@ -9,13 +9,10 @@ import {
getLimitFlagWithCustomValues,
getPaginatedQueryOptions,
} from '../../commandUtils/pagination';
import {
AppPlatform,
DistributionType,
BuildStatus as GraphQLBuildStatus,
} from '../../graphql/generated';
import { AppPlatform, BuildStatus as GraphQLBuildStatus } from '../../graphql/generated';
import { RequestedPlatform } from '../../platform';
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
import { buildDistributionTypeToGraphQLDistributionType } from '../../utils/buildDistribution';
import { enableJsonOutput } from '../../utils/json';
export default class BuildList extends EasCommand {
@@ -30,6 +27,7 @@ export default class BuildList extends EasCommand {
BuildStatus.NEW,
BuildStatus.IN_QUEUE,
BuildStatus.IN_PROGRESS,
BuildStatus.PENDING_CANCEL,
BuildStatus.ERRORED,
BuildStatus.FINISHED,
BuildStatus.CANCELED,
@@ -71,7 +69,7 @@ export default class BuildList extends EasCommand {
'non-interactive': nonInteractive,
} = flags;
const {
projectConfig: { projectId },
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildList, {
nonInteractive,
@@ -82,7 +80,8 @@ export default class BuildList extends EasCommand {
const platform = toAppPlatform(requestedPlatform);
const graphqlBuildStatus = toGraphQLBuildStatus(buildStatus);
const graphqlBuildDistribution = toGraphQLBuildDistribution(buildDistribution);
const graphqlBuildDistribution =
buildDistributionTypeToGraphQLDistributionType(buildDistribution);
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
await listAndRenderBuildsOnAppAsync(graphqlClient, {
@@ -125,6 +124,8 @@ const toGraphQLBuildStatus = (buildStatus?: BuildStatus): GraphQLBuildStatus | u
return GraphQLBuildStatus.InQueue;
} else if (buildStatus === BuildStatus.IN_PROGRESS) {
return GraphQLBuildStatus.InProgress;
} else if (buildStatus === BuildStatus.PENDING_CANCEL) {
return GraphQLBuildStatus.PendingCancel;
} else if (buildStatus === BuildStatus.ERRORED) {
return GraphQLBuildStatus.Errored;
} else if (buildStatus === BuildStatus.FINISHED) {
@@ -133,17 +134,3 @@ const toGraphQLBuildStatus = (buildStatus?: BuildStatus): GraphQLBuildStatus | u
return GraphQLBuildStatus.Canceled;
}
};
const toGraphQLBuildDistribution = (
buildDistribution?: BuildDistributionType
): DistributionType | undefined => {
if (buildDistribution === BuildDistributionType.STORE) {
return DistributionType.Store;
} else if (buildDistribution === BuildDistributionType.INTERNAL) {
return DistributionType.Internal;
} else if (buildDistribution === BuildDistributionType.SIMULATOR) {
return DistributionType.Simulator;
} else {
return undefined;
}
};
@@ -0,0 +1,298 @@
import { Platform } from '@expo/eas-build-job';
import { CredentialsSource, EasJson, EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
import { Flags } from '@oclif/core';
import assert from 'assert';
import chalk from 'chalk';
import { handleDeprecatedEasJsonAsync } from '.';
import { waitForBuildEndAsync } from '../../build/build';
import { ensureIosCredentialsForBuildResignAsync } from '../../build/ios/credentials';
import { prepareCredentialsToResign } from '../../build/ios/prepareJob';
import { listAndSelectBuildOnAppAsync } from '../../build/queries';
import { printBuildResults, printLogsUrls } from '../../build/utils/printBuildInfo';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { EasPaginatedQueryFlags } from '../../commandUtils/pagination';
import { CredentialsContext } from '../../credentials/context';
import {
BuildFragment,
BuildMode,
BuildStatus,
BuildWorkflow,
DistributionType,
IosJobOverridesInput,
ProjectArchiveSourceType,
StatuspageServiceName,
} from '../../graphql/generated';
import { BuildMutation } from '../../graphql/mutations/BuildMutation';
import { BuildQuery } from '../../graphql/queries/BuildQuery';
import { toAppPlatform } from '../../graphql/types/AppPlatform';
import Log from '../../log';
import { requestedPlatformDisplayNames, selectPlatformAsync } from '../../platform';
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
import { resolveTargetsAsync } from '../../project/ios/target';
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { maybeWarnAboutEasOutagesAsync } from '../../utils/statuspageService';
interface BuildResignFlags {
json: boolean;
nonInteractive: boolean;
offset?: number;
limit?: number;
platform?: Platform;
profile?: string;
maybeBuildId?: string;
wait: boolean;
}
interface RawBuildResignFlags {
json: boolean;
'non-interactive': boolean;
offset: number | undefined;
limit: number | undefined;
platform: 'android' | 'ios' | undefined;
profile: string | undefined;
wait: boolean;
id: string | undefined;
}
export default class BuildResign extends EasCommand {
static override description = 're-sign a build archive';
static override flags = {
platform: Flags.enum({
char: 'p',
options: ['android', 'ios'],
}),
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',
}),
wait: Flags.boolean({
default: true,
allowNo: true,
description: 'Wait for build(s) to complete.',
}),
id: Flags.string({
description: 'ID of the build to re-sign.',
}),
...EasPaginatedQueryFlags,
...EasNonInteractiveAndJsonFlags,
};
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
...this.ContextOptions.DynamicProjectConfig,
...this.ContextOptions.ProjectDir,
...this.ContextOptions.Analytics,
};
async runAsync(): Promise<void> {
const { flags: rawFlags } = await this.parse(BuildResign);
if (rawFlags.json) {
enableJsonOutput();
}
const flags = this.sanitizeFlags(rawFlags as RawBuildResignFlags);
const { limit, offset, nonInteractive } = flags;
const {
loggedIn: { actor, graphqlClient },
getDynamicPrivateProjectConfigAsync,
projectDir,
analytics,
} = await this.getContextAsync(BuildResign, {
nonInteractive: flags.nonInteractive,
});
const maybeBuild = flags.maybeBuildId
? await this.maybeGetBuildAsync(graphqlClient, flags.maybeBuildId)
: undefined;
const platform =
(maybeBuild?.platform.toLowerCase() as Platform | undefined) ??
(await selectPlatformAsync(flags.platform));
if (platform === Platform.ANDROID) {
throw new Error('Re-signing archives is not supported on Android yet.');
}
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasBuild]);
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
const easJsonCliConfig: EasJson['cli'] =
(await EasJsonUtils.getCliConfigAsync(easJsonAccessor)) ?? {};
const buildProfile = await EasJsonUtils.getBuildProfileAsync(
easJsonAccessor,
platform,
flags.profile ?? 'production'
);
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env });
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
const build = await this.ensureBuildSelectedAsync(
{ graphqlClient, projectId, platform, nonInteractive, limit, offset },
maybeBuild
);
const credentialsCtx = new CredentialsContext({
projectInfo: { exp, projectId },
nonInteractive,
projectDir,
user: actor,
graphqlClient,
analytics,
env: buildProfile.env,
easJsonCliConfig,
});
if (buildProfile.credentialsSource !== CredentialsSource.LOCAL && !nonInteractive) {
await credentialsCtx.appStore.ensureAuthenticatedAsync();
}
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
{
projectDir,
nonInteractive,
exp,
},
buildProfile
);
const targets = await resolveTargetsAsync({
projectDir,
exp,
xcodeBuildContext,
env: buildProfile.env,
});
const credentialsResult = await ensureIosCredentialsForBuildResignAsync(
credentialsCtx,
targets,
buildProfile
);
assert(build.artifacts?.applicationArchiveUrl, 'Missing application archive.');
const jobOverrides: IosJobOverridesInput = {
mode: BuildMode.Resign,
type: BuildWorkflow.Unknown,
resign: {
applicationArchiveSource: {
type: ProjectArchiveSourceType.Url,
url: build.artifacts?.applicationArchiveUrl,
},
},
secrets: prepareCredentialsToResign(credentialsResult.credentials),
builderEnvironment: { image: 'default' },
};
const newBuild = await BuildMutation.retryIosBuildAsync(graphqlClient, {
buildId: build.id,
jobOverrides,
});
Log.addNewLineIfNone();
printLogsUrls([newBuild]);
Log.newLine();
if (!flags.wait) {
if (flags.json) {
printJsonOnlyOutput(newBuild);
}
return;
}
const buildResult = await waitForBuildEndAsync(graphqlClient, {
buildIds: [newBuild.id],
accountName: account.name,
nonInteractive,
projectDir,
});
if (!flags.json) {
printBuildResults(buildResult);
} else {
assert(buildResult[0], 'missing build results');
printJsonOnlyOutput(buildResult[0]);
}
}
sanitizeFlags(flags: RawBuildResignFlags): BuildResignFlags {
const nonInteractive = flags['non-interactive'];
if (nonInteractive && !flags.id) {
throw new Error(
`${chalk.bold('--id')} is required when running with ${chalk.bold(
'--non-interactive'
)} flag.`
);
}
return {
json: flags.json,
nonInteractive,
offset: flags.offset,
limit: flags.limit,
platform: flags.platform as Platform | undefined,
profile: flags.profile,
maybeBuildId: flags.id,
wait: flags.wait,
};
}
async ensureBuildSelectedAsync(
{
graphqlClient,
projectId,
platform,
nonInteractive,
limit,
offset,
}: {
graphqlClient: ExpoGraphqlClient;
projectId: string;
platform: Platform;
nonInteractive: boolean;
limit?: number;
offset?: number;
},
maybeBuild?: BuildFragment
): Promise<BuildFragment> {
if (maybeBuild) {
return maybeBuild;
}
const build = await listAndSelectBuildOnAppAsync(graphqlClient, {
projectId,
title: 'Which build would you like to re-sign with new credentials?',
paginatedQueryOptions: {
limit,
offset: offset ?? 0,
nonInteractive,
json: false,
},
filter: {
distribution: DistributionType.Internal,
platform: toAppPlatform(platform),
status: BuildStatus.Finished,
},
});
if (!build) {
throw new Error('There are no builds that can be re-signed on this project.');
}
return build;
}
async maybeGetBuildAsync(
graphqlClient: ExpoGraphqlClient,
maybeBuildId?: string,
maybePlatform?: Platform
): Promise<BuildFragment | undefined> {
if (maybeBuildId) {
const build = await BuildQuery.byIdAsync(graphqlClient, maybeBuildId);
if (build.distribution !== DistributionType.Internal) {
throw new Error('This is not an internal distribution build.');
}
if (build.status !== BuildStatus.Finished) {
throw new Error('Only builds that finished successfully can be re-signed.');
}
if (maybePlatform && build.platform !== toAppPlatform(maybePlatform)) {
throw new Error(
`Build with ID ${maybeBuildId} was not created for platform ${requestedPlatformDisplayNames[maybePlatform]}.`
);
}
return build;
}
return undefined;
}
}
Loaded 100 of 335 files, more files were not shown because too many files have changed in this diff. Show more