Compare commits

...
570 Commits
Author SHA1 Message Date
Brent Vatne e773e77185 v0.23.0 2021-08-05 15:19:01 -07:00
Brent Vatne 893cbb2ec5 [eas-cli] Pass through build settings metadata context to app version resolver (#550) 2021-08-05 15:17:30 -07:00
jkh 1898de2609 [eas-cli] add type to generated manifest fragment (#535) 2021-08-05 06:50:20 -07:00
jkh fe03cc28c4 [eas-cli] stop sending deprecated storageBucket field (#547) 2021-08-03 13:55:00 -07:00
Wojciech Kozyra 5309b7b6ce Update CHANGELOG 2021-08-02 13:23:20 +02:00
Wojciech Kozyra bc9b8442d7 v0.22.4 2021-08-02 13:07:52 +02:00
Wojciech Kozyra a84762b00d [eas-cli] override distribution when building for simulator 2021-08-02 12:57:59 +02:00
Wojciech Kozyra 2d117e1d7e v0.22.3 2021-08-02 12:49:51 +02:00
Sallar 986674942d [eas-json][eas-cli] add simulator key to build profile json validator (#546)
* Add simulator to build profile json validator

* Update CHANGELOG.md

* Fix PR number

* Change the order of simulator key
2021-08-02 12:46:21 +02:00
Wojciech Kozyra 13c224589b Update CHANGELOG 2021-08-02 10:57:16 +02:00
Wojciech Kozyra 20b091fbfa v0.22.2 2021-08-02 10:35:08 +02:00
Wojciech Kozyra 168b6afdfa [eas-cli] do not read BuildSettings on managed project 2021-08-02 10:10:03 +02:00
Dominik Sokal 3c6320b929 Update CHANGELOG.md 2021-07-30 14:03:18 +02:00
Dominik Sokal 4f1dc582bd [ENG-1570] evaluate interpolated iOS version strings (#541)
* evaluate interpolated ios version strings

* update CHANGELOG
2021-07-30 14:00:31 +02:00
Wojciech Kozyra daf2ef1e30 [eas-cli] fix unknown state error (#543)
* [eas-cli] fix unknown state error

* update CHANGELOG
2021-07-30 13:32:22 +02:00
Wojciech Kozyra f703bd8b5a [eas-cli] validate metadata in build request (#542)
* [eas-cli] validate metadata in build request

* Update CHANGELOG
2021-07-30 13:23:47 +02:00
Dominik Sokal 997cd7058a [ENG-1475] Additional filters for eas build:list (#540)
* generate graphql code

* use new App.builds api

* add more filters to build:list

* update CHANGELOG
2021-07-30 10:31:51 +02:00
Wojciech Kozyra e896c2f6c5 Update CHANGELOG 2021-07-29 14:55:04 +02:00
Wojciech Kozyra 3d723ca426 Use new eas.json format + auto migrate from old format (#537)
* Use new eas.json format + aot migrate old format

* review feedback

* review feedback 2

* fix
2021-07-29 14:50:08 +02:00
Dominik Sokal c17e1b3fa2 [ENG-1600] warn when building managed project that does not extend @expo/metro-config in metro.config.js (#534)
* warn when building managed project that does not extend @expo/metro-config in metro.config.js

* update CHANGELOG

* address PR feedback

* fix lint warnings/errors
2021-07-29 09:33:37 +02:00
Quinlan Jung d70fc36485 update changelog 2021-07-28 15:48:22 -07:00
Quinlan Jung 47784ca6d3 v0.22.1 2021-07-28 14:58:33 -07:00
Quinlan Jung 3e5a3aca27 [eas-cli] fix keystore upload bug (#538)
* [eas-cli] fix upload bug

* changelog
2021-07-28 14:55:36 -07:00
Dominik Sokal 89b0227594 [chore] upgrade: eslint, prettier, etc. (#536)
* upgrade: eslint, prettier, etc.

* run prettier
2021-07-28 11:12:06 +02:00
Dominik Sokal b98a30e7c6 install missing dep: fast-glob 2021-07-27 13:16:23 +02:00
Dominik Sokal 5c7862a685 upgrade typescript + clean up deps (#533)
* upgrade typescript to 4.3.5

* remove unused dependencies

* upgrade tslib

* update CHANGELOG
2021-07-27 12:22:39 +02:00
Bartłomiej KlocekandDominik Sokal 82833a9eb4 [eas submit][android] Autodetect google-services JSON key path (#520)
* foundation

* detection logic

* add tests

* Update CHANGELOG, ignore `node_modules`

* Display a chooser for multiple files

* Improve tests

* move `filterAsync` to a separate file

* Apply suggestions from code review

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

* Fix indent

Co-authored-by: Dominik Sokal <[email protected]>
2021-07-27 12:22:25 +02:00
Dominik Sokal 66171c3c4b [ENG-1524] use envs from eas.json when resolving app config (#532)
* use envs from eas.json when resolving app config

* update CHANGELOG

* address PR feedback
2021-07-26 15:38:42 +02:00
Wojciech KozyraandDominik Sokal 179def91c7 [eas-cli] use .pbxproj file to check if native Xcode project is present (#531)
* [eas-cli] use .pbxproj file to check if native Xcode project is present

* Update CHANGELOG.md

* Update packages/eas-cli/src/project/ios/scheme.ts

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

Co-authored-by: Dominik Sokal <[email protected]>
2021-07-26 15:33:44 +02:00
Dominik Sokal 9f63ad4527 update CHANGELOG 2021-07-23 10:33:19 +02:00
Dominik Sokal 878028271a v0.22.0 2021-07-23 10:25:00 +02:00
Dominik Sokal d4e15bbcbf add iosEnterpriseProvisioning to build metadata (#522)
* generate graphql code

* add iosEnterpriseProvisioning to build metadata

* update CHANGELOG

* upgrade @expo/eas-build-job to 0.2.44
2021-07-23 10:09:19 +02:00
Evan Bacon 13bb056df1 Create and link capability identifiers automatically (#524)
* Create and link capability identifiers automatically

* changelog

* Update capabilityIdentifiers.ts

* Update capabilityIdentifiers.ts

* Added better assertion errors
2021-07-22 18:50:51 -06:00
Will Schurman 14d56904d2 [eas-cli] Upload exp config in extra when publishing or republishing (#514) 2021-07-22 10:30:23 -07:00
Dominik Sokal c2ffd05763 fall back to APK when building for internal distribution (#527)
* fall back to APK when building for internal distribution

* update CHANGELOG

* address PR feedback
2021-07-22 16:21:20 +02:00
Dominik Sokal 46e394fbef use default version code when it's not defined for managed project in app.json (#526)
* use default version code when it's not defined for managed project in app.json

* update CHANGELOG
2021-07-22 13:36:26 +02:00
Dominik Sokal 2613f0c2f9 enable lint rule for curly braces around blocks (#525) 2021-07-22 11:49:12 +02:00
Bartłomiej KlocekandWojciech Kozyra e5fd7c01f4 [eas submit] propagate build id to submission mutation (#518)
* Propagate build id

* Display build details in submission summary

* Update tests

* Add test for build id

* Unify build fragment naming

* Apply suggestion

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

Co-authored-by: Wojciech Kozyra <[email protected]>
2021-07-22 11:21:57 +02:00
Evan Bacon 73a0dd6e23 Fix capability syncing (#521)
* Update bundleIdCapabilities.ts

* Update CHANGELOG.md
2021-07-21 12:22:43 -06:00
Dominik Sokal a9dc7f9eeb don't show diff for generated files (yarn.lock, graphql files) 2021-07-21 10:43:22 +02:00
Bartłomiej KlocekandBrent Vatne c3836f23d1 [eas submit] Pretty-print iPad multitasking error message (#517)
* [eas submit] Pretty-print iPad multitasking error message

* Apply suggestion

Co-authored-by: Brent Vatne <[email protected]>

Co-authored-by: Brent Vatne <[email protected]>
2021-07-21 08:00:21 +02:00
Cedric van Putten bfd73cbb19 [eas-cli] Add force option to overwrite existing secrets (#513)
* [eas-cli] Add force option to overwrite existing secrets

* Add changelog entry
2021-07-15 17:15:29 +02:00
Wojciech Kozyra 6a1753edaa [eas-cli] handle blocked amplitude domains (#512)
* [eas-cli] handle blocked amplitude domains

* update CHANGELOG
2021-07-14 13:28:17 +02:00
Wojciech KozyraandBrent Vatne 558a73086d [eas-cli] add error message when free tier is disabled (#509)
* [eas-cli] add error message when free tier is disabled

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

Co-authored-by: Brent Vatne <[email protected]>

Co-authored-by: Brent Vatne <[email protected]>
2021-07-13 12:36:20 +02:00
jkh 15966fbd26 [eas-cli] un-hide project:* (#500) 2021-07-12 13:57:36 -07:00
Dominik Sokal 1e8b55383e update CHANGELOG 2021-07-12 10:51:57 +02:00
Dominik Sokal 6920ebb0ba v0.21.0 2021-07-12 10:43:10 +02:00
Dominik Sokal 55f35949e1 build:list / build:view - print runtime version (#508)
* build:list / build:view - print runtime version

* update CHANGELOG
2021-07-12 10:38:17 +02:00
Dominik Sokal ccb998a221 [ENG-1460] add runtime version to build metadata (#493)
* add runtime version to build metadata

* update CHANGELOG

* upgrade @expo/eas-build-job to 0.2.43
2021-07-12 09:46:00 +02:00
Dominik Sokal 30bc27b4c4 [ENG-1474] build:list / build:view: print more metadata (#504)
* build:list / build:view: print more metadata

* update CHANGELOG
2021-07-12 09:36:58 +02:00
Evan Bacon da3c95ccaf Added iOS 15 capabilities (#499)
* Added iOS 15 capabilities

* Update CHANGELOG.md
2021-07-09 14:18:52 -06:00
Dominik Sokal bbcbe1dceb update CHANGELOG 2021-07-09 10:41:01 +02:00
Dominik Sokal f15beea454 v0.20.0 2021-07-09 10:31:16 +02:00
Wojciech Kozyra 16e384be76 [eas-cli] Error message for eas build downtime (#502) 2021-07-09 10:04:17 +02:00
jkh e74e6d8fc5 [eas-cli] import UI in branch:* channel:* commands (#503) 2021-07-08 18:05:51 -07:00
jkh b6f4603107 [eas-cli] remove shameful console.log (#501) 2021-07-07 18:37:41 -07:00
Wojciech Kozyra 0e7d7f765a [eas-cli] unify generic and managed workflows (#497) 2021-07-07 12:30:06 +02:00
jkh 33b4d8d719 [eas-cli] add project:info command (#498) 2021-07-06 19:25:21 -07:00
Wojciech KozyraandBartłomiej Klocek 0d765748e3 [eas-cli] override value from app.json with param (#494)
* [eas-cli] override value from app.json with param

* update changelog

* Apply suggestions from code review

Co-authored-by: Bartłomiej Klocek <[email protected]>

Co-authored-by: Bartłomiej Klocek <[email protected]>
2021-07-05 15:42:13 +02:00
Dominik Sokal 93953f1424 bump @expo/config-plugins to 3.0.3 (#495)
* bump @expo/config-plugins to 3.0.3

* update CHANGELOG

* fix type mismatch
2021-07-05 14:15:17 +02:00
Quinlan Jung e9b67db05c update chagelog 2021-07-02 11:18:30 -07:00
Quinlan Jung 09bf912b9e v0.19.1 2021-07-02 11:02:09 -07:00
Quinlan Jung e701bacdaa [eas-cli] better debugging experience (#492)
* [eas-cli] better debugging

* fyi

* changelog
2021-07-02 10:59:16 -07:00
Quinlan JungandDominik Sokal cc22542031 [eas-cli] prompt to create project if not exists, more credentials guardrails (#491)
* [eas-cli] prompt to create project if not exists

* [eas-cli] more credentials manager guardrails

* Update packages/eas-cli/src/credentials/manager/ManageAndroid.ts

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

* Update packages/eas-cli/src/credentials/manager/ManageIos.ts

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

* Update packages/eas-cli/src/project/projectUtils.ts

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

* pr feedback

Co-authored-by: Dominik Sokal <[email protected]>
2021-07-02 10:53:26 -07:00
Dominik Sokal 6c53262b5b [ENG-1444] improve workflow detection (#490)
* improve workflow detection

* update CHANGELOG

* address PR feedback
2021-07-02 10:24:05 +02:00
Wojciech Kozyra 7d7d4995d8 update CHANGELOG 2021-07-01 12:23:13 +02:00
Wojciech Kozyra 977546c8c9 v0.19.0 2021-07-01 12:05:57 +02:00
Brent Vatne 53dcd4cb43 [eas-cli] Support configuring one platform at a time when running eas build (#483) 2021-06-30 17:52:37 -07:00
Wojciech Kozyra 9e18e738eb [eas-cli] use new build mutation (#485) 2021-06-29 11:54:11 +02:00
Dominik Sokal 252c7187d5 [ENG-1377] auto-suggest application id and bundle identifier (#487)
* Auto-suggest application id and bundle identifier

* update CHANGELOG

* address PR feedback
2021-06-29 10:51:48 +02:00
Quinlan Jung 90852b9557 update changelog 2021-06-28 16:01:48 -07:00
Quinlan Jung 0df16236a2 v0.18.3 2021-06-28 14:54:56 -07:00
Quinlan Jung ecaf5a4318 changelog stuff 2021-06-28 14:44:00 -07:00
Quinlan Jung 86e5e1a7b1 [eas-cli] integrate push key setup in ios builds (#473)
* [eas-cli] integrate push key setup in build flow

* nits

* fix lint

* pr feedback

* pr feedback
2021-06-28 14:43:15 -07:00
Quinlan Jung ab10094071 [eas-cli] remove nullthrows on keystore keyPassword (#484)
* [eas-cli] remove nullthrows on keystore keyPassword

* changelog
2021-06-28 10:34:45 -07:00
Dominik Sokal 2d536d73fe mock fs-extra so tests don't produce files (#480) 2021-06-28 09:43:43 +02:00
Evan Bacon 80ea8ee50b Support bundle identifiers that start with a dot (#459) 2021-06-25 15:41:48 -07:00
Brent Vatne b49c5f47d1 Update CHANGELOG 2021-06-25 15:16:06 -07:00
Brent Vatne a9da78946b v0.18.2 2021-06-25 14:57:32 -07:00
Brent Vatne b6d9accea4 [eas-cli] Fix inverted conditional so we actually only prompt about commit when index is dirty (#481) 2021-06-25 14:54:26 -07:00
Quinlan Jung 4611a65bce [eas-cli] show projects that depend on a push key (#472) 2021-06-25 13:55:16 -07:00
Quinlan Jung db29b3fc18 [eas-cli] make apple team optional in appropriate cases (#468)
* [eas-cli] make apple team optional in appropriate cases

* pr feedback
2021-06-25 13:53:37 -07:00
Dominik Sokal 2534a46bd3 [ENG-1396] change default ios image for sdk <= 41 (#479)
* change default ios image for sdk <= 41

* update CHANGELOG
2021-06-25 15:23:24 +02:00
Dominik Sokal 7def4b502e remove redundant type 2021-06-25 13:58:38 +02:00
Brent Vatne 4ea26d7ca3 Remove leftover CHANGELOG entry 2021-06-24 17:56:14 -07:00
Brent Vatne c4c8075b1f Update CHANGELOG 2021-06-24 17:55:06 -07:00
Brent Vatne 266545d2a0 v0.18.1 2021-06-24 17:43:58 -07:00
Brent Vatne ddd7046513 Add entry to CHANGELOG 2021-06-24 17:43:14 -07:00
Quinlan Jung 7a44eeb22b [eas-cli] add basic printing support for Apple App Specific Password (#474) 2021-06-24 17:42:00 -07:00
Brent Vatne 5bcf8263cf [eas-cli] Automatically migrate build credentials to EAS (#477) 2021-06-24 17:41:17 -07:00
Dominik Sokal f380ba0106 update CHANGELOG 2021-06-24 13:35:32 +02:00
Dominik Sokal 1a6d81f68e v0.18.0 2021-06-24 12:17:08 +02:00
Dominik Sokal 522818113b support new build status: new (#475)
* support new build status: 'new'

* update CHANGELOG
2021-06-24 12:07:44 +02:00
Hayden Daly f93d347665 [docs] update 'See code' links in README (#446)
Why
Majority of the See code links in the README resolve to nonexistent paths causing 404s. For example, build/commands/account/login.ts should resolve to src/commands/account/login.ts.

How
Simple find and replace with the links and updating relevant text using patch-readme.js script added to package.json.

Test Plan
Clicked through the links to make sure they all resolve to right locations.
2021-06-23 14:52:36 -07:00
Wojciech KozyraandDominik Sokal e96cee1c3b [eas-cli] support different vcs (#462)
* [eas-cli] add abstraction around VCS, implement git and local version

* Apply suggestions from code review

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

* review feedback

* update CHANGELOG

Co-authored-by: Dominik Sokal <[email protected]>
2021-06-23 12:05:31 +02:00
Quinlan Jung 4d46e0a8ac changelog stuff 2021-06-22 13:02:58 -07:00
Quinlan Jung 80e35c7524 [eas-cli] add 'remove push key' action (#470)
* [eas-cli] remove push key action

* todo
2021-06-22 12:23:31 -07:00
Quinlan Jung 186c26bc99 [eas-cli] setup push key action (#469) 2021-06-22 11:49:26 -07:00
jkh be6a7f54f9 [eas-cli][eas-json] add updates.channel to build metadata (#461) 2021-06-21 10:29:31 -07:00
Ville Immonen fff7aac5b6 Add an internal distribution with dev client to build:configure defaults (#465) 2021-06-21 12:51:27 +03:00
Quinlan Jung 0d90fbd3ff [eas-cli] create and assign push key actions (#467) 2021-06-18 10:32:05 -07:00
Quinlan Jung e71779049e [eas-cli] rename android mocks 2021-06-17 21:24:38 -07:00
Quinlan Jung 7da909e3c1 [eas-cli] fix android manager push description 2021-06-17 21:15:13 -07:00
Quinlan Jung e439fd14a9 changelog stuff 2021-06-17 11:19:45 -07:00
Ville Immonen ffed1b446a Record dev client builds created and downloaded to analytics (#454) 2021-06-17 15:28:38 +03:00
Quinlan Jung 7b65412ec6 [eas-cli] swap android manager for the refactored beta one (#460)
* [eas-cli] swap the android manager with the beta one

* [eas-cli] remove unused classes, remove new directory

* [eas-cli] delete credentials manager class

* [eas-cli] replace API newAndroid -> android

* [eas-cli] printCredentialsBeta -> printCredentials
2021-06-16 09:55:55 -07:00
Quinlan Jung f1cbdcc094 changelog stuff 2021-06-15 15:56:11 -07:00
Quinlan Jung ec5573efa6 [eas-cli] add push key graphql types (#456) 2021-06-15 11:14:33 -07:00
Quinlan JungandDominik Sokal 242da62271 [eas-cli] action to upload/download credentials.json to www (#455)
* [eas-cli] action to upload/download credentials.json to www

* Update packages/eas-cli/src/credentials/android/actions/new/SetupBuildCredentialsFromCredentialsJson.ts

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

* Update packages/eas-cli/src/credentials/android/actions/UpdateCredentialsJson.ts

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

Co-authored-by: Dominik Sokal <[email protected]>
2021-06-15 11:14:15 -07:00
Wojciech Kozyra 23d1221ef9 [eas-cli] add support for big sur image for ios builds (#457)
* [eas-cli] add support for big sure image for ios builds

* update CHANGELOG
2021-06-15 16:20:43 +02:00
Quinlan Jung 5402d8c74f changelog stuff 2021-06-14 19:02:47 -07:00
Dominik Sokal 176026645c fix typo: Expo CLI -> EAS CLI 2021-06-14 15:45:30 +02:00
Brent Vatne b0d4ed6c0e [eas-cli] Deprecate --skip-credentials-check (#442) 2021-06-10 20:27:06 -07:00
Quinlan Jung 2f7ac71921 [eas-cli] remove android fcm/keystore actions (#451) 2021-06-10 11:50:08 -07:00
Quinlan Jung b599315a85 [eas-cli] more manager dx improvements (#449)
* [eas-cli] improve ios manager dx

* add go back
2021-06-10 11:49:45 -07:00
Quinlan Jung e86d8aa77a [eas-cli] add additionalTypenames for creds mutation to invalidate urql cache 2021-06-09 19:38:16 -07:00
Quinlan Jung ab4ac87d52 [eas-cli] download and backup keystore action (#447)
* [eas-cli] download and backup keystore action

* pr feedback
2021-06-09 15:42:57 -07:00
Quinlan Jung af98fb1621 [eas-cli] refactor ios manager (#448) 2021-06-09 13:11:04 -07:00
Dominik Sokal 0ce7c1b3b4 clean up legacy stuff (#444)
* drop support for npm token in credentials.json

* remove --allow-experimantal flag

* update CHANGELOG
2021-06-09 14:31:30 +02:00
Dominik Sokal 2bdaa0d428 [ENG-1271] make iOS credentials manager work with multi-target projects (#441)
* make function for selecting Xcode scheme generic

* move XcodeBuildContext resolver

* adjust ios credentials manager to multitarget projects

* update CHANGELOG

* address PR feedback
2021-06-09 13:56:17 +02:00
Quinlan Jung 2486a4de97 [ENG-1242][eas-cli] copy Expo Classic credentials to EAS if user is eligible (#445)
* [eas-cli] copy Expo Classic credentials to EAS if user is eligible

* fix tsc

* fix lint

* pr feedback
2021-06-08 22:17:14 -07:00
kgc00 6137b215e9 [eas-cli] Introduce action stat (#437)
* Introduces a new abstract class for commands to inherit from -- EasCommand
* EasCommand does two things, login and analytics:
  * Reads local credentials or initiates login (depending on command requirements) 
  * Bootstraps analytics, sends an 'action' stat to track cli usage, and flushes events at the end of the commands lifecycle
* Build and Submit now subclass EasCommand
* Tests for EasCommand are included
2021-06-07 10:54:14 -07:00
Quinlan Jung 265ba16594 [eas-cli] refactor credential input types (#443)
* [eas-cli] use generated input types

* pulled cleaned up schema from www

* fix lint
2021-06-07 10:33:56 -07:00
Quinlan Jung a7bc3d0c98 [eas-cli] action to select android build credentials (#438)
* [eas-cli] action to select android build credentials

* oops

* pr feedback
2021-06-07 10:33:36 -07:00
Quinlan Jung ee618880d7 [eas-cli] fcm create and assign graphql actions (#440) 2021-06-04 14:03:57 -07:00
Quinlan Jung 2c95c8c310 [eas-cli] add fcm types for graphql (#439) 2021-06-04 14:02:38 -07:00
Dominik Sokal b90e37d9fc update CHANGELOG 2021-06-02 13:19:35 +02:00
Dominik Sokal 34563070f4 v0.17.0 2021-06-02 12:07:46 +02:00
Dominik Sokal 66b269f4bf update CHANGELOG 2021-06-02 12:06:41 +02:00
Brent Vatne 168cb4d4a0 [eas-cli] Fix git repo root path on Windows (#429)
* Fix git repo root path on Windows

* Update CHANGELOG
2021-06-02 12:03:33 +02:00
Quinlan Jung 91b531da43 [eas-cli] use correct method to determine android application identifier (#431)
* [eas-cli] use correct method to determine android application identifier

* pr feedback
2021-06-02 12:00:51 +02:00
Quinlan Jung 2e0d567361 update changelog 2021-06-01 11:59:51 -07:00
Quinlan Jung 81e6280cff [eas-cli] use new android build credentials provider (#427)
* [eas-cli] use new android build credentials provider

* pr feedback

* fix tsc

* pr feedback:
2021-06-01 11:56:37 -07:00
Quinlan Jung e2fda7b9d3 [eas-cli] refactor application id configuration (#432) 2021-06-01 10:34:08 -07:00
Dominik Sokal ede30807f5 fix bundle identifier getter (#434)
* fix bundle identifier getter

* update CHANGELOG
2021-06-01 14:25:03 +02:00
Brent Vatne 9f354013fa Update CHANGELOG 2021-05-26 15:09:59 -07:00
Brent Vatne 30552f134c v0.16.0 2021-05-26 14:49:42 -07:00
Brent Vatne e4beba9a21 Add env var to disable capability sync and extra logs when EXPO_DEBUG is set (#426) 2021-05-26 13:11:42 -07:00
Dominik Sokal b71ab7ec84 implement managed credentials support for multitarget iOS projects (#414)
* ignore --skip-credentials-check flag for ios builds

* account for parent bundle identifier when creating apple app identifier

* implement managed credentials support for multitarget ios projects

* update CHANGELOG.md

* address PR feedback

* upgrade @expo/config-plugins 1.0.32 -> 1.0.33

* fix lint warning

* address more PR feedback

* address PR feedback
2021-05-26 11:06:29 +02:00
Brent Vatne 596cfc372b Move updating the release on Github to after the release is completed (#418) 2021-05-25 15:56:55 +02:00
Dominik Sokal d5f6c5680c update CHANGELOG 2021-05-25 15:55:23 +02:00
Dominik Sokal 28c508ff62 v0.15.1 2021-05-25 15:44:36 +02:00
Dominik Sokal 97ff6f406d fix sending appBuildVersion to api (#423)
* generate graphql code

* fix sending appBuildVersion to api

* update CHANGELOG
2021-05-25 15:40:22 +02:00
Evan Bacon bd39b296fa Use config introspection to get all entitlements (#419)
* Use config introspection to get all entitlements

* Update CHANGELOG.md

* Updated getAppBuildGradle -> getAppBuildGradleFilePath

* Update RemoveProvisioningProfile.ts

* Update view.ts

* Update applicationId.ts

* Update entitlements.ts
2021-05-24 17:30:13 -07:00
Quinlan Jung bfbe1e930b [eas-cli] add tests for unencrypted/empty password keystores (#396)
* [eas-cli] add tests for unencrypted/empty password keystores

* detect unencrypted pem

* much sad

* much sad 2

* much sad

* much sad
2021-05-24 16:25:07 -07:00
jkh 3fe45176cc [eas-cli] make republish work smoothly with different platforms (#417) 2021-05-21 10:11:57 -07:00
Test User bffa6fbf03 Release 0.15.0 2021-05-20 14:58:23 -07:00
Test User 161808bc37 v0.15.0 2021-05-20 14:30:18 -07:00
jkh ac42b15acd [eas-cli] fix bug when publishing without platform flag (#415) 2021-05-20 07:55:48 -07:00
Dominik Sokal 47d21de4eb add appBuildVersion to build metadata (#413)
* add functions for reading buildNumber / versionCode

* add buildNumber and versionCode to metadata

* read proper app version for generic projects

* bump @expo/eas-build-job to 0.2.35

* address PR feedback

* update CHANGELOG.md
2021-05-20 11:40:17 +02:00
Wojciech Kozyra e12ffa9ebc docs for introducing breaking changes in local builds api (#408)
* docs for introducing breaking changes in local builds api

* review feedback
2021-05-19 14:21:35 +02:00
Dominik Sokal 2944023986 clean up test output 2021-05-19 11:36:30 +02:00
Dominik Sokal 49c1e7670d warn about deprecated android.package/ios.bundleIdentifier only once 2021-05-18 11:28:31 +02:00
Dominik Sokal b157b901e3 simplify app identifier management (#407)
* simplify app identifier management

* remove disableIosBundleIdentifierValidation from eas.json

* update CHANGELOG.md

* add unit tests

* address PR feedback
2021-05-18 09:42:58 +02:00
Brent Vatne 1a1f88f202 [eas-json] Include development-client in valid buildType for internal distribution (#410) 2021-05-17 18:14:07 -07:00
jkh d02d70da91 [eas-cli] put projectId caching behind EAS_ENABLE_PROJECT_ID flag (#412) 2021-05-17 12:48:36 -07:00
Evan Bacon c37f2957bb Bump @expo/[email protected] to support Apple server errors (#411) 2021-05-17 11:27:23 -07:00
Wojciech Kozyra a32f5fe57a [eas-cli] allow empty keystore password and optional key password (#398)
* [eas-cli] allow empty keystore password and optional key password

* update @expo/config and @expo/config-plugins

* update CHNAGELOG
2021-05-17 12:39:49 +02:00
Test User b7f6d36a63 [eas-cli] hide 'eas init' 2021-05-14 18:05:11 -07:00
Mike Hampton 2975a025ac Add expoCli parameter. (#409) 2021-05-14 15:36:05 -04:00
Wojciech Kozyra f4e396dde2 [eas-cli] fix validation for empty password in dist cert (#403)
* [eas-cli] fix validation for empty password in dist cert

* add tests

* change error message

* fix test description

* review feedback
2021-05-14 10:44:48 +02:00
Evan Bacon ef54911323 Dynamically toggle capabilities before build (#384)
* Enable associated-domains

* Update CHANGELOG.md

* Added support for native projects

* Update ensureAppExists-test.ts

* Validate entitlements from JSON

* removed unneeded tests

* Update bundleIdCapabilities.ts

* use Workflow

* Forward props everywhere

* refactor

* Added return types

* Update entitlements.ts

* Update bundleIdCapabilities.ts

* Update bundleIdCapabilities.ts

* Update bundleIdCapabilities.ts

* cleanup

* IosCapabilitiesOptions
2021-05-13 20:25:02 -07:00
Quinlan Jung 18b1074232 [eas-cli] setup android build credentials with graphql (#406)
* [eas-cli] setup android build credentials with graphql

* pr feedback
2021-05-13 16:37:28 -07:00
Jonathan 小林 Hales 8468e0c5ea [eas-cli] make branch name optional for branch:publish 2021-05-12 12:54:43 -07:00
jkh 436d5c9ec9 [eas-cli] query app by ID instead of fullName in listBranchesAsync (#405) 2021-05-12 10:04:05 -07:00
jkh 1f88f7e1a3 [eas-cli] replace ensureProjectExistsAsync with getProjectIdAsync (#404) 2021-05-12 09:34:55 -07:00
jkh 09682754f8 [eas-cli] add 'eas init' command (#402) 2021-05-12 07:49:05 -07:00
Mike HamptonandWojciech Kozyra 8e3d84b1e5 [eas-cli] Support buildConfiguration parameter. (#394)
* [eas-cli] Support buildConfiguration parameter.

* Update CHANGELOG.md

* Address PR comments.

* Update CHANGELOG.md

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

* Update new features in changelog.

Co-authored-by: Wojciech Kozyra <[email protected]>
2021-05-12 10:21:01 -04:00
Dominik Sokal 8bb2631192 Revert "[eas-cli] set default urql requestPolicy to network-only (#397)"
This reverts commit d97ed80867.

The proper fix has been implemented in #399.
2021-05-12 10:10:44 +02:00
Dominik Sokal 38a7fb6266 set empty save-prefix (#400) 2021-05-12 10:06:30 +02:00
Dominik Sokal 0ac2fb77a1 Pin versions in package.json (#399)
* pin versions in package.json

* update CHANGELOG.md
2021-05-12 10:04:09 +02:00
jkh f12774b539 [eas-cli] display the platforms supported by an update group (#391) 2021-05-11 09:03:47 -07:00
jkh d50bdedcec [eas-cli] add --platform flag to branch:publish command (#389) 2021-05-11 08:22:48 -07:00
Dominik Sokal 8d443e5992 update CONTRIBUTING.md 2021-05-11 12:01:52 +02:00
Dominik Sokal 2b5fb6de53 update CHANGELOG.md 2021-05-11 11:57:33 +02:00
Dominik Sokal dc86f27199 v0.14.1 2021-05-11 11:23:22 +02:00
jkh d97ed80867 [eas-cli] set default urql requestPolicy to network-only (#397)
* [eas-cli] set default urql requestPolicy to network-only

* add changelog
2021-05-11 11:14:55 +02:00
Quinlan Jung 3601b448fe [eas-cli] create keystore action (#387)
* [eas-cli] create android keystore action

* 1: some pr feedback
2021-05-10 16:38:17 -07:00
Quinlan Jung 68aab13d51 [eas-cli] Keystore utils and fixtures (#386)
* [eas-cli] Keystore utils and fixtures

validate using pem decryption instead

* 0: some pr feedback

* pr feedback
2021-05-10 16:25:07 -07:00
Brent Vatne 6cef13d756 Update CHANGELOG 2021-05-10 11:36:59 -07:00
Brent Vatne 1d45941f5d v0.14.0 2021-05-10 11:28:46 -07:00
Quinlan Jung 5e5badf11f [eas-cli] credentials manager DX nits (#383) 2021-05-03 19:16:14 -07:00
jkh 840c65946b [eas-cli] remove --force to fix eas publish (#381) 2021-05-03 15:35:52 -07:00
Quinlan Jung fdbd786126 [eas-cli] move from deprecated iosAppCredentialsArray to iosAppCredentialsList (#380) 2021-05-03 13:16:33 -07:00
Quinlan Jung d5365275e0 [eas-cli] beta android credentials manager (#378) 2021-04-29 12:07:20 -07:00
Quinlan Jung 886e48d3ee [eas-cli] android graphql types (#377) 2021-04-29 11:17:18 -07:00
Wojciech Kozyra 342c78a4f6 [eas-cli] disable pending build check (#373)
* [eas-cli] disable pending builds check

* update CHANGELOG.md
2021-04-28 15:13:51 +02:00
Axel Delafosse a483d94e26 [eas-cli] assert (#372) 2021-04-28 11:54:33 +02:00
Axel DelafosseandDominik Sokal b40412a7a1 [eas-cli] add QR code to install internal distribution build on device (#371)
* [website] add qr code to install internal distribution build on device

* [eas-cli] update changelog

* [eas-cli] sort imports

* [eas-cli] sort imports

* [eas-cli] improve getInstallUrl typing

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

* [eas-cli] fix typing

* [eas-cli] clean code

Co-authored-by: Dominik Sokal <[email protected]>
2021-04-28 11:43:21 +02:00
Quinlan Jung d652e04f7b [eas-cli] getAppAsync not used anywhere outside (#369) 2021-04-27 13:09:10 -07:00
Quinlan Jung 85326d1104 [eas-cli] merge beta and main cred utils (#368) 2021-04-27 13:08:51 -07:00
Quinlan Jung 28c087138b [eas-cli] replace printCredentials with printCredentialsBeta (#367) 2021-04-27 13:08:30 -07:00
jkh 8badf14ec3 [eas-cli] create asset keys without an extension (#366)
* Clients must be using expo-asset >= 8.3.2 in order to handle this new update format.
2021-04-27 06:02:12 -07:00
Wojciech Kozyra 4f1db29ef8 [eas-cli] increase build wait timeout (#370)
* [eas-cli] increase build wait timeout

* Update CHANGELOG.md
2021-04-27 14:19:23 +02:00
Quinlan Jung 2354dc1b71 update changelog 2021-04-26 12:16:21 -07:00
Quinlan Jung f106bfa6d7 [eas-cli] replace ctx.ios with ctx.newIos (#365)
remove old ctx.ios

ctx.newIos -> ctx.ios

fix lint

unused fixtures

remove unused fixtures

merge fixtures

fix lint
2021-04-26 12:11:36 -07:00
Quinlan Jung 1a7edbea06 [eas-cli] merge everything from actions/new to actions (#364)
delete unused actions

delete old inner class

remove uneeded dist cert utils

merge dist cert utils

remove unused functions of pprofile utils

pop out new directory
2021-04-26 12:10:27 -07:00
Quinlan Jung 5bfaee9e4d [eas-cli] swap beta credentials manager with current (#363) 2021-04-26 12:07:35 -07:00
Quinlan Jung 0cb2309d7d [eas-cli] add additionalTypenames to delete mutations to clear urql cache 2021-04-23 13:43:50 -07:00
Quinlan Jung 0415a1d97f changelog stuff 2021-04-23 12:47:59 -07:00
Quinlan Jung 249c60f4d1 [eas-cli] add remove provisioning profile to beta manager (#361) 2021-04-23 12:33:48 -07:00
Quinlan Jung c357cc378c [eas-cli] add high level actions to beta manager (#360) 2021-04-23 12:29:14 -07:00
Quinlan Jung f7c0d731b6 changelog stuff" 2021-04-22 22:48:38 -07:00
Quinlan Jung e852e3edac [eas-cli] add CreateDistCert and UseExistingDistCert to beta manager (#357)
* [eas-cli] add create and add existing dist cert options

* nits:

* fix dx
2021-04-22 17:27:17 -07:00
Quinlan Jung 2a283b74a3 [eas-cli][eas-json] add getBuildProfileNamesAsync helper (#351)
* [eas-cli][eas-json] getProfileNames helper

* tweak name

* pr feedback

* pr feedback
2021-04-22 13:47:18 -07:00
Quinlan Jung c749bc2372 [eas-cli] refactor UpdateCredentialsJson (#352)
* [eas-cli] refactor UpdateCredentialsJson

* fix broken tests from switching b64 pprofile test fixture

* fix lint

* add to beta manager
2021-04-22 12:02:45 -07:00
Brent Vatne 9c60f7190e Update CHANGELOG 2021-04-22 10:49:49 -07:00
Brent Vatne 8126b2276e v0.13.0 2021-04-22 10:16:06 -07:00
Dominik Sokal e1de21015f implement SetupInternalProvisioningProfile (#344) 2021-04-22 10:01:35 -07:00
Dominik Sokal 61e505f1f8 fix typo 2021-04-22 16:00:39 +02:00
Wojciech KozyraandDominik Sokal 2e3611161e fix api deprecation error (#359)
* fix api deprecation error

* update CHANGELOG.md

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

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

Co-authored-by: Dominik Sokal <[email protected]>
2021-04-22 14:08:25 +02:00
Dominik Sokal 95f5eaaacf update changelog 2021-04-22 11:40:42 +02:00
Dominik Sokal 87a3ea99e2 v0.12.0 2021-04-22 10:32:28 +02:00
Quinlan Jung ddab65a55d [eas-cli] adhoc opposite name (#358) 2021-04-22 10:29:37 +02:00
Wojciech Kozyra 0cf8f8ce0d [eas-cli] support --clear-cache (#355)
* [eas-cli] support --clear-cache

* update CHANGELOG.md

* fix param description
2021-04-22 10:17:09 +02:00
Dominik Sokal 2b531ec70c update contributing doc (#353) 2021-04-22 10:04:44 +02:00
Dominik Sokal 6cd7966904 send username as build metadata property (#354)
* send username as build metadata property

* update changelog

* upgrade @expo/eas-build-job to 0.2.27
2021-04-21 14:38:25 +02:00
Dominik Sokal ad72ab990f Update CHANGELOG.md 2021-04-21 09:20:08 +02:00
Brent Vatne b53412fc00 Update CHANGELOG 2021-04-20 15:29:06 -07:00
Brent Vatne cbb2de9e11 v0.11.1 2021-04-20 15:00:15 -07:00
Brent Vatne 593c42f601 [eas-cli] Bump @expo/apple-utils to fix initial iOS app capabilities sync 2021-04-20 14:59:27 -07:00
Quinlan Jung 84b10b9dad [eas-cli] fix lint 2021-04-20 14:14:40 -07:00
Quinlan Jung 0f286061e4 [eas-cli] tests for SelectIosDistributionTypeGraphqlFromBuildProfile 2021-04-20 14:13:44 -07:00
Quinlan Jung c8aad01480 [eas-cli] non interactive support, aesthetic nits 2021-04-20 14:12:12 -07:00
Quinlan Jung d70a6bee69 [eas-cli] resolve ios distribution types (#348)
* [eas-cli] resolve ios distribution types

* pr feedback

* pr feedback
2021-04-20 12:46:55 -07:00
Dominik Sokal 67799b5c54 update changelog 2021-04-20 15:36:23 +02:00
Dominik Sokal 05b8eef622 v0.11.0 2021-04-20 15:08:57 +02:00
Dominik Sokal 50e56b5bc4 fix building APKs for Android managed projects (#349)
* fix building APKs for Android managed projects

* update CHANGELOG
2021-04-20 14:39:58 +02:00
Dominik Sokal 30ab4e62ec simplify credentials providers - remove auto option (#345)
* simplify credentials providers - remove auto option

* update changelog

* address PR feedback
2021-04-20 10:39:37 +02:00
Dominik Sokal a1d901294b allow for registering devices when building app for internal distribution (#340)
* allow for registering devices when building app for internal distribution

* update CHANGELOG
2021-04-19 12:39:20 +02:00
Dominik Sokal eed818cfe5 refactor setting up adhoc provisioning profile (#338)
* refactor appstore provisioning profile validation

* refactor SetupAdhocProvisioningProfile

* update CHANGELOG

* address PR feedback
2021-04-19 12:29:37 +02:00
Dominik Sokal db0b6bfcf5 remove redundant finally block 2021-04-16 15:54:44 +02:00
Dominik Sokal da81c1c6df remove incorrect usage example from secret:list command 2021-04-16 14:16:41 +02:00
Dominik Sokal 42d3296f07 update CHANGELOG 2021-04-16 14:13:09 +02:00
Dominik Sokal f9b4b1a6e8 v0.10.0 2021-04-16 13:56:25 +02:00
Will Schurman d03603a75f Use special Expo SDK runtime version for managed projects (#336) 2021-04-15 11:15:02 -07:00
Juwan Wheatley ffa9e5ef66 [eas-cli] make secrets commands follow styleguide (#315)
* [eas-cli] make CLI more styleguide-compliant

* add --json flag

* update changelog

* json flag is a new feature, not a breaking change

* remove secrets JSON flag since we still show non-result output (and same for other commands)

* remove extra import
2021-04-15 13:05:28 -04:00
Dominik Sokal e7f3cf9c74 Pass --preserveWatchOutput to tsc --watch
This makes the 'yarn start' output preserve the output for previous tsc runs.
2021-04-15 16:42:04 +02:00
Dominik Sokal e82a4a0a57 replace all iOS -> Ios (#337) 2021-04-15 12:23:14 +02:00
Bartłomiej Klocek 175f07fac9 [eas cli][submit] Use GraphQL instead of REST endpoints (#333)
* Autogenerated gql code

* Add Submission Query

* Add Submission Mutation

* Use GQL generated types

* Remove Submission types

* Replace SubmissionPlatform with AppPlatform

* Update Fix tests
2021-04-15 11:43:02 +02:00
Dominik Sokal 2869744a5d add support for adhoc and universal distribution within enterprise teams (#335)
* [eas-json] add enterpriseProvisioning prop

* implement generating enterprise universal credentials

* fix tests
2021-04-15 10:42:59 +02:00
Dominik Sokal 99f898a483 refactor IosCredentialsProvider to use graphql (#329)
* refactor IosCredentialsProvider to use graphql

* add tests for IosCredentialsProvider

* address PR feedback

* fix tests
2021-04-15 10:23:42 +02:00
Dominik Sokal 1448db96e6 don't update capabilities if there's nothing to change (#334)
* don't update capabilities if there's nothing to change

* add tests

* update CHANGELOG
2021-04-15 10:19:30 +02:00
Quinlan Jung e02036a46a [eas-cli] ilovecats deletion 2021-04-14 14:31:04 -07:00
Quinlan JungandDominik Sokal 09f33263cb [eas-cli] use new graphql api in SetupBuildCredentialsFromCredentials… (#328)
* [eas-cli] use new graphql api in SetupBuildCredentialsFromCredentialsJson

* pr feedback

* tests

* Update packages/eas-cli/src/credentials/ios/actions/new/BuildCredentialsUtils.ts

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

* pr feedback

Co-authored-by: Dominik Sokal <[email protected]>
2021-04-14 14:28:06 -07:00
jkh 412b1ec74c [eas-cli] add update:delete command (#332) 2021-04-14 12:04:56 -07:00
jkh 9a1538fb4d add eas update:view [ID] (#327) 2021-04-14 11:04:58 -07:00
jkh 0ad9b31ee3 [eas-cli] update channel:list to handle rollouts (#331) 2021-04-14 11:04:49 -07:00
Wojciech Kozyra 4cbb7bb2f2 [eas-cli] Support for local builds (#305)
* [eas-cli] Support for local builds

* add CHANGELOG + mark as experimental

* review feedback
2021-04-14 09:08:06 +02:00
jkh ae109cd8c6 [eas-cli] handle branch with no updates gracefully (#330) 2021-04-13 07:11:54 -07:00
Dominik Sokal fbaba774a6 enable type checking for tests and mocks (#325)
* use separate tsconfig files for development and release

* fix type errors in tests

* add typecheck script

* address PR feedback
2021-04-13 11:49:07 +02:00
jkh 38bc8062a4 [eas-cli] make branch name a positional instead of a named arg (#326) 2021-04-12 10:50:41 -07:00
Dominik Sokal d41d184b55 add start script (#323) 2021-04-12 16:36:56 +02:00
jkh d6d32a7a7d [eas-cli] clean up formatting of list/view commands (#322) 2021-04-12 07:19:37 -07:00
Dominik Sokal 15a9532fa7 refactor credentials actions (#321)
* make SetupAdhocProvisioningProfile.runAsync return result

* make SetupDistributionCertificate.runAsync return result

* print ios credentials using new display function

* generate graphql code

* update CHANGELOG

* address PR feedback

* updates changes after running generate-graphql-code

* address more PR feedback

* fix lint warning
2021-04-12 11:38:06 +02:00
Dominik Sokal 3b9fb724fe update CONTRIBUTING.md 2021-04-12 10:41:36 +02:00
Dominik Sokal 33767b5635 update CHANGELOG 2021-04-12 10:18:27 +02:00
Test User 175fc2b30e v0.9.1 2021-04-09 14:43:36 -07:00
Quinlan Jung aec9347d69 [eas-cli] fix validation check of provisioning profile (#318)
* [eas-cli] fix validation check of provisioning profile

* changelog and stuff

* quin not dominik

* move the changelog thing
2021-04-09 13:05:25 -07:00
Quinlan Jung 91500d07c0 [eas-cli] replace SetupProvisioningProfile (#317)
* [eas-cli] replace SetupProvisioningProfile

* changelog

* more changelog
2021-04-09 13:02:12 -07:00
Test User 62ca14b504 Commit README updates 2021-04-09 10:13:28 -07:00
Test User 9158a95d57 v0.9.0 2021-04-09 09:59:29 -07:00
Test User d0f3e387e7 Bump @expo/config-plugins to fix eas build:configure on Windows 2021-04-09 09:58:43 -07:00
jkh 158856f8ec [eas-cli] upgrade @urql/core and add retry (#320) 2021-04-09 07:19:04 -07:00
Dominik Sokal 1e59957753 Update CHANGELOG.md 2021-04-09 11:50:36 +02:00
Dominik Sokal f97d46040c add commands for managing webhooks (#314)
* add webhook:create command

* add webhook:list command

* add webhook:view command

* add webhook:update command

* add webhook:delete command

* generate graphql code

* update CHANGELOG

* address PR feedback
2021-04-09 11:50:07 +02:00
Dominik Sokal d7485cb5d2 fix first ios build for a project without ios.bundleIdentifier in app.json (#319)
* fix first ios build for a project without ios.bundleIdentifier in app.json

* update CHANGELOG

* address PR feedback

* address more PR feedback
2021-04-09 11:31:17 +02:00
071fdb7f4a [eas submit] Added new common error messages (#311)
* [eas submit] Added new common error messages

* Update CHANGELOG

* Apply suggestions from code review

Co-authored-by: James Ide <[email protected]>
Co-authored-by: Brent Vatne <[email protected]>

Co-authored-by: James Ide <[email protected]>
Co-authored-by: Brent Vatne <[email protected]>
2021-04-09 08:17:33 +02:00
jkh 6dd96ffb20 [eas-cli] add more info to transient errors (#316) 2021-04-08 16:26:34 -04:00
Quinlan Jung f56fb0f7de [eas-cli] setupProvisioningProfile using graphql (#301)
* [eas-cli] setupProvisioningProfile using graphql

refactor

fix lint

setup provisioning profile

* refactor stuff

* add comments for ppl

* simplify logic

* tests

* pr feedback
2021-04-08 11:19:14 -07:00
Wojciech Kozyra 7d28f6add9 add LICENSE (#313) 2021-04-08 12:35:50 +02:00
Juwan Wheatley eebef3e461 [eas-cli] use autocomplete list for secrets:delete (#309)
* [eas-cli] use autocomplete list for secrets:delete

* update changelog

* check if secret is account-wide and tell user that it could affect multiple apps
2021-04-07 13:08:00 -04:00
Dominik Sokal 8336f630ea update CHANGELOG 2021-04-06 12:55:17 +02:00
Dominik Sokal 7d6bdbb6ed v0.8.1 2021-04-06 12:30:31 +02:00
Dominik Sokal 403b5c237f don't use graphql cache for polling builds (#310)
* don't use graphql cache for polling builds

* update CHANGELOG
2021-04-06 12:29:41 +02:00
Dominik Sokal 2367ed7911 update CHANGELOG.md 2021-04-06 11:21:13 +02:00
Dominik Sokal b650f76979 v0.8.0 2021-04-06 11:12:43 +02:00
Dominik Sokal 96e59c198d update CHANGELOG.md 2021-04-06 11:11:47 +02:00
Dominik Sokal bf1704d268 use graphql for generating upload sessions (#307)
* use graphql for generating upload sessions

* address PR feedback

* fix tests
2021-04-06 11:06:47 +02:00
Dominik Sokal 279dfd00c8 eas build - use GraphQL instead of REST endpoints for build queries (#306)
* use graphql for printing pending builds

* use graphql for polling build status

* remove some unused code
2021-04-02 12:10:59 +02:00
Dominik Sokal 4d2577865a eas build - use GraphQL instead of REST endpoints (#303)
* regenerate graphql schema

* use graphql mutations for creating builds
2021-04-02 12:06:10 +02:00
Quinlan Jung 9d88fba40e [eas-cli] configure provisioning profile using new api (#299)
* [eas-cli] configure provisioning profile using new api

change positional args

* configure prov prof: pr feedback

* fix lint
2021-04-01 23:16:34 -07:00
Juwan Wheatley 9d2353b4f7 [eas-cli] use flags for secrets:create and rename target to scope (#300)
* use flags instead of args for secrets:create

* rename target to scope

* show secret ID before timestamp

* add changelog entries and note about name validation source of truth
2021-04-01 16:13:44 -04:00
Dominik Sokal 44b338d50a unhide secrets:create and secrets:delete (#302) 2021-04-01 19:06:23 +02:00
Bartłomiej Klocek 62114cab3b [Submit] Skip tar extraction logic in CLI (#297)
* [submit] Skip tar extraction logic for URL

* Remove tar extraction utils

* Update CHANGELOG

* Simplified path handling code

* New unit tests

* Add URL validation
2021-04-01 18:11:23 +02:00
Dominik Sokal d426fb0355 fix string interpolation 2021-04-01 11:34:32 +02:00
Quinlan Jung 2a8d6683ff [eas-cli] fix typo in tests 2021-03-31 17:57:46 -07:00
Juwan WheatleyandDominik Sokal 7b18165390 [eas-cli] fix secret name validation when prompted (#298)
* [eas-cli] fix secret name validation when prompted

* add changelog entry

* Update CHANGELOG.md

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

Co-authored-by: Dominik Sokal <[email protected]>
2021-03-31 19:43:46 -04:00
Dominik Sokal 814a44ac27 change cache object schema + rename secretEnvs -> environmentSecrets (#295)
* change cache object schema

* update CHANGELOG.md

* rename secretEnvs -> environmentSecrets

* update @expo/eas-build-job to 0.2.22
2021-03-31 10:20:04 +02:00
jkh f5483c8996 [eas-cli] add command eas channel:rollout (#290) 2021-03-30 13:43:37 -07:00
Juwan Wheatley acdbed4762 [eas-cli] fix "irreversible" typo (#282) 2021-03-30 16:24:19 +02:00
Dominik Sokal f09e4b54cf add distribution/release channel to graphql formatter 2021-03-30 16:23:41 +02:00
Vincent Thibault 859fdf5e46 Add distribution/channel informations in build:view/list (#284)
* Add new informations in build:view/list

* Update CHANGELOG.md

* Add nullish coalescing operator
2021-03-30 16:17:16 +02:00
Wojciech Kozyra 6aa72d5b7d [eas-cli] add analytics support (#162) 2021-03-29 11:58:05 +02:00
Quinlan Jung 886900df29 [eas-cli] make dist cert setup generic with graphql api (#294)
* [eas-cli] make dist cert setup generic

* fix tsc
2021-03-26 13:17:07 -07:00
Quinlan Jung a450fed1f8 [eas-cli] create provisioning profile using graphql api (#293) 2021-03-26 13:16:37 -07:00
Dominik Sokal 4c78100adb use graphql to create unpublished app (#288) 2021-03-26 09:01:42 +01:00
Dominik Sokal 0317e6fe71 refactor build:list and build:view to use graphql (#286) 2021-03-26 08:56:04 +01:00
jkh 6825877344 [eas-cli] add support for rollouts to channel:view command (#289) 2021-03-24 07:04:07 -07:00
Dominik Sokal a47ea88d28 Update CHANGELOG.md 2021-03-23 10:37:57 +01:00
Wojciech Kozyra a9e7f304ec Update CHANGELOG.md 2021-03-22 13:36:10 +01:00
Wojciech Kozyra d92a2ae3f4 v0.7.0 2021-03-22 13:33:34 +01:00
Dominik Sokal b7dea35da4 better message when build was canceled 2021-03-22 12:02:08 +01:00
Wojciech Kozyra 5a5f11752d Update @expo/eas-build-job 2021-03-22 12:02:08 +01:00
Wojciech Kozyra b6c300b5c2 [eas-json] Detect dependency cycle for build profiles (#283)
* [eas-json] Detect dependency cycle for build profiles

* Update CHANGELOG.md

* review feedback
2021-03-22 11:15:38 +01:00
Wojciech Kozyra 0117dbfa5f [eas-cli] verify that "name" in app.sjon can be used as iOS target name (#280) 2021-03-18 12:15:52 +01:00
Dominik Sokal c106f8a1a4 fix project utils calls after merge 2021-03-18 09:55:55 +01:00
Dominik Sokal 8b9741a70c refactor project utils (#278) 2021-03-18 09:51:30 +01:00
Juwan Wheatley 271b85fd74 [eas-cli] create commands for managing environment secrets (#275)
* set up graphql queries and mutations

* add secrets: list command

* add create command

* list all secrets

* add deletion command, add ID to list, add EAS check to create

* update changelog

* fix typo

* use target enum

* use US cancel

* move AccountResolver to generic place

* secret isn't reassigned

* check for eas in delete

* make mutation inputs required

* update generated graphql files

* use findAccountByName directly instead of AccountResolver

* update list format

* move accountresolver back into device manager file

* restore old devicemanager source

* lint
2021-03-17 14:50:40 -04:00
Dominik Sokal 21a481790b [ENG-438] allow for installing custom bundler version (#277)
* allow for installing custom bundler version

* update CHANGELOG.md

* bump @expo/eas-build-job to 0.2.20

* fix printing docs url
2021-03-17 13:52:52 +01:00
Wojciech Kozyra ab5d9237f1 [eas-cli] update external errors logic (#276) 2021-03-16 17:10:37 +01:00
Wojciech Kozyra 756853f2f3 [eas-cli] autmatically commit when EAS_BUILD_AUTOCOMMIT is set (#271) 2021-03-15 09:23:30 +01:00
Bartłomiej Klocek 5eb80d15f6 [ENG-509][submit] Fix local archive prompt (#273) 2021-03-12 11:34:31 +01:00
Dominik Sokal a98043b4ad print common error messages when builds fail (#272)
* print common error messages when builds fail

* update CHANGELOG

* address PR feedback

* address more PR feedback
2021-03-12 10:29:33 +01:00
jkh d0818932e7 [eas-cli] add --republish and --group flags to branch:publish command. (#253) 2021-03-11 05:37:18 -08:00
Dominik Sokal 23b60ed385 fix typo 2021-03-09 11:08:39 +01:00
Dominik Sokal 5d2811bf21 update CHANGELOG 2021-03-09 11:05:14 +01:00
Dominik Sokal 7b202f2aa7 v0.6.0 2021-03-09 10:42:59 +01:00
Dominik SokalandWojciech Kozyra fa6e9c1735 [ENG-444] support internal distribution in non-interactive builds (#269)
* support internal distribution in non-interactive builds

* update CHANGELOG.md

* address PR feedback

* Update packages/eas-cli/src/credentials/ios/actions/new/SetupAdhocProvisioningProfile.ts

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

* address more PR feedback

* update error message

Co-authored-by: Wojciech Kozyra <[email protected]>
2021-03-09 10:40:52 +01:00
Wojciech KozyraandDominik Sokal c3300036d8 [eas-cli] handle unknown team name in device list (#268)
* [eas-cli] handle unknown team name in device list

* Update CHANGELOG.md

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

Co-authored-by: Dominik Sokal <[email protected]>
2021-03-05 14:05:06 +01:00
Dominik Sokal 5efcdb2940 [eas-cli] use Metadata type from @expo/eas-build-job (#267) 2021-03-05 09:43:23 +01:00
Wojciech Kozyra 8113450ea4 [eas-json] add experimental.disableIosBundleIdentifierValidation flag (#263) 2021-03-04 15:12:45 +01:00
Dominik Sokal d10b0c3c65 iOS builds: default to build configuration 'Release' (#266)
* iOS builds: default to build configuration 'Release'

* update CHANGELOG.md

* fix tests
2021-03-04 10:25:59 +01:00
Bartosz Kaszubowski e8de0e8712 add installation section to eas-cli readme (#255) 2021-03-03 16:53:50 -08:00
Bartłomiej Klocek e1da1099da [eas submit] UX improvements (#144)
* Add finish message for iOS Submissions

* Improve summary table

* Improve table look

* Update CHANGELOG
2021-03-03 16:57:08 +01:00
Dominik Sokal 264aa8fefa [ENG-326] send more build metadata: release channel, build profile, git commit hash (#265)
* send more build metadata: release channel, build profile, git commit hash

* update CHANGELOG.md

* address PR feedback

* upgrade @expo/config-plugins 1.0.19-alpha.0 -> 1.0.21-alpha.0

* address PR feedback
2021-03-03 16:00:59 +01:00
jkh 941d0294b7 [eas-cli] use generated graphql types (#262) 2021-03-03 06:53:54 -08:00
Wojciech Kozyra 366f4e8914 [eas-cli] display size of the archive (#264) 2021-03-03 14:11:31 +01:00
Dominik Sokal 1460c3c5a2 add first item to the PR checklist (#260) 2021-03-03 09:16:37 +01:00
jkh 6271472827 [eas-cli] add branch:delete command (#248) 2021-03-02 09:43:53 -08:00
Dominik Sokal 3bc4a054ae update CHANGELOG.md 2021-03-02 11:35:19 +01:00
Dominik Sokal dd85d559d7 v0.5.0 2021-03-02 11:14:43 +01:00
Wojciech Kozyra ab10b082e5 [eas-cli] fix submit with tar.gz files (#257)
* [eas-cli] fix submit with tar.gz files

* [eas-cli] update CHANGELOG.md
2021-03-02 10:52:19 +01:00
Dominik Sokal 60b60066f9 enable internal distribution support for self-managed credentials (#256)
* enable internal distribution support for self-managed credentials

* update CHANGELOG.md

* address PR feedback

* fix typo
2021-03-02 10:49:59 +01:00
Wojciech Kozyra 9e9e517698 [eas-cli] detect untracked files when checking for changes in repository (#259)
* [eas-cli] detect untracked files when checking for changes in repository

* Update CHANGELOG.md
2021-03-02 10:49:27 +01:00
Wojciech Kozyra 020c0f2b98 [eas-cli] commit all files before build (#251)
* [eas-cli] commit all files before build

* Update CHANGELOG.md
2021-03-01 10:08:25 +01:00
Wojciech Kozyra dd74ebdc51 [eas-cli] use error.expoApiV2ErrorCode to check error code (#252) 2021-03-01 09:24:22 +01:00
Wojciech KozyraandDominik Sokal ddf7381efb [eas-cli] EAS Build - build cache support (#247)
* [eas-cli] EAS Build - build cache support

* Update CHANGELOG

* Update CHANGELOG.md

Co-authored-by: Dominik Sokal <[email protected]>
2021-03-01 09:24:05 +01:00
Cedric van Puttenandjkh 3b67b912e4 [eas-cli] Add channel edit list and view commands (#249)
* [eas-cli] add channel:view command

* [eas-cli] add channel:list and channel:edit commands

* [eas-cli] Fix styling issue in channel edit command

* [eas-cli] Assume single branch and update when listing

* [eas-cli] Move graphql queries back to command files

* [eas-cli] Cleam up graphql limits

* [eas-cli] Show current branch when editing channel

* [eas-cli] Reword channel:edit description

Co-authored-by: jkh <[email protected]>

* [eas-cli] Reword channel:edit question

Co-authored-by: jkh <[email protected]>

* [eas-cli] Update table output of channel commands

* [eas-cli] Remove limit from properties and hardcode limit

Co-authored-by: jkh <[email protected]>

* [eas-cli] Reformat todos to fit repo better

Co-authored-by: jkh <[email protected]>

* [eas-cli] Remove limit from channel list query

Co-authored-by: jkh <[email protected]>
2021-02-27 14:54:02 +01:00
Cedric van Putten 37d7fa4510 [eas-cli] Fix json output for branch and channel commands (#250) 2021-02-27 14:30:03 +01:00
jkh 096601473a [eas-cli] fix tsc in prompt.ts (#254) 2021-02-26 07:42:28 -08:00
jkh 2e25ed81ca [eas-cli] rename fileHash to fileSHA256 (#244) 2021-02-23 04:35:48 -08:00
jkh 4e11ea1d77 [eas-cli] add default bundler for branch:publish (#237) 2021-02-23 04:28:37 -08:00
Dominik Sokal d9d470426b upgrade @expo/eas-build-job 0.2.12 -> 0.2.13 (#245)
* upgrade @expo/eas-build-job 0.2.12 -> 0.2.13

* fix tests

* update CHANGELOG.md
2021-02-23 12:52:24 +01:00
Dominik Sokal 119b4ad8b2 update CHANGELOG.md 2021-02-23 11:26:26 +01:00
Dominik Sokal ae1e9f000d update README.md 2021-02-23 11:25:46 +01:00
Dominik Sokal eb0505f8f8 v0.4.3 2021-02-23 11:12:02 +01:00
Dominik Sokal c8f7800ab5 update CHANGELOG.md 2021-02-23 11:10:15 +01:00
Wojciech Kozyra bc4e7becdd [eas-cli] use fixed version of @expo/eas-json (#243) 2021-02-22 15:03:06 +01:00
jkh 71ece19c40 [eas-cli] rename updateGroup, updateMessage, branchName, channelName (#242) 2021-02-19 12:07:39 -08:00
Dominik Sokal e9053c0d15 add support for ios simulator builds (#240)
* add support for ios simulator builds

* update CHANGELOG.md

* bump @expo/eas-build-job to 0.2.12

* fix lint errors
2021-02-19 13:35:06 +01:00
Dominik Sokal cf76296142 update CHANGELOG.md 2021-02-18 11:43:33 +01:00
Dominik Sokal 5e5d3626f4 v0.4.2 2021-02-18 11:36:14 +01:00
Dominik Sokal 5808c5b412 add CHANGELOG.md (#239) 2021-02-18 11:30:13 +01:00
Dominik Sokal cd64392d44 [eas-cli] bump @expo/config-plugins to 1.0.19-alpha.0 (#238) 2021-02-18 11:20:39 +01:00
Wojciech Kozyra 79f889e16c v0.4.1 2021-02-16 19:17:33 +01:00
Wojciech Kozyra 595bfecf1c [eas-cli] fix "buildType" is not allowed error 2021-02-16 19:15:10 +01:00
Dominik Sokal ced2e192eb v0.4.0 2021-02-16 15:08:05 +01:00
Dominik Sokal ee91612c07 [eas-cli][eas-json] add schemeBuildConfiguration to eas.json (#234)
* [eas-cli] use newer @expo/config-plugins

* [eas-cli][eas-json] add buildConfiguration to eas.json

* address PR feedback

* upgrade @expo/eas-build-job and @expo/config-plugins

* [eas-json] reuse type from @expo/eas-build-job

* [eas-json] fix exports
2021-02-16 14:40:57 +01:00
Quinlan Jung 4d815c1fd9 [eas-cli] tests for credential removal (#236)
* [eas-cli] tests for credential removal

* fix tsc
2021-02-15 18:11:51 -08:00
Quinlan Jung d2687a55b7 [eas-cli] basic RemoveDistCert (and RemoveProvisioningProfile) (#221)
* [www] remove dist cert and prov profile

* pr feedback

* fix tests

* cleanup removal code

* fix tsc
2021-02-15 17:50:24 -08:00
jkh 41142d33dc [eas-cli] remove trailing whitespace from default branch name (#235) 2021-02-15 13:12:52 -08:00
jkh 52ab6107c5 [eas-cli] rename EAS release to branch (#233) 2021-02-15 12:41:12 -08:00
Wojciech Kozyra 56ac348ed4 [eas-cli] configurable builder environment (#230) 2021-02-15 08:55:07 +01:00
Dominik Sokal 1ac0af53ab [ENG-9] [eas-cli] implement automatic version bumping for ios builds (#231)
* [eas-cli] implement automatic version bumping for ios builds

* [eas-cli] autoincrement -> autoIncrement

* [eas-cli] code style fix

* [eas-cli][eas-json] address PR feedback

* more pr feedback
2021-02-12 14:37:19 +01:00
Wojciech Kozyra f9361b471c [eas-cli] fix running builds from subdirectory (#229) 2021-02-09 11:08:38 +01:00
Paul Ridgway 8f39e0dcde Allow wait to be set to false for eas build #225 (#226) 2021-02-08 13:23:31 +01:00
Wojciech Kozyra 81de402d2d [eas-cli] add build:cancel command (#219)
* [eas-cli] add build:cancel command

* review feedback
2021-02-08 13:23:08 +01:00
Wojciech Kozyra 9d6e3f52d8 [eas-cli] Fix missing android.package error (#224) 2021-02-08 13:20:22 +01:00
Will Schurman 6a0a9bbaaa Fix potential broken this binding when passing logger function as argument (#227) 2021-02-04 09:38:02 -08:00
Will Schurman 24bc29b3cd Refactor log module so that weird __esModule mocks are not needed (#223) 2021-02-03 16:26:58 -08:00
Brent Vatne aac5ebff73 v0.3.1 2021-01-25 12:05:30 -08:00
Wojciech KozyraandDominik Sokal 61cc5b0a4a [eas-cli] support canceled build status (#217)
* [eas-cli] support `canceled` build status

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

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

Co-authored-by: Dominik Sokal <[email protected]>
2021-01-22 11:00:56 +01:00
Wojciech Kozyra 1383783c33 [eas-cli] check if tty is atched on prompt (#216) 2021-01-21 15:24:25 +01:00
Dominik Sokal 971129be6b [eas-cli] improve monorepo support - fix configuring expo-updates (#214)
* [eas-cli] improve monorepo support - fix configuring expo-updates

* [eas-cli] bump @expo/config-plugins to 1.0.15-alpha.0
2021-01-21 12:08:30 +01:00
Quinlan Jung f4fb10483a [eas-cli] filter stale profiles (#215)
* [eas-cli] filter stale profiles

* pr feedback
2021-01-20 15:30:54 -08:00
Wojciech Kozyra 256a0e1f22 [eas-cli] return non-zero exit code if prompt is displayed on CI (#208) 2021-01-20 14:57:08 +01:00
William Schurman b82f0b04fe v0.3.0 2021-01-19 11:57:24 -08:00
Quinlan Jung 9714420f90 [eas-cli] select dist certs (#205)
* [eas-cli] select dist certs

* remove validity unknown

* pr feedback

* tests
2021-01-19 11:50:02 -08:00
Dominik Sokal 0054f0bc26 update release instructions (#212) 2021-01-19 20:42:54 +01:00
Will Schurman 38f565d849 [eas-cli] 2FA support (#204) 2021-01-19 09:37:45 -08:00
Dominik Sokal 1aff7ae2a3 fix typo 2021-01-19 16:22:20 +01:00
Dominik Sokal 31020047ca v0.2.1 2021-01-19 11:52:08 +01:00
Dominik Sokal 26778ec52b [eas-cli] hide release:publish command (#211) 2021-01-19 11:49:19 +01:00
Dominik Sokal b27590f4f5 v0.2.0 2021-01-19 11:42:09 +01:00
Quinlan Jung ea2af5af04 [eas-cli] fix current tests (#210) 2021-01-18 19:17:35 -08:00
jkh 3f1a7a0da0 [eas-cli] rework expected bundle format for eas publish (#196) 2021-01-18 13:00:11 -08:00
Quinlan Jung 8184a13bf3 [eas-cli] tests for initial credentials fetch (#203)
* [eas-cli] test for initial credentials fetch

* fix date

* fix date

* fix date
2021-01-15 12:49:47 -08:00
Dominik Sokal 2f7be56bef [eas-cli] add support for building multi-target ios projects via credentials.json (#202)
* [eas-cli] add support for building multi-target ios projects via credentials.json

* [eas-cli] address PR feedback

* [eas-cli] bump @expo/config-plugins and @expo/eas-build-job
2021-01-15 10:49:30 +01:00
Dominik Sokal 5c166bb3b2 nit: Expo servers -> EAS servers (#206) 2021-01-15 09:50:03 +01:00
Wojciech Kozyra 4e76526843 [eas-cli] create tarbal with shallow clone fo repository (#195) 2021-01-14 11:17:18 +01:00
Wojciech Kozyra 0e6fae0632 simplify credentials manager (#200)
* simplify credentials manager

* rebase + fix ManageIosBeta
2021-01-14 10:51:38 +01:00
Wojciech KozyraandDominik Sokal 62a8fd1b02 [eas-cli] fix message for deprecation errors (#201)
* [eas-cli] fix message for deprecation errors

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

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

Co-authored-by: Dominik Sokal <[email protected]>
2021-01-14 10:21:13 +01:00
Quinlan Jung 5a8e252790 [eas-cli] initial fetch for credentials manager (#193)
* [eas-cli] initial credentials fetch for manager

* fix tsc

* oops
2021-01-13 14:32:18 -08:00
Quinlan Jung 78613cd7c0 [eas-cli] use autogen query types - credentials (#199)
* [eas-cli] use autogen query types - credentials

* pr feedback
2021-01-12 15:37:29 -08:00
Quinlan Jung 8f6f7f7e1c [eas-cli] use autogen query types (#198) 2021-01-12 15:04:35 -08:00
Quinlan Jung f46ad6b898 [eas-cli] autogen credential types, ensure unique identifiers 2021-01-11 16:20:44 -08:00
Quinlan Jung f153b08a1e [eas-cli] use more accurrate graphql types (#185)
* [eas-cli] use more accurrate gql types

* fix tsc and merge conflicts
2021-01-11 15:39:20 -08:00
Quinlan Jung 6b57f00d7d [eas-cli] apply caps name rule (#191) 2021-01-11 14:07:04 -08:00
Quinlan Jung de65ab702b [eas-cli] require id field rule (#192) 2021-01-11 14:01:24 -08:00
Quinlan Jung a87b735832 [eas-cli] apply named operations rule (#189) 2021-01-11 13:56:54 -08:00
Quinlan Jung afc6022529 [eas-cli] 2/n apply template literals rule (#188)
* [eas-cli] apply template literals rule

append doc to frag names

* pr feedback
2021-01-11 13:52:11 -08:00
Quinlan Jung 2d3bb21f60 [eas-cli] setup eslint for graphql (#187) 2021-01-11 12:37:28 -08:00
Dominik Sokal c26f478b45 [eas-cli] make test output cleaner (#194) 2021-01-11 14:32:20 +01:00
Wojciech Kozyra f958a0db3e [eas-cli] fix deprecation message expo-cli -> eas-cli 2021-01-11 12:14:24 +01:00
Dominik Sokal 6df1130140 [eas-cli] better wording 2021-01-11 11:25:37 +01:00
Quinlan Jung 260a771070 [eas-cli] gql nits (#186) 2021-01-08 16:53:48 -08:00
Quinlan Jung 6659ed97ca [eas-cli] generate fragment types (#184)
* [eas-cli] generate fragment types

* pr feedback
2021-01-08 12:44:27 -08:00
Quinlan Jung 4eae49fe58 [eas-cli] more gql type cleanup (#183) 2021-01-07 10:46:33 -08:00
Quinlan Jung 41fef5c0e4 [eas-cli] cleanup pesky Maybe types (#182) 2021-01-05 10:59:58 -08:00
jkh 8c4b45a439 [eas-cli] add release:publish 4/4 (#146) 2021-01-04 06:10:34 -08:00
Evan BaconandBrent Vatne e35b5e78d2 [eas cli] Surface Apple contract messages (#176)
* Added support for showing contract messages in CLI

* Update contractMessages.ts

* assert contract messages in more thoughtful places

* Update contractMessages-test.ts

* fix tests

* use wrap-ansi instead of wordwrap

* Update yarn.lock

* updated comments

* Apply suggestions from code review

Co-authored-by: Brent Vatne <[email protected]>

* prevent false positives

Co-authored-by: Brent Vatne <[email protected]>
2020-12-23 13:50:06 -08:00
Evan Bacon b04c1a2c20 use wrap-ansi instead of wordwrap (#177) 2020-12-23 12:40:53 -08:00
Evan Bacon 25d75b6f94 add terminal link support (#178) 2020-12-23 12:40:42 -08:00
Ville Immonen a2adacf5a3 Add release:list command (#91) 2020-12-22 17:32:13 +02:00
Cedric van Putten ca718d8da9 v0.1.2 2020-12-21 13:59:59 +01:00
Cedric van Putten 3e7b849ad1 [eas-cli] Force update plist library to fix missing invariant (#174) 2020-12-21 13:47:24 +01:00
Dominik Sokal 2ec646659d [eas-cli] fix exiting 'eas build' when there are other pending builds 2020-12-21 11:10:45 +01:00
Dominik Sokal c33e3d8a4c [eas-cli] eas build - print an error when there's another pending build (#169) 2020-12-21 10:51:01 +01:00
jkh d65c180746 [eas-cli] add publish.ts 3/4 (#147) 2020-12-18 08:43:34 -08:00
jkh 754af79e0c [expo-cli] set noImplicitReturns to true (#166) 2020-12-17 16:51:34 -08:00
Bartłomiej Klocek 3733cc4cf8 Fix no-newline typo 2020-12-16 11:56:43 +01:00
jkh 9e5c1740b4 [eas-cli] add graphql calls 2/4 (#149) 2020-12-15 18:01:21 -08:00
Brent Vatne 58919d37ab v0.1.1 2020-12-14 20:37:37 -08:00
Evan Bacon 2ec4279ac7 [submit] Unify app creation code (#158) 2020-12-14 20:36:12 -08:00
Evan Bacon 3052276c3b fix tests (#161)
* await test rejection

* fix tests
2020-12-14 16:07:16 -08:00
Evan Bacon 65a9238d48 [eas-cli] Simplify the prompts for auto generation (#156) 2020-12-14 16:03:31 -08:00
Evan Bacon 5a1bb94563 Improve logging (#154)
* Update configure.ts

* Update build.ts

* remove Successfully, fix casing

* improve credentials logging

* updated logging

* more minor changes

* Update RemoveKeystore.ts

* change log treatment

* fix tests

* fix lint
2020-12-14 15:41:41 -08:00
Bartłomiej Klocek 1094d3f9f7 [eas submit] Add missing iOS error messages (#125)
* Handle iOS missing app icon error
* Handle invalid signature error
2020-12-14 15:37:00 -08:00
Wojciech Kozyra d8452ce6f8 [eas-cli] Improve keytool detection (#152) 2020-12-14 15:23:03 -08:00
Evan Bacon a5be039fd2 Show tar logging if it takes a while (#155)
* Update repository.ts

* Update repository.ts
2020-12-14 15:22:39 -08:00
Evan Bacon 6178a54292 Simplify table logs (#157) 2020-12-14 15:22:23 -08:00
Brent Vatne 3a2e9c42ab v0.1.0 2020-12-14 14:16:58 -08:00
Brent Vatne 8bfb97aa1c [yarn.lock] Update lockfile 2020-12-14 14:16:17 -08:00
Cedric van Putten 4c32b9bdec [eas-cli] Use project account name in prepare managed job (#159) 2020-12-14 23:14:29 +01:00
Evan Bacon 6dcb29caa9 Update authenticate.ts 2020-12-14 13:56:06 -08:00
Evan Bacon 917a46eea5 Update credentials.ts 2020-12-14 13:18:30 -08:00
Cedric van PuttenandVille Immonen 2be9d92268 [eas-cli] Add support for Robot users (#108)
* [eas-cli] Add robot user integration

* [eas-cli] Reimplement robots integration with GraphQL types

* [eas-cli] Fix resolving credentials context account owner

* [eas-cli] Remove isExpoAdmin from user query

* [eas-cli] Add exp stub to @expo/config mock

* [eas-cli] Use passive account name resolver in credentials

* [eas-cli] Refactor to  passive account name in more areas

* [eas-cli] Add unit tests to user methods

* [eas-cli] Regenerate graphql types

* [eas-cli] Add project utils unit tests

* [eas-cli] Add strict return type to project account name helper

Co-authored-by: Ville Immonen <[email protected]>

* [eas-cli] Simplify expo config mock

Co-authored-by: Ville Immonen <[email protected]>

* [eas-cli] Emphasize on account instead of user type

Co-authored-by: Ville Immonen <[email protected]>

* [eas-cli] Add strict return type to actor username helper

Co-authored-by: Ville Immonen <[email protected]>

* [eas-cli] Add ensureAccounts helper to workaround graphql typing issue

* [eas-cli] Remove extraneous type check in credentials context

Co-authored-by: Ville Immonen <[email protected]>

Co-authored-by: Ville Immonen <[email protected]>
2020-12-14 21:50:06 +01:00
Evan Bacon 29e644ab1e Implement new progress logs (#153)
* Implement new loader

* added debug logging
2020-12-14 12:35:38 -08:00
Wojciech Kozyra 843a2fd10a [eas-cli] private bucket support for project archive (#142) 2020-12-14 15:22:00 +01:00
jkh 4d8e6cc53a [eas-cli] add helper functions for release:publish 1/4 (#148) 2020-12-13 16:12:26 -08:00
Brent Vatne a52e443a44 v0.1.0-alpha.23 2020-12-11 19:44:07 -08:00
Brent Vatne 70eb4a1b64 [eas-cli] Add warning if update is available 2020-12-11 19:36:12 -08:00
Brent Vatne 19b5cb9b86 Update README 2020-12-11 12:31:45 -08:00
Brent Vatne 5d026ea462 v0.1.0-alpha.22 2020-12-11 12:30:59 -08:00
Brent Vatne 9c972430bb [eas-cli] Only configure eas.json for the requested platform (#151)
* [eas-cli] Improve app is ready message when picking just one platform

* [eas-cli] Only configure eas.json for given platform, update if eas.json already exists
2020-12-11 10:19:41 -08:00
Satyajit Sahoo cff9a7a1d0 [eas-cli] Add device:list and device:view commands
This adds commands to view registered devices for an account.
2020-12-11 14:08:06 +01:00
Ville Immonen 3cbc670d90 [eas-cli] Update GraphQL types 2020-12-11 12:57:27 +02:00
Brent Vatne 5b39620619 [eas-cli] Handle case where expo object is not defined on app config for bundleIdentifier 2020-12-10 20:20:32 -08:00
Brent Vatne 8498547377 Commit generated README updates 2020-12-10 19:49:57 -08:00
Brent Vatne 175103ed32 v0.1.0-alpha.21 2020-12-10 17:57:09 -08:00
Brent Vatne e177b94ca1 [eas-cli] Slightly easier to read output when build is completed (#150) 2020-12-10 16:35:32 -08:00
jkh 193da3cf46 [eas-cli] fix mutation should be a query (#107) 2020-12-10 07:15:19 -08:00
Satyajit Sahoo f29dd0b89a [eas-cli] Add build:list and build:view commands
This commit adds build:list and build:view commands to show status of builds.

When running build:list, it shows last 10 builds, but it can be configured by passing status, platform and limit filters.

When running build:view, it shows the last build. An id param can be passed to view a specific build.
2020-12-10 13:35:09 +01:00
Ville Immonen 14cd1835dd Add size compare action (#139) 2020-12-10 14:10:14 +02:00
Wojciech Kozyra 01f82bb873 [eas-cli] fix apple auth on linux (#141) 2020-12-10 10:46:05 +01:00
Dominik Sokal 844eddf127 [eas-cli] add app name and app identifier to build metadata (to fix internal distribution for unpublished projects) (#140) 2020-12-10 10:44:52 +01:00
Brent Vatne bdb7bf8475 [eas-cli] Account for no expo.android property existing in app.json. Fixes #143 2020-12-09 19:56:30 -08:00
jkh 04b687a050 [eas-cli] add release:view (#96) 2020-12-09 05:06:10 -08:00
Ville Immonen a67bf6877a Add build:tarballs:linux to package.json
This is needed for the size compare action (#124)
2020-12-09 11:36:20 +02:00
Brent Vatne ea8fd7d4ff v0.1.0-alpha.20 2020-12-08 16:20:43 -08:00
Dominik Sokal bc69ef34ff [eas-cli] print better message when internal distribution build completes (#130) 2020-12-08 16:16:40 -08:00
Evan Bacon 38a0ef8b1e fetch all certificates, and filter locally (#135)
* filter certs locally

* remove unused
2020-12-08 12:41:40 -08:00
jkh fd21811e85 [eas-cli] add channel:create (#98) 2020-12-08 12:34:28 -08:00
Ville Immonen 17d4fbbf45 Fix the release action (#134)
* Fix the release build workflow

* v0.1.0-alpha.19
2020-12-08 22:02:50 +02:00
Brent Vatne 8f455237c7 [eas-cli] Prevent importing extraneous dependencies 2020-12-08 09:58:58 -08:00
Brent Vatne fd340c2bb5 v0.1.0-alpha.18 2020-12-08 09:39:16 -08:00
Brent Vatne 6e2accf00a [eas-cli] Move tempy to regular dependency 2020-12-08 09:38:55 -08:00
Wojciech KozyraandDominik Sokal 26e0ac7318 [eas-cli] detect missing android or ios directories (#123)
* [eas-cli] detect missing android or ios directories

Co-authored-by: Dominik Sokal <[email protected]>
2020-12-08 12:31:12 +01:00
Evan Bacon f58dc5db08 Update AppProduce.ts 2020-12-07 21:55:00 -08:00
Brent Vatne aba6b31bbd v0.1.0-alpha.17 2020-12-07 21:51:34 -08:00
Evan Bacon 5db237afd3 extract auth prompts out of apple-utils (#113)
* extract auth prompts out of apple-utils

* Update authCredentials.ts

* Update authCredentials.ts

* clean up

* remove unused
2020-12-07 21:47:48 -08:00
Evan Bacon 45815e2dc6 Improve error messages for two new cases (#126) 2020-12-07 21:47:30 -08:00
Evan Bacon d827a1dad9 typos and formatting (#128) 2020-12-07 21:46:54 -08:00
Evan Bacon 5a94367073 fix log alignment 2020-12-07 15:48:16 -08:00
Evan Bacon eab7c24d53 Updating -> syncing 2020-12-07 15:41:46 -08:00
Evan Bacon 24b4b9b59f Update SetupProvisioningProfile.ts 2020-12-07 15:33:39 -08:00
Evan Bacon f79d9b2f56 terminate silently on abort (#112) 2020-12-07 13:13:56 -08:00
Evan Bacon 0237c51f48 Implement auto resolver (#117) 2020-12-07 08:38:04 -08:00
Wojciech KozyraandDominik Sokal c49f6cce18 [eas-cli] Fix support for non-interactive and skip-credentials-check (#122)
* [eas-cli] Fix support for non-interactive and skip-credentials-check

* Apply suggestions from code review

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

* fix for internal distribution

Co-authored-by: Dominik Sokal <[email protected]>
2020-12-07 16:08:24 +01:00
Brent Vatne 61a3c01e26 [eas-cli] Remove v2 from the build details path 2020-12-05 15:38:06 -08:00
Brent Vatne 625e7b588a [eas-cli] Update "Logs url" to "Build details" 2020-12-05 15:37:54 -08:00
Brent Vatne 0817cb36b8 v0.1.0-alpha.16 2020-12-05 12:54:25 -08:00
Brent Vatne 46220028ab [eas-cli] Move getSessionSecret and getAccessToken to sessionStorage to avoid dependency cycle. Also add eslint rule to ensure it does not happen again 2020-12-05 12:53:35 -08:00
Brent Vatne b546716582 v0.1.0-alpha.15 2020-12-05 11:53:03 -08:00
Brent Vatne 46342c8cb1 [eas-cli] Bump config/config-plugins 2020-12-05 11:52:21 -08:00
Brent Vatne a281b500d4 [eas-cli] Fix package.json import path to eas-cli rather than monorepo root 2020-12-05 11:45:47 -08:00
Brent Vatne 101fda5ee3 v0.1.0-alpha.14 2020-12-05 11:32:04 -08:00
Brent Vatne 67fed5bcc1 [eas-cli] Bump @expo/config-plugins to fix missing invariant dependency 2020-12-05 11:31:45 -08:00
Brent Vatne aec4de430d v0.1.0-alpha.13 2020-12-05 11:17:28 -08:00
Brent Vatne e8ac985b89 [eas-cli] Move graphql from devDependencies to dependencies 2020-12-05 11:17:08 -08:00
Brent Vatne c937987772 [eas-cli][eas-json] Remove __test__ from build 2020-12-05 11:14:03 -08:00
Brent Vatne 03f4ab3e8b v0.1.0-alpha.12 2020-12-05 11:09:17 -08:00
Brent Vatne afeb8df13e [eas-json] Point main to build rather than src 2020-12-05 11:08:53 -08:00
Evan Bacon bc73db7656 Update IosSubmitter.ts 2020-12-04 18:59:26 -08:00
Brent Vatne 511b3f3e4a [cli] Update README from publish 2020-12-04 11:23:34 -08:00
Brent Vatne 0633cfefd0 [eas-cli] Add yarn before calling oclif-dev so it resolves from local install 2020-12-04 11:23:21 -08:00
Brent Vatne 77ea5a60a5 v0.1.0-alpha.11 2020-12-04 11:20:27 -08:00
Wojciech Kozyra 8a8c4b4382 [eas-cli] build configure and create improvments (#105) 2020-12-04 11:35:08 +01:00
Evan Bacon 75e3563e75 Fix typos 2020-12-03 17:40:21 -08:00
Evan Bacon 119dd2180c Update ServiceAccountSource.ts 2020-12-03 15:26:31 -08:00
Evan Bacon 12e98beec7 clean up spinners and logs (#111)
* Unify push spinners

* clean up spinner logs

* Update ConfigureProvisioningProfile.ts

* Update validateProvisioningProfile.ts

* Update ensureAppExists.ts

* Update ensureAppExists.ts

* Update create.ts

* Update provisioningProfileAdhoc.ts
2020-12-03 15:24:05 -08:00
Evan Bacon b829e00b5b cleaner logging + typos (#110)
* use shorter expo project server log

* "App" instead of "Artificat url"

* fix typos

* fix error

* typo

* typo

* EAS Account -> Expo Account

* Update ensureProjectExists.ts

* Update ensureProjectExists.ts
2020-12-03 14:39:37 -08:00
Evan Bacon fd56a19156 unify learn more url's format and clean up logs (#109)
* unify learn more url's format

* Downgrade error log to warning

* Update create.ts

* add underline to logs url

* unify eas warning
2020-12-03 10:59:13 -08:00
Wojciech Kozyra 2607430fa8 [eas-cli] validate bundleIdentifer and applicationId (#106)
* [eas-cli] validate bundleIdentifer and applicationId

* review feedback
2020-12-03 18:37:11 +01:00
Evan Bacon a449413a3e Fix certificate bug -- upgrade apple-utils to 11 2020-12-03 09:17:07 -08:00
Ville Immonen 15bb9f9b24 Hide work-in-progress commands from --help (#102)
✨
2020-12-03 05:26:16 -08:00
Brent Vatne dd2ef27115 [eas-cli] Improve wording around archiving and uploading project (#104)
* [eas-cli] Improve wording around archiving and uploading project

* [eas-cli] Build servers sounds a bit better
2020-12-03 11:25:20 +01:00
Dominik Sokal 250b257507 add PR template (#101) 2020-12-03 09:48:04 +01:00
Dominik SokalandWojciech Kozyra b32955136c [eas-cli] improve internal distribution on android (#100)
* [eas-cli] improve internal distribution on android

* [eas-cli] address feedback

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

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

* [eas-cli] add prompt

Co-authored-by: Wojciech Kozyra <[email protected]>
2020-12-03 09:45:20 +01:00
Wojciech Kozyra 8b73152765 [eas-cli] configure app identifier for iOS and Android (#93)
* [eas-cli] configure app identifier for iOS and Android

* review feedback + some fixes
2020-12-02 10:38:54 +01:00
Dominik SokalandVille Immonen 4c8181fa82 [eas-cli] disable eas build & submit for non-paying users (#87)
* [eas-cli] disable eas build & submit for non-paying users

* [eas-cli] better 404 check

* [eas-cli] add link to eas landing page

* Update packages/eas-cli/src/project/isEasEnabledForProject.ts

Co-authored-by: Ville Immonen <[email protected]>

* [eas-cli] don't use _.once for getProjectIdAsync

Co-authored-by: Ville Immonen <[email protected]>
2020-12-01 16:41:18 +01:00
Wojciech Kozyra aa54eca4ee Fail CI checks if there are lint warnings (#95)
* Fail CI checks if there are lint warnings

* fix lint warning
2020-12-01 12:39:21 +01:00
Bartłomiej Klocek 8a0a5a249e Fix typos 2020-11-30 21:14:01 +01:00
jkh 77e6f42161 [eas-cli] add release:rename (#77) 2020-11-30 08:28:41 -08:00
Ville Immonen 7c8603138b Update generated GraphQL types
This code was generated by running
    yarn generate-graphql-code
2020-11-30 17:50:33 +02:00
Wojciech Kozyra ebade7523e [eas-cli] commit changes when updating version (#94) 2020-11-30 15:36:38 +01:00
Ville Immonen 406605b509 Add publishConfig for lerna 2020-11-27 17:29:38 +02:00
Ville Immonen 77f881af67 v0.1.0-alpha.10 2020-11-27 17:24:53 +02:00
Evan Bacon 4714ddc8d6 Upgrade expo/config (#90) 2020-11-27 17:14:24 +02:00
Ville Immonen a94b187da5 Replace hand-written GraphQL schema types with generated types (#92)
* Rename generated.tsx to generated.ts

Importing from tsx resulted in an error and the file doesn't use any tsx features (JSX)

* Remove hand-written GraphQL types, update imports, rename enums

* Fix remaining type errors

* Correct generated.ts rule in .eslintignore
2020-11-27 17:09:48 +02:00
Wojciech Kozyra 636a1f110a remove duplicated dependecies from package.json 2020-11-27 16:03:12 +01:00
Wojciech Kozyra e6e8bbcaf6 improve user interaction for build and configure + cleanup types (#84) 2020-11-27 12:27:32 +01:00
Evan Bacon 4f49135c1f Improve error/warning messages (#88)
* Improve messages

* Update AppProduce.ts
2020-11-26 19:52:46 +01:00
Ville Immonen 10cc68357e v0.1.0-alpha.9 2020-11-26 17:12:09 +02:00
Ville Immonen 932f641ae7 Remove traveling-fastlane (#86) 2020-11-26 17:05:57 +02:00
Ville Immonen a139571ef5 v0.1.0-alpha.8 2020-11-26 16:31:39 +02:00
Bartłomiej Klocek bf067f99a9 [eas-cli] Support for App Store API in iOS submissions (#82)
* [ios submit] Initial support for App Store API

* Update language support

* Fixes

* Language list source and tests
2020-11-26 13:06:30 +01:00
Evan Bacon a6d6593cd3 prompt team once (#85)
* Update authenticate.ts

* Update authenticate.ts
2020-11-25 22:14:41 +01:00
Ville Immonen f485e4b6ef Fix sorting of dependencies 2020-11-25 18:06:13 +02:00
Ville Immonen df80eb1bd9 Rename @eas/config to @expo/eas-json (#83)
* Rename @eas/config to @expo/eas-json

* Fix eslint warnings

* Add missing newline at the end of file
2020-11-25 15:42:39 +02:00
Ville Immonen 2a67e0206b Add tsc to CI pipeline (#78) 2020-11-25 15:22:57 +02:00
Bartłomiej Klocek 2b5d91b622 Fix tsc error 2020-11-25 13:12:15 +01:00
Evan Bacon 1c5a6aae76 upgraded apple utils to support request contexts (#79)
* upgraded apple utils to support request contexts

* bump apple-utils

* Update ensureAppExists.ts

* refactor

* upgrade
2020-11-25 12:32:59 +01:00
Bartłomiej Klocek 2ae64e70ad [eas-cli] Add AppStore app creation to submission command (#65)
* Remove --app-specific-password flag

* Add fastlane utility

* Fastlane-based app store create

* Update flags description

* Password prompt type

* Update help

* Address some feedback

* Move to `eas submit`, bundleId, fyi, --asc-app-id

* Apply suggestions + improvements
2020-11-25 11:56:54 +01:00
Ville Immonen a302978636 Fix a typo in provisioningProfileAdhoc.ts (#80) 2020-11-24 17:15:32 +01:00
Dominik SokalandWojciech Kozyra 1ebf16cf08 [eas-cli] build - implement internal distribution (#59)
* [eas-cli] upgrade prompts to 2.0.9

* [config] add 'internal' key to build profiles

* [eas-cli] add support for generating credentials for internal distribution

* [config] fix tests

* [eas-cli] make sure internal distribution works fine with apple-utils

* [eas-cli] fix lint warning

* Update packages/eas-cli/src/credentials/manager/ManageIos.ts

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

* [eas-cli] address review feedback - part 1

* [eas-cli] address feedback - change 'internal: true' to 'distribution: internal'

* [eas-cli] upgrade traveling-fastlane to 1.15.3

* [eas-cli] fix lint warning

Co-authored-by: Wojciech Kozyra <[email protected]>
2020-11-24 13:59:13 +01:00
Ville Immonen 9a7e8953c7 Add support for grouping flags in --help (#76)
* Add support for grouping flags in --help

* Fix linter warnings

* Fix typo
2020-11-20 21:41:16 +02:00
Evan Bacon 047b8e278e Update provisioningProfileAdhoc.ts (#75) 2020-11-20 16:50:44 +01:00
Dominik Sokal 9878c07c09 [eas-cli] fix fixtures 2020-11-20 15:06:37 +01:00
Dominik Sokal acb07aa72a [eas-cli] add missing dep: figures 2020-11-20 15:06:25 +01:00
Evan Bacon e889347710 [eas-cli] Implement apple utils (#64)
* Implement apple api

* Update ensureAppExists.ts

* added dev types packages

* fix loading indicators

* refactor names

* Update provisioningProfileAdhoc.ts

* Update provisioningProfileAdhoc.ts

* Update provisioningProfileAdhoc.ts

* Update provisioningProfile.ts

* move method

* refactor

* Update authenticate.ts

* Properly throw AppleTooManyCertsError error

* refactor names

* Update provisioningProfile.ts

* batch

* Update distributionCertificate.ts

* Update bundleId.ts

* Update provisioningProfileAdhoc.ts

* Integrate auth state changes

* Update authenticate.ts

* remove extra auth check

* bump

* Update ensureAppExists.ts
2020-11-19 15:33:58 +01:00
Wojciech KozyraandDominik Sokal 3ab974856f [eas-cli] standalone credentials manager error handling (#74)
* [eas-cli] standalone credentials manager error handling

* Apply suggestions from code review

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

Co-authored-by: Dominik Sokal <[email protected]>
2020-11-19 14:40:02 +01:00
Quinlan JungandJuwan Wheatley f431feacf8 [eas-cli] graphql codegen (#70)
* graphql codegen

* Update packages/eas-cli/annotate-graphql-codegen.sh

Co-authored-by: Juwan Wheatley <[email protected]>

* pr feedback

* update annotation

Co-authored-by: Juwan Wheatley <[email protected]>
2020-11-18 13:21:58 -08:00
jkh 791e80b9fc [eas-cli] add command release:create (#71) 2020-11-18 10:24:26 -08:00
jkh 12138832a0 [eas-cli] relocate getProjectIdAsync (#73) 2020-11-18 08:45:32 -08:00
Wojciech Kozyra 302a94a9d8 [eas-cli] fix ignored errors in CredentialsManager 2020-11-18 17:42:34 +01:00
Dominik Sokal 03e754e0f6 [eas-cli] add graphql infra for new credentials (#69)
* [eas-cli] add graphql infra for new credentials

* [eas-cli] move queries/mutations for credentials to credentials/ios/api/graphql

* [eas-cli] address feedback

* [eas-cli] fix tests

* [eas-cli] address more feedback
2020-11-18 14:14:19 +01:00
Wojciech Kozyra 925cb44819 credentials.json support + fixes for managed (#66)
* [eas-cli] credentials.json support

* credentials.json support + fixes for managed workflow

* review feedback
2020-11-18 09:41:11 +01:00
Jonathan 小林 Hales ed798057a3 Revert "[eas-cli] add command release:create"
This reverts commit f08e1727bd.
2020-11-17 17:19:41 -08:00
Jonathan 小林 Hales f08e1727bd [eas-cli] add command release:create 2020-11-17 17:19:19 -08:00
Ville Immonen c8c5cb589a Fix NO_COLOR: unbound variable 2020-11-12 23:41:36 +02:00
Ville Immonen fe1b34559e Build macOS tarballs on macOS (#62) 2020-11-12 21:37:37 +02:00
Ville Immonen 4df45dd8c2 Consistently use log.ts for all logging (#61) 2020-11-11 11:54:57 +02:00
Ville Immonen fa6e894bfb Align account command names with the style guide (#60) 2020-11-11 11:26:43 +02:00
Cedric van Putten 9ed15d2b5d Add codecov commit status checks to monitor coverage (#26)
* Add code coverage paths and disable by default
* Add codecov configuration and integrate in workflow
2020-11-10 17:23:22 +02:00
Ville Immonen 07d78b31f4 Update readme (generated with oclif-dev readme) 2020-11-10 12:59:49 +02:00
Bartłomiej Klocek f342c87cf0 [eas-cli] Basic Submission command for iOS (#56)
* iOS archive type

* Extract promptForAppleId in credentials

* iOS upload core functionality

* Revert extracting "promptForAppleId" from credentials

Discussed with @wkozyra95

* Minor renames and suggestions

* Improve infering archive source type

* iOS submit command test

* Extract common code, print summary (#58)

* Submitter class hierarchy

* Extract commons from SubmitCommand

* Submission Summary

* Remove note about storing passwords

* Suggestions and fixes
2020-11-10 07:59:56 +01:00
Ville Immonen ed8d7229d0 Add Bash installer (#34) 2020-11-09 18:08:11 +02:00
Ville Immonen 2f9d388543 Add release workflow (#54) 2020-11-09 17:02:07 +02:00
Wojciech Kozyra 20a3324aab [expo-cli] configure EAS Build (#57) 2020-11-04 12:33:07 +01:00
Wojciech Kozyra 0d3008e8de use fixed @xpo/eas-build-cli package 2020-11-04 09:29:02 +01:00
Dominik Sokal 78e0732440 [eas-cli] device:create - implement typing in UDIDs (#40) 2020-10-27 16:59:33 +02:00
Bartłomiej Klocek 91e87afa74 [eas-cli] Move all GraphQL to one directory (#38)
* Move all graphql, no magic yet

* Error handling

* GraphQL variables & naming suggestions
2020-10-26 16:25:36 +01:00
Bartłomiej Klocek 3461815e74 [eas-cli][tests] Remove console logs from submission tests (#39)
* Remove console logs from submission tests

* Extract ora mock to __mocks__
2020-10-23 16:13:03 +02:00
Wojciech Kozyra 9b807bb2da [eas-cli] add build:create command (#36)
* [eas-cli] add `build:create` command

* review feedback
2020-10-22 14:16:00 +02:00
Satyajit Sahoo 8b53ca7d2f [config] Add 'releaseChannel' to generic build schema (#37)
Backports the changes from https://github.com/expo/expo-cli/pull/2765
2020-10-22 14:13:30 +02:00
Ville Immonen 943eb7087f Add tsc build to test GH action (#35) 2020-10-19 21:36:44 +03:00
Bartłomiej Klocek 9a14346b0b [eas-cli] move android submission command from expo-cli (#30)
* Add command and flags

* Copy Android submission-service code from Expo-CLI

* Delete all occurences of offline submission mode

* Fix prompts

* Remove all XDL usages

* Turtle v2 builds and fix upload

* Refactor exports

* Fixes and suggestions

* Latest build graphql

* Apply suggestions

* Remove validator lib

* Revert iOS archive type

* UUID validate signature
2020-10-16 15:52:48 +02:00
Dominik Sokal 56fb45f242 [eas-cli] device:create - implement generating registration URL (#33)
* [eas-cli] device:create - implement generating registration URL

* [eas-cli] address pr feedback
2020-10-15 16:23:17 +02:00
450 changed files with 66981 additions and 5866 deletions

No files matched your search

+6 -1
View File
@@ -1,2 +1,7 @@
build
**/build/**
!packages/eas-cli/src/commands/build/**
!packages/eas-cli/src/build/**
packages/eas-cli/src/graphql/generated.ts
node_modules
+2
View File
@@ -10,6 +10,7 @@ module.exports = {
jasmine: false,
},
rules: {
'no-console':'warn',
'no-constant-condition': ['warn', { checkLoops: false }],
'sort-imports': [
'warn',
@@ -18,6 +19,7 @@ module.exports = {
},
],
'flowtype/no-types-missing-file-annotation': 'off',
'curly': 'warn',
},
settings: {
react: {
Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

+1
View File
@@ -0,0 +1 @@
**/yarn.lock linguist-generated
+18
View File
@@ -0,0 +1,18 @@
<!-- 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.
- [ ] I've tagged the changelog entry with `[EAS BUILD API]` if it's a part of a breaking change to EAS Build API (only possible when updating `@expo/eas-build-job` package).
# Why
Please describe the motivation for this PR, and link to relevant GitHub issues, forums posts, or feature requests.
# How
How did you build this feature or fix this bug and why?
# Test Plan
Please describe how you tested this change and how a reviewer could reproduce your test, especially if this PR does not include automated tests! If possible, please also provide terminal output and/or screenshots demonstrating your test/reproduction.
+18
View File
@@ -0,0 +1,18 @@
name: Installer tests
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize]
jobs:
test:
name: Run the installer
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v2
- run: bash bin/install.sh
- run: eas --version
+115
View File
@@ -0,0 +1,115 @@
on:
push:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
name: Release workflow
jobs:
release:
name: Create a release
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
with:
tag_name: ${{ github.ref }}
draft: true
build-linux:
name: Build for Linux
runs-on: ubuntu-latest
needs: release
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '14'
- run: yarn install --frozen-lockfile --check-files
- run: yarn build
- name: Build tarballs
working-directory: ${{ github.workspace }}/packages/eas-cli
run: yarn oclif-dev pack --targets linux-x64,linux-arm
- name: Upload linux-x64 tarball
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: packages/eas-cli/dist/eas-linux-x64.tar.gz
asset_name: eas-linux-x64.tar.gz
asset_content_type: application/gzip
- name: Upload linux-arm tarball
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: packages/eas-cli/dist/eas-linux-arm.tar.gz
asset_name: eas-linux-arm.tar.gz
asset_content_type: application/gzip
build-mac:
name: Build for macOS
# Use macOS to get the correct platform specific dependencies
runs-on: macos-latest
needs: release
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '14'
- run: yarn install --frozen-lockfile --check-files
- run: yarn build
- name: Build tarballs
working-directory: ${{ github.workspace }}/packages/eas-cli
run: yarn oclif-dev pack --targets darwin-x64
- name: Upload darwin-x64 tarball
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: packages/eas-cli/dist/eas-darwin-x64.tar.gz
asset_name: eas-darwin-x64.tar.gz
asset_content_type: application/gzip
build-windows:
name: Build for Windows
runs-on: ubuntu-latest
needs: release
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '14'
- run: yarn install --frozen-lockfile --check-files
- run: sudo apt-get install nsis
- run: yarn build
- run: yarn oclif-dev pack:win
working-directory: ${{ github.workspace }}/packages/eas-cli
- id: x64
run: echo "::set-output name=filename::$(ls eas-*-x64.exe)"
working-directory: ${{ github.workspace }}/packages/eas-cli/dist/win
- id: x86
run: echo "::set-output name=filename::$(ls eas-*-x86.exe)"
working-directory: ${{ github.workspace }}/packages/eas-cli/dist/win
- name: Upload x64 installer
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: packages/eas-cli/dist/win/${{ steps.x64.outputs.filename }}
asset_name: ${{ steps.x64.outputs.filename }}
asset_content_type: application/gzip
- name: Upload x86 installer
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: packages/eas-cli/dist/win/${{ steps.x86.outputs.filename }}
asset_name: ${{ steps.x86.outputs.filename }}
asset_content_type: application/gzip
+18
View File
@@ -0,0 +1,18 @@
name: Size
on:
pull_request:
types: [synchronize, opened]
jobs:
compare:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '14'
- uses: preactjs/compressed-size-action@v2
with:
build-script: 'build:tarballs:linux'
repo-token: '${{ secrets.GITHUB_TOKEN }}'
pattern: './packages/eas-cli/dist/*.tar.gz'
compression: 'none'
+13 -3
View File
@@ -11,15 +11,25 @@ jobs:
strategy:
matrix:
node: ['12', '14']
include:
- node: '14'
coverage: true
name: Test with Node ${{ matrix.node }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: yarn install --frozen-lockfile --check-files
- run: yarn typecheck
- run: yarn test
- run: yarn lint
if: ${{ !matrix.coverage }}
- run: yarn test --coverage
if: ${{ matrix.coverage }}
- run: yarn lint --max-warnings=0
- run: shellcheck bin/*.sh
- uses: codecov/codecov-action@v1
if: ${{ matrix.coverage }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
+7 -1
View File
@@ -1,8 +1,14 @@
*-debug.log
*-error.log
**/build/**
!packages/eas-cli/src/commands/build/**
!packages/eas-cli/src/build/**
dist
build
/package-lock.json
tmp
node_modules
coverage
.history
.DS_Store
+1
View File
@@ -0,0 +1 @@
save-prefix ""
+463
View File
@@ -0,0 +1,463 @@
# Changelog
This is the log of notable changes to EAS CLI and related packages.
## main
### 🛠 Breaking changes
- Stop including deprecated storageBucket field when publishing ([547](https://github.com/expo/eas-cli/pull/547)) by [@jkhales](https://github.com/jkhales)
### 🎉 New features
### 🐛 Bug fixes
- Pass through metadata context to app version resolver. ([#550](https://github.com/expo/eas-cli/pull/550) by [@brentvatne](https://github.com/brentvatne))
### 🧹 Chores
## [0.22.4](https://github.com/expo/eas-cli/releases/tag/v0.22.4) - 2021-08-02
### 🐛 Bug fixes
- Fix build profile schema validation when `simulator` key is present for iOS ([#546](https://github.com/expo/eas-cli/pull/546) by [@sallar](https://github.com/sallar))
## [0.22.2](https://github.com/expo/eas-cli/releases/tag/v0.22.2) - 2021-08-02
### 🛠 Breaking changes
- Introduce new format of eas.json (old eas.json will be migrated automatically). ([#537](https://github.com/expo/eas-cli/pull/537) by [@wkozyra95](https://github.com/wkozyra95))
### 🎉 New features
- Validate metro config for managed workflow projects. ([#534](https://github.com/expo/eas-cli/pull/534) by [@dsokal](https://github.com/dsokal))
- Add more filter params to `build:list`. ([#540](https://github.com/expo/eas-cli/pull/540) by [@dsokal](https://github.com/dsokal))
- Evaluate interpolated iOS version strings for metadata. ([#541](https://github.com/expo/eas-cli/pull/541) by [@dsokal](https://github.com/dsokal))
### 🐛 Bug fixes
- Validate metadata client side to print better errors. ([#542](https://github.com/expo/eas-cli/pull/542) by [@wkozyra95](https://github.com/wkozyra95))
- Fix `new` build status when building both paltforms. ([#543](https://github.com/expo/eas-cli/pull/543) by [@wkozyra95](https://github.com/wkozyra95))
## [0.22.1](https://github.com/expo/eas-cli/releases/tag/v0.22.1) - 2021-07-28
### 🎉 New features
- Include the file extension for update's assets in the manifest fragment. ([#532](https://github.com/expo/eas-cli/pull/535) by [@jkhales](https://github.com/jkhales))
- Use env variables from `eas.json` when evaluating `app.config.js`. ([#532](https://github.com/expo/eas-cli/pull/532) by [@dsokal](https://github.com/dsokal))
### 🐛 Bug fixes
- Fix workflow detection when xcodeproj is an empty directory. ([#531](https://github.com/expo/eas-cli/pull/531) by [@wkozyra95](https://github.com/wkozyra95))
- Fix Android Keystore upload. ([#538](https://github.com/expo/eas-cli/pull/538) by [@quinlanj](https://github.com/quinlanj))
### 🧹 Chores
- Upgrade typescript to 4.3.5. ([#533](https://github.com/expo/eas-cli/pull/533) by [@dsokal](https://github.com/dsokal))
## [0.22.0](https://github.com/expo/eas-cli/releases/tag/v0.22.0) - 2021-07-23
### 🎉 New features
- Added support for automatically creating and linking iOS capability identifiers (Apple Pay, iCloud Containers, App Groups). ([#524](https://github.com/expo/eas-cli/pull/524) by [@EvanBacon](https://github.com/EvanBacon))
- Add `secret:create --force` command to overwrite existing secrets. ([#513](https://github.com/expo/eas-cli/pull/513) by [@bycedric](https://github.com/bycedric))
- Fall back to APK when building for internal distribution. ([#527](https://github.com/expo/eas-cli/pull/527) by [@dsokal](https://github.com/dsokal))
- Add `iosEnterpriseProvisioning` to build metadata. ([#522](https://github.com/expo/eas-cli/pull/522) by [@dsokal](https://github.com/dsokal))
- Autodetect Google Services JSON key path in `eas submit -p android`. ([#520](https://github.com/expo/eas-cli/pull/297) by [@barthap](https://github.com/barthap))
### 🐛 Bug fixes
- Fix iOS capability syncing on build. ([#521](https://github.com/expo/eas-cli/pull/521) by [@EvanBacon](https://github.com/EvanBacon))
- Fix unhandled error when amplitude domains are blocked. ([#512](https://github.com/expo/eas-cli/pull/512) by [@wkozyra95](https://github.com/wkozyra95))
- Use default value for `appBuildVersion` in build metadata when building an Android managed project. ([#526](https://github.com/expo/eas-cli/pull/526) by [@dsokal](https://github.com/dsokal))
## [0.21.0](https://github.com/expo/eas-cli/releases/tag/v0.21.0) - 2021-07-12
### 🎉 New features
- Add `project:*` commands. ([#500](https://github.com/expo/eas-cli/pull/500) by [@jkhales](https://github.com/jkhales))
- Added support for iOS 15 capabilities: Communication Notifications, Time Sensitive Notifications, Group Activities, and Family Controls. ([#499](https://github.com/expo/eas-cli/pull/499) by [@EvanBacon](https://github.com/EvanBacon))
- Show more build metadata in `build:view` and `build:list`. ([#504](https://github.com/expo/eas-cli/pull/504) and [#508](https://github.com/expo/eas-cli/pull/508) by [@dsokal](https://github.com/dsokal))
- Add runtime version to build metadata. ([#493](https://github.com/expo/eas-cli/pull/493) by [@dsokal](https://github.com/dsokal))
## [0.20.0](https://github.com/expo/eas-cli/releases/tag/v0.20.0) - 2021-07-09
### 🛠 Breaking changes
- Unifify generic and managed workflow, deprecate `workflow` field. ([#497](https://github.com/expo/eas-cli/pull/497) by [@wkozyra95](https://github.com/wkozyra95))
### 🐛 Bug fixes
- Fix runtime version checks. ([#495](https://github.com/expo/eas-cli/pull/495) by [@dsokal](https://github.com/dsokal))
- Resolve `--android-package` correctly in `eas submit` command. ([#494](https://github.com/expo/eas-cli/pull/494) by [@wkozyra95](https://github.com/wkozyra95))
## [0.19.1](https://github.com/expo/eas-cli/releases/tag/v0.19.1) - 2021-07-02
### 🎉 New features
- Improve project workflow detection (fixes the case where the `android` and/or `ios` directories are git-ignored). ([#490](https://github.com/expo/eas-cli/pull/490) by [@dsokal](https://github.com/dsokal))
- Improve credentials workflow with project creation and current working directories ([#491](https://github.com/expo/eas-cli/pull/491) by [@quinlanj](https://github.com/quinlanj))
### 🐛 Bug fixes
- Better debugging experience ([#492](https://github.com/expo/eas-cli/pull/492) by [@quinlanj](https://github.com/quinlanj))
## [0.19.0](https://github.com/expo/eas-cli/releases/tag/v0.19.0) - 2021-07-01
### 🎉 New features
- Auto-suggest application id and bundle identifier when running `eas build:configure` for a managed project. ([#487](https://github.com/expo/eas-cli/pull/487) by [@dsokal](https://github.com/dsokal))
- Support configuring one platform at a time when running `eas build`. ([#483](https://github.com/expo/eas-cli/pull/483) by [@brentvatne](https://github.com/brentvatne))
## [0.18.3](https://github.com/expo/eas-cli/releases/tag/v0.18.3) - 2021-06-28
### 🎉 New features
- Support bundle identifiers that begin with `.`. ([#459](https://github.com/expo/eas-cli/pull/459) by [@EvanBacon](https://github.com/EvanBacon))
- Push Key setup integrated with ios build command ([#473](https://github.com/expo/eas-cli/pull/473) by [@quinlanj](https://github.com/quinlanj))
### 🐛 Bug fixes
- Fix android builds for PKCS12 keystores. ([#484](https://github.com/expo/eas-cli/pull/484) by [@quinlanj](https://github.com/quinlanj))
## [0.18.2](https://github.com/expo/eas-cli/releases/tag/v0.18.2) - 2021-06-25
### 🎉 New features
- Change default iOS image for projects with Expo SDK <= 41 (SDK 41 won't build with Xcode 12.5). ([#479](https://github.com/expo/eas-cli/pull/479) by [@dsokal](https://github.com/dsokal))
- Make Apple team optional in appropriate cases. ([#468](https://github.com/expo/eas-cli/pull/468) by [@quinlanj](https://github.com/quinlanj))
- Show projects that depend on a push key. ([#472](https://github.com/expo/eas-cli/pull/472) by [@quinlanj](https://github.com/quinlanj))
### 🐛 Bug fixes
- Fix inverted conditional so we actually only prompt about commit when index is dirty. ([#481](https://github.com/expo/eas-cli/pull/481) by [@brentvatne](https://github.com/brentvatne))
## [0.18.1](https://github.com/expo/eas-cli/releases/tag/v0.18.1) - 2021-06-24
### 🎉 New features
- Add basic printing support for Apple App Specific Password. ([#474](https://github.com/expo/eas-cli/pull/474) by [@quinlanj](https://github.com/quinlanj))
### 🐛 Bug fixes
- Fix CLI UI getting blocked on credentials migration, leading to partially migrated state. ([#477](https://github.com/expo/eas-cli/pull/477) by [@brentvatne](https://github.com/brentvatne))
## [0.18.0](https://github.com/expo/eas-cli/releases/tag/v0.18.0) - 2021-06-24
### 🛠 Breaking changes
- Drop support for `experimental.npmToken` in credentials.json, EAS Secrets can be used instead. ([#444](https://github.com/expo/eas-cli/pull/444) by [@dsokal](https://github.com/dsokal))
- Remove `--allow-experimental` flag from `eas build:configure` as it has no effect now. ([#444](https://github.com/expo/eas-cli/pull/444) by [@dsokal](https://github.com/dsokal))
### 🎉 New features
- Make credentials manager work with multi-target iOS projects. ([#441](https://github.com/expo/eas-cli/pull/441) by [@dsokal](https://github.com/dsokal))
- Copy over credentials from Expo Classic to EAS. ([#445](https://github.com/expo/eas-cli/pull/445) by [@quinlanj](https://github.com/quinlanj))
- Add Big Sur image for iOS builds. ([#457](https://github.com/expo/eas-cli/pull/457) by [@wkozyra95](https://github.com/wkozyra95))
- New version of Android credentials manager. ([#460](https://github.com/expo/eas-cli/pull/460) by [@quinlanj](https://github.com/quinlanj))
- Add an internal distribution with dev client to `build:configure` defaults. ([#465](https://github.com/expo/eas-cli/pull/465) by [@fson](https://github.com/fson))
- Add `updates.channel` to build metadata. ([#461](https://github.com/expo/eas-cli/pull/461) by [@jkhales](https://github.com/jkhales))
- iOS push key setup and management now available in `eas-cli credentials`. ([#469](https://github.com/expo/eas-cli/pull/469) [#470](https://github.com/expo/eas-cli/pull/470) by [@quinlanj](https://github.com/quinlanj))
- Support new build status: `new`. ([#475](https://github.com/expo/eas-cli/pull/475) by [@dsokal](https://github.com/dsokal))
### 🧹 Chores
- Deprecate `--skip-credentials-check` flag because it doesn't do anything and is no longer needed. ([#442](https://github.com/expo/eas-cli/pull/442) by [@brentvatne](https://github.com/brentvatne))
- Move android credentials code to new Graphql API. ([#439](https://github.com/expo/eas-cli/pull/439) [#440](https://github.com/expo/eas-cli/pull/440) [#438](https://github.com/expo/eas-cli/pull/438) [#443](https://github.com/expo/eas-cli/pull/443) [#447](https://github.com/expo/eas-cli/pull/447) [#451](https://github.com/expo/eas-cli/pull/451) [#455](https://github.com/expo/eas-cli/pull/455) by [@quinlanj](https://github.com/quinlanj))
- Prepare Graphql infra to support iOS push keys. ([#456](https://github.com/expo/eas-cli/pull/456) by [@quinlanj](https://github.com/quinlanj))
- Improve credentials DX ([#448](https://github.com/expo/eas-cli/pull/448) [#449](https://github.com/expo/eas-cli/pull/449) by [@quinlanj](https://github.com/quinlanj))
- Add analytics on dev client builds. ([#454](https://github.com/expo/eas-cli/pull/454) by [@fson](https://github.com/fson))
- Support non-git projects. ([#462](https://github.com/expo/eas-cli/pull/462) by [@wkozyra95](https://github.com/wkozyra95))
## [0.17.0](https://github.com/expo/eas-cli/releases/tag/v0.17.0) - 2021-06-02
### 🐛 Bug fixes
- Fix bundle identifier resolution when native target is not provided. ([#434](https://github.com/expo/eas-cli/pull/434) by [@dsokal](https://github.com/dsokal))
- Fix git repo root path getter on Windows. ([#429](https://github.com/expo/eas-cli/pull/429) by [@brentvatne](https://github.com/brentvatne))
- Fix resolving Android application identifier. ([#431](https://github.com/expo/eas-cli/pull/431) by [@quinlanj](https://github.com/quinlanj))
### 🧹 Chores
- Android credentials setup now on Graphql API. ([#434](https://github.com/expo/eas-cli/pull/427) by [@quinlanj](https://github.com/quinlanj))
## [0.16.0](https://github.com/expo/eas-cli/releases/tag/v0.16.0) - 2021-05-26
### 🎉 New features
- Opt out of capability syncing with `EXPO_NO_CAPABILITY_SYNC=1`. ([#426](https://github.com/expo/eas-cli/pull/426) by [@brentvatne](https://github.com/brentvatne))
- Add more verbose logging around capability syncing to help debug reported issues. ([#426](https://github.com/expo/eas-cli/pull/426) by [@brentvatne](https://github.com/brentvatne))
- Add managed credentials support for multi-target iOS projects. ([#414](https://github.com/expo/eas-cli/pull/414) by [@dsokal](https://github.com/dsokal))
## [0.15.1](https://github.com/expo/eas-cli/releases/tag/v0.15.1) - 2021-05-25
### 🎉 New features
- Support auto capabilities in managed workflow using Expo config plugin introspection. ([#419](https://github.com/expo/eas-cli/pull/419) by [@EvanBacon](https://github.com/EvanBacon))
### 🐛 Bug fixes
- Fix sending `appBuildVersion` as part of the build metadata. ([#423](https://github.com/expo/eas-cli/pull/423) by [@dsokal](https://github.com/dsokal))
## [0.15.0](https://github.com/expo/eas-cli/releases/tag/v0.15.0) - 2021-05-20
### 🛠 Breaking changes
- `[EAS BUILD API]` Remove "Auto" option for `schemeBuildConfiguration` and make the old "Auto" behaviour the default. ([#394](https://github.com/expo/eas-cli/pull/394) by [@randomhajile](https://github.com/randomhajile))
- Remove `experimental.disableIosBundleIdentifierValidation` flag from eas.json. ([#407](https://github.com/expo/eas-cli/pull/407) by [@dsokal](https://github.com/dsokal))
- Deprecate `android.package` and `ios.bundleIdentifier` in app config for generic projects. EAS CLI depends on the values in native code now. ([#407](https://github.com/expo/eas-cli/pull/407) by [@dsokal](https://github.com/dsokal))
- Remove application id synchronization (`android.package` and `ios.bundleIdentifier`) between app.json and native code when running builds. ([#407](https://github.com/expo/eas-cli/pull/407) by [@dsokal](https://github.com/dsokal))
### 🎉 New features
- Auto sync associated domains capability before building. ([#384](https://github.com/expo/eas-cli/pull/384) by [@EvanBacon](https://github.com/EvanBacon))
- Add eas init command. [#402](https://github.com/expo/eas-cli/pull/402) by [@jkhales](https://github.com/jkhales))
- Allow for arbitrary string values in `schemeBuildConfiguration`. ([#394](https://github.com/expo/eas-cli/pull/394) by [@randomhajile](https://github.com/randomhajile))
- Allow for installing custom `expo-cli` version on EAS Build. ([#409](https://github.com/expo/eas-cli/pull/409) by [@randomhajile](https://github.com/randomhajile))
- Support PKCS kesytores for Android. ([#398](https://github.com/expo/eas-cli/pull/398) by [@wkozyra95](https://github.com/wkozyra95))
- Support empty passwords in Android and iOS keystores. ([#398](https://github.com/expo/eas-cli/pull/398) by [@wkozyra95](https://github.com/wkozyra95))
- Add more build metadata - `appBuildVersion`. ([#413](https://github.com/expo/eas-cli/pull/413) by [@dsokal](https://github.com/dsokal))
### 🐛 Bug fixes
- Fix failure when publishing without the platform flag. ([#415](https://github.com/expo/eas-cli/pull/415) by [@jkhales](https://github.com/jkhales))
- Pin versions in package.json. ([#399](https://github.com/expo/eas-cli/pull/399) by [@dsokal](https://github.com/dsokal))
- Revert [0ac2f](https://github.com/expo/eas-cli/commit/0ac2fb77a118df609381c4e350aa68609340c3cd) as the root cause of the issue has been fixed in [#399](https://github.com/expo/eas-cli/pull/399).
- Include development-client in valid buildType for internal distribution. ([#410](https://github.com/expo/eas-cli/pull/410) by [@brentvatne](https://github.com/brentvatne))
## [0.14.1](https://github.com/expo/eas-cli/releases/tag/v0.14.1) - 2021-05-11
### 🐛 Bug fixes
- Make the default urql requestPolicy 'network-only'. ([#397](https://github.com/expo/eas-cli/pull/397) by [@jkhales](https://github.com/jkhales))
## [0.14.0](https://github.com/expo/eas-cli/releases/tag/v0.14.0) - 2021-05-10
### 🛠 Breaking changes
- Make eas branch:publish work with expo-cli >= 4.4.3 ([#366](https://github.com/expo/eas-cli/pull/366) by [@jkhales](https://github.com/jkhales))
- Create asset keys without an extension. ([#366](https://github.com/expo/eas-cli/pull/366) by [@jkhales](https://github.com/jkhales))
### 🎉 New features
- Display platforms supported by an update group ([#391](https://github.com/expo/eas-cli/pull/391) by [@jkhales](https://github.com/jkhales))
- Add a --platform flag to branch:publish command ([#389](https://github.com/expo/eas-cli/pull/389) by [@jkhales](https://github.com/jkhales))
- Release new credentials manager. ([#363](https://github.com/expo/eas-cli/pull/363) by [@quinlanj](https://github.com/quinlanj))
- Add QR code to install internal distribution build on device. ([#371](https://github.com/expo/eas-cli/pull/371) by [@axeldelafosse](https://github.com/axeldelafosse))
### 🧹 Chores
- Cleanup unused code. ([#364](https://github.com/expo/eas-cli/pull/364), [#365](https://github.com/expo/eas-cli/pull/365) by [@quinlanj](https://github.com/quinlanj))
- Redesign UX in beta credentials manager. ([#360](https://github.com/expo/eas-cli/pull/360) by [@quinlanj](https://github.com/quinlanj))
- Port more options to the beta credentials manager. ([#352](https://github.com/expo/eas-cli/pull/352), [357](https://github.com/expo/eas-cli/pull/357), [361](https://github.com/expo/eas-cli/pull/361) by [@quinlanj](https://github.com/quinlanj))
- Add getBuildProfileNamesAsync helper to EasJsonReader. ([#351](https://github.com/expo/eas-cli/pull/351) by [@quinlanj](https://github.com/quinlanj))
- Increase build timeout to 1 hour. ([#370](https://github.com/expo/eas-cli/pull/370) by [@wkozyra95](https://github.com/wkozyra95))
- Remove check for pending builds. ([#373](https://github.com/expo/eas-cli/pull/373) by [@wkozyra95](https://github.com/wkozyra95))
## [0.13.0](https://github.com/expo/eas-cli/releases/tag/v0.13.0) - 2021-04-22
### 🎉 New features
- Implement offline distribution certificate validation when running a build in non-interactive mode. ([#344](https://github.com/expo/eas-cli/pull/344) by [@dsokal](https://github.com/dsokal))
- Add support for building internal distribution apps for Apple Enterprise Teams. ([#344](https://github.com/expo/eas-cli/pull/344) by [@dsokal](https://github.com/dsokal))
### 🐛 Bug fixes
- Display descriptive error message when API for EAS Build changes. ([#359](https://github.com/expo/eas-cli/pull/359) by [@wkozyra95](https://github.com/wkozyra95))
## [0.12.0](https://github.com/expo/eas-cli/releases/tag/v0.12.0) - 2021-04-22
### 🎉 New features
- Add the initiating user's username to the build metadata. ([#354](https://github.com/expo/eas-cli/pull/354) by [@dsokal](https://github.com/dsokal))
- Add `--clear-cache` flag for `eas build`. ([#355](https://github.com/expo/eas-cli/pull/355) by [@wkozyra95](https://github.com/wkozyra95))
### 🐛 Bug fixes
- Fix the bug where the account name was used as the username. ([#354](https://github.com/expo/eas-cli/pull/354) by [@dsokal](https://github.com/dsokal))
## [0.11.1](https://github.com/expo/eas-cli/releases/tag/v0.11.1) - 2021-04-20
### 🎉 New features
- Resolve ios distribution types. ([#348](https://github.com/expo/eas-cli/pull/348) by [@quinlanj](https://github.com/quinlanj))
### 🐛 Bug fixes
- Bump version of `@expo/apple-utils` to fix capabilities sync immediately after initial identifier registration. ([593c4](https://github.com/expo/eas-cli/commit/593c42f601396deba1751489343ec07b0a8876da) by [@brentvatne](https://github.com/brentvatne))
## [0.11.0](https://github.com/expo/eas-cli/releases/tag/v0.11.0) - 2021-04-20
### 🛠 Breaking changes
- `"credentialsSource": "auto"` is now deprecated. The option will use `"remote"` as the default value. ([#345](https://github.com/expo/eas-cli/pull/345) by [@dsokal](https://github.com/dsokal))
### 🎉 New features
- Add validation when setting up ad-hoc provisioning profile in non-interactive mode. ([#338](https://github.com/expo/eas-cli/pull/338) by [@dsokal](https://github.com/dsokal))
- Allow for registering devices when running an ad-hoc build for the first time. ([#340](https://github.com/expo/eas-cli/pull/340) by [@dsokal](https://github.com/dsokal))
### 🐛 Bug fixes
- Fix the bug where all Android managed builds produced AAB archives. The `buildType` property from `eas.json` is not ignored now. ([#349](https://github.com/expo/eas-cli/pull/349) by [@dsokal](https://github.com/dsokal))
## [0.10.0](https://github.com/expo/eas-cli/releases/tag/v0.10.0) - 2021-04-16
### 🛠 Breaking changes
- `secrets:<COMMAND>` is renamed to `secret:<COMMAND>` ([#315](https://github.com/expo/eas-cli/pull/315) by [@fiberjw](https://github.com/fiberjw))
- `secret:delete` now accepts `id` through a flag rather than an argument ([#315](https://github.com/expo/eas-cli/pull/315) by [@fiberjw](https://github.com/fiberjw))
### 🎉 New features
- Use special Expo SDK runtime version for managed projects ([#336](https://github.com/expo/eas-cli/pull/336)) by [@wschurman](https://github.com/wschurman))
### 🐛 Bug fixes
- Fix the behavior where the provisioning profile was invalidated after syncing bundle id capabilities. ([#334](https://github.com/expo/eas-cli/pull/334) by [@dsokal](https://github.com/dsokal))
### 🧹 Chores
- Change build credentials summary format. ([#321](https://github.com/expo/eas-cli/pull/321) by [@dsokal](https://github.com/dsokal))
## [0.9.1](https://github.com/expo/eas-cli/releases/tag/v0.9.1) - 2021-04-09
### 🐛 Bug fixes
- Fix provisioning profile validation to actually check status. ([#318](https://github.com/expo/eas-cli/pull/318) by [@quinlanj](https://github.com/quinlanj))
### 🧹 Chores
- Replace Credentials REST API calls with GraphQL counterparts, also improved DX. ([#293](https://github.com/expo/eas-cli/pull/293), [#299](https://github.com/expo/eas-cli/pull/299), [#301](https://github.com/expo/eas-cli/pull/301), [#317](https://github.com/expo/eas-cli/pull/317) by [@quinlanj](https://github.com/quinlanj))
## [0.9.0](https://github.com/expo/eas-cli/releases/tag/v0.9.0) - 2021-04-09
### 🎉 New features
- Retry graphQL call on network or transient server errors. ([#320](https://github.com/expo/eas-cli/pull/320) by [@jkhales](https://github.com/jkhales))
- Display more friendly error messages when `eas submit` fails. ([#311](https://github.com/expo/eas-cli/pull/297) by [@barthap](https://github.com/barthap))
- Add support for managing webhooks (new commands: `webhook:create`, `webhook:view`, `webhook:list`, `webhook:update`, and `webhook:delete`). ([#314](https://github.com/expo/eas-cli/pull/314) by [@dsokal](https://github.com/dsokal))
- Support for local builds [experimental]. ([#305](https://github.com/expo/eas-cli/pull/305) by [@wkozyra95](https://github.com/wkozyra95))
### 🐛 Bug fixes
- Fix the issue where the first iOS build fails for a project without `ios.bundleIdentifier` set in `app.json`. ([#319](https://github.com/expo/eas-cli/pull/319) by [@dsokal](https://github.com/dsokal))
- Bump @expo/config-plugins to fix `eas build:configure` on Windows. ([d0f3e](https://github.com/expo/eas-cli/commit/d0f3e387e7f31dbce4c3b93b8d083c7d20d9e30d) by [@brentvatne](https://github.com/brentvatne))
- Replace REST API calls with GraphQL counterparts. ([#333](https://github.com/expo/eas-cli/pull/333) by [@barthap](https://github.com/barthap))
## [0.8.1](https://github.com/expo/eas-cli/releases/tag/v0.8.1) - 2021-04-06
### 🐛 Bug fixes
- Fix the issue where the build status never got updated when running `eas build`. ([#310](https://github.com/expo/eas-cli/pull/310) by [@dsokal](https://github.com/dsokal))
## [0.8.0](https://github.com/expo/eas-cli/releases/tag/v0.8.0) - 2021-04-06
### 🛠 Breaking changes
- Change the way of disabling cache - add `cache.disabled` field. ([#295](https://github.com/expo/eas-cli/pull/295) by [@dsokal](https://github.com/dsokal))
- `secrets:create` now uses flags rather than positional arguments ([#300](https://github.com/expo/eas-cli/pull/300) by [@fiberjw](https://github.com/fiberjw))
- `secrets:create`'s `target` arg is now called `scope` ([#300](https://github.com/expo/eas-cli/pull/300) by [@fiberjw](https://github.com/fiberjw))
- `secrets:list`'s `target` property is now called `scope` ([#300](https://github.com/expo/eas-cli/pull/300) by [@fiberjw](https://github.com/fiberjw))
- `secrets:delete`'s `ID` arg is now optional ([#309](https://github.com/expo/eas-cli/pull/309) by [@fiberjw](https://github.com/fiberjw))
- `secrets:delete`'s now allows users to choose secrets from a list ([#309](https://github.com/expo/eas-cli/pull/309) by [@fiberjw](https://github.com/fiberjw))
### 🎉 New features
- `build:view` and `build:list` now showing the distribution type (store / internal) and release channel. ([#284](https://github.com/expo/eas-cli/pull/284) by [@vthibault](https://github.com/vthibault))
- Add analytics to EAS Build. ([#162](https://github.com/expo/eas-cli/pull/162) by [@wkozyra95](https://github.com/wkozyra95))
- Improve tar archive support in EAS Submit. ([#297](https://github.com/expo/eas-cli/pull/297) by [@barthap](https://github.com/barthap))
### 🐛 Bug fixes
- Fix environment secret creation prompt. ([#298](https://github.com/expo/eas-cli/pull/298) by [@fiberjw](https://github.com/fiberjw))
### 🧹 Chores
- Replace REST API calls with GraphQL counterparts. ([#286](https://github.com/expo/eas-cli/pull/286), [#288](https://github.com/expo/eas-cli/pull/288), [#303](https://github.com/expo/eas-cli/pull/303), [#306](https://github.com/expo/eas-cli/pull/306), [#307](https://github.com/expo/eas-cli/pull/307) by [@dsokal](https://github.com/dsokal))
## [0.7.0](https://github.com/expo/eas-cli/releases/tag/v0.7.0) - 2021-03-22
### 🎉 New features
- Print common error messages when builds fail. ([#272](https://github.com/expo/eas-cli/pull/272) by [@dsokal](https://github.com/dsokal))
- Commit automatically if `EAS_BUILD_AUTOCOMMIT` is set. ([#271](https://github.com/expo/eas-cli/pull/271) by [@wkozyra95](https://github.com/wkozyra95))
- Allow for installing custom `bundler` version on EAS Build. ([#277](https://github.com/expo/eas-cli/pull/277) by [@dsokal](https://github.com/dsokal))
- Add support for managing environment secrets. ([#275](https://github.com/expo/eas-cli/pull/275) by [@fiberjw](https://github.com/fiberjw))
### 🐛 Bug fixes
- Fix `eas submit` local archive prompt for `.aab` files when submitting for iOS. ([#273](https://github.com/expo/eas-cli/pull/273) by [@barthap](https://github.com/barthap))
- Verify whether "name" field in app.json contains any alphanumeric characters. ([#280](https://github.com/expo/eas-cli/pull/280) by [@wkozyra95](https://github.com/wkozyra95))
- Detect dependency cycles in eas.json build profiles. ([#283](https://github.com/expo/eas-cli/pull/283) by [@wkozyra95](https://github.com/wkozyra95))
## [0.6.0](https://github.com/expo/eas-cli/releases/tag/v0.6.0) - 2021-03-09
### 🛠 Breaking changes
- Generic iOS projects: build release builds by default. ([#266](https://github.com/expo/eas-cli/pull/266) by [@dsokal](https://github.com/dsokal))
### 🎉 New features
- Log the size of the archived project when uploading. ([#264](https://github.com/expo/eas-cli/pull/264) by [@wkozyra95](https://github.com/wkozyra95))
- Add more build metadata (release channel, build profile name, git commit hash). ([#265](https://github.com/expo/eas-cli/pull/265) by [@dsokal](https://github.com/dsokal))
- Display App Store link after successful submission. ([#144](https://github.com/expo/eas-cli/pull/144) by [@barthap](https://github.com/barthap))
- Add `experimental.disableIosBundleIdentifierValidation` flag to eas.json. ([#263](https://github.com/expo/eas-cli/pull/263) by [@wkozyra95](https://github.com/wkozyra95))
- Support internal distribution in non-interactive builds. ([#269](https://github.com/expo/eas-cli/pull/269) by [@dsokal](https://github.com/dsokal))
### 🐛 Bug fixes
- Print Apple Team ID in the output of `device:list` when the team name is unknown. ([#268](https://github.com/expo/eas-cli/pull/268) by [@wkozyra95](https://github.com/wkozyra95))
## [0.5.0](https://github.com/expo/eas-cli/releases/tag/v0.5.0) - 2021-03-02
### 🎉 New features
- Add support for build cache. ([#247](https://github.com/expo/eas-cli/pull/247) by [@wkozyra95](https://github.com/wkozyra95))
- Enable internal distribution support for self-managed credentials. ([#256](https://github.com/expo/eas-cli/pull/256) by [@dsokal](https://github.com/dsokal))
### 🐛 Bug fixes
- Make sure all files are committed before build. ([#251](https://github.com/expo/eas-cli/pull/251) by [@wkozyra95](https://github.com/wkozyra95))
- Fix `eas submit` support for tar.gz files. ([#257](https://github.com/expo/eas-cli/pull/257)) by [@wkozyra95](https://github.com/wkozyra95))
- Show untracked files when checking `git status`. ([#259](https://github.com/expo/eas-cli/pull/259) by [@wkozyra95](https://github.com/wkozyra95))
### 🧹 Chores
- Upgrade `@expo/eas-build-job` from `0.2.12` to `0.2.13`. ([#245](https://github.com/expo/eas-cli/pull/245) by [@dsokal](https://github.com/dsokal))
## [0.4.3](https://github.com/expo/eas-cli/releases/tag/v0.4.3) - 2021-02-23
### 🎉 New features
- Add support for iOS simulator builds. ([#240](https://github.com/expo/eas-cli/pull/240) by [@dsokal](https://github.com/dsokal))
### 🐛 Bug fixes
- Use fixed version of `@expo/eas-json`. ([#243](https://github.com/expo/eas-cli/pull/243) by [@wkozyra95](https://github.com/wkozyra95))
## [0.4.2](https://github.com/expo/eas-cli/releases/tag/v0.4.2) - 2021-02-18
### 🐛 Bug fixes
- Fix detecting application target (iOS builds). ([#238](https://github.com/expo/eas-cli/pull/238) by [@dsokal](https://github.com/dsokal))
## [0.4.1](https://github.com/expo/eas-cli/releases/tag/v0.4.1) - 2021-02-16
### 🐛 Bug fixes
- Fix `"buildType" is not allowed` error. ([#595bf](https://github.com/expo/eas-cli/commit/595bfecf1cbff0f76e7fd2049fe16f6f38bbe150) by [@wkozyra95](https://github.com/wkozyra95))
## [0.4.0](https://github.com/expo/eas-cli/releases/tag/v0.4.0) - 2021-02-16
### 🎉 New features
- Add build:cancel command. ([#219](https://github.com/expo/eas-cli/pull/219) by [@wkozyra95](https://github.com/wkozyra95))
- Implement version auto increment for iOS builds. ([#231](https://github.com/expo/eas-cli/pull/231) by [@dsokal](https://github.com/dsokal))
- Add support for builder environment customizations. ([#230](https://github.com/expo/eas-cli/pull/230) by [@wkozyra95](https://github.com/wkozyra95))
- Add `schemeBuildConfiguration` option for generic iOS builds. ([#234](https://github.com/expo/eas-cli/pull/234) by [@dsokal](https://github.com/dsokal))
### 🐛 Bug fixes
- Fix `--no-wait` flag for `eas build`. ([#226](https://github.com/expo/eas-cli/pull/226) by [@paul-ridgway](https://github.com/paul-ridgway))
- Fix running builds from project subdirectories. ([#229](https://github.com/expo/eas-cli/pull/229) by [@wkozyra95](https://github.com/wkozyra95))
+39
View File
@@ -0,0 +1,39 @@
## Releasing a new version
1. Run `yarn release` in the repository root folder. This will create a new tag like `v1.0.0`.
1. **Wait until the `release` GitHub workflow has finished.**
![Wait for all three jobs to complete](./.gh-assets/release-workflow.png)
1. Run `yarn lerna publish from-git` in the repository root folder. This will publish the packages to npm.
- This command works only if `HEAD` is on the release commit, e.g. when `git log -1` prints:
```
commit cbb2de9e118bfbcb5a597b261d2dbef0bb5d4b0b (tag: v0.11.1)
Author: Brent Vatne <[email protected]>
Date: Tue Apr 20 15:00:15 2021 -0700
v0.11.1
```
- If you run `yarn lerna publish from-git` on any other commit, it'll print something like:
```
lerna notice from-git No tagged release found
lerna success No changed packages to publish
```
- If you ever run into the above issue, use `git checkout RELEASE_COMMIT_HASH` to checkout to the release commit. Later, when you've published the packages to npm, run `git checkout main` to go back to the latest commit.
1. Update `CHANGELOG.md`.
- Add the new section with the release version in the title.
- Leave the `main` section empty (without any changelog entries).
1. Once the release is ready to go, you can go to https://github.com/expo/eas-cli/releases and mark it as not draft.
- Remember to copy the changelog entries from `CHANGELOG.md`.
- Before hitting the submit button, make sure there are 5 assets available under the release entry.
![There are 5 assets for each release](./.gh-assets/draft-release.png)
1. If any of the changelog entries have been tagged with `[EAS BUILD API]`
- Open `package.json` and note the version of `@expo/eas-build-job` used in the eas-cli.
- Find the `Publish` commit in [expo/eas-build](https://github.com/expo/eas-build/commit/) for that version. In most cases, it will be the latest `Publish` commit.
- Find the `eas-cli-local-build-plugin` version published in that commit.
![example publish commit](./.gh-assets/eas-build-publish-commit.png)
- Run `npm dist-tags add eas-cli-local-build-plugin@VERSION latest` with the version from the previous step.
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020-present 650 Industries, Inc. (aka Expo)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+131
View File
@@ -0,0 +1,131 @@
#!/bin/bash
# This script downloads the latest release version of EAS CLI from GitHub and
# installs it in /usr/local/bin
{
# All code is inside a block to ensure the script executes only when
# downloaded completely.
set -euo pipefail
prefix="${EAS_PREFIX:-/usr/local}"
temp_dir="$(mktemp -d)"
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]
then
# Use colors when stdout is a terminal and NO_COLOR isn't set.
text_bold=$'\033[1m'
text_normal=$'\033[22m'
text_red=$'\033[31m'
text_default_color=$'\033[39m'
else
# Disable colors
text_bold=""
text_normal=""
text_red=""
text_default_color=""
fi
abort() {
echo -e "$text_bold$text_red$1$text_normal$text_default_color"
exit 1
}
install() {
needs_sudo=""
# Make sure path exists and can be written to by the current user.
if ! mkdir -p "$prefix/lib" || [[ ! -w "$prefix/lib" ]]
then
needs_sudo=true
fi
if ! mkdir -p "$prefix/bin" || [[ ! -w "$prefix/bin" ]]
then
needs_sudo=true
fi
if ! rm -rf "$prefix/lib/eas"
then
needs_sudo=true
fi
# Run commands with sudo if necessary.
if [ -n "$needs_sudo" ]
then
if [ "$EUID" -ne 0 ]
then
echo "Installing requires superuser access."
echo "The sudo command will prompt for your password."
fi
sudo rm -rf "$prefix/lib/eas"
sudo mv "$temp_dir/eas" "$prefix/lib/eas"
sudo ln -fs "$prefix/lib/eas/bin/eas" "$prefix/bin/eas"
else
mv "$temp_dir/eas" "$prefix/lib/eas"
ln -fs "$prefix/lib/eas/bin/eas" "$prefix/bin/eas"
fi
}
if [[ ! ":$PATH:" == *":$prefix/bin:"* ]]; then
abort "Your path is missing $prefix/bin, you need to add this to \$PATH use this installer."
fi
hardware="$(uname -m)"
case "$hardware" in
x86_64)
arch="x64"
;;
arm*)
arch="arm"
;;
*)
abort "Unsupported CPU architecture $hardware."
;;
esac
case "$(uname -s)" in
Darwin*)
platform="darwin"
if [[ "$arch" != "x64" ]]; then
abort "Unsupported CPU architecture $hardware on macOS."
fi
;;
Linux*)
platform="linux"
;;
*)
abort "This installer is supported only on macOS, Linux, and Windows Subsystem for Linux."
;;
esac
url="https://github.com/expo/eas-cli/releases/latest/download/eas-$platform-$arch.tar.gz"
echo "Installing EAS CLI from $url"
mkdir -p "$temp_dir"
cd "$temp_dir"
echo
echo "Downloading..."
if [ "$(command -v curl)" ]
then
curl --location --progress-bar "$url" | tar xz
else
wget -O- "$url" | tar xz
fi
# Move the installation over and link the executable (may require sudo)
install
# Test the bundled node binary and if it doesn't work remove it (fall back to
# node installed in path).
"$prefix/lib/eas/bin/node" -v > /dev/null || {
rm "$prefix/lib/eas/bin/node"
}
# Test the CLI
executable=$(command -v eas) || abort "Could not run eas after installation."
echo
echo -e "EAS CLI is installed to ${executable}"
eas --version
echo
echo -e "Type ${text_bold}eas --help${text_normal} to get started."
}
+18
View File
@@ -0,0 +1,18 @@
comment: false
coverage:
round: up
precision: 2
range: 30..100
status:
patch: false
project:
default:
informational: true
eas-cli:
informational: true
paths:
- packages/eas-cli
eas-json:
informational: true
paths:
- packages/eas-json
+1 -1
View File
@@ -1,7 +1,7 @@
module.exports = {
projects: [
require('./packages/eas-cli/jest.config.js'),
require('./packages/config/jest.config.js'),
require('./packages/eas-json/jest.config.js'),
],
testPathIgnorePatterns: ['.*'],
};
+1 -6
View File
@@ -1,11 +1,6 @@
module.exports = function (api) {
api.cache(true);
return {
// Only use this when running tests
env: {
test: {
presets: ['@expo/babel-preset-cli'],
},
},
presets: ['@expo/babel-preset-cli'],
};
};
+1
View File
@@ -5,4 +5,5 @@ module.exports = {
'^.+\\.[jt]sx?$': ['babel-jest', { configFile: require.resolve('./babel.config.js') }],
},
watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
collectCoverageFrom: ['<rootDir>/src/**/*.{ts,tsx,js,jsx}'],
};
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.0.0"
"version": "0.23.0"
}
+11 -3
View File
@@ -4,17 +4,25 @@
"packages/*"
],
"scripts": {
"build": "lerna run build",
"build:tarballs:linux": "yarn build && yarn workspace eas-cli oclif-dev pack --targets linux-x64",
"typecheck": "lerna run typecheck",
"start": "lerna run watch --parallel",
"watch": "yarn start",
"eas": "packages/eas-cli/bin/run",
"lint": "eslint . --ext .ts",
"release": "lerna version",
"test": "jest"
},
"devDependencies": {
"@expo/oclif-dev-cli": "1.24.0-expo",
"@types/jest": "^26.0.13",
"eslint": "^6.6.0",
"eslint-config-universe": "^4.0.0",
"eslint": "7.31.0",
"eslint-config-universe": "7.0.1",
"eslint-plugin-graphql": "4.0.0",
"jest": "^26.4.2",
"jest-watch-typeahead": "^0.6.1",
"lerna": "^3.22.1",
"prettier": "^2.0.5"
"prettier": "2.3.2"
}
}
-53
View File
@@ -1,53 +0,0 @@
// Workflow is representing different value than BuildType from @expo/build-tools
// Each workflow has a set of BuildTypes available
// - Generic workflow allows to build 'generic' and 'generic-client'
// - Managed workflow allows to build 'managed' and 'managed-client'
export enum Workflow {
Generic = 'generic',
Managed = 'managed',
}
export enum CredentialsSource {
LOCAL = 'local',
REMOTE = 'remote',
AUTO = 'auto',
}
export interface AndroidManagedBuildProfile {
workflow: Workflow.Managed;
credentialsSource: CredentialsSource;
buildType?: 'apk' | 'app-bundle';
}
export interface AndroidGenericBuildProfile {
workflow: Workflow.Generic;
credentialsSource: CredentialsSource;
gradleCommand?: string;
artifactPath?: string;
withoutCredentials?: boolean;
}
export interface iOSManagedBuildProfile {
workflow: Workflow.Managed;
credentialsSource: CredentialsSource;
buildType?: 'archive' | 'simulator';
}
export interface iOSGenericBuildProfile {
workflow: Workflow.Generic;
credentialsSource: CredentialsSource;
scheme?: string;
artifactPath?: string;
}
export type AndroidBuildProfile = AndroidManagedBuildProfile | AndroidGenericBuildProfile;
export type iOSBuildProfile = iOSManagedBuildProfile | iOSGenericBuildProfile;
export type BuildProfile = AndroidBuildProfile | iOSBuildProfile;
// EasConfig represents eas.json with one specific profile
export interface EasConfig {
builds: {
android?: AndroidManagedBuildProfile | AndroidGenericBuildProfile;
ios?: iOSManagedBuildProfile | iOSGenericBuildProfile;
};
}
-94
View File
@@ -1,94 +0,0 @@
import { Platform } from '@expo/build-tools';
import fs from 'fs-extra';
import path from 'path';
import {
AndroidBuildProfile,
BuildProfile,
EasConfig,
Workflow,
iOSBuildProfile,
} from './Config.types';
import { EasJsonSchema, schemaBuildProfileMap } from './EasJsonSchema';
interface EasJson {
builds: {
android?: { [key: string]: AndroidBuildProfile };
ios?: { [key: string]: iOSBuildProfile };
};
}
interface BuildProfilePreValidation {
workflow: Workflow;
}
export class EasJsonReader {
constructor(private projectDir: string, private platform: 'android' | 'ios' | 'all') {}
public async readAsync(buildProfileName: string): Promise<EasConfig> {
const easJson = await this.readFile();
let androidConfig;
if (['android', 'all'].includes(this.platform)) {
androidConfig = this.validateBuildProfile<AndroidBuildProfile>(
Platform.Android,
buildProfileName,
easJson.builds?.android?.[buildProfileName]
);
}
let iosConfig;
if (['ios', 'all'].includes(this.platform)) {
iosConfig = this.validateBuildProfile<iOSBuildProfile>(
Platform.iOS,
buildProfileName,
easJson.builds?.ios?.[buildProfileName]
);
}
return {
builds: {
...(androidConfig ? { android: androidConfig } : {}),
...(iosConfig ? { ios: iosConfig } : {}),
},
};
}
private validateBuildProfile<T extends BuildProfile>(
platform: 'android' | 'ios' | 'all',
buildProfileName: string,
buildProfile?: BuildProfilePreValidation
): T {
if (!buildProfile) {
throw new Error(`There is no profile named ${buildProfileName} for platform ${platform}`);
}
const schema = schemaBuildProfileMap[platform][buildProfile?.workflow];
if (!schema) {
throw new Error('invalid workflow'); // this should be validated earlier
}
const { value, error } = schema.validate(buildProfile, {
stripUnknown: true,
convert: true,
abortEarly: false,
});
if (error) {
throw new Error(
`Object "${platform}.${buildProfileName}" in eas.json is not valid [${error.toString()}]`
);
}
return value;
}
private async readFile(): Promise<EasJson> {
const rawFile = await fs.readFile(path.join(this.projectDir, 'eas.json'), 'utf-8');
const json = JSON.parse(rawFile);
const { value, error } = EasJsonSchema.validate(json, {
abortEarly: false,
});
if (error) {
throw new Error(`eas.json is not valid [${error.toString()}]`);
}
return value;
}
}
-58
View File
@@ -1,58 +0,0 @@
import Joi from '@hapi/joi';
const AndroidGenericSchema = Joi.object({
workflow: Joi.string().valid('generic').required(),
credentialsSource: Joi.string().valid('local', 'remote', 'auto').default('auto'),
gradleCommand: Joi.string(),
artifactPath: Joi.string(),
withoutCredentials: Joi.boolean(),
});
const AndroidManagedSchema = Joi.object({
workflow: Joi.string().valid('managed').required(),
credentialsSource: Joi.string().valid('local', 'remote', 'auto').default('auto'),
buildType: Joi.string().valid('apk', 'app-bundle').default('app-bundle'),
});
const iOSGenericSchema = Joi.object({
workflow: Joi.string().valid('generic').required(),
credentialsSource: Joi.string().valid('local', 'remote', 'auto').default('auto'),
scheme: Joi.string(),
artifactPath: Joi.string(),
});
const iOSManagedSchema = Joi.object({
workflow: Joi.string().valid('managed').required(),
credentialsSource: Joi.string().valid('local', 'remote', 'auto').default('auto'),
buildType: Joi.string().valid('archive', 'simulator'),
});
const schemaBuildProfileMap: Record<string, Record<string, Joi.Schema>> = {
android: {
generic: AndroidGenericSchema,
managed: AndroidManagedSchema,
},
ios: {
managed: iOSManagedSchema,
generic: iOSGenericSchema,
},
};
const EasJsonSchema = Joi.object({
builds: Joi.object({
android: Joi.object().pattern(
Joi.string(),
Joi.object({
workflow: Joi.string().valid('generic', 'managed').required(),
}).unknown(true) // profile is validated further only if build is for that platform
),
ios: Joi.object().pattern(
Joi.string(),
Joi.object({
workflow: Joi.string().valid('generic', 'managed').required(),
}).unknown(true) // profile is validated further only if build is for that platform
),
}),
});
export { EasJsonSchema, schemaBuildProfileMap };
@@ -1,191 +0,0 @@
import fs from 'fs-extra';
import { vol } from 'memfs';
import { EasJsonReader } from '../EasJsonReader';
jest.mock('fs');
beforeEach(async () => {
vol.reset();
await fs.mkdirp('/project');
});
test('minimal valid android eas.json', async () => {
await fs.writeJson('/project/eas.json', {
builds: {
android: {
release: { workflow: 'generic' },
},
},
});
const reader = new EasJsonReader('/project', 'android');
const easJson = await reader.readAsync('release');
expect({
builds: {
android: {
workflow: 'generic',
credentialsSource: 'auto',
},
},
}).toEqual(easJson);
});
test('minimal valid ios eas.json', async () => {
await fs.writeJson('/project/eas.json', {
builds: {
ios: {
release: { workflow: 'generic' },
},
},
});
const reader = new EasJsonReader('/project', 'ios');
const easJson = await reader.readAsync('release');
expect({
builds: {
ios: {
credentialsSource: 'auto',
workflow: 'generic',
},
},
}).toEqual(easJson);
});
test('minimal valid eas.json for both platforms', async () => {
await fs.writeJson('/project/eas.json', {
builds: {
android: {
release: { workflow: 'generic' },
},
ios: {
release: { workflow: 'generic' },
},
},
});
const reader = new EasJsonReader('/project', 'all');
const easJson = await reader.readAsync('release');
expect({
builds: {
android: { workflow: 'generic', credentialsSource: 'auto' },
ios: { workflow: 'generic', credentialsSource: 'auto' },
},
}).toEqual(easJson);
});
test('valid eas.json with both platform, but reading only android', async () => {
await fs.writeJson('/project/eas.json', {
builds: {
ios: {
release: { workflow: 'generic' },
},
android: {
release: { workflow: 'generic' },
},
},
});
const reader = new EasJsonReader('/project', 'android');
const easJson = await reader.readAsync('release');
expect({
builds: {
android: { workflow: 'generic', credentialsSource: 'auto' },
},
}).toEqual(easJson);
});
test('valid eas.json for debug builds', async () => {
await fs.writeJson('/project/eas.json', {
builds: {
ios: {
release: { workflow: 'managed' },
debug: { workflow: 'managed', buildType: 'simulator' },
},
android: {
release: { workflow: 'generic' },
debug: {
workflow: 'generic',
gradleCommand: ':app:assembleDebug',
withoutCredentials: true,
},
},
},
});
const reader = new EasJsonReader('/project', 'all');
const easJson = await reader.readAsync('debug');
expect({
builds: {
android: {
credentialsSource: 'auto',
workflow: 'generic',
gradleCommand: ':app:assembleDebug',
withoutCredentials: true,
},
ios: {
credentialsSource: 'auto',
workflow: 'managed',
buildType: 'simulator',
},
},
}).toEqual(easJson);
});
test('invalid eas.json with missing preset', async () => {
await fs.writeJson('/project/eas.json', {
builds: {
android: {
release: { workflow: 'generic' },
},
},
});
const reader = new EasJsonReader('/project', 'android');
const promise = reader.readAsync('debug');
await expect(promise).rejects.toThrowError(
'There is no profile named debug for platform android'
);
});
test('invalid eas.json when using buildType for wrong platform', async () => {
await fs.writeJson('/project/eas.json', {
builds: {
android: {
release: { workflow: 'managed', buildType: 'archive' },
},
},
});
const reader = new EasJsonReader('/project', 'android');
const promise = reader.readAsync('release');
await expect(promise).rejects.toThrowError(
'Object "android.release" in eas.json is not valid [ValidationError: "buildType" must be one of [apk, app-bundle]]'
);
});
test('invalid eas.json when missing workflow', async () => {
await fs.writeJson('/project/eas.json', {
builds: {
android: {
release: { buildType: 'apk' },
},
},
});
const reader = new EasJsonReader('/project', 'android');
const promise = reader.readAsync('release');
await expect(promise).rejects.toThrowError(
'eas.json is not valid [ValidationError: "builds.android.release.workflow" is required]'
);
});
test('empty json', async () => {
await fs.writeJson('/project/eas.json', {});
const reader = new EasJsonReader('/project', 'android');
const promise = reader.readAsync('release');
await expect(promise).rejects.toThrowError(
'There is no profile named release for platform android'
);
});
-12
View File
@@ -1,12 +0,0 @@
export {
Workflow,
CredentialsSource,
AndroidManagedBuildProfile,
AndroidGenericBuildProfile,
AndroidBuildProfile,
iOSManagedBuildProfile,
iOSGenericBuildProfile,
iOSBuildProfile,
EasConfig,
} from './Config.types';
export { EasJsonReader } from './EasJsonReader';
+37
View File
@@ -0,0 +1,37 @@
module.exports = {
rules: {
'import/no-cycle': 'error',
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: ['**/__tests__/**/*', '**/__mocks__/**/*'] },
],
'graphql/template-strings': [
'error',
{
env: 'apollo',
schemaJson: require('./graphql.schema.json'),
},
],
'graphql/named-operations': [
'error',
{
schemaJson: require('./graphql.schema.json'),
},
],
'graphql/required-fields': [
'error',
{
env: 'apollo',
schemaJson: require('./graphql.schema.json'),
requiredFields: ['id'],
},
],
'graphql/capitalized-type-name': [
'error',
{
schemaJson: require('./graphql.schema.json'),
},
],
},
plugins: ['graphql'],
};
+2
View File
@@ -0,0 +1,2 @@
graphql.schema.json linguist-generated
src/graphql/generated.ts linguist-generated
+419 -65
View File
@@ -7,69 +7,252 @@ EAS command line tool
[![Downloads/week](https://img.shields.io/npm/dw/eas-cli.svg)](https://npmjs.org/package/eas-cli)
[![License](https://img.shields.io/npm/l/eas-cli.svg)](https://github.com/expo/eas-cli/blob/main/package.json)
<!-- toc -->
* [Installation](#installation)
* [Usage](#usage)
* [Commands](#commands)
- [eas-cli](#eas-cli)
- [Usage](#usage)
- [Commands](#commands)
<!-- tocstop -->
# Installation
```sh
npm install -g eas-cli
# or
yarn global add eas-cli
```
# Usage
<!-- usage -->
```sh-session
$ npm install -g eas-cli
$ eas COMMAND
running command...
$ eas (-v|--version|version)
eas-cli/0.0.0 darwin-x64 node-v12.13.0
$ eas --help [COMMAND]
USAGE
$ eas COMMAND
...
```sh
eas COMMAND
# runs the command
eas (-v|--version|version)
# prints the version
eas --help COMMAND
# outputs help for specific command
```
<!-- usagestop -->
# Commands
<!-- commands -->
* [`eas account:login`](#eas-accountlogin)
* [`eas account:logout`](#eas-accountlogout)
* [`eas account:view`](#eas-accountview)
* [`eas analytics [STATUS]`](#eas-analytics-status)
* [`eas build`](#eas-build)
* [`eas build:cancel [BUILD_ID]`](#eas-buildcancel-build_id)
* [`eas build:configure`](#eas-buildconfigure)
* [`eas build:list`](#eas-buildlist)
* [`eas build:view [BUILD_ID]`](#eas-buildview-build_id)
* [`eas config`](#eas-config)
* [`eas credentials`](#eas-credentials)
* [`eas device:create`](#eas-devicecreate)
* [`eas device:list`](#eas-devicelist)
* [`eas device:view [UDID]`](#eas-deviceview-udid)
* [`eas help [COMMAND]`](#eas-help-command)
* [`eas project:info`](#eas-projectinfo)
* [`eas project:init`](#eas-projectinit)
* [`eas secret:create`](#eas-secretcreate)
* [`eas secret:delete`](#eas-secretdelete)
* [`eas secret:list`](#eas-secretlist)
* [`eas submit --platform=(android|ios)`](#eas-submit---platformandroidios)
* [`eas webhook:create`](#eas-webhookcreate)
* [`eas webhook:delete [ID]`](#eas-webhookdelete-id)
* [`eas webhook:list`](#eas-webhooklist)
* [`eas webhook:update`](#eas-webhookupdate)
* [`eas webhook:view ID`](#eas-webhookview-id)
- [`eas build`](#eas-build)
- [`eas build:status`](#eas-buildstatus)
- [`eas help [COMMAND]`](#eas-help-command)
- [`eas login`](#eas-login)
- [`eas logout`](#eas-logout)
- [`eas update`](#eas-update)
- [`eas update:show`](#eas-updateshow)
- [`eas whoami`](#eas-whoami)
## `eas account:login`
log in with your Expo account
```
USAGE
$ eas account:login
ALIASES
$ eas login
```
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
log out
```
USAGE
$ eas account:logout
ALIASES
$ eas logout
```
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view`
show the username you are logged in as
```
USAGE
$ eas account:view
ALIASES
$ eas whoami
```
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
View or change analytics settings
```
USAGE
$ eas analytics [STATUS]
```
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/analytics.ts)_
## `eas build`
build an app binary for your project
Start a build
```
USAGE
$ eas build
OPTIONS
-p, --platform=(android|ios|all)
--clear-cache Clear cache before the build
--local Run build locally [experimental]
--non-interactive Run command in --non-interactive mode
--profile=profile [default: release] Name of the build profile from eas.json
--skip-project-configuration Skip project configuration
--[no-]wait Wait for build(s) to complete
```
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.0.0/src/commands/build/index.ts)_
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:status`
## `eas build:cancel [BUILD_ID]`
get the status of the latest builds for your project
Cancel a build.
```
USAGE
$ eas build:status
OPTIONS
--platform=(all|android|ios)
--status=(in-queue|in-progress|errored|finished)
$ eas build:cancel [BUILD_ID]
```
_See code: [src/commands/build/status.ts](https://github.com/expo/eas-cli/blob/v0.0.0/src/commands/build/status.ts)_
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
Configure the project to support EAS Build.
```
USAGE
$ eas build:configure
OPTIONS
-p, --platform=(android|ios|all) Platform to configure
```
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:list`
list all builds for your project
```
USAGE
$ eas build:list
OPTIONS
--appBuildVersion=appBuildVersion
--appIdentifier=appIdentifier
--appVersion=appVersion
--buildProfile=buildProfile
--channel=channel
--distribution=(store|internal|simulator)
--gitCommitHash=gitCommitHash
--limit=limit
--platform=(all|android|ios)
--runtimeVersion=runtimeVersion
--sdkVersion=sdkVersion
--status=(new|in-queue|in-progress|errored|finished|canceled)
```
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:view [BUILD_ID]`
view a build for your project
```
USAGE
$ eas build:view [BUILD_ID]
```
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/build/view.ts)_
## `eas config`
Show the eas.json config
```
USAGE
$ eas config
OPTIONS
-p, --platform=(android|ios)
--profile=profile
```
## `eas credentials`
Manage your credentials
```
USAGE
$ eas credentials
```
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/credentials.ts)_
## `eas device:create`
register new Apple Devices to use for internal distribution
```
USAGE
$ eas device:create
```
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:list`
list all registered devices for your account
```
USAGE
$ eas device:list
OPTIONS
--apple-team-id=apple-team-id
```
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:view [UDID]`
view a device for your project
```
USAGE
$ eas device:view [UDID]
```
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/device/view.ts)_
## `eas help [COMMAND]`
@@ -88,62 +271,233 @@ OPTIONS
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.0/src/commands/help.ts)_
## `eas login`
## `eas project:info`
log in with your EAS account
information about the current project
```
USAGE
$ eas login
$ eas project:info
```
_See code: [src/commands/login.ts](https://github.com/expo/eas-cli/blob/v0.0.0/src/commands/login.ts)_
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/project/info.ts)_
## `eas logout`
## `eas project:init`
log out
create or link an EAS project
```
USAGE
$ eas logout
```
_See code: [src/commands/logout.ts](https://github.com/expo/eas-cli/blob/v0.0.0/src/commands/logout.ts)_
## `eas update`
create a revision for given channel
```
USAGE
$ eas update
$ eas project:init
ALIASES
$ eas update:publish
$ eas init
```
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v0.0.0/src/commands/update/index.ts)_
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/project/init.ts)_
## `eas update:show`
## `eas secret:create`
details about a particular revision
Create an environment secret on the current project or owner account.
```
USAGE
$ eas update:show
$ eas secret:create
OPTIONS
--force Delete and recreate existing secrets
--name=name Name of the secret
--scope=(account|project) [default: project] Scope for the secret
--value=value Value of the secret
```
_See code: [src/commands/update/show.ts](https://github.com/expo/eas-cli/blob/v0.0.0/src/commands/update/show.ts)_
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/secret/create.ts)_
## `eas whoami`
## `eas secret:delete`
show the username you are logged in as
Delete an environment secret by ID.
```
USAGE
$ eas whoami
$ eas secret:delete
OPTIONS
--id=id ID of the secret to delete
DESCRIPTION
Unsure where to find the secret's ID? Run eas secrets:list
```
_See code: [src/commands/whoami.ts](https://github.com/expo/eas-cli/blob/v0.0.0/src/commands/whoami.ts)_
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/secret/delete.ts)_
## `eas secret:list`
Lists environment secrets available for your current app
```
USAGE
$ eas secret:list
```
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/secret/list.ts)_
## `eas submit --platform=(android|ios)`
Submits build artifact to app store
```
USAGE
$ eas submit --platform=(android|ios)
OPTIONS
-p, --platform=(android|ios) For which platform you want to submit a build
Android specific options=android-package Android package name (default: expo.android.package
from app config)
iOS specific options=app-name The name of your app as it will appear on the App
Store (default: expo.name from app config)
iOS specific options=apple-id Your Apple ID username (you can also set
EXPO_APPLE_ID env variable)
iOS specific options=apple-team-id Your Apple Developer Team ID
iOS specific options=asc-app-id App Store Connect unique application Apple ID
number. Providing this param results in skipping
app creation step. Learn more.
iOS specific options=bundle-identifier Your iOS Bundle Identifier (default:
expo.ios.bundleIdentifier from app config)
iOS specific options=company-name The name of your company, needed only for the first
upload of any app to App Store
--id=id ID of the build to submit
Android specific options=key Path to the JSON key used to authenticate with
Google Play
iOS specific options=language [default: en-US] Primary language (e.g. English,
German, ...)
--latest Submit the latest build for specified platform
--path=path Path to the .apk/.aab file
Android specific options=(completed|draft|halted|inProgress) [default: completed] Release status (used when
uploading new APKs/AABs)
iOS specific options=sku An unique ID for your app that is not visible on
the App Store, will be generated unless provided
Android specific options=(production|beta|alpha|internal|rollout) [default: internal] The track of the application to
use
Android specific options=(apk|aab) Android archive type
--url=url App archive url
--verbose Always print logs from Submission Service
ALIASES
$ eas build:submit
EXAMPLES
$ eas submit --platform=ios
- Fully interactive iOS submission
$ eas submit --platform=android
- Fully interactive Android submission
$ eas submit -p android --latest --key=/path/to/google-services.json
- Minimal non-interactive Android submission, however it can ask you for other params if not specified
$ EXPO_APPLE_APP_SPECIFIC_PASSWORD=xxx eas submit -p ios --latest [email protected] --asc-app-id=1234567890,
- Minimal non-interactive iOS submission, assuming you already have an app in App Store Connect
and provide its App ID
```
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/submit.ts)_
## `eas webhook:create`
Create a webhook on the current project.
```
USAGE
$ eas webhook:create
OPTIONS
--event=(BUILD) [default: BUILD] Event type that triggers the webhook
--secret=secret Secret used to create a hash signature of the request payload, provided in the 'Expo-Signature'
header.
--url=url Webhook URL
```
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
Delete a webhook on the current project.
```
USAGE
$ eas webhook:delete [ID]
ARGUMENTS
ID ID of the webhook to delete
```
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
List webhooks on the current project.
```
USAGE
$ eas webhook:list
OPTIONS
--event=(BUILD) Event type that triggers the webhook
```
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
Create a webhook on the current project.
```
USAGE
$ eas webhook:update
OPTIONS
--event=(BUILD) [default: BUILD] Event type that triggers the webhook
--id=id (required) Webhook ID
--secret=secret Secret used to create a hash signature of the request payload, provided in the 'Expo-Signature'
header.
--url=url Webhook URL
```
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
View a webhook on the current project.
```
USAGE
$ eas webhook:view ID
ARGUMENTS
ID ID of the webhook to view
```
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.23.0/packages/eas-cli/src/commands/webhook/view.ts)_
<!-- commandsstop -->
@@ -1,3 +0,0 @@
const getConfig = jest.fn();
export { getConfig };
+7
View File
@@ -1,2 +1,9 @@
import { fs } from 'memfs';
// needed because of a weird bug with tempy (dependency of @expo/config-plugins)
fs.mkdirSync('/tmp');
if (process.env.TMPDIR) {
fs.mkdirSync(process.env.TMPDIR, { recursive: true });
}
module.exports = fs;
+7
View File
@@ -0,0 +1,7 @@
export default jest.fn().mockReturnValue({
start: jest.fn().mockReturnValue({
fail: jest.fn(),
succeed: jest.fn(),
}),
succeed: jest.fn(),
});
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env sh
OUTFILE="src/graphql/generated.ts"
content="/**
* This file was generated using GraphQL Codegen
* Command: yarn generate-graphql-code
* Run this during development for automatic type generation when editing GraphQL documents
* For more info and docs, visit https://graphql-code-generator.com/
*/
"
printf '%s\n%s\n' "$content" "$(cat $OUTFILE)" > $OUTFILE
+20
View File
@@ -0,0 +1,20 @@
overwrite: true
schema: 'https://staging.exp.host/--/graphql'
documents:
- 'src/graphql/**/!(*.d).{ts,tsx}'
- 'src/credentials/ios/api/graphql/**/!(*.d).{ts,tsx}'
- 'src/credentials/android/api/graphql/**/!(*.d).{ts,tsx}'
- 'src/commands/**/*.ts'
generates:
src/graphql/generated.ts:
plugins:
- 'typescript'
- 'typescript-operations'
config:
dedupeOperationSuffix: true
hooks:
afterOneFileWrite:
- ./annotate-graphql-codegen.sh
./graphql.schema.json:
plugins:
- 'introspection'
+22962
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -2,4 +2,6 @@ module.exports = {
...require('../../jest/jest.config.js'),
rootDir: __dirname,
roots: ['src', '__mocks__'],
globalSetup: './jest/global-setup.ts',
setupFilesAfterEnv: ['./jest/setup-after-env.ts'],
};
+5
View File
@@ -0,0 +1,5 @@
module.exports = async () => {
process.env.TZ = 'UTC';
process.env.EAS_NO_VCS = '1';
process.env.EAS_PROJECT_ROOT = '/app';
};
+6
View File
@@ -0,0 +1,6 @@
/* eslint-disable no-console */
beforeAll(() => {
console.log = jest.fn();
console.warn = jest.fn();
console.error = jest.fn();
});
+124 -49
View File
@@ -1,73 +1,106 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "0.0.0",
"version": "0.23.0",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
},
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@eas/config": "0.0.1",
"@expo/config": "^3.3.9",
"@expo/json-file": "^8.2.24",
"@expo/plist": "^0.0.10",
"@expo/spawn-async": "^1.5.0",
"@oclif/command": "^1",
"@oclif/config": "^1",
"@oclif/plugin-help": "^3",
"@urql/core": "^1.13.0",
"chalk": "^4.1.0",
"dateformat": "^3.0.3",
"env-paths": "^2.2.0",
"form-data": "^3.0.0",
"fs-extra": "^9.0.1",
"got": "^11.5.2",
"graphql-tag": "^2.11.0",
"keychain": "^1.3.0",
"lodash": "^4.17.20",
"md5-file": "^5.0.0",
"minimatch": "^3.0.4",
"node-fetch": "^2.6.1",
"node-forge": "^0.10.0",
"ora": "^5.1.0",
"pkg-dir": "^4.2.0",
"progress": "^2.0.3",
"prompts": "^2.3.2",
"tslib": "^1",
"untildify": "^4.0.0",
"uuid": "^8.3.0",
"wordwrap": "^1.0.0"
"@amplitude/identify": "1.7.0",
"@amplitude/node": "1.7.0",
"@expo/apple-utils": "0.0.0-alpha.24",
"@expo/config": "3.3.42",
"@expo/config-plugins": "3.0.3",
"@expo/eas-build-job": "0.2.45",
"@expo/eas-json": "^0.23.0",
"@expo/json-file": "8.2.25",
"@expo/pkcs12": "0.0.4",
"@expo/plist": "0.0.11",
"@expo/plugin-warn-if-update-available": "1.7.0",
"@expo/results": "1.0.0",
"@expo/sdk-runtime-versions": "1.0.0",
"@expo/spawn-async": "1.5.0",
"@hapi/joi": "17.1.1",
"@oclif/command": "1.8.0",
"@oclif/config": "1.17.0",
"@oclif/errors": "1.3.3",
"@oclif/plugin-help": "3.2.0",
"@urql/core": "2.0.0",
"@urql/exchange-retry": "0.2.0",
"chalk": "4.1.0",
"cli-table3": "0.6.0",
"dateformat": "3.0.3",
"env-paths": "2.2.0",
"fast-glob": "3.2.7",
"figures": "3.2.0",
"form-data": "3.0.0",
"fs-extra": "9.0.1",
"getenv": "1.0.0",
"got": "11.5.2",
"graphql": "15.3.0",
"graphql-tag": "2.11.0",
"ignore": "5.1.8",
"indent-string": "4.0.0",
"jks-js": "1.0.0",
"keychain": "1.3.0",
"lodash": "4.17.20",
"mime": "2.4.7",
"minimatch": "3.0.4",
"nanoid": "3.1.23",
"node-fetch": "2.6.1",
"node-forge": "0.10.0",
"nullthrows": "1.1.1",
"ora": "5.1.0",
"pkg-dir": "4.2.0",
"progress": "2.0.3",
"prompts": "2.3.2",
"qrcode-terminal": "0.12.0",
"resolve-from": "5.0.0",
"semver": "7.3.4",
"strip-ansi": "6.0.0",
"tar": "6.0.5",
"tempy": "0.7.0",
"terminal-link": "2.1.1",
"timeago.js": "4.0.2",
"tslib": "2.3.0",
"turndown": "7.0.0",
"untildify": "4.0.0",
"uuid": "8.3.0",
"wrap-ansi": "7.0.0"
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.11.5",
"@babel/preset-typescript": "^7.10.4",
"@expo/babel-preset-cli": "^0.2.17",
"@expo/oclif-dev-cli": "1.23.0-expo",
"@graphql-codegen/cli": "^1.19.2",
"@graphql-codegen/introspection": "^1.18.0",
"@graphql-codegen/typescript": "^1.17.11",
"@graphql-codegen/typescript-operations": "^1.17.10",
"@types/dateformat": "^3.0.1",
"@types/form-data": "^2.5.0",
"@types/fs-extra": "^9.0.1",
"@types/getenv": "^1.0.0",
"@types/lodash": "^4.14.161",
"@types/mime": "^2.0.3",
"@types/node": "^12",
"@types/node-fetch": "^2.5.7",
"@types/node-forge": "^0.9.5",
"@types/progress": "^2.0.3",
"@types/prompts": "^2.0.8",
"@types/prompts": "^2.0.9",
"@types/semver": "^7.3.4",
"@types/tar": "^4.0.3",
"@types/tough-cookie": "4.0.1",
"@types/uuid": "^8.3.0",
"@types/wordwrap": "^1.0.0",
"@vercel/ncc": "^0.23.0",
"@types/wrap-ansi": "^3.0.0",
"axios": "0.21.1",
"babel-jest": "^26.3.0",
"graphql": "^15.3.0",
"memfs": "^3.2.0",
"tempy": "^0.7.0",
"ts-node": "^8",
"typescript": "^3.9.7",
"wonka": "^4.0.14"
},
"optionalDependencies": {
"@expo/traveling-fastlane-darwin": "1.15.1",
"@expo/traveling-fastlane-linux": "1.15.1"
"mockdate": "^3.0.2",
"nock": "^13.0.5",
"typescript": "4.3.5"
},
"engines": {
"node": ">=8.0.0"
@@ -89,26 +122,68 @@
"bin": "eas",
"commands": "./build/commands",
"plugins": [
"@oclif/plugin-help"
"@oclif/plugin-help",
"@expo/plugin-warn-if-update-available"
],
"helpClass": "./build/help",
"topics": {
"account": {
"description": "manage your account"
},
"build": {
"description": "build app binaries"
},
"channel": {
"description": "manage update channels"
},
"device": {
"description": "manage your Apple devices for internal distribution"
},
"project": {
"description": "manage your project"
},
"release": {
"description": "manage update releases"
},
"secret": {
"description": "manage project and account secrets"
},
"update": {
"description": "roll out changes"
"description": "manage individual updates"
},
"webhook": {
"description": "manage webhooks"
}
},
"warn-if-update-available": {
"timeoutInDays": 0.5,
"message": "<%= chalk.bold('★') %> <%= chalk.bold(config.name + '@' + latest) %> <%= chalk.bold('is now available, please upgrade.') %>\n<%= chalk.dim('Proceeding with outdated version') %>\n"
},
"update": {
"node": {
"version": "12.13.0"
},
"s3": {
"templates": {
"target": {
"unversioned": "<%- bin %>-<%- platform %>-<%- arch %><%- ext %>",
"versioned": "<%- bin %>-<%- platform %>-<%- arch %><%- ext %>"
}
}
}
}
},
"repository": "expo/eas-cli",
"scripts": {
"postpack": "rm -f oclif.manifest.json",
"prepack": "rm -rf build && tsc -b && oclif-dev manifest && oclif-dev readme",
"prepack": "rm -rf build && yarn build && yarn oclif-dev manifest && yarn oclif-dev readme && node patch-readme",
"build": "tsc --project tsconfig.build.json",
"watch": "yarn build --watch --preserveWatchOutput",
"typecheck": "tsc",
"test": "jest",
"version": "oclif-dev readme && git add README.md"
"version": "yarn oclif-dev readme && node patch-readme && git add README.md",
"generate-graphql-code": "graphql-codegen --config graphql-codegen.yml"
},
"types": "build/index.d.ts"
"types": "build/index.d.ts",
"gitHead": "161808bc37c255fdea6801d69d23dbc725c056c3"
}
+10
View File
@@ -0,0 +1,10 @@
const fs = require('fs');
// Patch `oclif-dev readme` path and link generation
let readmeContent = fs.readFileSync('README.md', 'utf8');
readmeContent = readmeContent.replace(/\[build\//g, '[src/');
readmeContent = readmeContent.replace(/build\/commands/g, 'packages/eas-cli/src/commands');
fs.writeFileSync('README.md', readmeContent);
// eslint-disable-next-line no-console
console.log('Patched README path generation');
+27
View File
@@ -0,0 +1,27 @@
import { JSONValue } from '@expo/json-file';
import { HTTPError, RequestError } from 'got/dist/source';
export default class ApiV2Error extends RequestError {
readonly name = 'ApiV2Error';
readonly expoApiV2ErrorCode: string;
readonly expoApiV2ErrorDetails?: JSONValue;
readonly expoApiV2ErrorServerStack?: string;
readonly expoApiV2ErrorMetadata?: object;
constructor(
originalError: HTTPError,
response: {
message: string;
code: string;
stack?: string;
details?: JSONValue;
metadata?: object;
}
) {
super(response.message, originalError, originalError.request);
this.expoApiV2ErrorCode = response.code;
this.expoApiV2ErrorDetails = response.details;
this.expoApiV2ErrorServerStack = response.stack;
this.expoApiV2ErrorMetadata = response.metadata;
}
}
@@ -0,0 +1,53 @@
import assert from 'assert';
import { RequestError } from 'got/dist/source';
import nock from 'nock';
import ApiV2Error from '../ApiV2Error';
import { apiClient, getExpoApiBaseUrl } from '../api';
describe('apiClient', () => {
it('converts Expo APIv2 error to ApiV2Error', async () => {
nock(getExpoApiBaseUrl())
.post('/--/api/v2/test')
.reply(400, {
errors: [
{
message: 'hellomessage',
code: 'TEST_CODE',
stack: 'line 1: hello',
details: { who: 'world' },
metadata: { an: 'object' },
},
],
});
let error: Error | null = null;
try {
await apiClient.post('test');
} catch (e) {
error = e;
}
expect(error).toBeInstanceOf(ApiV2Error);
assert(error instanceof ApiV2Error);
expect(error.message).toEqual('hellomessage');
expect(error.expoApiV2ErrorCode).toEqual('TEST_CODE');
expect(error.expoApiV2ErrorDetails).toEqual({ who: 'world' });
expect(error.expoApiV2ErrorMetadata).toEqual({ an: 'object' });
expect(error.expoApiV2ErrorServerStack).toEqual('line 1: hello');
});
it('does not convert non-APIv2 error to ApiV2Error', async () => {
nock(getExpoApiBaseUrl()).post('/--/api/v2/test').reply(500, 'Something went wrong');
let error: Error | null = null;
try {
await apiClient.post('test');
} catch (e) {
error = e;
}
expect(error).toBeInstanceOf(RequestError);
expect(error).not.toBeInstanceOf(ApiV2Error);
});
});
@@ -0,0 +1,62 @@
import { Actor } from '../user/User';
interface MockProject {
projectRoot: string;
projectTree: Record<string, string>;
appJSON: AppJSON;
packageJSON: PackageJSON;
}
interface PackageJSON {
name: string;
version: string;
description: string;
main: string;
}
interface AppJSON {
expo: {
name: string;
version: string;
slug: string;
sdkVersion: string;
owner?: string;
[key: string]: any;
};
}
function createTestProject(
user: Actor,
appJSONExtraData?: Record<string, object | string>
): MockProject {
const projectRoot = '/test-project';
const packageJSON: PackageJSON = {
name: 'testing123',
version: '0.1.0',
description: 'fake description',
main: 'index.js',
};
const owner = appJSONExtraData?.owner as string | undefined;
const appJSON: AppJSON = {
expo: {
name: 'testing 123',
version: '0.1.0',
slug: 'testing-123',
sdkVersion: '33.0.0',
owner: owner ?? (user.__typename === 'User' ? user.username : undefined),
...appJSONExtraData,
},
};
return {
appJSON,
packageJSON,
projectRoot,
projectTree: {
[projectRoot + '/package.json']: JSON.stringify(packageJSON, null, 2),
[projectRoot + '/app.json']: JSON.stringify(appJSON, null, 2),
},
};
}
export { createTestProject };
+106
View File
@@ -0,0 +1,106 @@
import { Identify } from '@amplitude/identify';
import * as Amplitude from '@amplitude/node';
import os from 'os';
import { v4 as uuidv4 } from 'uuid';
import UserSettings from './user/UserSettings';
/**
* We use require() to exclude package.json from TypeScript's analysis since it lives outside
* the src directory and would change the directory structure of the emitted files
* under the build directory
*/
const packageJSON = require('../package.json');
const PLATFORM_TO_ANALYTICS_PLATFORM: { [platform: string]: string } = {
darwin: 'Mac',
win32: 'Windows',
linux: 'Linux',
};
let client: Amplitude.NodeClient | null = null;
let userIdentifyCalled = false;
let identifyData: {
userId: string;
deviceId: string;
identify: Identify;
} | null = null;
export async function initAsync(): Promise<void> {
if (process.env.DISABLE_EAS_ANALYTICS) {
await UserSettings.setAsync('amplitudeEnabled', false);
}
const amplitudeEnabled = await UserSettings.getAsync('amplitudeEnabled', true);
if (amplitudeEnabled) {
const apiKey =
process.env.EXPO_STAGING || process.env.EXPO_LOCAL
? 'cdebbc678931403439486c4750781544'
: '4ac443afd5073c0df6169291db1d3495';
client = Amplitude.init(apiKey, { retryClass: new Amplitude.OfflineRetryHandler(apiKey) });
}
}
export async function setUserDataAsync(
userId: string,
properties: Record<string, any>
): Promise<void> {
const identify = new Identify();
Object.entries(properties).forEach(([property, value]) => {
identify.set(property, value);
});
const savedDeviceId = await UserSettings.getAsync('amplitudeDeviceId', null);
const deviceId = savedDeviceId ?? uuidv4();
if (!savedDeviceId) {
await UserSettings.setAsync('amplitudeDeviceId', deviceId);
}
identifyData = {
userId,
deviceId,
identify,
};
ensureUserIdentified();
}
export async function flushAsync(): Promise<void> {
if (client) {
await client.flush();
}
}
export function logEvent(name: string, properties: Record<string, any> = {}) {
if (client) {
ensureUserIdentified();
const { userId, deviceId } = identifyData ?? {};
client.logEvent({
event_type: name,
event_properties: properties,
...(userId && { user_id: userId }),
...(deviceId && { device_id: deviceId }),
...getContext(),
});
}
}
function ensureUserIdentified() {
if (client && !userIdentifyCalled && identifyData) {
client.identify(identifyData.userId, identifyData.deviceId, identifyData.identify);
userIdentifyCalled = true;
}
}
function getContext() {
const platform = PLATFORM_TO_ANALYTICS_PLATFORM[os.platform()] || os.platform();
return {
os_name: platform,
os_version: os.release(),
device_brand: platform,
device_model: platform,
source_version: packageJSON?.version,
source: 'eas cli',
};
}
export enum AnalyticsEvent {
ACTION = 'action', // generic event type which is used to determine the 'daily active user' stat, include an `action: eas ${subcommand}` property inside of the event properties object
}
+30 -38
View File
@@ -1,16 +1,7 @@
import { createClient as createUrqlClient } from '@urql/core';
import got, { NormalizedOptions } from 'got';
import fetch from 'node-fetch';
import got, { HTTPError, NormalizedOptions, RequestError } from 'got';
import { getAccessToken, getSessionSecret } from './user/User';
type AccessTokenHeaders = {
authorization: string;
};
type SessionHeaders = {
'expo-session': string;
};
import ApiV2Error from './ApiV2Error';
import { getAccessToken, getSessionSecret } from './user/sessionStorage';
export const apiClient = got.extend({
prefixUrl: getExpoApiBaseUrl() + '/--/api/v2/',
@@ -28,31 +19,22 @@ export const apiClient = got.extend({
}
},
],
},
});
export const graphqlClient = createUrqlClient({
url: getExpoApiBaseUrl() + '/--/graphql',
// @ts-expect-error Type 'typeof fetch' is not assignable to type '(input: RequestInfo, init?: RequestInit | undefined) => Promise<Response>'.
fetch,
fetchOptions: (): { headers?: AccessTokenHeaders | SessionHeaders } => {
const token = getAccessToken();
if (token) {
return {
headers: {
authorization: `Bearer ${token}`,
},
};
}
const sessionSecret = getSessionSecret();
if (sessionSecret) {
return {
headers: {
'expo-session': sessionSecret,
},
};
}
return {};
beforeError: [
(error: RequestError): RequestError => {
if (error instanceof HTTPError) {
let result: { [key: string]: any };
try {
result = JSON.parse(error.response.body as string);
} catch (e2) {
return error;
}
if (result.errors?.length) {
return new ApiV2Error(error, result.errors[0]);
}
}
return error;
},
],
},
});
@@ -60,8 +42,18 @@ export function getExpoApiBaseUrl(): string {
if (process.env.EXPO_STAGING) {
return `https://staging.exp.host`;
} else if (process.env.EXPO_LOCAL) {
return `http://exp.test`;
return `http://127.0.0.1:3000`;
} else {
return `https://exp.host`;
}
}
export function getExpoWebsiteBaseUrl(): string {
if (process.env.EXPO_STAGING) {
return `https://staging.expo.io`;
} else if (process.env.EXPO_LOCAL) {
return `http://expo.test`;
} else {
return `https://expo.io`;
}
}
@@ -0,0 +1,115 @@
import { ExpoConfig } from '@expo/config';
import { AndroidConfig, AndroidManifest } from '@expo/config-plugins';
import fs from 'fs-extra';
import Log from '../../log';
import { getProjectAccountName } from '../../project/projectUtils';
import { ensureLoggedInAsync } from '../../user/actions';
import { ensureValidVersions } from '../utils/updates';
export async function configureUpdatesAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
ensureValidVersions(exp);
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
const buildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(projectDir);
const buildGradleContents = await fs.readFile(buildGradlePath, 'utf8');
if (!AndroidConfig.Updates.isBuildGradleConfigured(projectDir, buildGradleContents)) {
const updatedBuildGradleContents =
AndroidConfig.Updates.ensureBuildGradleContainsConfigurationScript(
projectDir,
buildGradleContents
);
await fs.writeFile(buildGradlePath, updatedBuildGradleContents);
}
const androidManifestPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectDir);
const androidManifest = await getAndroidManifestAsync(projectDir);
if (!AndroidConfig.Updates.isMainApplicationMetaDataSynced(exp, androidManifest, accountName)) {
const result = AndroidConfig.Updates.setUpdatesConfig(exp, androidManifest, accountName);
await AndroidConfig.Manifest.writeAndroidManifestAsync(androidManifestPath, result);
}
}
export async function syncUpdatesConfigurationAsync(
projectDir: string,
exp: ExpoConfig
): Promise<void> {
ensureValidVersions(exp);
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
try {
await ensureUpdatesConfiguredAsync(projectDir, exp);
} catch (error) {
Log.error(
'expo-updates module is not configured. Please run "eas build:configure" first to configure the project'
);
throw error;
}
const androidManifestPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectDir);
let androidManifest = await getAndroidManifestAsync(projectDir);
if (!AndroidConfig.Updates.areVersionsSynced(exp, androidManifest)) {
androidManifest = AndroidConfig.Updates.setVersionsConfig(exp, androidManifest);
await AndroidConfig.Manifest.writeAndroidManifestAsync(androidManifestPath, androidManifest);
}
if (!AndroidConfig.Updates.isMainApplicationMetaDataSynced(exp, androidManifest, accountName)) {
Log.warn(
'Native project configuration is not synced with values present in your app.json, run "eas build:configure" to make sure all values are applied in the native project'
);
}
}
async function ensureUpdatesConfiguredAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
const buildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(projectDir);
const buildGradleContents = await fs.readFile(buildGradlePath, 'utf8');
if (!AndroidConfig.Updates.isBuildGradleConfigured(projectDir, buildGradleContents)) {
const gradleScriptApply = AndroidConfig.Updates.formatApplyLineForBuildGradle(projectDir);
throw new Error(`Missing ${gradleScriptApply} in ${buildGradlePath}`);
}
const androidManifest = await getAndroidManifestAsync(projectDir);
if (!AndroidConfig.Updates.isMainApplicationMetaDataSet(androidManifest)) {
throw new Error('Missing values in AndroidManifest.xml');
}
}
export async function readReleaseChannelSafelyAsync(projectDir: string): Promise<string | null> {
try {
const androidManifest = await getAndroidManifestAsync(projectDir);
return AndroidConfig.Manifest.getMainApplicationMetaDataValue(
androidManifest,
AndroidConfig.Updates.Config.RELEASE_CHANNEL
);
} catch (err) {
return null;
}
}
export async function readChannelSafelyAsync(projectDir: string): Promise<string | null> {
try {
const androidManifest = await getAndroidManifestAsync(projectDir);
const stringifiedRequestHeaders = AndroidConfig.Manifest.getMainApplicationMetaDataValue(
androidManifest,
AndroidConfig.Updates.Config.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY
);
if (!stringifiedRequestHeaders) {
return null;
}
return JSON.parse(stringifiedRequestHeaders)['expo-channel-name'] ?? null;
} catch (err) {
return null;
}
}
async function getAndroidManifestAsync(projectDir: string): Promise<AndroidManifest> {
const androidManifestPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectDir);
if (!androidManifestPath) {
throw new Error(`Could not find AndroidManifest.xml in project directory: "${projectDir}"`);
}
return AndroidConfig.Manifest.readAndroidManifestAsync(androidManifestPath);
}
@@ -0,0 +1,108 @@
import fs from 'fs-extra';
import { vol } from 'memfs';
import os from 'os';
import { readChannelSafelyAsync } from '../UpdatesModule';
jest.mock('fs');
afterAll(() => {
// do not remove the following line
// this fixes a weird error with tempy in @expo/image-utils
fs.removeSync(os.tmpdir());
});
beforeEach(() => {
vol.reset();
// do not remove the following line
// this fixes a weird error with tempy in @expo/image-utils
fs.mkdirpSync(os.tmpdir());
});
const testChannel = 'test-channel';
describe(readChannelSafelyAsync, () => {
it('retrieves the channel when it is defined natively', async () => {
vol.fromJSON(
{
'./android/app/src/main/AndroidManifest.xml': AndroidManifestWithChannel,
},
'/expo-project'
);
const channel = await readChannelSafelyAsync('/expo-project');
expect(channel).toBe(testChannel);
});
it('returns null if the channel is not defined natively', async () => {
vol.fromJSON(
{
'./android/app/src/main/AndroidManifest.xml': AndroidManifestNoChannel,
},
'/expo-project'
);
const channel = await readChannelSafelyAsync('/expo-project');
expect(channel).toBeNull();
});
});
const AndroidManifestWithChannel = `
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.expo.mycoolapp">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="true"
android:theme="@style/AppTheme">
<meta-data android:name="expo.modules.updates.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY" android:value='{"expo-channel-name":"${testChannel}"}'/>
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>
`;
const AndroidManifestNoChannel = `
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.expo.mycoolapp">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>
`;
@@ -0,0 +1,115 @@
import { ExpoConfig } from '@expo/config';
import fs from 'fs-extra';
import { vol } from 'memfs';
import os from 'os';
import { readVersionCodeAsync, readVersionNameAsync } from '../version';
jest.mock('fs');
afterAll(() => {
// do not remove the following line
// this fixes a weird error with tempy in @expo/image-utils
fs.removeSync(os.tmpdir());
});
beforeEach(() => {
vol.reset();
// do not remove the following line
// this fixes a weird error with tempy in @expo/image-utils
fs.mkdirpSync(os.tmpdir());
});
describe(readVersionCodeAsync, () => {
describe('generic project', () => {
it('reads the version code from native code', async () => {
const exp = initGenericProject();
const versionCode = await readVersionCodeAsync('/repo', exp);
expect(versionCode).toBe(123);
});
});
describe('managed project', () => {
it('reads the version code from expo config', async () => {
const exp = initManagedProject();
const versionCode = await readVersionCodeAsync('/repo', exp);
expect(versionCode).toBe(123);
});
});
});
describe(readVersionNameAsync, () => {
describe('generic project', () => {
it('reads the version name from native code', async () => {
const exp = initGenericProject();
const versionName = await readVersionNameAsync('/repo', exp);
expect(versionName).toBe('1.0');
});
});
describe('managed project', () => {
it('reads the version from expo config', async () => {
const exp = initManagedProject();
const versionName = await readVersionNameAsync('/repo', exp);
expect(versionName).toBe('1.0.0');
});
});
});
function initGenericProject(): ExpoConfig {
vol.fromJSON(
{
'./app.json': JSON.stringify({
expo: {
version: '1.0.0',
android: {
versionCode: 1,
},
},
}),
'./android/app/build.gradle': `android {
defaultConfig {
applicationId "com.expo.testapp"
versionCode 123
versionName "1.0"
}
}`,
'./android/app/src/main/AndroidManifest.xml': 'fake',
},
'/repo'
);
const fakeExp: ExpoConfig = {
name: 'myproject',
slug: 'myproject',
version: '1.0.0',
android: {
versionCode: 123,
},
};
return fakeExp;
}
function initManagedProject(): ExpoConfig {
vol.fromJSON(
{
'./app.json': JSON.stringify({
expo: {
version: '1.0.0',
android: {
versionCode: 123,
},
},
}),
},
'/repo'
);
const fakeExp: ExpoConfig = {
name: 'myproject',
slug: 'myproject',
version: '1.0.0',
android: {
versionCode: 123,
},
};
return fakeExp;
}
+114
View File
@@ -0,0 +1,114 @@
import { Android, Metadata, Workflow } from '@expo/eas-build-job';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
import AndroidCredentialsProvider, {
AndroidCredentials,
} from '../../credentials/android/AndroidCredentialsProvider';
import { createCredentialsContextAsync } from '../../credentials/context';
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
import Log from '../../log';
import {
ensureApplicationIdIsDefinedForManagedProjectAsync,
getApplicationIdAsync,
} from '../../project/android/applicationId';
import { toggleConfirmAsync } from '../../prompts';
import { findAccountByName } from '../../user/Account';
import {
BuildRequestSender,
CredentialsResult,
prepareBuildRequestForPlatformAsync,
} from '../build';
import { BuildContext } from '../context';
import { transformMetadata } from '../graphql';
import { Platform } from '../types';
import { logCredentialsSource } from '../utils/credentials';
import { validateAndSyncProjectConfigurationAsync } from './configure';
import { transformJob } from './graphql';
import { prepareJobAsync } from './prepareJob';
export async function prepareAndroidBuildAsync(
ctx: BuildContext<Platform.ANDROID>
): Promise<BuildRequestSender> {
const { buildProfile } = ctx;
if (buildProfile.distribution === 'internal' && buildProfile.gradleCommand?.match(/bundle/)) {
Log.addNewLineIfNone();
Log.warn(
`You're building your Android app for internal distribution. However, we've detected that the Gradle command you defined (${chalk.underline(
buildProfile.gradleCommand
)}) includes string 'bundle'.
This means that it will most likely produce an AAB and you will not be able to install it on your Android devices straight from the Expo website.`
);
Log.newLine();
const confirmed = await toggleConfirmAsync({ message: 'Would you like to proceed?' });
if (!confirmed) {
Log.error('Please update eas.json and come back again.');
process.exit(1);
}
}
if (ctx.workflow === Workflow.MANAGED) {
await ensureApplicationIdIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp);
}
return await prepareBuildRequestForPlatformAsync({
ctx,
ensureCredentialsAsync: ensureAndroidCredentialsAsync,
ensureProjectConfiguredAsync: async () => {
if (ctx.workflow === Workflow.GENERIC) {
await validateAndSyncProjectConfigurationAsync(ctx.projectDir, ctx.exp);
}
},
getMetadataContext: () => ({}),
prepareJobAsync,
sendBuildRequestAsync: async (
appId: string,
job: Android.Job,
metadata: Metadata
): Promise<BuildResult> => {
const graphqlMetadata = transformMetadata(metadata);
const graphqlJob = transformJob(job);
return await BuildMutation.createAndroidBuildAsync({
appId,
job: graphqlJob,
metadata: graphqlMetadata,
});
},
});
}
function shouldProvideCredentials(ctx: BuildContext<Platform.ANDROID>): boolean {
return !ctx.buildProfile.withoutCredentials;
}
async function ensureAndroidCredentialsAsync(
ctx: BuildContext<Platform.ANDROID>
): Promise<CredentialsResult<AndroidCredentials> | undefined> {
if (!shouldProvideCredentials(ctx)) {
return;
}
const androidApplicationIdentifier = await getApplicationIdAsync(ctx.projectDir, ctx.exp);
const provider = new AndroidCredentialsProvider(
await createCredentialsContextAsync(ctx.projectDir, {
exp: ctx.exp,
nonInteractive: ctx.nonInteractive,
}),
{
app: {
account: nullthrows(
findAccountByName(ctx.user.accounts, ctx.accountName),
`You do not have access to account: ${ctx.accountName}`
),
projectName: ctx.projectName,
androidApplicationIdentifier,
},
}
);
const { credentialsSource } = ctx.buildProfile;
logCredentialsSource(credentialsSource, Platform.ANDROID);
return {
credentials: await provider.getCredentialsAsync(credentialsSource),
source: credentialsSource,
};
}
@@ -0,0 +1,45 @@
import { ExpoConfig } from '@expo/config';
import { AndroidConfig } from '@expo/config-plugins';
import Log from '../../log';
import {
ensureApplicationIdIsDefinedForManagedProjectAsync,
warnIfAndroidPackageDefinedInAppConfigForGenericProject,
} from '../../project/android/applicationId';
import vcs from '../../vcs';
import { ConfigureContext } from '../context';
import { isExpoUpdatesInstalled } from '../utils/updates';
import { configureUpdatesAsync, syncUpdatesConfigurationAsync } from './UpdatesModule';
export async function configureAndroidAsync(ctx: ConfigureContext): Promise<void> {
if (!ctx.hasAndroidNativeProject) {
await ensureApplicationIdIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp);
return;
}
warnIfAndroidPackageDefinedInAppConfigForGenericProject(ctx.projectDir, ctx.exp);
await AndroidConfig.EasBuild.configureEasBuildAsync(ctx.projectDir);
const easGradlePath = AndroidConfig.EasBuild.getEasBuildGradlePath(ctx.projectDir);
await vcs.trackFileAsync(easGradlePath);
if (isExpoUpdatesInstalled(ctx.projectDir)) {
await configureUpdatesAsync(ctx.projectDir, ctx.exp);
}
Log.withTick('Android project configured');
}
export async function validateAndSyncProjectConfigurationAsync(
projectDir: string,
exp: ExpoConfig
): Promise<void> {
if (!(await AndroidConfig.EasBuild.isEasBuildGradleConfiguredAsync(projectDir))) {
throw new Error(
'Project is not configured. Please run "eas build:configure" to configure the project.'
);
}
if (isExpoUpdatesInstalled(projectDir)) {
await syncUpdatesConfigurationAsync(projectDir, exp);
}
}
@@ -0,0 +1,31 @@
import { Android } from '@expo/eas-build-job';
import { AndroidBuildType, AndroidJobInput } from '../../graphql/generated';
import { transformProjectArchive, transformWorkflow } from '../graphql';
export function transformJob(job: Android.Job): AndroidJobInput {
return {
type: transformWorkflow(job.type),
projectArchive: transformProjectArchive(job.projectArchive),
projectRootDirectory: job.projectRootDirectory,
releaseChannel: job.releaseChannel,
updates: job.updates,
secrets: job.secrets,
builderEnvironment: job.builderEnvironment,
cache: job.cache,
gradleCommand: job.gradleCommand,
artifactPath: job.artifactPath,
username: job.username,
buildType: job.buildType && transformBuildType(job.buildType),
};
}
function transformBuildType(buildType: Android.BuildType): AndroidBuildType {
if (buildType === Android.BuildType.APK) {
return AndroidBuildType.Apk;
} else if (buildType === Android.BuildType.APP_BUNDLE) {
return AndroidBuildType.AppBundle;
} else {
return AndroidBuildType.DevelopmentClient;
}
}
@@ -0,0 +1,81 @@
import { Android, ArchiveSource, Job, sanitizeJob } from '@expo/eas-build-job';
import { AndroidBuildProfile } from '@expo/eas-json';
import path from 'path';
import { AndroidCredentials } from '../../credentials/android/AndroidCredentialsProvider';
import { getUsername } from '../../project/projectUtils';
import { ensureLoggedInAsync } from '../../user/actions';
import vcs from '../../vcs';
import { BuildContext } from '../context';
import { Platform } from '../types';
interface JobData {
projectArchive: ArchiveSource;
credentials?: AndroidCredentials;
}
const cacheDefaults = {
disabled: false,
customPaths: [],
cacheDefaultPaths: true,
};
export async function prepareJobAsync(
ctx: BuildContext<Platform.ANDROID>,
jobData: JobData
): Promise<Job> {
const username = getUsername(ctx.exp, await ensureLoggedInAsync());
const buildProfile: AndroidBuildProfile = ctx.buildProfile;
const projectRootDirectory = path.relative(await vcs.getRootPathAsync(), ctx.projectDir) || '.';
const { credentials } = jobData;
const buildCredentials = credentials
? {
buildCredentials: {
keystore: {
dataBase64: credentials.keystore.keystore,
keystorePassword: credentials.keystore.keystorePassword,
keyAlias: credentials.keystore.keyAlias,
keyPassword: credentials.keystore.keyPassword,
},
},
}
: {};
let buildType = ctx.buildProfile.buildType;
if (!buildType && !buildProfile.gradleCommand && ctx.buildProfile.distribution === 'internal') {
buildType = Android.BuildType.APK;
}
const job: Android.Job = {
type: ctx.workflow,
platform: Platform.ANDROID,
projectRootDirectory,
projectArchive: jobData.projectArchive,
builderEnvironment: {
image: buildProfile.image ?? 'default',
node: buildProfile.node,
yarn: buildProfile.yarn,
ndk: buildProfile.ndk,
expoCli: buildProfile.expoCli,
env: buildProfile.env,
},
cache: {
...cacheDefaults,
...buildProfile.cache,
clear: ctx.clearCache,
},
secrets: {
...buildCredentials,
},
releaseChannel: ctx.buildProfile.releaseChannel,
updates: { channel: ctx.buildProfile.channel },
gradleCommand: buildProfile.gradleCommand,
artifactPath: buildProfile.artifactPath,
username,
buildType: buildProfile.developmentClient ? Android.BuildType.DEVELOPMENT_CLIENT : buildType,
};
return sanitizeJob(job);
}
@@ -0,0 +1,50 @@
import { ExpoConfig } from '@expo/config';
import { AndroidConfig } from '@expo/config-plugins';
import { Platform, Workflow } from '@expo/eas-build-job';
import fs from 'fs-extra';
import { resolveWorkflowAsync } from '../../project/workflow';
export async function readVersionCodeAsync(
projectDir: string,
exp: ExpoConfig
): Promise<number | undefined> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
if (workflow === Workflow.GENERIC) {
const buildGradle = readBuildGradle(projectDir);
const matchResult = buildGradle?.match(/versionCode (.*)/);
if (matchResult) {
return Number(matchResult[1]);
} else {
return undefined;
}
} else {
return AndroidConfig.Version.getVersionCode(exp) ?? 1;
}
}
export async function readVersionNameAsync(
projectDir: string,
exp: ExpoConfig
): Promise<string | undefined> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
if (workflow === Workflow.GENERIC) {
const buildGradle = readBuildGradle(projectDir);
const matchResult = buildGradle?.match(/versionName ["'](.*)["']/);
if (matchResult) {
return matchResult[1];
} else {
return undefined;
}
} else {
return exp.version;
}
}
function readBuildGradle(projectDir: string): string | undefined {
const buildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(projectDir);
if (!fs.pathExistsSync(buildGradlePath)) {
return undefined;
}
return fs.readFileSync(buildGradlePath, 'utf8');
}
+375
View File
@@ -0,0 +1,375 @@
import { ArchiveSource, ArchiveSourceType, Job, Metadata } from '@expo/eas-build-job';
import { CredentialsSource } from '@expo/eas-json';
import chalk from 'chalk';
import fs from 'fs-extra';
import nullthrows from 'nullthrows';
import ora from 'ora';
import { BuildFragment, BuildStatus, UploadSessionType } from '../graphql/generated';
import { BuildResult } from '../graphql/mutations/BuildMutation';
import { BuildQuery } from '../graphql/queries/BuildQuery';
import Log from '../log';
import { promptAsync } from '../prompts';
import { uploadAsync } from '../uploads';
import { formatBytes } from '../utils/files';
import { createProgressTracker } from '../utils/progress';
import { sleep } from '../utils/promise';
import vcs from '../vcs';
import { requestedPlatformDisplayNames } from './constants';
import { BuildContext } from './context';
import { runLocalBuildAsync } from './local';
import { MetadataContext, collectMetadata } from './metadata';
import { Platform, TrackingContext } from './types';
import Analytics, { Event } from './utils/analytics';
import { printDeprecationWarnings } from './utils/printBuildInfo';
import { commitPromptAsync, makeProjectTarballAsync } from './utils/repository';
export interface CredentialsResult<Credentials> {
source: CredentialsSource.LOCAL | CredentialsSource.REMOTE;
credentials: Credentials;
}
export interface JobData<Credentials> {
credentials?: Credentials;
projectArchive: ArchiveSource;
}
interface Builder<TPlatform extends Platform, Credentials, TJob extends Job> {
ctx: BuildContext<TPlatform>;
ensureCredentialsAsync(
ctx: BuildContext<TPlatform>
): Promise<CredentialsResult<Credentials> | undefined>;
ensureProjectConfiguredAsync(ctx: BuildContext<TPlatform>): Promise<void>;
getMetadataContext: () => MetadataContext<TPlatform>;
prepareJobAsync(ctx: BuildContext<TPlatform>, jobData: JobData<Credentials>): Promise<Job>;
sendBuildRequestAsync(appId: string, job: TJob, metadata: Metadata): Promise<BuildResult>;
}
export type BuildRequestSender = () => Promise<BuildFragment | undefined>;
export async function prepareBuildRequestForPlatformAsync<
TPlatform extends Platform,
Credentials,
TJob extends Job
>(builder: Builder<TPlatform, Credentials, TJob>): Promise<BuildRequestSender> {
const { ctx } = builder;
const credentialsResult = await withAnalyticsAsync(
async () => await builder.ensureCredentialsAsync(ctx),
{
successEvent: Event.GATHER_CREDENTIALS_SUCCESS,
failureEvent: Event.GATHER_CREDENTIALS_FAIL,
trackingCtx: ctx.trackingCtx,
}
);
if (!ctx.skipProjectConfiguration) {
await withAnalyticsAsync(async () => await builder.ensureProjectConfiguredAsync(ctx), {
successEvent: Event.CONFIGURE_PROJECT_SUCCESS,
failureEvent: Event.CONFIGURE_PROJECT_FAIL,
trackingCtx: ctx.trackingCtx,
});
}
if (await vcs.hasUncommittedChangesAsync()) {
Log.addNewLineIfNone();
await reviewAndCommitChangesAsync(
`[EAS Build] Run EAS Build for ${requestedPlatformDisplayNames[ctx.platform as Platform]}`,
{ nonInteractive: ctx.nonInteractive }
);
}
const projectArchive = ctx.local
? ({
type: ArchiveSourceType.PATH,
path: (await makeProjectTarballAsync()).path,
} as const)
: ({
type: ArchiveSourceType.S3,
bucketKey: await uploadProjectAsync(ctx),
} as const);
const metadataContext = builder.getMetadataContext();
const metadata = await collectMetadata(ctx, metadataContext);
const job = await builder.prepareJobAsync(ctx, {
projectArchive,
credentials: credentialsResult?.credentials,
});
return async () => {
if (ctx.local) {
await runLocalBuildAsync(job);
return undefined;
} else {
try {
return await sendBuildRequestAsync(builder, job, metadata);
} catch (error) {
if (error?.graphQLErrors?.[0]?.extensions?.errorCode === 'TURTLE_DEPRECATED_JOB_FORMAT') {
Log.error('EAS Build API has changed, please 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(
'EAS Build is down for maintenance, please try again later. Check 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, please try again later. Check https://status.expo.dev/ for updates.'
);
throw new Error('Build request failed.');
} else if (
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_TOO_MANY_PENDING_BUILDS'
) {
Log.error(
`You have already reached the maximum number of pending ${
requestedPlatformDisplayNames[job.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, please report the issue.'
);
}
throw error;
}
}
};
}
async function uploadProjectAsync<TPlatform extends Platform>(
ctx: BuildContext<TPlatform>
): Promise<string> {
let projectTarballPath;
try {
return await withAnalyticsAsync(
async () => {
const projectTarball = await makeProjectTarballAsync();
projectTarballPath = projectTarball.path;
const { bucketKey } = await uploadAsync(
UploadSessionType.EasBuildProjectSources,
projectTarball.path,
createProgressTracker({
total: projectTarball.size,
message: ratio =>
`Uploading to EAS Build (${formatBytes(projectTarball.size * ratio)} / ${formatBytes(
projectTarball.size
)})`,
completedMessage: `Uploaded to EAS`,
})
);
return bucketKey;
},
{
successEvent: Event.PROJECT_UPLOAD_SUCCESS,
failureEvent: Event.PROJECT_UPLOAD_FAIL,
trackingCtx: ctx.trackingCtx,
}
);
} finally {
if (projectTarballPath) {
await fs.remove(projectTarballPath);
}
}
}
async function sendBuildRequestAsync<TPlatform extends Platform, Credentials, TJob extends Job>(
builder: Builder<TPlatform, Credentials, TJob>,
job: TJob,
metadata: Metadata
): Promise<BuildFragment> {
const { ctx } = builder;
return await withAnalyticsAsync(
async () => {
if (Log.isDebug) {
Log.log(`Starting ${requestedPlatformDisplayNames[job.platform]} build`);
}
const { build, deprecationInfo } = await builder.sendBuildRequestAsync(
ctx.projectId,
job,
metadata
);
printDeprecationWarnings(deprecationInfo);
return build;
},
{
successEvent: Event.BUILD_REQUEST_SUCCESS,
failureEvent: Event.BUILD_REQUEST_FAIL,
trackingCtx: ctx.trackingCtx,
}
);
}
async function withAnalyticsAsync<Result>(
fn: () => Promise<Result>,
analytics: {
successEvent: Event;
failureEvent: Event;
trackingCtx: TrackingContext;
}
): Promise<Result> {
try {
const result = await fn();
Analytics.logEvent(analytics.successEvent, analytics.trackingCtx);
return result;
} catch (error) {
Analytics.logEvent(analytics.failureEvent, {
...analytics.trackingCtx,
reason: error.message,
});
throw error;
}
}
enum ShouldCommitChanges {
Yes,
ShowDiffFirst,
Abort,
}
async function reviewAndCommitChangesAsync(
initialCommitMessage: string,
{ nonInteractive, askedFirstTime = true }: { nonInteractive: boolean; askedFirstTime?: boolean }
): Promise<void> {
if (process.env.EAS_BUILD_AUTOCOMMIT) {
await vcs.commitAsync({ commitMessage: initialCommitMessage, commitAllFiles: false });
Log.withTick('Committed changes.');
return;
}
if (nonInteractive) {
throw new Error(
'Cannot commit changes when --non-interactive is specified. Run the command in interactive mode or set EAS_BUILD_AUTOCOMMIT=1 in your environment.'
);
}
const { selected } = await promptAsync({
type: 'select',
name: 'selected',
message: 'Can we commit these changes to git for you?',
choices: [
{ title: 'Yes', value: ShouldCommitChanges.Yes },
...(askedFirstTime
? [{ title: 'Show the diff and ask me again', value: ShouldCommitChanges.ShowDiffFirst }]
: []),
{
title: 'Abort build process',
value: ShouldCommitChanges.Abort,
},
],
});
if (selected === ShouldCommitChanges.Abort) {
throw new Error(
"Aborting, run the command again once you're ready. Make sure to commit any changes you've made."
);
} else if (selected === ShouldCommitChanges.Yes) {
await commitPromptAsync({ initialCommitMessage });
Log.withTick('Committed changes.');
} else if (selected === ShouldCommitChanges.ShowDiffFirst) {
await vcs.showDiffAsync();
await reviewAndCommitChangesAsync(initialCommitMessage, {
nonInteractive,
askedFirstTime: false,
});
}
}
export async function waitForBuildEndAsync(
buildIds: string[],
{ timeoutSec = 3600, intervalSec = 30 } = {}
): Promise<(BuildFragment | null)[]> {
Log.log(
`Waiting for build${buildIds.length > 1 ? 's' : ''} to complete. You can press Ctrl+C to exit.`
);
const spinner = ora().start();
let time = new Date().getTime();
const endTime = time + timeoutSec * 1000;
while (time <= endTime) {
const builds: (BuildFragment | null)[] = await Promise.all(
buildIds.map(async buildId => {
try {
return await BuildQuery.byIdAsync(buildId, { useCache: false });
} catch (err) {
return null;
}
})
);
if (builds.length === 1) {
const build = nullthrows(builds[0]);
switch (build.status) {
case BuildStatus.Finished:
spinner.succeed('Build finished');
return builds;
case BuildStatus.New:
spinner.text = 'Build created';
break;
case BuildStatus.InQueue:
spinner.text = 'Build queued...';
break;
case BuildStatus.Canceled:
spinner.text = 'Build canceled';
spinner.stopAndPersist();
return builds;
case BuildStatus.InProgress:
spinner.text = 'Build in progress...';
break;
case BuildStatus.Errored:
spinner.fail('Build failed');
if (build.error) {
return builds;
} else {
throw new Error(`Standalone build failed!`);
}
default:
spinner.warn('Unknown status.');
throw new Error(`Unknown status: ${builds} - aborting!`);
}
} else {
if (builds.filter(build => build?.status === BuildStatus.Finished).length === builds.length) {
spinner.succeed('All builds have finished');
return builds;
} else if (
builds.filter(build =>
build?.status
? [BuildStatus.Finished, BuildStatus.Errored, BuildStatus.Canceled].includes(
build.status
)
: false
).length === builds.length
) {
spinner.fail('Some of the builds were canceled or failed.');
return builds;
} else {
const newBuilds = builds.filter(build => build?.status === BuildStatus.New).length;
const inQueue = builds.filter(build => build?.status === BuildStatus.InQueue).length;
const inProgress = builds.filter(build => build?.status === BuildStatus.InProgress).length;
const errored = builds.filter(build => build?.status === BuildStatus.Errored).length;
const finished = builds.filter(build => build?.status === BuildStatus.Finished).length;
const canceled = builds.filter(build => build?.status === BuildStatus.Canceled).length;
const unknownState = builds.length - newBuilds - inQueue - inProgress - errored - finished;
spinner.text = [
newBuilds && `Builds created: ${newBuilds}`,
inQueue && `Builds in queue: ${inQueue}`,
inProgress && `Builds in progress: ${inProgress}`,
canceled && `Builds canceled: ${canceled}`,
errored && chalk.red(`Builds failed: ${errored}`),
finished && chalk.green(`Builds finished: ${finished}`),
unknownState && chalk.red(`Builds in unknown state: ${unknownState}`),
]
.filter(i => i)
.join('\t');
}
}
time = new Date().getTime();
await sleep(intervalSec * 1000);
}
spinner.warn('Timed out');
throw new Error(
'Timeout reached! It is taking longer than expected to finish the build, aborting...'
);
}
+173
View File
@@ -0,0 +1,173 @@
import { getConfig } from '@expo/config';
import { Platform, Workflow } from '@expo/eas-build-job';
import { EasJsonReader } from '@expo/eas-json';
import { error } from '@oclif/errors';
import chalk from 'chalk';
import fs from 'fs-extra';
import Log, { learnMore } from '../log';
import { resolveWorkflowAsync } from '../project/workflow';
import { confirmAsync, promptAsync } from '../prompts';
import { ensureLoggedInAsync } from '../user/actions';
import vcs from '../vcs';
import { configureAndroidAsync } from './android/configure';
import { ConfigureContext } from './context';
import { configureIosAsync } from './ios/configure';
import { RequestedPlatform } from './types';
import { commitPromptAsync, maybeBailOnRepoStatusAsync } from './utils/repository';
const configureCommitMessage = {
[RequestedPlatform.Android]: 'Configure EAS Build for Android',
[RequestedPlatform.Ios]: 'Configure EAS Build for iOS',
[RequestedPlatform.All]: 'Configure EAS Build',
};
export async function ensureProjectConfiguredAsync(
projectDir: string,
requestedPlatform: RequestedPlatform
): Promise<void> {
if (await fs.pathExists(EasJsonReader.formatEasJsonPath(projectDir))) {
return;
}
const message = 'This project is not configured to build with EAS. Set it up now?';
const confirm = await confirmAsync({ message });
if (confirm) {
await configureAsync({
projectDir,
platform: requestedPlatform,
});
if (await vcs.hasUncommittedChangesAsync()) {
error(
'Build process requires clean working tree, please commit all your changes and run `eas build` again',
{ exit: 1 }
);
}
} else {
error(
`Aborting, please run ${chalk.bold('eas build:configure')} or create eas.json (${learnMore(
'https://docs.expo.io/build/eas-json'
)})`,
{ exit: 1 }
);
}
}
export async function configureAsync(options: {
platform: RequestedPlatform;
projectDir: string;
}): Promise<void> {
await vcs.ensureRepoExistsAsync();
await maybeBailOnRepoStatusAsync();
const { exp } = getConfig(options.projectDir, { skipSDKVersionRequirement: true });
const ctx: ConfigureContext = {
user: await ensureLoggedInAsync(),
projectDir: options.projectDir,
exp,
requestedPlatform: options.platform,
shouldConfigureAndroid: [RequestedPlatform.All, RequestedPlatform.Android].includes(
options.platform
),
shouldConfigureIos: [RequestedPlatform.All, RequestedPlatform.Ios].includes(options.platform),
hasAndroidNativeProject:
(await resolveWorkflowAsync(options.projectDir, Platform.ANDROID)) === Workflow.GENERIC,
hasIosNativeProject:
(await resolveWorkflowAsync(options.projectDir, Platform.IOS)) === Workflow.GENERIC,
};
Log.newLine();
await ensureEasJsonExistsAsync(ctx);
if (ctx.shouldConfigureAndroid) {
await configureAndroidAsync(ctx);
}
if (ctx.shouldConfigureIos) {
await configureIosAsync(ctx);
}
if (await vcs.hasUncommittedChangesAsync()) {
Log.newLine();
await reviewAndCommitChangesAsync(configureCommitMessage[options.platform]);
} else {
Log.newLine();
Log.withTick('No changes were necessary, the project is already configured correctly.');
}
}
const MANAGED_DEFAULTS = {
release: {},
development: {
developmentClient: true,
distribution: 'internal',
},
};
const GENERIC_DEFAULTS = {
release: {},
development: {
distribution: 'internal',
android: {
gradleCommand: ':app:assembleDebug',
},
ios: {
buildConfiguration: 'Debug',
},
},
};
export async function ensureEasJsonExistsAsync(ctx: ConfigureContext): Promise<void> {
const easJsonPath = EasJsonReader.formatEasJsonPath(ctx.projectDir);
if (await fs.pathExists(easJsonPath)) {
const reader = new EasJsonReader(ctx.projectDir);
await reader.readAndValidateAsync();
Log.withTick('Validated eas.json');
return;
}
const easJson =
ctx.hasAndroidNativeProject && ctx.hasIosNativeProject
? { build: GENERIC_DEFAULTS }
: { build: MANAGED_DEFAULTS };
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
await vcs.trackFileAsync(easJsonPath);
Log.withTick('Generated eas.json');
}
enum ShouldCommitChanges {
Yes,
ShowDiffFirst,
Skip,
}
async function reviewAndCommitChangesAsync(
initialCommitMessage: string,
askedFirstTime: boolean = true
): Promise<void> {
const { selected } = await promptAsync({
type: 'select',
name: 'selected',
message: 'Can we commit these changes to git for you?',
choices: [
{ title: 'Yes', value: ShouldCommitChanges.Yes },
...(askedFirstTime
? [{ title: 'Show the diff and ask me again', value: ShouldCommitChanges.ShowDiffFirst }]
: []),
{
title: 'Skip committing changes, I will do it later on my own',
value: ShouldCommitChanges.Skip,
},
],
});
if (selected === ShouldCommitChanges.Yes) {
await commitPromptAsync({ initialCommitMessage });
Log.withTick('Committed changes');
} else if (selected === ShouldCommitChanges.ShowDiffFirst) {
await vcs.showDiffAsync();
await reviewAndCommitChangesAsync(initialCommitMessage, false);
}
}
+18
View File
@@ -0,0 +1,18 @@
import { AppPlatform } from '../graphql/generated';
import { RequestedPlatform } from './types';
export const requestedPlatformDisplayNames: Record<RequestedPlatform, string> = {
[RequestedPlatform.Ios]: 'iOS',
[RequestedPlatform.Android]: 'Android',
[RequestedPlatform.All]: 'Android and iOS',
};
export const appPlatformDisplayNames: Record<AppPlatform, string> = {
[AppPlatform.Android]: 'Android',
[AppPlatform.Ios]: 'iOS',
};
export const appPlatformEmojis = {
[AppPlatform.Ios]: '🍎',
[AppPlatform.Android]: '🤖',
};
+143
View File
@@ -0,0 +1,143 @@
import { ExpoConfig } from '@expo/config';
import { Workflow } from '@expo/eas-build-job';
import { AndroidBuildProfile, BuildProfile, IosBuildProfile } from '@expo/eas-json';
import JsonFile from '@expo/json-file';
import resolveFrom from 'resolve-from';
import { v4 as uuidv4 } from 'uuid';
import { getExpoConfig } from '../project/expoConfig';
import { getProjectAccountName, getProjectIdAsync } from '../project/projectUtils';
import { resolveWorkflowAsync } from '../project/workflow';
import { findAccountByName } from '../user/Account';
import { Actor } from '../user/User';
import { ensureLoggedInAsync } from '../user/actions';
import { Platform, RequestedPlatform, TrackingContext } from './types';
import Analytics, { Event } from './utils/analytics';
export interface ConfigureContext {
user: Actor;
projectDir: string;
exp: ExpoConfig;
requestedPlatform: RequestedPlatform;
shouldConfigureAndroid: boolean;
shouldConfigureIos: boolean;
hasAndroidNativeProject: boolean;
hasIosNativeProject: boolean;
}
export interface BuildContext<T extends Platform> {
accountName: string;
buildProfile: BuildProfile<T>;
buildProfileName: string;
clearCache: boolean;
exp: ExpoConfig;
local: boolean;
nonInteractive: boolean;
platform: T;
projectDir: string;
projectId: string;
projectName: string;
skipProjectConfiguration: boolean;
trackingCtx: TrackingContext;
user: Actor;
workflow: Workflow;
}
export async function createBuildContextAsync<T extends Platform>({
buildProfileName,
buildProfile,
clearCache = false,
local,
nonInteractive = false,
platform,
projectDir,
skipProjectConfiguration = false,
}: {
buildProfileName: string;
buildProfile: BuildProfile<T>;
clearCache: boolean;
local: boolean;
nonInteractive: boolean;
platform: T;
projectDir: string;
skipProjectConfiguration: boolean;
}): Promise<BuildContext<T>> {
const exp = getExpoConfig(projectDir, { env: buildProfile.env });
const user = await ensureLoggedInAsync();
const accountName = getProjectAccountName(exp, user);
const projectName = exp.slug;
const projectId = await getProjectIdAsync(exp, { env: buildProfile.env });
const workflow = await resolveWorkflowAsync(projectDir, platform);
const accountId = findAccountByName(user.accounts, accountName)?.id;
const devClientProperties = getDevClientEventProperties({
platform,
projectDir,
buildProfile,
});
const trackingCtx = {
tracking_id: uuidv4(),
platform,
...(accountId && { account_id: accountId }),
account_name: accountName,
project_id: projectId,
project_type: workflow,
...devClientProperties,
};
Analytics.logEvent(Event.BUILD_COMMAND, trackingCtx);
return {
accountName,
buildProfile,
buildProfileName,
clearCache,
exp,
local,
nonInteractive,
platform,
projectDir,
projectId,
projectName,
skipProjectConfiguration,
trackingCtx,
user,
workflow,
};
}
function getDevClientEventProperties({
platform,
projectDir,
buildProfile,
}: {
platform: Platform;
projectDir: string;
buildProfile: AndroidBuildProfile | IosBuildProfile;
}): Partial<TrackingContext> {
let includesDevClient;
const version = tryGetDevClientVersion(projectDir);
if (platform === Platform.ANDROID && 'gradleCommand' in buildProfile) {
includesDevClient = Boolean(version && buildProfile.gradleCommand?.includes('Debug'));
} else if (platform === Platform.IOS && 'buildConfiguration' in buildProfile) {
includesDevClient = Boolean(version && buildProfile.buildConfiguration === 'Debug');
} else if (buildProfile.developmentClient) {
includesDevClient = true;
} else {
includesDevClient = false;
}
if (version) {
return { dev_client: includesDevClient, dev_client_version: version };
} else {
return { dev_client: includesDevClient };
}
}
function tryGetDevClientVersion(projectDir: string): string | null {
try {
const pkg = JsonFile.read(resolveFrom(projectDir, 'expo-dev-client/package.json'));
return pkg.version?.toString() ?? null;
} catch {
return null;
}
}
+78
View File
@@ -0,0 +1,78 @@
import { ArchiveSource, ArchiveSourceType, Metadata, Workflow } from '@expo/eas-build-job';
import {
BuildCredentialsSource,
BuildIosEnterpriseProvisioning,
BuildMetadataInput,
BuildWorkflow,
DistributionType,
ProjectArchiveSourceInput,
ProjectArchiveSourceType,
} from '../graphql/generated';
export function transformProjectArchive(archiveSource: ArchiveSource): ProjectArchiveSourceInput {
if (archiveSource.type === ArchiveSourceType.S3) {
return {
type: ProjectArchiveSourceType.S3,
bucketKey: archiveSource.bucketKey,
};
} else if (archiveSource.type === ArchiveSourceType.URL) {
return {
type: ProjectArchiveSourceType.Url,
url: archiveSource.url,
};
} else {
throw new Error(`Unsupported project archive source type: '${archiveSource.type}'`);
}
}
export function transformMetadata(metadata: Metadata): BuildMetadataInput {
return {
...metadata,
credentialsSource:
metadata.credentialsSource && transformCredentialsSource(metadata.credentialsSource),
distribution: metadata.distribution && transformDistribution(metadata.distribution),
workflow: metadata.workflow && transformWorkflow(metadata.workflow),
iosEnterpriseProvisioning:
metadata.iosEnterpriseProvisioning &&
transformIosEnterpriseProvisioning(metadata.iosEnterpriseProvisioning),
};
}
function transformCredentialsSource(
credentialsSource: Metadata['credentialsSource']
): BuildCredentialsSource {
if (credentialsSource === 'local') {
return BuildCredentialsSource.Local;
} else {
return BuildCredentialsSource.Remote;
}
}
function transformDistribution(distribution: Metadata['distribution']): DistributionType {
if (distribution === 'internal') {
return DistributionType.Internal;
} else if (distribution === 'simulator') {
return DistributionType.Simulator;
} else {
return DistributionType.Store;
}
}
export function transformWorkflow(workflow: Workflow): BuildWorkflow {
if (workflow === Workflow.GENERIC) {
return BuildWorkflow.Generic;
} else {
return BuildWorkflow.Managed;
}
}
export function transformIosEnterpriseProvisioning(
enterpriseProvisioning: Metadata['iosEnterpriseProvisioning']
): BuildIosEnterpriseProvisioning {
if (enterpriseProvisioning === 'adhoc') {
return BuildIosEnterpriseProvisioning.Adhoc;
} else {
return BuildIosEnterpriseProvisioning.Universal;
}
}
@@ -0,0 +1,107 @@
import { ExpoConfig } from '@expo/config';
import { IOSConfig } from '@expo/config-plugins';
import fs from 'fs-extra';
import Log from '../../log';
import { getProjectAccountName } from '../../project/projectUtils';
import { ensureLoggedInAsync } from '../../user/actions';
import vcs from '../../vcs';
import { ensureValidVersions } from '../utils/updates';
import { readPlistAsync, writePlistAsync } from './plist';
export async function configureUpdatesAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
ensureValidVersions(exp);
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
let xcodeProject = IOSConfig.XcodeUtils.getPbxproj(projectDir);
if (!IOSConfig.Updates.isShellScriptBuildPhaseConfigured(projectDir, xcodeProject)) {
xcodeProject = IOSConfig.Updates.ensureBundleReactNativePhaseContainsConfigurationScript(
projectDir,
xcodeProject
);
await fs.writeFile(IOSConfig.Paths.getPBXProjectPath(projectDir), xcodeProject.writeSync());
}
let expoPlist = await readExpoPlistAsync(projectDir);
if (!IOSConfig.Updates.isPlistConfigurationSynced(exp, expoPlist, accountName)) {
expoPlist = IOSConfig.Updates.setUpdatesConfig(exp, expoPlist, accountName);
await writeExpoPlistAsync(projectDir, expoPlist);
}
// TODO: ensure ExpoPlist in pbxproj
}
export async function syncUpdatesConfigurationAsync(
projectDir: string,
exp: ExpoConfig
): Promise<void> {
ensureValidVersions(exp);
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
try {
await ensureUpdatesConfiguredAsync(projectDir, exp);
} catch (error) {
Log.error(
'expo-updates module is not configured. Please run "eas build:configure" first to configure the project'
);
throw error;
}
let expoPlist = await readExpoPlistAsync(projectDir);
if (!IOSConfig.Updates.isPlistVersionConfigurationSynced(exp, expoPlist)) {
expoPlist = IOSConfig.Updates.setVersionsConfig(exp, expoPlist);
await writeExpoPlistAsync(projectDir, expoPlist);
}
if (!IOSConfig.Updates.isPlistConfigurationSynced(exp, expoPlist, accountName)) {
Log.warn(
'Native project configuration is not synced with values present in your app.json, run "eas build:configure" to make sure all values are applied in the native project'
);
}
}
async function ensureUpdatesConfiguredAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
const xcodeProject = IOSConfig.XcodeUtils.getPbxproj(projectDir);
if (!IOSConfig.Updates.isShellScriptBuildPhaseConfigured(projectDir, xcodeProject)) {
const script = 'expo-updates/scripts/create-manifest-ios.sh';
const buildPhase = '"Bundle React Native code and images"';
throw new Error(`Path to ${script} is missing in a ${buildPhase} build phase.`);
}
const expoPlist = await readExpoPlistAsync(projectDir);
if (!IOSConfig.Updates.isPlistConfigurationSet(expoPlist)) {
throw new Error('Missing values in Expo.plist');
}
}
async function readExpoPlistAsync(projectDir: string): Promise<IOSConfig.ExpoPlist> {
const expoPlistPath = IOSConfig.Paths.getExpoPlistPath(projectDir);
return (await readPlistAsync(expoPlistPath)) as IOSConfig.ExpoPlist;
}
async function writeExpoPlistAsync(
projectDir: string,
expoPlist: IOSConfig.ExpoPlist
): Promise<void> {
const expoPlistPath = IOSConfig.Paths.getExpoPlistPath(projectDir);
await writePlistAsync(expoPlistPath, expoPlist);
await vcs.trackFileAsync(expoPlistPath);
}
export async function readReleaseChannelSafelyAsync(projectDir: string): Promise<string | null> {
try {
const expoPlist = await readExpoPlistAsync(projectDir);
return expoPlist[IOSConfig.Updates.Config.RELEASE_CHANNEL] ?? null;
} catch (err) {
return null;
}
}
export async function readChannelSafelyAsync(projectDir: string): Promise<string | null> {
try {
const expoPlist: any = await readExpoPlistAsync(projectDir); // TODO-JJ remove any once IOSConfig.ExpoPlist is updated to include `EXUpdatesRequestHeaders : Record<string,string>`
const updatesRequestHeaders = expoPlist['EXUpdatesRequestHeaders'] ?? {}; //TODO-JJ 'EXUpdatesRequestHeaders' IosConfig.Updates.Config.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY once https://github.com/expo/expo-cli/pull/3571 is published
return updatesRequestHeaders['expo-channel-name'] ?? null;
} catch (err) {
return null;
}
}
@@ -0,0 +1,88 @@
import fs from 'fs-extra';
import { vol } from 'memfs';
import os from 'os';
import { readChannelSafelyAsync } from '../UpdatesModule';
jest.mock('fs');
afterAll(() => {
// do not remove the following line
// this fixes a weird error with tempy in @expo/image-utils
fs.removeSync(os.tmpdir());
});
beforeEach(() => {
vol.reset();
// do not remove the following line
// this fixes a weird error with tempy in @expo/image-utils
fs.mkdirpSync(os.tmpdir());
});
const testChannel = 'test-channel';
describe(readChannelSafelyAsync, () => {
it('retrieves the channel when it is defined natively', async () => {
vol.fromJSON(
{
'ios/expo-project/Supporting/Expo.plist': ExpoPlistWithChannel,
'ios/expo-project/AppDelegate.m': '',
},
'/expo-project'
);
const channel = await readChannelSafelyAsync('/expo-project');
expect(channel).toBe(testChannel);
});
it('returns null if the channel is not defined natively', async () => {
vol.fromJSON(
{
'ios/expo-project/Supporting/Expo.plist': ExpoPlistWithOutChannel,
'ios/expo-project/AppDelegate.m': '',
},
'/expo-project'
);
const channel = await readChannelSafelyAsync('/expo-project');
expect(channel).toBeNull();
});
});
const ExpoPlistWithChannel = `
<plist version="1.0">
<dict>
<key>EXUpdatesCheckOnLaunch</key>
<string>ALWAYS</string>
<key>EXUpdatesEnabled</key>
<true/>
<key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer>
<key>EXUpdatesReleaseChannel</key>
<string>default</string>
<key>EXUpdatesSDKVersion</key>
<string>40.0.0</string>
<key>EXUpdatesURL</key>
<string>https://exp.host/@user/expo-project</string>
<key>EXUpdatesRequestHeaders</key>
<dict>
<key>expo-channel-name</key>
<string>${testChannel}</string>
</dict>
</dict>
</plist>
`;
const ExpoPlistWithOutChannel = `
<plist version="1.0">
<dict>
<key>EXUpdatesCheckOnLaunch</key>
<string>ALWAYS</string>
<key>EXUpdatesEnabled</key>
<true/>
<key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer>
<key>EXUpdatesReleaseChannel</key>
<string>default</string>
<key>EXUpdatesSDKVersion</key>
<string>40.0.0</string>
<key>EXUpdatesURL</key>
<string>https://exp.host/@user/expo-project</string>
</dict>
</plist>
`;
@@ -0,0 +1,256 @@
import { ExpoConfig } from '@expo/config';
import { IOSConfig } from '@expo/config-plugins';
import fs from 'fs-extra';
import { vol } from 'memfs';
import os from 'os';
import { readPlistAsync } from '../plist';
import {
BumpStrategy,
bumpVersionAsync,
bumpVersionInAppJsonAsync,
readBuildNumberAsync,
readShortVersionAsync,
} from '../version';
jest.mock('fs');
afterAll(() => {
// do not remove the following line
// this fixes a weird error with tempy in @expo/image-utils
fs.removeSync(os.tmpdir());
});
beforeEach(() => {
vol.reset();
// do not remove the following line
// this fixes a weird error with tempy in @expo/image-utils
fs.mkdirpSync(os.tmpdir());
});
// generic workflow
describe(bumpVersionAsync, () => {
it('bumps expo.ios.buildNumber and CFBundleVersion when strategy = BumpStrategy.BUILD_NUMBER', async () => {
const fakeExp = initGenericProject();
await bumpVersionAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/repo',
exp: fakeExp,
});
const appJSON = await fs.readJSON('/repo/app.json');
const infoPlist = (await readPlistAsync(
'/repo/ios/myproject/Info.plist'
)) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('2');
expect(appJSON.expo.version).toBe('1.0.0');
expect(appJSON.expo.ios.buildNumber).toBe('2');
expect(infoPlist['CFBundleShortVersionString']).toBe('1.0.0');
expect(infoPlist['CFBundleVersion']).toBe('2');
});
it('bumps expo.version and CFBundleShortVersionString when strategy = BumpStrategy.SHORT_VERSION', async () => {
const fakeExp = initGenericProject();
await bumpVersionAsync({
bumpStrategy: BumpStrategy.SHORT_VERSION,
projectDir: '/repo',
exp: fakeExp,
});
const appJSON = await fs.readJSON('/repo/app.json');
const infoPlist = (await readPlistAsync(
'/repo/ios/myproject/Info.plist'
)) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.1');
expect(fakeExp.ios?.buildNumber).toBe('1');
expect(appJSON.expo.version).toBe('1.0.1');
expect(appJSON.expo.ios.buildNumber).toBe('1');
expect(infoPlist['CFBundleShortVersionString']).toBe('1.0.1');
expect(infoPlist['CFBundleVersion']).toBe('1');
});
it('does not bump any version when strategy = BumpStrategy.NOOP', async () => {
const fakeExp = initGenericProject();
await bumpVersionAsync({
bumpStrategy: BumpStrategy.NOOP,
projectDir: '/repo',
exp: fakeExp,
});
const appJSON = await fs.readJSON('/repo/app.json');
const infoPlist = (await readPlistAsync(
'/repo/ios/myproject/Info.plist'
)) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('1');
expect(appJSON.expo.version).toBe('1.0.0');
expect(appJSON.expo.ios.buildNumber).toBe('1');
expect(infoPlist['CFBundleShortVersionString']).toBe('1.0.0');
expect(infoPlist['CFBundleVersion']).toBe('1');
});
});
// managed workflow
describe(bumpVersionInAppJsonAsync, () => {
it('bumps expo.ios.buildNumber when strategy = BumpStrategy.BUILD_NUMBER', async () => {
const fakeExp = initManagedProject();
await bumpVersionInAppJsonAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/repo',
exp: fakeExp,
});
const appJSON = await fs.readJSON('/repo/app.json');
expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('2');
expect(appJSON.expo.version).toBe('1.0.0');
expect(appJSON.expo.ios.buildNumber).toBe('2');
});
it('bumps expo.version when strategy = BumpStrategy.SHORT_VERSION', async () => {
const fakeExp = initManagedProject();
await bumpVersionInAppJsonAsync({
bumpStrategy: BumpStrategy.SHORT_VERSION,
projectDir: '/repo',
exp: fakeExp,
});
const appJSON = await fs.readJSON('/repo/app.json');
expect(fakeExp.version).toBe('1.0.1');
expect(fakeExp.ios?.buildNumber).toBe('1');
expect(appJSON.expo.version).toBe('1.0.1');
expect(appJSON.expo.ios.buildNumber).toBe('1');
});
it('does not bump any version when strategy = BumpStrategy.NOOP', async () => {
const fakeExp = initManagedProject();
await bumpVersionInAppJsonAsync({
bumpStrategy: BumpStrategy.NOOP,
projectDir: '/repo',
exp: fakeExp,
});
const appJSON = await fs.readJSON('/repo/app.json');
expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('1');
expect(appJSON.expo.version).toBe('1.0.0');
expect(appJSON.expo.ios.buildNumber).toBe('1');
});
});
describe(readBuildNumberAsync, () => {
describe('generic project', () => {
it('reads the build number from native code', async () => {
const exp = initGenericProject();
const buildNumber = await readBuildNumberAsync('/repo', exp, {});
expect(buildNumber).toBe('1');
});
});
describe('managed project', () => {
it('reads the build number from expo config', async () => {
const exp = initManagedProject();
const buildNumber = await readBuildNumberAsync('/repo', exp, {});
expect(buildNumber).toBe('1');
});
});
});
describe(readShortVersionAsync, () => {
describe('generic project', () => {
it('reads the short version from native code', async () => {
const exp = initGenericProject();
const shortVersion = await readShortVersionAsync('/repo', exp, {});
expect(shortVersion).toBe('1.0.0');
});
it('evaluates interpolated build number', async () => {
const exp = initGenericProject({
shortVersion: '$(CURRENT_PROJECT_VERSION)',
});
const buildNumber = await readShortVersionAsync('/repo', exp, {
CURRENT_PROJECT_VERSION: '1.0.0',
});
expect(buildNumber).toBe('1.0.0');
});
});
describe('managed project', () => {
it('reads the version from app config', async () => {
const exp = initGenericProject();
const shortVersion = await readShortVersionAsync('/repo', exp, {});
expect(shortVersion).toBe('1.0.0');
});
});
});
function initGenericProject({
shortVersion = '1.0.0',
version = '1',
}: { shortVersion?: string; version?: string } = {}): ExpoConfig {
vol.fromJSON(
{
'./app.json': JSON.stringify({
expo: {
version: '1.0.0',
ios: {
buildNumber: '1',
},
},
}),
'./ios/myproject/Info.plist': `<?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">
<dict>
<key>CFBundleShortVersionString</key>
<string>${shortVersion}</string>
<key>CFBundleVersion</key>
<string>${version}</string>
</dict>
</plist>`,
},
'/repo'
);
const fakeExp: ExpoConfig = {
name: 'myproject',
slug: 'myproject',
version: '1.0.0',
ios: {
buildNumber: '1',
},
};
return fakeExp;
}
function initManagedProject(): ExpoConfig {
vol.fromJSON(
{
'./app.json': JSON.stringify({
expo: {
version: '1.0.0',
ios: {
buildNumber: '1',
},
},
}),
},
'/repo'
);
const fakeExp: ExpoConfig = {
name: 'myproject',
slug: 'myproject',
version: '1.0.0',
ios: {
buildNumber: '1',
},
};
return fakeExp;
}
+89
View File
@@ -0,0 +1,89 @@
import { IOSConfig } from '@expo/config-plugins';
import { Ios, Job, Metadata, Workflow } from '@expo/eas-build-job';
import { IosCredentials } from '../../credentials/ios/types';
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
import { ensureBundleIdentifierIsDefinedForManagedProjectAsync } from '../../project/ios/bundleIdentifier';
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
import { findApplicationTarget, resolveTargetsAsync } from '../../project/ios/target';
import { BuildRequestSender, JobData, prepareBuildRequestForPlatformAsync } from '../build';
import { BuildContext } from '../context';
import { transformMetadata } from '../graphql';
import { IosMetadataContext } from '../metadata';
import { Platform } from '../types';
import { validateAndSyncProjectConfigurationAsync } from './configure';
import { ensureIosCredentialsAsync } from './credentials';
import { transformJob } from './graphql';
import { prepareJobAsync } from './prepareJob';
export async function prepareIosBuildAsync(
ctx: BuildContext<Platform.IOS>
): Promise<BuildRequestSender> {
const { buildProfile } = ctx;
if (ctx.workflow === Workflow.MANAGED) {
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp);
}
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
{
projectDir: ctx.projectDir,
nonInteractive: ctx.nonInteractive,
exp: ctx.exp,
},
buildProfile
);
const targets = await resolveTargetsAsync(
{
projectDir: ctx.projectDir,
exp: ctx.exp,
},
xcodeBuildContext
);
return await prepareBuildRequestForPlatformAsync({
ctx,
ensureCredentialsAsync: async (ctx: BuildContext<Platform.IOS>) => {
return ensureIosCredentialsAsync(ctx, targets);
},
ensureProjectConfiguredAsync: async () => {
await validateAndSyncProjectConfigurationAsync({
projectDir: ctx.projectDir,
exp: ctx.exp,
buildProfile,
});
},
getMetadataContext: (): IosMetadataContext => {
if (ctx.workflow === Workflow.MANAGED) {
return { buildSettings: {} };
}
const applicationTarget = findApplicationTarget(targets);
const project = IOSConfig.XcodeUtils.getPbxproj(ctx.projectDir);
const xcBuildConfiguration = IOSConfig.Target.getXCBuildConfigurationFromPbxproj(project, {
targetName: applicationTarget.targetName,
buildConfiguration: applicationTarget.buildConfiguration,
});
const buildSettings = xcBuildConfiguration?.buildSettings ?? {};
return { buildSettings };
},
prepareJobAsync: async (
ctx: BuildContext<Platform.IOS>,
jobData: JobData<IosCredentials>
): Promise<Job> => {
return await prepareJobAsync(ctx, { ...jobData, buildScheme: xcodeBuildContext.buildScheme });
},
sendBuildRequestAsync: async (
appId: string,
job: Ios.Job,
metadata: Metadata
): Promise<BuildResult> => {
const graphqlMetadata = transformMetadata(metadata);
const graphqlJob = transformJob(job);
return await BuildMutation.createIosBuildAsync({
appId,
job: graphqlJob,
metadata: graphqlMetadata,
});
},
});
}
@@ -0,0 +1,63 @@
import { ExpoConfig } from '@expo/config';
import { Platform, Workflow } from '@expo/eas-build-job';
import { IosBuildProfile, VersionAutoIncrement } from '@expo/eas-json';
import Log from '../../log';
import {
ensureBundleIdentifierIsDefinedForManagedProjectAsync,
warnIfBundleIdentifierDefinedInAppConfigForGenericProject,
} from '../../project/ios/bundleIdentifier';
import { resolveWorkflowAsync } from '../../project/workflow';
import { ConfigureContext } from '../context';
import { isExpoUpdatesInstalled } from '../utils/updates';
import { configureUpdatesAsync, syncUpdatesConfigurationAsync } from './UpdatesModule';
import { BumpStrategy, bumpVersionAsync, bumpVersionInAppJsonAsync } from './version';
export async function configureIosAsync(ctx: ConfigureContext): Promise<void> {
if (!ctx.hasIosNativeProject) {
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp);
return;
}
warnIfBundleIdentifierDefinedInAppConfigForGenericProject(ctx.projectDir, ctx.exp);
if (isExpoUpdatesInstalled(ctx.projectDir)) {
await configureUpdatesAsync(ctx.projectDir, ctx.exp);
}
Log.withTick('iOS project configured');
}
export async function validateAndSyncProjectConfigurationAsync({
projectDir,
exp,
buildProfile,
}: {
projectDir: string;
exp: ExpoConfig;
buildProfile: IosBuildProfile;
}): Promise<void> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
const { autoIncrement } = buildProfile;
const versionBumpStrategy = resolveVersionBumpStrategy(autoIncrement ?? false);
if (workflow === Workflow.GENERIC) {
if (isExpoUpdatesInstalled(projectDir)) {
await syncUpdatesConfigurationAsync(projectDir, exp);
}
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
} else {
await bumpVersionInAppJsonAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
}
}
function resolveVersionBumpStrategy(autoIncrement: VersionAutoIncrement): BumpStrategy {
if (autoIncrement === true) {
return BumpStrategy.BUILD_NUMBER;
} else if (autoIncrement === false) {
return BumpStrategy.NOOP;
} else if (autoIncrement === 'buildNumber') {
return BumpStrategy.BUILD_NUMBER;
} else {
return BumpStrategy.SHORT_VERSION;
}
}
@@ -0,0 +1,45 @@
import { createCredentialsContextAsync } from '../../credentials/context';
import IosCredentialsProvider from '../../credentials/ios/IosCredentialsProvider';
import { getAppFromContext } from '../../credentials/ios/actions/BuildCredentialsUtils';
import { resolveEntitlementsJsonAsync } from '../../credentials/ios/appstore/entitlements';
import { IosCredentials, Target } from '../../credentials/ios/types';
import { CredentialsResult } from '../build';
import { BuildContext } from '../context';
import { Platform } from '../types';
import { logCredentialsSource } from '../utils/credentials';
export async function ensureIosCredentialsAsync(
buildCtx: BuildContext<Platform.IOS>,
targets: Target[]
): Promise<CredentialsResult<IosCredentials> | undefined> {
if (!shouldProvideCredentials(buildCtx)) {
return;
}
const credentialsCtx = await createCredentialsContextAsync(buildCtx.projectDir, {
exp: buildCtx.exp,
nonInteractive: buildCtx.nonInteractive,
});
const provider = new IosCredentialsProvider(credentialsCtx, {
app: getAppFromContext(credentialsCtx),
targets,
iosCapabilitiesOptions: {
entitlements: await resolveEntitlementsJsonAsync(buildCtx.projectDir, buildCtx.workflow),
},
distribution: buildCtx.buildProfile.distribution ?? 'store',
enterpriseProvisioning: buildCtx.buildProfile.enterpriseProvisioning,
});
const { credentialsSource } = buildCtx.buildProfile;
logCredentialsSource(credentialsSource, Platform.IOS);
return {
credentials: await provider.getCredentialsAsync(credentialsSource),
source: credentialsSource,
};
}
function shouldProvideCredentials(buildCtx: BuildContext<Platform.IOS>): boolean {
return !buildCtx.buildProfile.simulator;
}
+71
View File
@@ -0,0 +1,71 @@
import { Env, Ios } from '@expo/eas-build-job';
import nullthrows from 'nullthrows';
import {
DistributionType,
IosBuildType,
IosJobInput,
IosJobSecretsInput,
} from '../../graphql/generated';
import { transformProjectArchive, transformWorkflow } from '../graphql';
export function transformJob(job: Ios.Job): IosJobInput {
return {
type: transformWorkflow(job.type),
projectArchive: transformProjectArchive(job.projectArchive),
projectRootDirectory: job.projectRootDirectory,
releaseChannel: job.releaseChannel,
updates: job.updates,
distribution: job.distribution && transformDistributionType(job.distribution),
secrets: transformIosSecrets(job.secrets),
builderEnvironment: job.builderEnvironment,
cache: job.cache,
scheme: job.scheme,
buildConfiguration: job.buildConfiguration,
artifactPath: job.artifactPath,
buildType: job.buildType && transformBuildType(job.buildType),
username: job.username,
};
}
function transformDistributionType(distributionType: Ios.DistributionType): DistributionType {
if (distributionType === 'store') {
return DistributionType.Store;
} else if (distributionType === 'internal') {
return DistributionType.Internal;
} else {
return DistributionType.Simulator;
}
}
function transformIosSecrets(secrets: {
buildCredentials?: Ios.BuildCredentials;
env?: Env;
}): IosJobSecretsInput {
const buildCredentials: IosJobSecretsInput['buildCredentials'] = [];
for (const targetName of Object.keys(secrets.buildCredentials ?? {})) {
buildCredentials.push({
targetName,
provisioningProfileBase64: nullthrows(secrets.buildCredentials?.[targetName])
.provisioningProfileBase64,
distributionCertificate: nullthrows(secrets.buildCredentials?.[targetName])
.distributionCertificate,
});
}
return {
buildCredentials,
environmentSecrets: secrets.env,
};
}
function transformBuildType(buildType: Ios.BuildType): IosBuildType {
if (buildType === Ios.BuildType.DEVELOPMENT_CLIENT) {
return IosBuildType.DevelopmentClient;
} else {
return IosBuildType.Release;
}
}
+22
View File
@@ -0,0 +1,22 @@
import { IOSConfig } from '@expo/config-plugins';
import plist from '@expo/plist';
import fs from 'fs-extra';
import path from 'path';
export async function readPlistAsync(plistPath: string): Promise<object> {
if (await fs.pathExists(plistPath)) {
const expoPlistContent = await fs.readFile(plistPath, 'utf8');
return plist.parse(expoPlistContent);
} else {
return {};
}
}
export async function writePlistAsync(
plistPath: string,
plistObject: IOSConfig.ExpoPlist | IOSConfig.InfoPlist
): Promise<void> {
const contents = plist.build(plistObject);
await fs.mkdirp(path.dirname(plistPath));
await fs.writeFile(plistPath, contents);
}
@@ -0,0 +1,96 @@
import { ArchiveSource, Ios, Job, sanitizeJob } from '@expo/eas-build-job';
import path from 'path';
import semver from 'semver';
import { IosCredentials, TargetCredentials } from '../../credentials/ios/types';
import { getUsername } from '../../project/projectUtils';
import { ensureLoggedInAsync } from '../../user/actions';
import vcs from '../../vcs';
import { BuildContext } from '../context';
import { Platform } from '../types';
interface JobData {
projectArchive: ArchiveSource;
credentials?: IosCredentials;
buildScheme: string;
}
const cacheDefaults = {
disabled: false,
customPaths: [],
cacheDefaultPaths: true,
};
export async function prepareJobAsync(
ctx: BuildContext<Platform.IOS>,
jobData: JobData
): Promise<Job> {
const projectRootDirectory = path.relative(await vcs.getRootPathAsync(), ctx.projectDir) || '.';
const username = getUsername(ctx.exp, await ensureLoggedInAsync());
const buildCredentials: Ios.Job['secrets']['buildCredentials'] = {};
if (jobData.credentials) {
const targetNames = Object.keys(jobData.credentials);
for (const targetName of targetNames) {
buildCredentials[targetName] = prepareTargetCredentials(jobData.credentials[targetName]);
}
}
const job: Ios.Job = {
type: ctx.workflow,
platform: Platform.IOS,
projectArchive: jobData.projectArchive,
projectRootDirectory,
distribution: ctx.buildProfile.simulator ? 'simulator' : ctx.buildProfile.distribution,
builderEnvironment: {
image: resolveImage(ctx),
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,
},
cache: {
...cacheDefaults,
...ctx.buildProfile.cache,
clear: ctx.clearCache,
},
secrets: {
buildCredentials,
},
releaseChannel: ctx.buildProfile.releaseChannel,
updates: { channel: ctx.buildProfile.channel },
scheme: jobData.buildScheme,
buildConfiguration: ctx.buildProfile.buildConfiguration,
artifactPath: ctx.buildProfile.artifactPath,
buildType: ctx.buildProfile.developmentClient
? Ios.BuildType.DEVELOPMENT_CLIENT
: Ios.BuildType.RELEASE,
username,
};
return sanitizeJob(job);
}
function resolveImage(ctx: BuildContext<Platform.IOS>): Ios.BuilderEnvironment['image'] {
// see https://linear.app/expo/issue/ENG-1396/make-default-image-dependent-on-sdk-version
if (!ctx.buildProfile.image && ctx.exp.sdkVersion) {
const majorSdkVersion = semver.major(ctx.exp.sdkVersion);
if (majorSdkVersion <= 41) {
return 'macos-catalina-10.15-xcode-12.4';
}
}
return ctx.buildProfile.image ?? 'default';
}
function prepareTargetCredentials(targetCredentials: TargetCredentials): Ios.TargetCredentials {
return {
provisioningProfileBase64: targetCredentials.provisioningProfile,
distributionCertificate: {
dataBase64: targetCredentials.distributionCertificate.certificateP12,
password: targetCredentials.distributionCertificate.certificatePassword,
},
};
}
+174
View File
@@ -0,0 +1,174 @@
import { ExpoConfig, getConfigFilePaths } from '@expo/config';
import { IOSConfig } from '@expo/config-plugins';
import { Platform, Workflow } from '@expo/eas-build-job';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
import semver from 'semver';
import type { XCBuildConfiguration } from 'xcode';
import Log from '../../log';
import { resolveWorkflowAsync } from '../../project/workflow';
import { promptAsync } from '../../prompts';
import { updateAppJsonConfigAsync } from '../utils/appJson';
import { evaluateString } from '../utils/template';
import { readPlistAsync, writePlistAsync } from './plist';
export enum BumpStrategy {
SHORT_VERSION,
BUILD_NUMBER,
NOOP,
}
export async function bumpVersionAsync({
bumpStrategy,
projectDir,
exp,
}: {
projectDir: string;
exp: ExpoConfig;
bumpStrategy: BumpStrategy;
}): Promise<void> {
if (bumpStrategy === BumpStrategy.NOOP) {
return;
}
ensureStaticConfigExists(projectDir);
const infoPlist = await readInfoPlistAsync(projectDir);
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
Log.log('Updated versions in app.json');
await writeVersionsToInfoPlistAsync({ projectDir, exp, infoPlist });
Log.log('Synchronized versions with Info.plist');
}
export async function bumpVersionInAppJsonAsync({
bumpStrategy,
projectDir,
exp,
}: {
bumpStrategy: BumpStrategy;
projectDir: string;
exp: ExpoConfig;
}): Promise<void> {
if (bumpStrategy === BumpStrategy.NOOP) {
return;
}
ensureStaticConfigExists(projectDir);
Log.addNewLineIfNone();
if (bumpStrategy === BumpStrategy.SHORT_VERSION) {
const shortVersion = IOSConfig.Version.getVersion(exp);
if (semver.valid(shortVersion)) {
const bumpedShortVersion = nullthrows(semver.inc(shortVersion, 'patch'));
Log.log(
`Bumping ${chalk.bold('expo.version')} from ${chalk.bold(shortVersion)} to ${chalk.bold(
bumpedShortVersion
)}`
);
await updateAppJsonConfigAsync({ projectDir, exp }, config => {
config.version = bumpedShortVersion;
});
} else {
Log.log(`${chalk.bold('expo.version')} = ${chalk.bold(shortVersion)} is not a valid semver`);
const { bumpedShortVersion } = await promptAsync({
type: 'text',
name: 'bumpedShortVersion',
message: 'What is the next version?',
});
await updateAppJsonConfigAsync({ projectDir, exp }, config => {
config.version = bumpedShortVersion;
});
}
} else {
const buildNumber = IOSConfig.Version.getBuildNumber(exp);
if (buildNumber.match(/^\d+(\.\d+)*$/)) {
const comps = buildNumber.split('.');
comps[comps.length - 1] = String(Number(comps[comps.length - 1]) + 1);
const bumpedBuildNumber = comps.join('.');
Log.log(
`Bumping ${chalk.bold('expo.ios.buildNumber')} from ${chalk.bold(
buildNumber
)} to ${chalk.bold(bumpedBuildNumber)}`
);
await updateAppJsonConfigAsync({ projectDir, exp }, config => {
config.ios = { ...config.ios, buildNumber: String(bumpedBuildNumber) };
});
} else {
Log.log(`${chalk.bold('expo.ios.buildNumber')} = ${chalk.bold(buildNumber)} is not a number`);
const { bumpedBuildNumber } = await promptAsync({
type: 'text',
name: 'bumpedBuildNumber',
message: 'What is the next build number?',
});
await updateAppJsonConfigAsync({ projectDir, exp }, config => {
config.ios = { ...config.ios, buildNumber: String(bumpedBuildNumber) };
});
}
}
}
export async function readShortVersionAsync(
projectDir: string,
exp: ExpoConfig,
buildSettings: XCBuildConfiguration['buildSettings']
): Promise<string | undefined> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
if (workflow === Workflow.GENERIC) {
const infoPlist = await readInfoPlistAsync(projectDir);
return (
infoPlist.CFBundleShortVersionString &&
evaluateString(infoPlist.CFBundleShortVersionString, buildSettings)
);
} else {
return exp.version;
}
}
export async function readBuildNumberAsync(
projectDir: string,
exp: ExpoConfig,
buildSettings: XCBuildConfiguration['buildSettings']
): Promise<string | undefined> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
if (workflow === Workflow.GENERIC) {
const infoPlist = await readInfoPlistAsync(projectDir);
return infoPlist.CFBundleVersion && evaluateString(infoPlist.CFBundleVersion, buildSettings);
} else {
return IOSConfig.Version.getBuildNumber(exp);
}
}
async function writeVersionsToInfoPlistAsync({
projectDir,
exp,
infoPlist,
}: {
projectDir: string;
exp: ExpoConfig;
infoPlist: IOSConfig.InfoPlist;
}): Promise<IOSConfig.InfoPlist> {
let updatedInfoPlist = IOSConfig.Version.setVersion(exp, infoPlist);
updatedInfoPlist = IOSConfig.Version.setBuildNumber(exp, updatedInfoPlist);
await writeInfoPlistAsync({ projectDir, infoPlist: updatedInfoPlist });
return updatedInfoPlist;
}
async function readInfoPlistAsync(projectDir: string): Promise<IOSConfig.InfoPlist> {
const infoPlistPath = IOSConfig.Paths.getInfoPlistPath(projectDir);
return (await readPlistAsync(infoPlistPath)) as IOSConfig.InfoPlist;
}
async function writeInfoPlistAsync({
projectDir,
infoPlist,
}: {
projectDir: string;
infoPlist: IOSConfig.InfoPlist;
}): Promise<void> {
const infoPlistPath = IOSConfig.Paths.getInfoPlistPath(projectDir);
await writePlistAsync(infoPlistPath, infoPlist);
}
function ensureStaticConfigExists(projectDir: string): void {
const paths = getConfigFilePaths(projectDir);
if (!paths.staticConfigPath) {
throw new Error('autoIncrement option is not supported when using app.config.js');
}
}
+45
View File
@@ -0,0 +1,45 @@
import { Job } from '@expo/eas-build-job';
import spawnAsync from '@expo/spawn-async';
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import Log from '../log';
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
export async function runLocalBuildAsync(job: Job): Promise<void> {
const execNameOrPath =
process.env.EAS_LOCAL_BUILD_PLUGIN_PATH ??
(await findWithNodeResolution()) ??
PLUGIN_PACKAGE_NAME;
try {
const arg = Buffer.from(JSON.stringify({ job })).toString('base64');
await spawnAsync(execNameOrPath, [arg], {
stdio: 'inherit',
});
} catch (err) {
if (err.code === 'ENOENT') {
Log.warn(`Could not resolve executable ${execNameOrPath}.`);
Log.warn(
`Install ${PLUGIN_PACKAGE_NAME} package from npm e.g. ${chalk.bold(
`npm install -g ${PLUGIN_PACKAGE_NAME}`
)} and make sure it's in PATH.`
);
throw err;
}
}
}
async function findWithNodeResolution(): Promise<string | undefined> {
try {
const resolvedPath = path.resolve(
path.dirname(require.resolve(PLUGIN_PACKAGE_NAME)),
'../bin/run'
);
if (await fs.pathExists(resolvedPath)) {
return resolvedPath;
}
} catch {}
return undefined;
}
+158
View File
@@ -0,0 +1,158 @@
import { Metadata, sanitizeMetadata } from '@expo/eas-build-job';
import { IosEnterpriseProvisioning } from '@expo/eas-json';
import type { XCBuildConfiguration } from 'xcode';
import { getApplicationIdAsync } from '../project/android/applicationId';
import { getBundleIdentifierAsync } from '../project/ios/bundleIdentifier';
import { getUsername } from '../project/projectUtils';
import { ensureLoggedInAsync } from '../user/actions';
import vcs from '../vcs';
import {
readChannelSafelyAsync as readAndroidChannelSafelyAsync,
readReleaseChannelSafelyAsync as readAndroidReleaseChannelSafelyAsync,
} from './android/UpdatesModule';
import { readVersionCodeAsync, readVersionNameAsync } from './android/version';
import { BuildContext } from './context';
import {
readChannelSafelyAsync as readIosChannelSafelyAsync,
readReleaseChannelSafelyAsync as readIosReleaseChannelSafelyAsync,
} from './ios/UpdatesModule';
import { readBuildNumberAsync, readShortVersionAsync } from './ios/version';
import { Platform } from './types';
import { isExpoUpdatesInstalled } from './utils/updates';
/**
* We use require() to exclude package.json from TypeScript's analysis since it lives outside
* the src directory and would change the directory structure of the emitted files
* under the build directory
*/
const packageJSON = require('../../package.json');
export type MetadataContext<T extends Platform> = T extends Platform.ANDROID
? object
: IosMetadataContext;
export interface IosMetadataContext {
buildSettings: XCBuildConfiguration['buildSettings'];
}
export async function collectMetadata<T extends Platform>(
ctx: BuildContext<T>,
platformContext?: MetadataContext<T>
): Promise<Metadata> {
const channelOrReleaseChannel = await resolveChannelOrReleaseChannelAsync(ctx);
const metadata = {
trackingContext: ctx.trackingCtx,
appVersion: await resolveAppVersionAsync(ctx, platformContext),
appBuildVersion: await resolveAppBuildVersionAsync(ctx, platformContext),
cliVersion: packageJSON.version,
workflow: ctx.workflow,
credentialsSource: ctx.buildProfile.credentialsSource,
sdkVersion: ctx.exp.sdkVersion,
runtimeVersion: ctx.exp.runtimeVersion,
...channelOrReleaseChannel,
distribution: ctx.buildProfile.distribution ?? 'store',
appName: ctx.exp.name,
appIdentifier: await resolveAppIdentifierAsync(ctx),
buildProfile: ctx.buildProfileName,
gitCommitHash: await vcs.getCommitHashAsync(),
username: getUsername(ctx.exp, await ensureLoggedInAsync()),
...(ctx.platform === Platform.IOS && {
iosEnterpriseProvisioning: resolveIosEnterpriseProvisioning(
ctx as BuildContext<Platform.IOS>
),
}),
};
return sanitizeMetadata(metadata);
}
async function resolveAppVersionAsync<T extends Platform>(
ctx: BuildContext<T>,
platformContext?: MetadataContext<T>
): Promise<string | undefined> {
if (ctx.platform === Platform.IOS) {
const iosContext = platformContext as IosMetadataContext | undefined;
return await readShortVersionAsync(ctx.projectDir, ctx.exp, iosContext?.buildSettings ?? {});
} else {
return await readVersionNameAsync(ctx.projectDir, ctx.exp);
}
}
async function resolveAppBuildVersionAsync<T extends Platform>(
ctx: BuildContext<T>,
platformContext?: MetadataContext<T>
): Promise<string | undefined> {
if (ctx.platform === Platform.IOS) {
const iosContext = platformContext as IosMetadataContext | undefined;
return await readBuildNumberAsync(ctx.projectDir, ctx.exp, iosContext?.buildSettings ?? {});
} else {
const versionCode = await readVersionCodeAsync(ctx.projectDir, ctx.exp);
return versionCode !== undefined ? String(versionCode) : undefined;
}
}
async function resolveAppIdentifierAsync<T extends Platform>(
ctx: BuildContext<T>
): Promise<string> {
if (ctx.platform === Platform.IOS) {
return await getBundleIdentifierAsync(ctx.projectDir, ctx.exp);
} else {
return await getApplicationIdAsync(ctx.projectDir, ctx.exp);
}
}
async function resolveChannelOrReleaseChannelAsync<T extends Platform>(
ctx: BuildContext<T>
): Promise<{ channel: string } | { releaseChannel: string } | null> {
if (!isExpoUpdatesInstalled(ctx.projectDir)) {
return null;
}
if (ctx.buildProfile.channel) {
return { channel: ctx.buildProfile.channel };
}
if (ctx.buildProfile.releaseChannel) {
return { releaseChannel: ctx.buildProfile.releaseChannel };
}
const channel = await getNativeChannelAsync(ctx);
if (channel) {
return { channel };
}
const releaseChannel = await getNativeReleaseChannelAsync(ctx);
return { releaseChannel };
}
async function getNativeReleaseChannelAsync<T extends Platform>(
ctx: BuildContext<T>
): Promise<string> {
switch (ctx.platform) {
case Platform.ANDROID: {
return (await readAndroidReleaseChannelSafelyAsync(ctx.projectDir)) ?? 'default';
}
case Platform.IOS: {
return (await readIosReleaseChannelSafelyAsync(ctx.projectDir)) ?? 'default';
}
default:
return 'default';
}
}
async function getNativeChannelAsync<T extends Platform>(
ctx: BuildContext<T>
): Promise<string | undefined> {
switch (ctx.platform) {
case Platform.ANDROID: {
return (await readAndroidChannelSafelyAsync(ctx.projectDir)) ?? undefined;
}
case Platform.IOS: {
return (await readIosChannelSafelyAsync(ctx.projectDir)) ?? undefined;
}
}
return undefined;
}
function resolveIosEnterpriseProvisioning(
ctx: BuildContext<Platform.IOS>
): IosEnterpriseProvisioning | undefined {
return ctx.buildProfile.enterpriseProvisioning;
}
+24
View File
@@ -0,0 +1,24 @@
export { Platform } from '@expo/eas-build-job';
export enum RequestedPlatform {
Android = 'android',
Ios = 'ios',
All = 'all',
}
export enum BuildStatus {
NEW = 'new',
IN_QUEUE = 'in-queue',
IN_PROGRESS = 'in-progress',
ERRORED = 'errored',
FINISHED = 'finished',
CANCELED = 'canceled',
}
export enum BuildDistributionType {
STORE = 'store',
INTERNAL = 'internal',
SIMULATOR = 'simulator',
}
export type TrackingContext = Record<string, string | number | boolean>;
@@ -0,0 +1,27 @@
import { logEvent } from '../../analytics';
export enum Event {
BUILD_COMMAND = 'build cli build command',
PROJECT_UPLOAD_SUCCESS = 'build cli project upload success',
PROJECT_UPLOAD_FAIL = 'build cli project upload fail',
GATHER_CREDENTIALS_SUCCESS = 'build cli gather credentials success',
GATHER_CREDENTIALS_FAIL = 'build cli gather credentials fail',
CONFIGURE_PROJECT_SUCCESS = 'build cli configure project success',
CONFIGURE_PROJECT_FAIL = 'build cli configure project fail',
BUILD_REQUEST_SUCCESS = 'build cli build request success',
BUILD_REQUEST_FAIL = 'build cli build request fail',
BUILD_STATUS_COMMAND = 'build cli build status',
CREDENTIALS_SYNC_COMMAND = 'build cli credentials sync command',
CREDENTIALS_SYNC_UPDATE_LOCAL_SUCCESS = 'build cli credentials sync update local success',
CREDENTIALS_SYNC_UPDATE_LOCAL_FAIL = 'build cli credentials sync update local fail',
CREDENTIALS_SYNC_UPDATE_REMOTE_SUCCESS = 'build cli credentials sync update remote success',
CREDENTIALS_SYNC_UPDATE_REMOTE_FAIL = 'build cli credentials sync update remote fail',
}
export default {
logEvent(name: Event, properties: Record<string, any>) {
logEvent(name, properties);
},
};
@@ -0,0 +1,24 @@
import * as ExpoConfig from '@expo/config';
import assert from 'assert';
import fs from 'fs-extra';
export async function updateAppJsonConfigAsync(
{
projectDir,
exp,
}: {
projectDir: string;
exp: ExpoConfig.ExpoConfig;
},
modifyConfig: (config: any) => void
): Promise<void> {
const paths = ExpoConfig.getConfigFilePaths(projectDir);
assert(paths.staticConfigPath, "can't update dynamic config");
const rawStaticConfig = await fs.readJSON(paths.staticConfigPath);
rawStaticConfig.expo = rawStaticConfig.expo ?? {};
modifyConfig(rawStaticConfig.expo);
await fs.writeJson(paths.staticConfigPath, rawStaticConfig, { spaces: 2 });
modifyConfig(exp);
}
@@ -0,0 +1,16 @@
import { Platform } from '@expo/eas-build-job';
import { CredentialsSource } from '@expo/eas-json';
import chalk from 'chalk';
import Log from '../../log';
import { requestedPlatformDisplayNames } from '../constants';
export function logCredentialsSource(credentialsSource: CredentialsSource, platform: Platform) {
let message = `Using ${credentialsSource} ${requestedPlatformDisplayNames[platform]} credentials`;
if (credentialsSource === CredentialsSource.LOCAL) {
message += ` ${chalk.dim('(credentials.json)')}`;
} else if (credentialsSource === CredentialsSource.REMOTE) {
message += ` ${chalk.dim('(Expo server)')}`;
}
Log.succeed(message);
}
@@ -0,0 +1,130 @@
import chalk from 'chalk';
import {
AppPlatform,
BuildFragment,
BuildStatus as GraphQLBuildStatus,
} from '../../graphql/generated';
import formatFields from '../../utils/formatFields';
import { appPlatformDisplayNames } from '../constants';
import { getBuildLogsUrl } from './url';
export function formatGraphQLBuild(build: BuildFragment) {
const actor = getActorName(build);
const fields: { label: string; value?: string | null }[] = [
{ label: 'ID', value: build.id },
{
label: 'Platform',
value: appPlatformDisplayNames[build.platform],
},
{
label: 'Status',
get value() {
switch (build.status) {
case GraphQLBuildStatus.New:
return chalk.blue('new');
case GraphQLBuildStatus.InQueue:
return chalk.blue('in queue');
case GraphQLBuildStatus.InProgress:
return chalk.blue('in progress');
case GraphQLBuildStatus.Canceled:
return chalk.gray('canceled');
case GraphQLBuildStatus.Finished:
return chalk.green('finished');
case GraphQLBuildStatus.Errored:
return chalk.red('errored');
default:
return 'unknown';
}
},
},
{
label: 'Distribution',
value: build.distribution?.toLowerCase(),
},
{
label: 'Enterprise Provisioning',
value: build.iosEnterpriseProvisioning?.toLowerCase(),
},
{
label: 'Release Channel',
value: build.releaseChannel,
},
{
label: 'Channel',
value: build.channel,
},
{
label: 'SDK Version',
value: build.sdkVersion,
},
{
label: 'Runtime Version',
value: build.runtimeVersion,
},
{
label: 'Version',
value: build.appVersion,
},
{
label: build.platform === AppPlatform.Android ? 'Version code' : 'Build number',
value: build.appBuildVersion,
},
{
label: 'Commit',
value: build.gitCommitHash,
},
{
label: 'Logs',
value: getBuildLogsUrl(build),
},
{
label: 'Artifact',
get value() {
switch (build.status) {
case GraphQLBuildStatus.New:
case GraphQLBuildStatus.InQueue:
case GraphQLBuildStatus.InProgress:
return '<in progress>';
case GraphQLBuildStatus.Canceled:
case GraphQLBuildStatus.Errored:
return null;
case GraphQLBuildStatus.Finished: {
const url = build.artifacts?.buildUrl;
return url ? url : chalk.red('not found');
}
default:
return null;
}
},
},
{ label: 'Started at', value: new Date(build.createdAt).toLocaleString() },
{
label: 'Finished at',
value: [
GraphQLBuildStatus.New,
GraphQLBuildStatus.InQueue,
GraphQLBuildStatus.InProgress,
].includes(build.status)
? '<in progress>'
: new Date(build.updatedAt).toLocaleString(),
},
{ label: 'Started by', value: actor ?? 'unknown' },
];
const filteredFields = fields.filter(({ value }) => value !== undefined && value !== null) as {
label: string;
value: string;
}[];
return formatFields(filteredFields);
}
const getActorName = (build: BuildFragment): string => {
if (!build.initiatingActor) {
return 'unknown';
} else if (build.initiatingActor.__typename === 'User') {
return build.initiatingActor.username;
} else {
return build.initiatingActor.firstName ?? 'unknown';
}
};
@@ -0,0 +1,109 @@
import assert from 'assert';
import chalk from 'chalk';
import indentString from 'indent-string';
import qrcodeTerminal from 'qrcode-terminal';
import {
BuildError,
BuildFragment,
BuildStatus,
DistributionType,
EasBuildDeprecationInfo,
EasBuildDeprecationInfoType,
} from '../../graphql/generated';
import Log, { learnMore } from '../../log';
import { appPlatformDisplayNames, appPlatformEmojis } from '../constants';
import { getBuildLogsUrl, getInternalDistributionInstallUrl } from './url';
export function printLogsUrls(builds: BuildFragment[]): void {
if (builds.length === 1) {
Log.log(`Build details: ${chalk.underline(getBuildLogsUrl(builds[0]))}`);
} else {
builds.forEach(build => {
const logsUrl = getBuildLogsUrl(build);
Log.log(
`${appPlatformDisplayNames[build.platform]} build details: ${chalk.underline(logsUrl)}`
);
});
}
}
export function printBuildResults(builds: (BuildFragment | null)[]): void {
Log.newLine();
if (builds.length === 1) {
const [build] = builds;
assert(build, 'Build should be defined');
printBuildResult(build);
} else {
(builds.filter(i => i) as BuildFragment[]).forEach(build => printBuildResult(build));
}
}
function printBuildResult(build: BuildFragment): void {
Log.addNewLineIfNone();
if (build.status === BuildStatus.Errored) {
const userError = build.error;
Log.error(
`${appPlatformEmojis[build.platform]} ${
appPlatformDisplayNames[build.platform]
} build failed${userError ? ':' : ''}`
);
if (userError) {
printUserError(userError);
}
return;
}
if (build.status === BuildStatus.Canceled) {
Log.error(
`${appPlatformEmojis[build.platform]} ${
appPlatformDisplayNames[build.platform]
} build was canceled`
);
return;
}
if (build.distribution === DistributionType.Internal) {
const logsUrl = getBuildLogsUrl(build);
const installUrl = getInternalDistributionInstallUrl(build);
qrcodeTerminal.generate(installUrl, code => Log.log(`${indentString(code, 2)}\n`));
Log.log(
`${appPlatformEmojis[build.platform]} Open this link on your ${
appPlatformDisplayNames[build.platform]
} devices (or scan the QR code) to install the app:`
);
Log.log(`${chalk.underline(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)}`);
}
}
export function printDeprecationWarnings(deprecationInfo?: EasBuildDeprecationInfo | null): void {
if (!deprecationInfo) {
return;
}
if (deprecationInfo.type === EasBuildDeprecationInfoType.Internal) {
Log.warn('This command is using API that soon will be deprecated, please update eas-cli.');
Log.warn("Changes won't affect your project config.");
Log.warn(deprecationInfo.message);
} else if (deprecationInfo.type === EasBuildDeprecationInfoType.UserFacing) {
Log.warn('This command is using API that soon will be deprecated, please update eas-cli.');
Log.warn(
'There might be some changes necessary to your project config, latest eas-cli will provide more specific error messages.'
);
Log.warn(deprecationInfo.message);
} else {
Log.warn('An unexpected warning was encountered. Please report it as a bug:');
Log.warn(deprecationInfo);
}
}
export function printUserError(error: BuildError): void {
Log.error(error.message);
if (error.docsUrl) {
Log.error(learnMore(error.docsUrl, { dim: false }));
}
}
@@ -0,0 +1,123 @@
import chalk from 'chalk';
import fs from 'fs-extra';
import ora from 'ora';
import path from 'path';
import tar from 'tar';
import { v4 as uuidv4 } from 'uuid';
import Log from '../../log';
import { confirmAsync, promptAsync } from '../../prompts';
import { formatBytes } from '../../utils/files';
import { getTmpDirectory } from '../../utils/paths';
import { endTimer, formatMilliseconds, startTimer } from '../../utils/timer';
import vcs from '../../vcs';
export async function maybeBailOnRepoStatusAsync(): Promise<void> {
if (!(await vcs.hasUncommittedChangesAsync())) {
return;
}
Log.addNewLineIfNone();
Log.warn(`${chalk.bold('Warning!')} Your repository working tree is dirty.`);
Log.log(
`It's recommended to ${chalk.bold(
'commit all your changes before proceeding'
)}, so you can revert the changes made by this command if necessary.`
);
const answer = await confirmAsync({
message: `Would you like to proceed?`,
});
if (!answer) {
throw new Error('Please commit all changes. Aborting...');
}
}
export async function ensureRepoIsCleanAsync(nonInteractive = false): Promise<void> {
if (!(await vcs.hasUncommittedChangesAsync())) {
return;
}
Log.addNewLineIfNone();
Log.warn(`${chalk.bold('Warning!')} Your repository working tree is dirty.`);
Log.log(
`This operation needs to be run on a clean working tree, please ${chalk.bold(
'commit all your changes before proceeding'
)}.`
);
if (nonInteractive) {
throw new Error('Please commit all changes. Aborting...');
}
const answer = await confirmAsync({
message: `Commit changes to git?`,
});
if (answer) {
await commitPromptAsync({ commitAllFiles: true });
} else {
throw new Error('Please commit all changes. Aborting...');
}
}
export async function commitPromptAsync({
initialCommitMessage,
commitAllFiles,
}: {
initialCommitMessage?: string;
commitAllFiles?: boolean;
} = {}): Promise<void> {
const { message } = await promptAsync({
type: 'text',
name: 'message',
message: 'Commit message:',
initial: initialCommitMessage,
validate: (input: string) => input !== '',
});
await vcs.commitAsync({ commitAllFiles, commitMessage: message });
}
export async function makeProjectTarballAsync(): Promise<{ path: string; size: number }> {
const spinner = ora('Compressing project files');
await fs.mkdirp(getTmpDirectory());
const shallowClonePath = path.join(getTmpDirectory(), `${uuidv4()}-shallow-clone`);
const tarPath = path.join(getTmpDirectory(), `${uuidv4()}.tar.gz`);
// If the compression takes longer then a second, show the spinner.
// This can happen when the user has a lot of resources or doesn't ignore their CocoaPods.
// A basic project on a Mac can compress in roughly ~40ms.
// A fairly complex project without CocoaPods ignored can take up to 30s.
const timer = setTimeout(
() => {
spinner.start();
},
Log.isDebug ? 1 : 1000
);
// TODO: Possibly warn after more time about unoptimized assets.
const compressTimerLabel = 'makeProjectTarballAsync';
startTimer(compressTimerLabel);
try {
await vcs.makeShallowCopyAsync(shallowClonePath);
await tar.create({ cwd: shallowClonePath, file: tarPath, prefix: 'project', gzip: true }, [
'.',
]);
} catch (err) {
clearTimeout(timer);
if (spinner.isSpinning) {
spinner.fail();
}
throw err;
} finally {
await fs.remove(shallowClonePath);
}
clearTimeout(timer);
const { size } = await fs.stat(tarPath);
const duration = endTimer(compressTimerLabel);
if (spinner.isSpinning) {
const prettyTime = formatMilliseconds(duration);
spinner.succeed(
`Compressed project files ${chalk.dim(`${prettyTime} (${formatBytes(size)})`)}`
);
}
return { size, path: tarPath };
}
@@ -0,0 +1,5 @@
import template from 'lodash/template';
export function evaluateString(s: string, vars: Record<string, any>): string {
return template(s, { interpolate: /\$\(([\s\S]+?)\)/g })(vars);
}
@@ -0,0 +1,14 @@
import { ExpoConfig, getPackageJson } from '@expo/config';
export function isExpoUpdatesInstalled(projectDir: string) {
const packageJson = getPackageJson(projectDir);
return packageJson.dependencies && 'expo-updates' in packageJson.dependencies;
}
export function ensureValidVersions(exp: ExpoConfig): void {
if (!exp.runtimeVersion && !exp.sdkVersion) {
throw new Error(
"Couldn't find either 'runtimeVersion' or 'sdkVersion' to configure 'expo-updates'. Please specify at least one of these properties under the 'expo' key in 'app.json'"
);
}
}
+31
View File
@@ -0,0 +1,31 @@
import assert from 'assert';
import { getExpoApiBaseUrl, getExpoWebsiteBaseUrl } from '../../api';
import { AppPlatform, BuildFragment } from '../../graphql/generated';
export function getBuildLogsUrl(build: BuildFragment): string {
const { project } = build;
if (project.__typename === 'App') {
return `${getExpoWebsiteBaseUrl()}/accounts/${project.ownerAccount.name}/projects/${
project.name
}/builds/${build.id}`;
} else {
return `${getExpoWebsiteBaseUrl()}/builds/${build.id}`;
}
}
export function getArtifactUrl(artifactId: string): string {
return `${getExpoWebsiteBaseUrl()}/artifacts/${artifactId}`;
}
export function getInternalDistributionInstallUrl(build: BuildFragment): string {
if (build.platform === AppPlatform.Ios) {
return `itms-services://?action=download-manifest;url=${getExpoApiBaseUrl()}/--/api/v2/projects/${
build.project.id
}/builds/${build.id}/manifest.plist`;
}
assert(build.artifacts?.buildUrl, 'buildUrl is missing');
return build.artifacts.buildUrl;
}
@@ -0,0 +1,38 @@
import { Command } from '@oclif/command';
import {
AnalyticsEvent,
flushAsync as flushAnalyticsAsync,
initAsync as initAnalyticsAsync,
logEvent,
} from '../analytics';
import { getUserAsync } from '../user/User';
import { ensureLoggedInAsync } from '../user/actions';
export default abstract class EasCommand extends Command {
/**
* When user data is unavailable locally, determines if the command will
* force the user to log in
*/
protected requiresAuthentication = true;
async init() {
await initAnalyticsAsync();
if (this.requiresAuthentication) {
await ensureLoggedInAsync();
} else {
await getUserAsync();
}
logEvent(AnalyticsEvent.ACTION, {
// id is assigned by oclif in constructor based on the filepath:
// commands/submit === submit, commands/build/list === build:list
action: `eas ${this.id}`,
});
}
async finally(err: Error) {
await flushAnalyticsAsync();
return super.finally(err);
}
}
@@ -0,0 +1,78 @@
import { flushAsync, initAsync, logEvent } from '../../analytics';
import { jester as mockJester } from '../../credentials/__tests__/fixtures-constants';
import { getUserAsync } from '../../user/User';
import { ensureLoggedInAsync } from '../../user/actions';
import EasCommand from '../EasCommand';
import TestEasCommand from './TestEasCommand';
describe(EasCommand.name, () => {
beforeAll(() => {
TestEasCommand.prototype.authValue = jest.fn().mockImplementation(() => true);
jest.mock('../../user/User', () => ({ getUserAsync: jest.fn(() => mockJester) }));
jest.mock('../../user/actions', () => ({ ensureLoggedInAsync: jest.fn(() => mockJester) }));
jest.mock('../../analytics', () => {
const { AnalyticsEvent } = jest.requireActual('../../analytics');
return {
AnalyticsEvent,
logEvent: jest.fn(),
initAsync: jest.fn(),
flushAsync: jest.fn(),
};
});
});
afterEach(() => {
(ensureLoggedInAsync as jest.Mock).mockClear();
(initAsync as jest.Mock).mockClear();
(flushAsync as jest.Mock).mockClear();
(logEvent as jest.Mock).mockClear();
});
describe('without exceptions', () => {
it('ensures the user is logged in', async () => {
await TestEasCommand.run();
expect(ensureLoggedInAsync).toHaveReturnedWith(mockJester);
});
it('ensures the user data is read from cache', async () => {
(TestEasCommand.prototype.authValue as jest.Mock).mockImplementationOnce(() => false);
await TestEasCommand.run();
expect(getUserAsync).toHaveReturnedWith(mockJester);
});
it('initializes analytics', async () => {
await TestEasCommand.run();
expect(initAsync).toHaveBeenCalled();
});
it('flushes analytics', async () => {
await TestEasCommand.run();
expect(flushAsync).toHaveBeenCalled();
});
it('logs events', async () => {
await TestEasCommand.run();
expect(logEvent).toHaveBeenCalledWith('action', {
action: `eas ${TestEasCommand.id}`,
});
});
});
describe('after exceptions', () => {
it('flushes analytics', async () => {
try {
await TestEasCommand.run().then(() => {
throw new Error('foo');
});
} catch (error) {}
expect(flushAsync).toHaveBeenCalled();
});
});
});
@@ -0,0 +1,13 @@
import EasCommand from '../EasCommand';
export default class TestEasCommand extends EasCommand {
requiresAuthentication = this.authValue();
authValue() {
return false;
}
async run() {}
}
TestEasCommand.id = 'TestEasCommand'; // normally oclif will assign ids, but b/c this is located outside the commands folder it will not
@@ -0,0 +1,16 @@
import { Command } from '@oclif/command';
import Log from '../../log';
import { showLoginPromptAsync } from '../../user/actions';
export default class AccountLogin extends Command {
static description = 'log in with your Expo account';
static aliases = ['login'];
async run() {
Log.log('Log in to EAS');
await showLoginPromptAsync();
Log.log('Logged in');
}
}
@@ -0,0 +1,15 @@
import { Command } from '@oclif/command';
import Log from '../../log';
import { logoutAsync } from '../../user/User';
export default class AccountLogout extends Command {
static description = 'log out';
static aliases = ['logout'];
async run() {
await logoutAsync();
Log.log('Logged out');
}
}
@@ -0,0 +1,21 @@
import { Command } from '@oclif/command';
import chalk from 'chalk';
import Log from '../../log';
import { getActorDisplayName, getUserAsync } from '../../user/User';
export default class AccountView extends Command {
static description = 'show the username you are logged in as';
static aliases = ['whoami'];
async run() {
const user = await getUserAsync();
if (user) {
Log.log(chalk.green(getActorDisplayName(user)));
} else {
Log.warn('Not logged in');
process.exit(1);
}
}
}
@@ -0,0 +1,25 @@
import { Command } from '@oclif/command';
import Log from '../log';
import UserSettings from '../user/UserSettings';
export default class AnalyticsView extends Command {
static description = 'View or change analytics settings';
static args = [{ name: 'STATUS', options: ['on', 'off'] }];
async run() {
const { STATUS: status } = this.parse(AnalyticsView).args;
if (status) {
await UserSettings.setAsync('amplitudeEnabled', status === 'on');
Log.withTick(`${status === 'on' ? 'Enabling' : 'Disabling'} analytics.`);
} else {
const amplitudeEnabled = await UserSettings.getAsync('amplitudeEnabled', null);
Log.log(
`Analytics are ${
amplitudeEnabled === false ? 'disabled' : 'enabled'
} on this eas-cli installation.`
);
}
}
}
@@ -0,0 +1,112 @@
import { getConfig } from '@expo/config';
import { Command, flags } from '@oclif/command';
import chalk from 'chalk';
import gql from 'graphql-tag';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import {
CreateUpdateBranchForAppMutation,
CreateUpdateBranchForAppMutationVariables,
UpdateBranch,
} from '../../graphql/generated';
import Log from '../../log';
import {
findProjectRootAsync,
getProjectFullNameAsync,
getProjectIdAsync,
} from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import vcs from '../../vcs';
export async function createUpdateBranchOnAppAsync({
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 Command {
static hidden = true;
static description = 'Create a branch on the current project.';
static args = [
{
name: 'name',
required: false,
description: 'Name of the branch to create',
},
];
static flags = {
json: flags.boolean({
description: 'return a json with the new branch ID and name.',
default: false,
}),
};
async run() {
let {
args: { name },
flags,
} = this.parse(BranchCreate);
const projectDir = await findProjectRootAsync(process.cwd());
if (!projectDir) {
throw new Error('Please run this command inside a project directory.');
}
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const fullName = await getProjectFullNameAsync(exp);
const projectId = await getProjectIdAsync(exp);
if (!name) {
const validationMessage = 'Branch name may not be empty.';
if (flags.json) {
throw new Error(validationMessage);
}
({ name } = await promptAsync({
type: 'text',
name: 'name',
message: 'Please name the branch:',
initial:
(await vcs.getBranchNameAsync()) || `branch-${Math.random().toString(36).substr(2, 4)}`,
validate: value => (value ? true : validationMessage),
}));
}
const newBranch = await createUpdateBranchOnAppAsync({ appId: projectId, name });
if (flags.json) {
Log.log(newBranch);
return;
}
Log.withTick(
`️Created a new branch: ${chalk.bold(newBranch.name)} on project ${chalk.bold(fullName)}.`
);
}
}
@@ -0,0 +1,153 @@
import { getConfig } from '@expo/config';
import { Command, flags } from '@oclif/command';
import chalk from 'chalk';
import gql from 'graphql-tag';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import {
DeleteUpdateBranchMutation,
DeleteUpdateBranchMutationVariables,
DeleteUpdateBranchResult,
GetBranchInfoQuery,
GetBranchInfoQueryVariables,
} from '../../graphql/generated';
import Log from '../../log';
import {
findProjectRootAsync,
getProjectFullNameAsync,
getProjectIdAsync,
} from '../../project/projectUtils';
import { promptAsync, toggleConfirmAsync } from '../../prompts';
async function getBranchInfoAsync({
appId,
name,
}: GetBranchInfoQueryVariables): Promise<GetBranchInfoQuery> {
const data = await withErrorHandlingAsync(
graphqlClient
.query<GetBranchInfoQuery, GetBranchInfoQueryVariables>(
gql`
query GetBranchInfo($appId: String!, $name: String!) {
app {
byId(appId: $appId) {
id
updateBranchByName(name: $name) {
id
name
}
}
}
}
`,
{
appId,
name,
}
)
.toPromise()
);
return data;
}
async function deleteBranchOnAppAsync({
branchId,
}: DeleteUpdateBranchMutationVariables): Promise<DeleteUpdateBranchResult> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<DeleteUpdateBranchMutation, DeleteUpdateBranchMutationVariables>(
gql`
mutation DeleteUpdateBranch($branchId: ID!) {
updateBranch {
deleteUpdateBranch(branchId: $branchId) {
id
}
}
}
`,
{
branchId,
}
)
.toPromise()
);
return data.updateBranch.deleteUpdateBranch;
}
export default class BranchDelete extends Command {
static hidden = true;
static description = 'Republish an update group';
static args = [
{
name: 'name',
required: false,
description: 'Name of the branch to delete',
},
];
static flags = {
json: flags.boolean({
description: `return JSON with the edited branch's ID and name.`,
default: false,
}),
};
async run() {
let {
args: { name },
flags: { json: jsonFlag },
} = this.parse(BranchDelete);
const projectDir = await findProjectRootAsync(process.cwd());
if (!projectDir) {
throw new Error('Please run this command inside a project directory.');
}
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const fullName = await getProjectFullNameAsync(exp);
const projectId = await getProjectIdAsync(exp);
if (!name) {
const validationMessage = 'branch name may not be empty.';
if (jsonFlag) {
throw new Error(validationMessage);
}
({ name } = await promptAsync({
type: 'text',
name: 'name',
message: 'Please enter the name of the branch to delete:',
validate: value => (value ? true : validationMessage),
}));
}
const data = await getBranchInfoAsync({ appId: projectId, name });
const branchId = data.app?.byId.updateBranchByName?.id;
if (!branchId) {
throw new Error(`Could not find branch ${name} on ${fullName}`);
}
if (!jsonFlag) {
Log.addNewLineIfNone();
Log.warn(
`You are about to permamently delete branch: "${name}" and all of the updates published on it.` +
`\nThis action is irreversible.`
);
Log.newLine();
const confirmed = await toggleConfirmAsync({ message: 'Are you sure you wish to proceed?' });
if (!confirmed) {
Log.error(`Cancelled deletion of branch: "${name}".`);
process.exit(1);
}
}
const deletionResult = await deleteBranchOnAppAsync({
branchId,
});
if (jsonFlag) {
Log.log(JSON.stringify(deletionResult));
}
Log.withTick(
`️Deleted branch "${name}" and all of its updates on project ${chalk.bold(fullName)}.`
);
}
}
@@ -0,0 +1,112 @@
import { getConfig } from '@expo/config';
import { Command, flags } from '@oclif/command';
import chalk from 'chalk';
import CliTable from 'cli-table3';
import gql from 'graphql-tag';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import { BranchesByAppQuery, BranchesByAppQueryVariables } from '../../graphql/generated';
import Log from '../../log';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { UPDATE_COLUMNS, formatUpdate, getPlatformsForGroup } from '../../update/utils';
const BRANCHES_LIMIT = 10_000;
export async function listBranchesAsync({ projectId }: { projectId: string }) {
const data = await withErrorHandlingAsync(
graphqlClient
.query<BranchesByAppQuery, BranchesByAppQueryVariables>(
gql`
query BranchesByAppQuery($appId: String!, $limit: Int!) {
app {
byId(appId: $appId) {
id
updateBranches(offset: 0, limit: $limit) {
id
name
updates(offset: 0, limit: 10) {
id
actor {
__typename
id
... on User {
username
}
... on Robot {
firstName
}
}
createdAt
message
runtimeVersion
group
platform
}
}
}
}
}
`,
{
appId: projectId,
limit: BRANCHES_LIMIT,
}
)
.toPromise()
);
return data?.app?.byId.updateBranches ?? [];
}
export default class BranchList extends Command {
static hidden = true;
static description = 'List all branches on this project.';
static flags = {
json: flags.boolean({
description: 'return output as JSON',
default: false,
}),
};
async run() {
const { flags } = this.parse(BranchList);
const projectDir = await findProjectRootAsync(process.cwd());
if (!projectDir) {
throw new Error('Please run this command inside a project directory.');
}
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const branches = await listBranchesAsync({ projectId });
if (flags.json) {
Log.log(JSON.stringify(branches, null, 2));
} else {
const table = new CliTable({
head: ['branch', ...UPDATE_COLUMNS],
wordWrap: true,
});
table.push(
...branches.map(branch => [
branch.name,
formatUpdate(branch.updates[0]),
branch.updates[0]?.runtimeVersion ?? 'N/A',
branch.updates[0]?.group ?? 'N/A',
getPlatformsForGroup({
updates: branch.updates,
group: branch.updates[0]?.group,
}),
])
);
Log.addNewLineIfNone();
Log.log(chalk.bold('Branches with their most recent update group:'));
Log.log(table.toString());
if (branches.length >= BRANCHES_LIMIT) {
Log.warn(`Showing first ${BRANCHES_LIMIT} branches, some results might be omitted.`);
}
}
}
}
@@ -0,0 +1,329 @@
import { getConfig, getDefaultTarget } from '@expo/config';
import { getRuntimeVersionForSDKVersion } from '@expo/sdk-runtime-versions';
import { Command, flags } from '@oclif/command';
import assert from 'assert';
import chalk from 'chalk';
import dateFormat from 'dateformat';
import gql from 'graphql-tag';
import { uniqBy } from 'lodash';
import ora from 'ora';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import {
Actor,
GetUpdateGroupAsyncQuery,
RootQueryUpdatesByGroupArgs,
Update,
UpdateInfoGroup,
} from '../../graphql/generated';
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
import Log from '../../log';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import {
PublishPlatform,
buildBundlesAsync,
buildUpdateInfoGroupAsync,
collectAssets,
uploadAssetsAsync,
} from '../../project/publish';
import { promptAsync, selectAsync } from '../../prompts';
import { formatUpdate } from '../../update/utils';
import formatFields from '../../utils/formatFields';
import vcs from '../../vcs';
import { listBranchesAsync } from './list';
import { viewUpdateBranchAsync } from './view';
export const defaultPublishPlatforms: PublishPlatform[] = ['android', 'ios'];
type PlatformFlag = PublishPlatform | 'all';
async function getUpdateGroupAsync({
group,
}: RootQueryUpdatesByGroupArgs): Promise<GetUpdateGroupAsyncQuery['updatesByGroup']> {
const { updatesByGroup } = await withErrorHandlingAsync(
graphqlClient
.query<GetUpdateGroupAsyncQuery, RootQueryUpdatesByGroupArgs>(
gql`
query getUpdateGroupAsync($group: ID!) {
updatesByGroup(group: $group) {
id
group
runtimeVersion
manifestFragment
platform
message
}
}
`,
{
group,
}
)
.toPromise()
);
return updatesByGroup;
}
export default class BranchPublish extends Command {
static hidden = true;
static description = 'Publish an update group to a branch.';
static args = [
{
name: 'name',
description: 'Name of the branch to publish on',
},
];
static flags = {
message: flags.string({
description: 'Short message describing the updates.',
required: false,
}),
republish: flags.boolean({
description: 'republish an update group',
exclusive: ['input-dir', 'skip-bundler'],
}),
group: flags.string({
description: 'update group to republish',
exclusive: ['input-dir', 'skip-bundler'],
}),
'input-dir': flags.string({
description: 'location of the bundle',
default: 'dist',
required: false,
}),
'skip-bundler': flags.boolean({
description: `skip running Expo CLI to bundle the app before publishing`,
default: false,
}),
platform: flags.enum({
char: 'p',
description: `Only publish to a single platform`,
options: [...defaultPublishPlatforms, 'all'],
default: 'all',
required: false,
}),
json: flags.boolean({
description: `return a json with the new update group.`,
default: false,
}),
};
async run() {
let {
args: { name },
flags: {
json: jsonFlag,
message,
republish,
group,
'input-dir': inputDir,
'skip-bundler': skipBundler,
},
} = this.parse(BranchPublish);
const platformFlag = this.parse(BranchPublish).flags.platform as PlatformFlag;
// If a group was specified, that means we are republishing it.
republish = group ? true : republish;
const projectDir = await findProjectRootAsync(process.cwd());
if (!projectDir) {
throw new Error('Please run this command inside a project directory.');
}
const { exp } = getConfig(projectDir, {
skipSDKVersionRequirement: true,
isPublicConfig: true,
});
let { runtimeVersion, sdkVersion } = exp;
// When a SDK version is supplied instead of a runtime version and we're in the managed workflow
// construct the runtimeVersion with special meaning indicating that the runtime is an
// Expo SDK preset runtime that can be launched in Expo Go.
const isManagedProject = getDefaultTarget(projectDir) === 'managed';
if (!runtimeVersion && sdkVersion && isManagedProject) {
Log.withTick('Generating runtime version from sdk version');
runtimeVersion = getRuntimeVersionForSDKVersion(sdkVersion);
}
if (!runtimeVersion) {
throw new Error(
"Couldn't find 'runtimeVersion'. Please specify it under the 'expo' key in 'app.json'"
);
}
const projectId = await getProjectIdAsync(exp);
if (!name) {
const validationMessage = 'branch name may not be empty.';
if (jsonFlag) {
throw new Error(validationMessage);
}
const branches = await listBranchesAsync({ projectId });
name = await selectAsync<string>(
'which branch would you like to publish on?',
branches.map(branch => {
return {
title: `${branch.name} ${chalk.grey(
`- current update: ${formatUpdate(branch.updates[0])}`
)}`,
value: branch.name,
};
})
);
}
assert(name, 'branch name must be specified.');
const { id: branchId, updates } = await viewUpdateBranchAsync({
appId: projectId,
name,
});
let updateInfoGroup: UpdateInfoGroup = {};
let oldMessage: string, oldRuntimeVersion: string;
if (republish) {
// If we are republishing, we don't need to worry about building the bundle or uploading the assets.
// Instead we get the `updateInfoGroup` from the update we wish to republish.
let updatesToRepublish: Pick<
Update,
'group' | 'message' | 'runtimeVersion' | 'manifestFragment' | 'platform'
>[];
if (group) {
updatesToRepublish = await getUpdateGroupAsync({ group });
} else {
// Drop into interactive mode if the user has not specified an update group to republish.
if (jsonFlag) {
throw new Error('You must specify the update group to republish.');
}
const updateGroups = uniqBy(updates, u => u.group)
.filter(update => {
// Only show groups that have updates on the specified platform(s).
return platformFlag === 'all' || update.platform === platformFlag;
})
.map(update => ({
title: formatUpdateTitle(update),
value: update.group,
}));
if (updateGroups.length === 0) {
throw new Error(
`There are no updates on branch "${name}" published on the platform(s) ${platformFlag}. Did you mean to publish a new update instead?`
);
}
const selectedUpdateGroup = await selectAsync<string>(
'which update would you like to republish?',
updateGroups
);
updatesToRepublish = updates.filter(update => update.group === selectedUpdateGroup);
}
const updatesToRepublishFilteredByPlatform = updatesToRepublish.filter(
// Only republish to the specified platforms
update => platformFlag === 'all' || update.platform === platformFlag
);
if (updatesToRepublishFilteredByPlatform.length === 0) {
throw new Error(
`There are no updates on branch "${name}" published on the platform(s) "${platformFlag}" with group ID "${
group ? group : updatesToRepublish[0].group
}". Did you mean to publish a new update instead?`
);
}
let publicationPlatformMessage: string;
if (platformFlag === 'all') {
if (updatesToRepublishFilteredByPlatform.length !== defaultPublishPlatforms.length) {
Log.warn(`You are republishing an update that wasn't published for all platforms.`);
}
publicationPlatformMessage = `The republished update will appear on the same plaforms it was originally published on: ${updatesToRepublishFilteredByPlatform
.map(update => update.platform)
.join(',')}`;
} else {
publicationPlatformMessage = `The republished update will appear only on: ${platformFlag}`;
}
Log.withTick(publicationPlatformMessage);
for (const update of updatesToRepublishFilteredByPlatform) {
const { manifestFragment } = update;
const platform = update.platform as PublishPlatform;
updateInfoGroup[platform] = JSON.parse(manifestFragment);
}
// These are the same for each member of an update group
group = updatesToRepublishFilteredByPlatform[0].group;
oldMessage = updatesToRepublishFilteredByPlatform[0].message ?? '';
oldRuntimeVersion = updatesToRepublishFilteredByPlatform[0].runtimeVersion;
} else {
// build bundle and upload assets for a new publish
if (!skipBundler) {
await buildBundlesAsync({ projectDir, inputDir });
}
const assetSpinner = ora('Uploading assets...').start();
try {
const platforms = platformFlag === 'all' ? defaultPublishPlatforms : [platformFlag];
const assets = collectAssets({ inputDir: inputDir!, platforms });
await uploadAssetsAsync(assets);
updateInfoGroup = await buildUpdateInfoGroupAsync(assets, exp);
assetSpinner.succeed('Uploaded assets!');
} catch (e) {
assetSpinner.fail('Failed to upload assets');
throw e;
}
}
if (!message) {
const validationMessage = 'publish message may not be empty.';
if (jsonFlag) {
throw new Error(validationMessage);
}
({ publishMessage: message } = await promptAsync({
type: 'text',
name: 'publishMessage',
message: `Please enter a publication message.`,
initial: republish
? `Republish "${oldMessage!}" - group: ${group}`
: (await vcs.getLastCommitMessageAsync())?.trim(),
validate: value => (value ? true : validationMessage),
}));
}
let newUpdateGroup;
const publishSpinner = ora('Publishing...').start();
try {
newUpdateGroup = await PublishMutation.publishUpdateGroupAsync({
branchId,
updateInfoGroup,
runtimeVersion: republish ? oldRuntimeVersion! : runtimeVersion,
message,
});
publishSpinner.succeed('Published!');
} catch (e) {
publishSpinner.fail('Failed to published updates');
throw e;
}
if (jsonFlag) {
Log.log(JSON.stringify(newUpdateGroup));
} else {
Log.log(
formatFields([
{ label: 'branch', value: name },
{ label: 'runtime version', value: runtimeVersion },
{ label: 'update group ID', value: newUpdateGroup.group },
{ label: 'message', value: message },
])
);
}
}
}
function formatUpdateTitle(
update: Pick<Update, 'message' | 'createdAt' | 'runtimeVersion'> & {
actor?: Pick<Actor, 'firstName'> | null;
}
): string {
const { message, createdAt, actor, runtimeVersion } = update;
return `[${dateFormat(createdAt, 'mmm dd HH:MM')} by ${
actor?.firstName ?? 'unknown'
}, runtimeVersion: ${runtimeVersion}] ${message}`;
}
@@ -0,0 +1,127 @@
import { getConfig } from '@expo/config';
import { Command, flags } from '@oclif/command';
import chalk from 'chalk';
import gql from 'graphql-tag';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import {
EditUpdateBranchInput,
EditUpdateBranchMutation,
EditUpdateBranchMutationVariables,
UpdateBranch,
} from '../../graphql/generated';
import Log from '../../log';
import {
findProjectRootAsync,
getProjectFullNameAsync,
getProjectIdAsync,
} from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
async function renameUpdateBranchOnAppAsync({
appId,
name,
newName,
}: EditUpdateBranchInput): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<EditUpdateBranchMutation, EditUpdateBranchMutationVariables>(
gql`
mutation EditUpdateBranch($input: EditUpdateBranchInput!) {
updateBranch {
editUpdateBranch(input: $input) {
id
name
}
}
}
`,
{
input: {
appId,
name,
newName,
},
}
)
.toPromise()
);
return data.updateBranch.editUpdateBranch;
}
export default class BranchRename extends Command {
static hidden = true;
static description = 'Rename a branch.';
static flags = {
from: flags.string({
description: 'current name of the branch.',
required: false,
}),
to: flags.string({
description: 'new name of the branch.',
required: false,
}),
json: flags.boolean({
description: `return a json with the edited branch's ID and name.`,
default: false,
}),
};
async run() {
let {
flags: { json: jsonFlag, from: currentName, to: newName },
} = this.parse(BranchRename);
const projectDir = await findProjectRootAsync(process.cwd());
if (!projectDir) {
throw new Error('Please run this command inside a project directory.');
}
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const fullName = await getProjectFullNameAsync(exp);
const projectId = await getProjectIdAsync(exp);
if (!currentName) {
const validationMessage = 'current name may not be empty.';
if (jsonFlag) {
throw new Error(validationMessage);
}
({ currentName } = await promptAsync({
type: 'text',
name: 'currentName',
message: 'Please enter the current name of the branch to rename:',
validate: value => (value ? true : validationMessage),
}));
}
if (!newName) {
const validationMessage = 'new name may not be empty.';
if (jsonFlag) {
throw new Error(validationMessage);
}
({ newName } = await promptAsync({
type: 'text',
name: 'newName',
message: `Please rename ${currentName}`,
validate: value => (value ? true : validationMessage),
}));
}
const editedBranch = await renameUpdateBranchOnAppAsync({
appId: projectId,
name: currentName!,
newName: newName!,
});
if (jsonFlag) {
Log.log(JSON.stringify(editedBranch));
return;
}
Log.withTick(
`️Renamed branch from ${currentName} to ${chalk.bold(
editedBranch.name
)} on project ${chalk.bold(fullName)}.`
);
}
}
@@ -0,0 +1,177 @@
import { getConfig } from '@expo/config';
import { Command, flags } from '@oclif/command';
import chalk from 'chalk';
import Table from 'cli-table3';
import gql from 'graphql-tag';
import { groupBy } from 'lodash';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import {
Maybe,
Robot,
Update,
UpdateBranch,
User,
ViewBranchQuery,
ViewBranchQueryVariables,
} from '../../graphql/generated';
import Log from '../../log';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { UPDATE_COLUMNS, formatUpdate, getPlatformsForGroup } from '../../update/utils';
import formatFields from '../../utils/formatFields';
const PAGE_LIMIT = 10_000;
export async function viewUpdateBranchAsync({
appId,
name,
}: Pick<ViewBranchQueryVariables, 'appId' | 'name'>): Promise<
Pick<UpdateBranch, 'id' | 'name'> & {
updates: (Pick<
Update,
'id' | 'group' | 'message' | 'createdAt' | 'runtimeVersion' | 'platform' | 'manifestFragment'
> & {
actor?: Maybe<Pick<User, 'firstName' | 'id'> | Pick<Robot, 'firstName' | 'id'>>;
})[];
}
> {
const data = await withErrorHandlingAsync(
graphqlClient
.query<ViewBranchQuery, ViewBranchQueryVariables>(
gql`
query ViewBranch($appId: String!, $name: String!, $limit: Int!) {
app {
byId(appId: $appId) {
id
updateBranchByName(name: $name) {
id
name
updates(offset: 0, limit: $limit) {
id
group
message
createdAt
runtimeVersion
platform
manifestFragment
actor {
id
... on User {
username
}
... on Robot {
firstName
}
}
}
}
}
}
}
`,
{
appId,
name,
limit: PAGE_LIMIT,
}
)
.toPromise()
);
const updateBranch = data.app?.byId.updateBranchByName;
if (!updateBranch) {
throw new Error(`Could not find branch "${name}"`);
}
return updateBranch;
}
export default class BranchView extends Command {
static hidden = true;
static description = 'View a branch.';
static args = [
{
name: 'name',
required: false,
description: 'Name of the branch to view',
},
];
static flags = {
json: flags.boolean({
description: `return a json with the branch's ID name and recent update groups.`,
default: false,
}),
};
async run() {
let {
args: { name },
flags: { json: jsonFlag },
} = this.parse(BranchView);
const projectDir = await findProjectRootAsync(process.cwd());
if (!projectDir) {
throw new Error('Please run this command inside a project directory.');
}
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
if (!name) {
const validationMessage = 'Branch name may not be empty.';
if (jsonFlag) {
throw new Error(validationMessage);
}
({ name } = await promptAsync({
type: 'text',
name: 'name',
message: 'Please enter the name of the branch to view:',
validate: value => (value ? true : validationMessage),
}));
}
const UpdateBranch = await viewUpdateBranchAsync({
appId: projectId,
name,
});
const updates = Object.values(groupBy(UpdateBranch.updates, u => u.group)).map(
group => group[0]
);
if (jsonFlag) {
Log.log(JSON.stringify({ ...UpdateBranch, updates }));
return;
}
const groupTable = new Table({
head: UPDATE_COLUMNS,
wordWrap: true,
});
for (const update of updates) {
groupTable.push([
formatUpdate(update),
update.runtimeVersion,
update.group,
getPlatformsForGroup({
updates: UpdateBranch.updates,
group: update.group,
}),
]);
}
Log.addNewLineIfNone();
Log.log(chalk.bold('Branch:'));
Log.log(
formatFields([
{ label: 'Name', value: UpdateBranch.name },
{ label: 'ID', value: UpdateBranch.id },
])
);
Log.addNewLineIfNone();
Log.log(chalk.bold('Recently published update groups:'));
Log.log(groupTable.toString());
}
}
@@ -0,0 +1,148 @@
import { getConfig } from '@expo/config';
import { Command } from '@oclif/command';
import chalk from 'chalk';
import gql from 'graphql-tag';
import ora from 'ora';
import { appPlatformEmojis } from '../../build/constants';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import {
Build,
BuildStatus,
CancelBuildMutation,
CancelBuildMutationVariables,
} from '../../graphql/generated';
import { BuildQuery } from '../../graphql/queries/BuildQuery';
import Log from '../../log';
import {
findProjectRootAsync,
getProjectFullNameAsync,
getProjectIdAsync,
} from '../../project/projectUtils';
import { confirmAsync, selectAsync } from '../../prompts';
async function cancelBuildAsync(buildId: string): Promise<Pick<Build, 'id' | 'status'>> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<CancelBuildMutation, CancelBuildMutationVariables>(
gql`
mutation CancelBuildMutation($buildId: ID!) {
build(buildId: $buildId) {
cancel {
id
status
}
}
}
`,
{ buildId }
)
.toPromise()
);
return data.build!.cancel;
}
function formatUnfinishedBuild(
build: Pick<Build, 'id' | 'platform' | 'status' | 'createdAt'>
): string {
const platform = appPlatformEmojis[build.platform];
const startTime = new Date(build.createdAt).toLocaleString();
let statusText: string;
if (build.status === BuildStatus.New) {
statusText = 'new';
} else if (build.status === BuildStatus.InQueue) {
statusText = 'in queue';
} else {
statusText = 'in progress';
}
const status = chalk.blue(statusText);
return `${platform} Started at: ${startTime}, Status: ${status}, Id: ${build.id}`;
}
async function selectBuildToCancelAsync(
projectId: string,
projectFullName: string
): Promise<string | null> {
const spinner = ora().start('Fetching the uncompleted builds…');
let builds;
try {
const [newBuilds, inQueueBuilds, inProgressBuilds] = await Promise.all([
BuildQuery.allForAppAsync(projectId, { filter: { status: BuildStatus.New } }),
BuildQuery.allForAppAsync(projectId, { filter: { status: BuildStatus.InQueue } }),
BuildQuery.allForAppAsync(projectId, { filter: { status: BuildStatus.InProgress } }),
]);
spinner.stop();
builds = [...newBuilds, ...inQueueBuilds, ...inProgressBuilds];
} catch (error) {
spinner.fail(
`Something went wrong and we couldn't fetch the builds for the project ${projectFullName}.`
);
throw error;
}
if (builds.length === 0) {
Log.warn(`There aren't any uncompleted builds for the project ${projectFullName}.`);
return null;
} else if (builds.length === 1) {
Log.log('Found one build');
Log.log(formatUnfinishedBuild(builds[0]));
await confirmAsync({
message: 'Do you want to cancel it?',
});
return builds[0].id;
} else {
const buildId = await selectAsync<string>(
'Which build do you want to cancel?',
builds.map(build => ({
title: formatUnfinishedBuild(build),
value: build.id,
}))
);
return buildId;
}
}
async function ensureBuildExistsAsync(buildId: string): Promise<void> {
try {
await BuildQuery.byIdAsync(buildId);
} catch (err) {
throw new Error(`Couldn't find a build matching the id ${buildId}`);
}
}
export default class BuildCancel extends Command {
static description = 'Cancel a build.';
static args = [{ name: 'BUILD_ID' }];
async run() {
const { BUILD_ID: buildIdFromArg } = this.parse(BuildCancel).args;
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const projectFullName = await getProjectFullNameAsync(exp);
if (buildIdFromArg) {
await ensureBuildExistsAsync(buildIdFromArg);
}
const buildId = buildIdFromArg || (await selectBuildToCancelAsync(projectId, projectFullName));
if (!buildId) {
return;
}
const spinner = ora().start('Canceling the build…');
try {
const { status } = await cancelBuildAsync(buildId);
if (status === BuildStatus.Canceled) {
spinner.succeed('Build canceled');
} else {
spinner.text = 'Build is already completed';
spinner.stopAndPersist();
}
} catch (error) {
spinner.fail(`Something went wrong and we couldn't cancel your build ${buildId}`);
throw error;
}
}
}
@@ -0,0 +1,85 @@
import { Command, flags } from '@oclif/command';
import chalk from 'chalk';
import { configureAsync } from '../../build/configure';
import { RequestedPlatform } from '../../build/types';
import Log, { learnMore } from '../../log';
import { findProjectRootAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { ensureLoggedInAsync } from '../../user/actions';
export default class BuildConfigure extends Command {
static description = 'Configure the project to support EAS Build.';
static flags = {
platform: flags.enum({
description: 'Platform to configure',
char: 'p',
options: ['android', 'ios', 'all'],
}),
};
async run() {
const { flags } = this.parse(BuildConfigure);
Log.log(
'💡 The following process will configure your iOS and/or Android project to be compatible with EAS Build. These changes only apply to your local project files and you can safely revert them at any time.'
);
Log.newLine();
const platform =
(flags.platform as RequestedPlatform | undefined) ?? (await promptForPlatformAsync());
await ensureLoggedInAsync();
await configureAsync({
platform,
projectDir: (await findProjectRootAsync()) ?? process.cwd(),
});
Log.newLine();
logSuccess(platform);
}
}
function logSuccess(platform: RequestedPlatform) {
let platformsText = 'iOS and Android projects are';
let storesText = 'the Apple App Store or Google Play Store';
if (platform === 'android') {
platformsText = 'Android project is';
storesText = 'the Google Play Store';
} else if (platform === 'ios') {
platformsText = 'iOS project is';
storesText = 'the Apple App Store';
}
Log.log(`🎉 Your ${platformsText} ready to build.
- Run ${chalk.bold('eas build')} when you are ready to create your first build.
- Once the build is completed, run ${chalk.bold('eas submit')} to upload the app to ${storesText}
- ${learnMore('https://docs.expo.io/build/introduction', {
learnMoreMessage: 'Learn more about other capabilities of EAS Build',
})}`);
}
async function promptForPlatformAsync(): Promise<RequestedPlatform> {
const { platform } = await promptAsync({
type: 'select',
message: 'Which platforms would you like to configure for EAS Build?',
name: 'platform',
choices: [
{
title: 'All',
value: RequestedPlatform.All,
},
{
title: 'iOS',
value: RequestedPlatform.Ios,
},
{
title: 'Android',
value: RequestedPlatform.Android,
},
],
});
return platform;
}
+257 -6
View File
@@ -1,11 +1,262 @@
import { Command } from '@oclif/command';
import { Workflow } from '@expo/eas-build-job';
import {
EasJsonReader,
hasMismatchedExtendsAsync,
isUsingDeprecatedFormatAsync,
migrateAsync,
} from '@expo/eas-json';
import { flags } from '@oclif/command';
import { error, exit } from '@oclif/errors';
export default class BuildIndex extends Command {
static description = 'build an app binary for your project';
import { prepareAndroidBuildAsync } from '../../build/android/build';
import { BuildRequestSender, waitForBuildEndAsync } from '../../build/build';
import { ensureProjectConfiguredAsync } from '../../build/configure';
import { BuildContext, createBuildContextAsync } from '../../build/context';
import { prepareIosBuildAsync } from '../../build/ios/build';
import { Platform, RequestedPlatform } from '../../build/types';
import { printBuildResults, printLogsUrls } from '../../build/utils/printBuildInfo';
import { ensureRepoIsCleanAsync } from '../../build/utils/repository';
import EasCommand from '../../commandUtils/EasCommand';
import { BuildFragment, BuildStatus } from '../../graphql/generated';
import Log from '../../log';
import {
EAS_UNAVAILABLE_MESSAGE,
isEasEnabledForProjectAsync,
} from '../../project/isEasEnabledForProject';
import { validateMetroConfigForManagedWorkflowAsync } from '../../project/metroConfig';
import { findProjectRootAsync } from '../../project/projectUtils';
import { confirmAsync, promptAsync } from '../../prompts';
import vcs from '../../vcs';
static flags = {};
interface RawBuildFlags {
platform?: string;
'skip-credentials-check': boolean;
'skip-project-configuration': boolean;
profile: string;
'non-interactive': boolean;
local: boolean;
wait: boolean;
'clear-cache': boolean;
}
async run() {
throw new Error('Not implemented');
interface BuildFlags {
requestedPlatform: RequestedPlatform;
skipProjectConfiguration: boolean;
profile: string;
nonInteractive: boolean;
local: boolean;
wait: boolean;
clearCache: boolean;
}
export default class Build extends EasCommand {
static description = 'Start a build';
static flags = {
platform: flags.enum({ char: 'p', options: ['android', 'ios', 'all'] }),
'skip-credentials-check': flags.boolean({
default: false,
hidden: true,
}),
'skip-project-configuration': flags.boolean({
default: false,
description: 'Skip project configuration',
}),
profile: flags.string({
default: 'release',
description: 'Name of the build profile from eas.json',
}),
'non-interactive': flags.boolean({
default: false,
description: 'Run command in --non-interactive mode',
}),
local: flags.boolean({
default: false,
description: 'Run build locally [experimental]',
}),
wait: flags.boolean({
default: true,
allowNo: true,
description: 'Wait for build(s) to complete',
}),
'clear-cache': flags.boolean({
default: false,
description: 'Clear cache before the build',
}),
};
async run(): Promise<void> {
const { flags: rawFlags } = this.parse(Build);
const flags = await this.sanitizeFlagsAsync(rawFlags);
const { requestedPlatform } = flags;
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
await vcs.ensureRepoExistsAsync();
await ensureRepoIsCleanAsync(flags.nonInteractive);
await ensureProjectConfiguredAsync(projectDir, requestedPlatform);
const easJsonReader = new EasJsonReader(projectDir);
const platformsToBuild = this.getPlatformsToBuild(requestedPlatform);
const startedBuilds: BuildFragment[] = [];
let metroConfigValidated = false;
for (const platform of platformsToBuild) {
const ctx = await createBuildContextAsync({
buildProfileName: flags.profile,
clearCache: flags.clearCache,
buildProfile: await easJsonReader.readBuildProfileAsync(flags.profile, platform),
local: flags.local,
nonInteractive: flags.nonInteractive,
platform,
projectDir,
skipProjectConfiguration: flags.skipProjectConfiguration,
});
if (ctx.workflow === Workflow.MANAGED && !metroConfigValidated) {
await validateMetroConfigForManagedWorkflowAsync(ctx);
metroConfigValidated = true;
}
if (!ctx.local && !(await isEasEnabledForProjectAsync(ctx.projectId))) {
error(EAS_UNAVAILABLE_MESSAGE, { exit: 1 });
}
const maybeBuild = await this.startBuildAsync(ctx);
if (maybeBuild) {
startedBuilds.push(maybeBuild);
}
}
if (flags.local) {
return;
}
Log.newLine();
printLogsUrls(startedBuilds);
Log.newLine();
if (flags.wait) {
const builds = await waitForBuildEndAsync(startedBuilds.map(build => build.id));
printBuildResults(builds);
this.exitWithNonZeroCodeIfSomeBuildsFailed(builds);
}
}
private async sanitizeFlagsAsync(flags: RawBuildFlags): Promise<BuildFlags> {
const nonInteractive = flags['non-interactive'];
if (!flags.platform && nonInteractive) {
throw new Error('--platform is required when building in non-interactive mode');
}
const requestedPlatform =
(flags.platform as RequestedPlatform | undefined) ?? (await this.promptForPlatformAsync());
if (flags.local) {
if (requestedPlatform === RequestedPlatform.All) {
error('Builds for multiple platforms are not supported with flag --local', { exit: 1 });
} else if (process.platform !== 'darwin' && requestedPlatform === RequestedPlatform.Ios) {
error('Unsupported platform, macOS is required to build apps for iOS', { exit: 1 });
}
}
if (flags['skip-credentials-check']) {
Log.warnDeprecatedFlag(
'skip-credentials-check',
'Build credentials validation is always skipped with the --non-interactive flag. You can also skip interactively.'
);
Log.newLine();
}
return {
requestedPlatform,
skipProjectConfiguration: flags['skip-project-configuration'],
profile: flags['profile'],
nonInteractive,
local: flags['local'],
wait: flags['wait'],
clearCache: flags['clear-cache'],
};
}
private async promptForPlatformAsync(): Promise<RequestedPlatform> {
const { platform } = await promptAsync({
type: 'select',
message: 'Build for platforms',
name: 'platform',
choices: [
{ title: 'All', value: RequestedPlatform.All },
{ title: 'iOS', value: RequestedPlatform.Ios },
{ title: 'Android', value: RequestedPlatform.Android },
],
});
return platform;
}
private getPlatformsToBuild(requestedPlatform: RequestedPlatform): Platform[] {
if (requestedPlatform === RequestedPlatform.All) {
return [Platform.ANDROID, Platform.IOS];
} else if (requestedPlatform === RequestedPlatform.Android) {
return [Platform.ANDROID];
} else {
return [Platform.IOS];
}
}
private async startBuildAsync(ctx: BuildContext<Platform>): Promise<BuildFragment | undefined> {
let sendBuildRequestAsync: BuildRequestSender;
if (ctx.platform === Platform.ANDROID) {
sendBuildRequestAsync = await prepareAndroidBuildAsync(ctx as BuildContext<Platform.ANDROID>);
} else {
sendBuildRequestAsync = await prepareIosBuildAsync(ctx as BuildContext<Platform.IOS>);
}
return await sendBuildRequestAsync();
}
private exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds: (BuildFragment | null)[]): void {
const failedBuilds = (maybeBuilds.filter(i => i) as BuildFragment[]).filter(
i => i.status === BuildStatus.Errored
);
if (failedBuilds.length > 0) {
exit(1);
}
}
}
export async function handleDeprecatedEasJsonAsync(
projectDir: string,
nonInteractive: boolean
): Promise<void> {
if (await isUsingDeprecatedFormatAsync(projectDir)) {
const hasMismatchedExtendsKeys = await hasMismatchedExtendsAsync(projectDir);
if (nonInteractive) {
Log.error('We detected that your eas.json is using a deprecated format.');
Log.error(
'We will convert it automatically if run this command without --non-interactive flag. Alternatively, you can update eas.json manually according to https://docs.expo.io/build/eas-json'
);
error('Unsupported eas.json format', { exit: 1 });
}
const confirm = await confirmAsync({
message:
'We detected that your eas.json is using a deprecated format, do you want to migrate to the new format?',
});
if (confirm) {
await migrateAsync(projectDir);
if (hasMismatchedExtendsKeys) {
Log.warn(
'"extends" keyword can only be migrated automatically to the new format if both Android and iOS profiles extend base profiles with the same names for both platforms'
);
Log.warn(
'Migration was successful, but you need to manually adjust the extend rules for your profiles'
);
error('Fix eas.json manually', { exit: 1 });
}
} else {
Log.error(
"Aborting, update your eas.json according to https://docs.expo.io/build/eas-json and run 'eas build' again"
);
error('Unsupported eas.json format', { exit: 1 });
}
}
}
+158
View File
@@ -0,0 +1,158 @@
import { getConfig } from '@expo/config';
import { Command, flags } from '@oclif/command';
import chalk from 'chalk';
import ora from 'ora';
import { BuildDistributionType, BuildStatus, RequestedPlatform } from '../../build/types';
import { formatGraphQLBuild } from '../../build/utils/formatBuild';
import {
AppPlatform,
DistributionType,
BuildStatus as GraphQLBuildStatus,
} from '../../graphql/generated';
import { BuildQuery } from '../../graphql/queries/BuildQuery';
import Log from '../../log';
import {
findProjectRootAsync,
getProjectFullNameAsync,
getProjectIdAsync,
} from '../../project/projectUtils';
export default class BuildList extends Command {
static description = 'list all builds for your project';
static flags = {
platform: flags.enum({
options: [RequestedPlatform.All, RequestedPlatform.Android, RequestedPlatform.Ios],
}),
status: flags.enum({
options: [
BuildStatus.NEW,
BuildStatus.IN_QUEUE,
BuildStatus.IN_PROGRESS,
BuildStatus.ERRORED,
BuildStatus.FINISHED,
BuildStatus.CANCELED,
],
}),
distribution: flags.enum({
options: [
BuildDistributionType.STORE,
BuildDistributionType.INTERNAL,
BuildDistributionType.SIMULATOR,
],
}),
channel: flags.string(),
appVersion: flags.string(),
appBuildVersion: flags.string(),
sdkVersion: flags.string(),
runtimeVersion: flags.string(),
appIdentifier: flags.string(),
buildProfile: flags.string(),
gitCommitHash: flags.string(),
limit: flags.integer(),
};
async run() {
const { flags } = this.parse(BuildList);
const {
platform: requestedPlatform,
status: buildStatus,
distribution: buildDistribution,
limit = 10,
} = flags;
const platform = toAppPlatform(requestedPlatform);
const graphqlBuildStatus = toGraphQLBuildStatus(buildStatus);
const graphqlBuildDistribution = toGraphQLBuildDistribution(buildDistribution);
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const projectName = await getProjectFullNameAsync(exp);
const spinner = ora().start('Fetching the build list for the project…');
try {
const builds = await BuildQuery.allForAppAsync(projectId, {
limit,
filter: {
platform,
status: graphqlBuildStatus,
distribution: graphqlBuildDistribution,
channel: flags.channel,
appVersion: flags.appVersion,
appBuildVersion: flags.appBuildVersion,
sdkVersion: flags.sdkVersion,
runtimeVersion: flags.runtimeVersion,
appIdentifier: flags.appIdentifier,
buildProfile: flags.buildProfile,
gitCommitHash: flags.gitCommitHash,
},
});
if (builds.length) {
if (platform || graphqlBuildStatus) {
spinner.succeed(
`Showing ${builds.length} matching builds for the project ${projectName}`
);
} else {
spinner.succeed(`Showing last ${builds.length} builds for the project ${projectName}`);
}
const list = builds
.map(build => formatGraphQLBuild(build))
.join(`\n\n${chalk.dim('———')}\n\n`);
Log.log(`\n${list}`);
} else {
spinner.fail(`Couldn't find any builds for the project ${projectName}`);
}
} catch (e) {
spinner.fail(`Something went wrong and we couldn't fetch the build list ${projectName}`);
throw e;
}
}
}
const toAppPlatform = (requestedPlatform?: RequestedPlatform): AppPlatform | undefined => {
if (!requestedPlatform || requestedPlatform === RequestedPlatform.All) {
return undefined;
} else if (requestedPlatform === RequestedPlatform.Android) {
return AppPlatform.Android;
} else {
return AppPlatform.Ios;
}
};
const toGraphQLBuildStatus = (buildStatus?: BuildStatus): GraphQLBuildStatus | undefined => {
if (!buildStatus) {
return undefined;
} else if (buildStatus === BuildStatus.NEW) {
return GraphQLBuildStatus.New;
} else if (buildStatus === BuildStatus.IN_QUEUE) {
return GraphQLBuildStatus.InQueue;
} else if (buildStatus === BuildStatus.IN_PROGRESS) {
return GraphQLBuildStatus.InProgress;
} else if (buildStatus === BuildStatus.ERRORED) {
return GraphQLBuildStatus.Errored;
} else if (buildStatus === BuildStatus.FINISHED) {
return GraphQLBuildStatus.Finished;
} else {
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;
}
};
@@ -1,15 +0,0 @@
import { Command, flags } from '@oclif/command';
export default class BuildStatus extends Command {
static description = 'get the status of the latest builds for your project';
static flags = {
platform: flags.enum({ options: ['all', 'android', 'ios'] }),
status: flags.enum({ options: ['in-queue', 'in-progress', 'errored', 'finished'] }),
};
async run() {
// const { flags } = this.parse(Add);
throw new Error('Not implemented');
}
}
Loaded 100 of 450 files, more files were not shown because too many files have changed in this diff. Show more