Compare commits

...
101 Commits
Author SHA1 Message Date
Dominik Sokal d4ffb07402 v1.2.0 2022-08-29 11:39:02 +02:00
Evan Bacon 0b2b6ddc14 feat(eas-cli): add 2022 Apple capabilities (#1307)
* feat(eas-cli): add 2022 Apple capabilities

* updated tests

* updated tests and third-party

* update CHANGELOG.md

* updated names

* Update CHANGELOG.md
2022-08-29 11:37:00 +02:00
Szymon Dziedzic 0cff5ef83f [eas-cli] warn about EAS outages (#1312)
* [eas-cli] warn users about EAS Build outages

* [eas-cli] warn users about EAS Submit outages

* [eas-cli] show warnings about EAS outages as a first thing when running build and submit commands

* [eas-cli] don't return null as a result of StatuspageServiceQuery

* [eas-cli] remove unneccessary if check

* [eas-cli] rename warnAboutEasOutagesAsync function to maybeWarnAboutEasOutagesAsync and remove please word from output

* [eas-cli] warn about outages when running `eas update` command

* [eas-cli] improve outage warning communicate

* [eas-cli] move maybeWarnAboutEasOutagesAsync call after flags and project dir validation

* [eas-cli] minor refactor after code review

* [eas-cli] change warning message on EAS outage

* [eas-cli] put platform selection and validation logic into ensurePlatformSelectedAsync function

* update CHANGELOG.md

* [eas-cli] use ensurePlatformSelectedAsync in submit command
2022-08-29 11:29:51 +02:00
Dominik Sokal 396c45269e add eas.json option to skip push notifications setup (#1315)
* add eas.json option to skip push notifications setup

* address PR feedback

* update CHANGELOG.md
2022-08-29 10:19:43 +02:00
Brent Vatne 98ad9798c5 Copy changes to EAS Submit output (#1318)
* Change e-mail -> email

* Update link to build to point to TestFlight

* update CHANGELOG.md
2022-08-29 09:30:20 +02:00
Dominik Sokal b4741293ba improve instructions on setting eas.projectId in app.json (#1316)
* improve instructions on setting eas.projectId in app.json

* address PR feedback

* update CHANGELOG.md
2022-08-26 09:49:41 +02:00
Dominik SokalandBrent Vatne 43a6aa826b [ENG-6125] print warning when eas-cli is installed as dependency (#1310)
* print warning when eas-cli is installed as dependency

* address PR feedback

* Apply suggestions from code review

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

* PR feedback

* Apply suggestions from code review

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

* update CHANGELOG.md

Co-authored-by: Brent Vatne <[email protected]>
2022-08-26 09:24:47 +02:00
Wojciech Kozyra e0a9384e56 update CHANGELOG 2022-08-25 10:21:57 +02:00
Dominik Sokal b5ca8c63d9 add new common submit error 2022-08-25 10:18:10 +02:00
Will Schurman c98401131a [eas-cli] Fix EnvironmentSecretsQuery param name (#1305) 2022-08-24 08:18:16 -07:00
Will Schurman c1cd0f584f [eas-cli] Use AppQuery instead of ProjectQuery (#1309) 2022-08-24 08:16:04 -07:00
Will Schurman f82aae7fc9 [eas-cli] Replace promptToCreateProjectIfNotExistsAsync with getProjectIdAsync (#1303) 2022-08-24 08:14:06 -07:00
Dominik Sokal c349ad7a40 Update CHANGELOG.md 2022-08-24 09:07:03 +02:00
Expo CI 3500a4d5ec update CHANGELOG.md 2022-08-23 19:43:43 +00:00
Quinlan Jung 149d07b28e v1.1.1 2022-08-23 12:33:01 -07:00
Quinlan Jung e248bf3fc3 [ENG-6057][cli] api key creation: exponential backoff for slow apple propagation (#1302)
* [cli] exponential backoff for slow apple propagation

* sdf

* update CHANGELOG.md

* pr feedback
2022-08-23 21:25:48 +02:00
Wojciech Kozyra f07b692ead [eas-cli] Handle trailing backslash in gitignore (#1306)
* [eas-cli] Handle trailing backslash in gitignore

* update CHANGELOG.md
2022-08-23 14:18:06 +02:00
Szymon Dziedzic ebfe69535f [eas-json] add support for autoIncrement option at root of build profile (#1298)
* [eas-json] support `autoIncrement` at root of build profile

* [eas-json] Start `autoIncrement` markdown enum description with capital letter

* update CHANGELOG.md

* [eas-json] use interfaces instead of types

* [eas-json] fix issues with types and improve schema descriptions

* [eas-json] remove unneccessary comment

* [eas-json] make EasJsonBuildProfile interface again

* [eas-json] remove unneccessary semicolon

* [eas-json] use only boolean values for autoIncrement flag

* [eas-json] improve schema description for autoIncrement option

* [eas-json] remove change from changelog

* update CHANGELOG.md

* improve changelog entry
2022-08-23 11:36:06 +02:00
Expo CI 58dbee8dee update CHANGELOG.md 2022-08-23 08:10:02 +00:00
Dominik Sokal feca0dc721 v1.1.0 2022-08-23 10:02:07 +02:00
Will Schurman 81831d156d [eas-cli] Prevent throwing dynamic app config write error when configuring project id (#1301) 2022-08-22 17:08:23 -07:00
Jon Samp 55c859cea7 [eas-cli] Removes "please" from output (#1250)
* Removes "please" from output

* updates changelog

* incorporate feedback from kim
2022-08-22 13:18:57 -04:00
Bartosz Kaszubowski 613748b2f0 [cli] remove unused dependencies (#1297) 2022-08-22 11:37:24 +02:00
Bartosz Kaszubowski bbd4bca83d [cli] correct Changelog (#1295) 2022-08-22 10:11:38 +02:00
Bartosz KaszubowskiandDominik Sokal 007866f278 [cli] Credentials: support platform flag (#1277)
Co-authored-by: Dominik Sokal <[email protected]>
2022-08-22 10:03:53 +02:00
Expo CI 1d2e9818ed update CHANGELOG.md 2022-08-22 08:02:35 +00:00
Dominik Sokal 34deafe440 v1.0.0 2022-08-22 09:55:27 +02:00
Dominik Sokal 55d8b650d3 remove timeout when waiting for build / submit (#1289)
* remove timeout when waiting for build / submit

* update CHANGELOG.md
2022-08-22 09:43:57 +02:00
Will Schurman 3ef6802d55 [eas-cli] Limit the number of SignedAssetUploadSpecifications fetched at a time (#1287) 2022-08-19 10:11:26 -07:00
Dominik Sokal e9484ffa5a update CODEOWNERS 2022-08-19 13:51:38 +02:00
Will Schurman c2b50a3a08 [eas-cli] Remove git merge artifact from CHANGELOG (#1285) 2022-08-17 11:31:02 -07:00
Will Schurman 06bb202bdf [eas-cli] Improve reliability of update asset presigned upload requests (#1278) 2022-08-17 10:59:18 -07:00
Will Schurman 64b67e9ea7 [eas-cli] Configure EAS project ID in app config in all cases (#1282) 2022-08-17 10:12:14 -07:00
faf1e90bed [eas-cli] Add js metadata config file support (#1270)
* [eas-cli] Add js metadata config file support

* [eas-cli] Add js metadata config file download support

* update CHANGELOG.md

* [eas-cli] Only save static files when pulling metadata

* [eas-cli] Support config functions and async functions in metadata

* [eas-cli] Tweak the metadata logs

* [eas-cli] Improve the dynamic config return validation

* [eas-cli] Apply improved language suggestions from the pros

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

* [eas-cli] Apply code changes from review

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

* [eas-cli] Split out load config logging and move validation to single method

* [eas-cli] Fix missed renamed function

Co-authored-by: Dominik Sokal <[email protected]>
Co-authored-by: Wojciech Kozyra <[email protected]>
2022-08-17 16:16:52 +02:00
c8935e199b [workspace] improve DX while working on Windows (#1272)
Co-authored-by: Dominik Sokal <[email protected]>
Co-authored-by: Wojciech Kozyra <[email protected]>
2022-08-16 15:10:55 +02:00
Wojciech Kozyra af76a9ce48 [eas-cli] remove shell check (#1281) 2022-08-16 13:54:22 +02:00
Wojciech Kozyra 3986e2fbb7 [eas-cli] remove install script (#1280)
* [eas-cli] remove installer

* update CHANGELOG.md
2022-08-16 13:27:13 +02:00
Dominik Sokal 1f97e8cc1c [eas-cli] init git repo in monorepo root (#1279)
* [eas-cli] init git repo in monorepo root

* update CHANGELOG.md
2022-08-16 11:02:05 +02:00
dependabot[bot] be1e2d7189 Bump simple-plist from 1.1.1 to 1.3.1 (#1139)
Bumps [simple-plist](https://github.com/wollardj/simple-plist) from 1.1.1 to 1.3.1.
- [Release notes](https://github.com/wollardj/simple-plist/releases)
- [Commits](https://github.com/wollardj/simple-plist/compare/v1.1.1...v1.3.1)

---
updated-dependencies:
- dependency-name: simple-plist
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-16 10:13:25 +02:00
Evan Bacon 8c4d7a6d0e fix: log binding was incorrect after ora instance (#1113)
* fix: log binding was incorrect after ora instance

* update CHANGELOG.md
2022-08-15 12:50:17 +02:00
Bartosz Kaszubowski 0849bf80b0 [workspace] bump Node used by oclif (#1273) 2022-08-12 15:20:40 +02:00
Expo CI 57ea4cd8c2 update CHANGELOG.md 2022-08-12 11:43:54 +00:00
Wojciech Kozyra 4c95900299 v0.60.0 2022-08-12 13:33:40 +02:00
Wojciech KozyraandDominik Sokal 94238f3274 [eas-cli] Add warning about unsupported policy. (#1271)
* [eas-cli] Add warning about unsupported policy.

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

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

* update CHANGELOG.md

Co-authored-by: Dominik Sokal <[email protected]>
2022-08-12 13:20:06 +02:00
Wojciech Kozyra 361230ffd2 [eas-cli] add appVersion runtime policy (#1267)
* Update packages

* update CHANGELOG.md
2022-08-12 11:33:49 +02:00
Cedric van Putten 88065c2837 [eas-json] Add metadata path to ios submit profile (#1269)
* [eas-json] Add metadata path to ios submit profile

* update CHANGELOG.md
2022-08-12 10:19:33 +02:00
dependabot[bot] 7f7b59ccfc Bump ejs from 3.1.6 to 3.1.8 (#1125)
Bumps [ejs](https://github.com/mde/ejs) from 3.1.6 to 3.1.8.
- [Release notes](https://github.com/mde/ejs/releases)
- [Changelog](https://github.com/mde/ejs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mde/ejs/compare/v3.1.6...v3.1.8)

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

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-11 12:14:08 +02:00
Expo CI 2a9f9d4695 update CHANGELOG.md 2022-08-10 10:15:59 +00:00
Dominik Sokal 8281a68e2e v0.59.0 2022-08-10 12:03:38 +02:00
Dominik Sokal cf47843712 upgrade dependencies (#1262)
* upgrade dependencies

* regenerate graphql code

* install ts-node

* update CHANGELOG.md
2022-08-10 11:54:17 +02:00
Wojciech Kozyra 7901fad195 yarn lint 2022-08-10 10:45:57 +02:00
Wojciech Kozyra df346764c8 [eas-cli] Validate only if using remote version source (#1261)
* [eas-cli] Validate only if using remote version source

* update CHANGELOG.md
2022-08-10 10:36:42 +02:00
Wojciech Kozyra 8024d33b25 [eas-cli] Improve wording for version bumps (#1260)
* [eas-cli] Improve wording for version bumps

* update CHANGELOG.md
2022-08-10 10:31:40 +02:00
Dominik Sokal e41d9f6430 Update CHANGELOG.md 2022-08-09 15:59:36 +02:00
Dominik Sokal 822313e90e upgrade eas-cli-local-build-plugin to 0.0.99 2022-08-09 15:56:40 +02:00
Expo CI 6c4f4c0049 update CHANGELOG.md 2022-08-09 11:44:42 +00:00
Dominik Sokal bb249345a7 v0.58.0 2022-08-09 13:31:08 +02:00
Dominik Sokal c1716e3b5a Update CHANGELOG.md 2022-08-09 12:11:05 +02:00
Dominik Sokal 40a6f3dda3 [ENG-3865] pass build metadata to local plugin (#1256)
* pass build metadata to local plugin

* upgrade eas-cli-local-build-plugin to 0.0.98
2022-08-09 12:05:04 +02:00
Wojciech Kozyra 8cadda2099 [eas-cli] add submit info to json only output (#1246)
* add submit info to json only output

* review feedback

* update CHANGELOG.md
2022-08-09 11:09:44 +02:00
Brent Vatne 8837214db2 [eas-cli] Swallow error when unable to track file in no commit workflow (#1109)
* [eas-cli] Swallow error when unable to track file in no commit workflow

* Include a warning when failing to track a file with GitNoCommitClient

* update CHANGELOG.md
2022-08-06 16:34:22 -07:00
kirb 1812b85e39 [eas-cli] add asset limit warning on publish flow (#1243) 2022-08-05 11:39:00 -07:00
Cedric van Putten 5024164408 [eas-cli] Add version selector to store config for metadata (#1194)
* [eas-cli] Add version selector to store config for metadata

* [eas-cli] Add version snippet to emphasize on app version

* [eas-cli] Add the missing phased release nock endpoints
2022-08-04 07:53:24 -07:00
Wojciech Kozyra b7983f4998 [eas-json] stop including sources in the npm package (#1248)
* [eas-json] Stop including sources in npm package

* update CHANGELOG.md
2022-08-04 15:43:00 +02:00
Wojciech Kozyra 7eea81ef83 [eas-json] move eas.json schema to eas-cli repo (#1247) 2022-08-04 15:05:05 +02:00
Cedric van Putten 5b9b4f9a2b [eas-cli] Finalize the store config release properties (#1191)
* [eas-cli] Clean up the store config release options

* [eas-cli] Add basic implementation of phased release

* [eas-cli] Fix outdated config writer tests
2022-08-04 03:04:09 -07:00
Cedric van Putten f862b7744b [eas-cli] Remove eas metadata preview until implementation is done (#1195) 2022-08-04 02:39:55 -07:00
Dominik Sokal e0b38f79dc [ENG-5893] add runWithNoWaitFlag and runFromCI to build metadata (#1245)
* generate graphql code

* add runWithNoWaitFlag and runFromCI to metadata

* send analytics events

* address PR feedback

* address more PR feedback

* upgrade @expo/eas-build-job
2022-08-04 09:52:00 +02:00
Wojciech Kozyra a709b9fd23 [eas-cli] managed versions support (#1232) 2022-08-03 12:52:26 +02:00
Expo CI ad2109103a update CHANGELOG.md 2022-08-03 07:48:35 +00:00
Dominik Sokal f797ae1c0a v0.57.0 2022-08-03 09:38:31 +02:00
Dominik Sokal f0287fab8a Update CHANGELOG.md 2022-08-03 09:37:14 +02:00
kirb 68a2b14e8b [eas-cli] disallow .only from being commited (#1242) 2022-08-02 09:38:47 -07:00
Dominik Sokal 04e55c33c1 [ENG-5836] add git commit message to metadata (#1241)
* generate graphql code

* upgrade @expo/eas-build-job

* add git commit message to build metadata
2022-08-01 13:35:23 +02:00
kirb 95a7137caa [eas-cli] Add unified pagination API for branch commands (#1213) 2022-07-29 18:51:52 -07:00
Dominik Sokal f9978baba2 [ENG-5836] add --message param to eas build (#1237)
* generate graphql code

* [eas-cli] allow specifying build message

* upgrade @expo/eas-build-job

* update CHANGELOG.md
2022-07-29 10:41:55 +02:00
Dominik Sokal 2023f6161d [ENG-5622] allow importing devices from Apple Developer Portal (#1236)
* allow importing devices from Apple Developer Portal

* update CHANGELOG.md
2022-07-29 10:30:18 +02:00
Expo CI a4f35466c3 update CHANGELOG.md 2022-07-28 09:53:15 +00:00
Dominik Sokal 5f73b0a2d7 v0.56.0 2022-07-28 11:43:00 +02:00
James Ide d194f18b8e Set Kirby and Will as codeowners for EAS Update CLI (#1233) 2022-07-26 09:22:45 -07:00
Wojciech Kozyra 17e1a145e8 [eas-cli] Fix newlines in cli deprecation message (#1231)
* [eas-cli] Fix newlines in cli deprecation message

* Update CHANGELOG
2022-07-26 10:46:09 +02:00
Kim Brandwijk 7ebb547fd9 [eas-cli] Invoke export with sourcemaps on update (#1228) 2022-07-22 12:47:37 +02:00
Wojciech Kozyra 6eca9ad564 [eas-cli] Add eas build:version:sync command (#1219) 2022-07-21 16:04:15 +02:00
Dominik Sokal 229f00a8ed Update CHANGELOG.md 2022-07-21 10:25:32 +02:00
Jérémy Barbet 2bbf7f2b6d chore: add better message on how to self-upgrade eas-cli (#1222)
* fix: make sure we add a line when needed while runing commands

* chore: add instruction on how to upgrade + remove ending new line

* chore: don't make the * bold
2022-07-21 10:24:10 +02:00
Dominik SokalandBrent Vatne d578abc69a [ENG-5017] display queue type when waiting (#1217)
* display queue type when waiting

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

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

* update CHANGELOG.md

Co-authored-by: Brent Vatne <[email protected]>
2022-07-20 09:36:22 +02:00
Wojciech Kozyra 89253f5767 [eas-cli] Add eas build:version:set command (#1209) 2022-07-18 13:26:46 +02:00
Kim Brandwijk dd5afd354a [cli] Change wording on build warning for bundle id/package name (#1211) 2022-07-16 20:43:36 +02:00
Dominik Sokal bdcc768354 rename uploadAsync -> uploadFileAtPathToS3Async (#1216) 2022-07-15 12:13:18 +02:00
Dominik Sokal 9cd2bafac5 fix CHANGELOG bot (#1215)
* upgrade node to 18.6.0 and yarn to 1.22.19

* upgrade dependencies
2022-07-15 12:13:09 +02:00
Dominik Sokal 69515ab47b fix S3 upload URLs (#1207) 2022-07-13 13:00:30 +02:00
Will Schurman ac5f7ab186 [eas-cli] Show spinner instead of silently timing out during asset upload (#1206) 2022-07-12 16:41:51 -07:00
Wojciech Kozyra 265c7eacc5 [eas-json] Increase max number of chained "extends" in build profiles (#1208) 2022-07-12 13:46:40 +02:00
Expo CI 98229581ca update CHANGELOG.md 2022-07-12 00:36:36 +00:00
Will Schurman a718fd10d3 v0.55.1 2022-07-11 17:25:37 -07:00
Will Schurman 78a63b8ca1 [eas-cli] Fix asset upload crash (#1205) 2022-07-11 16:50:09 -07:00
Cedric van Putten 7487f413de [eas-cli] Temporarily increase coverage memory to stabilize CI (#1196) 2022-07-11 19:40:32 +02:00
Expo CI da5f4e644a update CHANGELOG.md 2022-07-11 10:28:38 +00:00
Dominik Sokal be38117012 v0.55.0 2022-07-11 11:49:04 +02:00
Wojciech Kozyra 8f3314f188 [eas-cli] Keep using AndroidManifest.xml as a workflow marker (#1204) 2022-07-11 11:05:36 +02:00
Wojciech KozyraandDominik Sokal 31b6cc9ab6 [eas-cli] allow applicationId override (#1203)
* allow applicationId override

* Apply suggestions from code review

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

* update CHANGELOG.md

* update CHANGELOG.md

Co-authored-by: Dominik Sokal <[email protected]>
2022-07-11 10:19:06 +02:00
193 changed files with 12701 additions and 5073 deletions

No files matched your search

+1
View File
@@ -1 +1,2 @@
* text eol=lf
**/yarn.lock linguist-generated
-20
View File
@@ -1,20 +0,0 @@
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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: eas --version
+4 -1
View File
@@ -27,8 +27,11 @@ jobs:
if: ${{ !matrix.coverage }}
- run: yarn test --coverage
if: ${{ matrix.coverage }}
env:
# Oclif seems to cause OOM errors when testing with coverage enabled.
# Remove this whenever we solved those OOM errors.
NODE_OPTIONS: --max-old-space-size=4096
- run: yarn lint --max-warnings=0
- run: shellcheck bin/*.sh
- uses: codecov/codecov-action@v1
if: ${{ matrix.coverage }}
with:
+2 -1
View File
@@ -4,5 +4,6 @@
"singleQuote": true,
"bracketSameLine": true,
"trailingComma": "es5",
"arrowParens": "avoid"
"arrowParens": "avoid",
"endOfLine": "auto"
}
+137
View File
@@ -8,11 +8,148 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features
- Add 2022 Apple capabilities. ([#1307](https://github.com/expo/eas-cli/pull/1307) by [@EvanBacon](https://github.com/EvanBacon))
- Print warning when eas-cli is installed as project dependency. ([#1310](https://github.com/expo/eas-cli/pull/1310) by [@dsokal](https://github.com/dsokal))
- Add eas.json to skip push notifications credentials setup. ([#1315](https://github.com/expo/eas-cli/pull/1315) by [@dsokal](https://github.com/dsokal))
- Warn about EAS outages when running `build`, `submit`, and `update` commands. ([#1312](https://github.com/expo/eas-cli/pull/1312) by [@szdziedzic](https://github.com/szdziedzic))
### 🐛 Bug fixes
- Replace promptToCreateProjectIfNotExistsAsync with getProjectIdAsync. ([#1303](https://github.com/expo/eas-cli/pull/1303) by [@wschurman](https://github.com/wschurman))
- Use AppQuery instead of ProjectQuery. ([#1304](https://github.com/expo/eas-cli/pull/1304) by [@wschurman](https://github.com/wschurman))
### 🧹 Chores
- Improve instructions on setting `extra.eas.projectId` in app configuration. ([#1316](https://github.com/expo/eas-cli/pull/1316) by [@dsokal](https://github.com/dsokal))
- Improve copy in EAS Submit - "e-mail" -> email, and make the link to app on App Store go directly to the TestFlight tab. ([#1318](https://github.com/expo/eas-cli/pull/1318) by [@brentvatne](https://github.com/brentvatne))
## [1.1.1](https://github.com/expo/eas-cli/releases/tag/v1.1.1) - 2022-08-23
### 🎉 New features
- Add support for `autoIncrement` option at the root of the build profile. ([#1298](https://github.com/expo/eas-cli/pull/1298) by [@szdziedzic](https://github.com/szdziedzic))
### 🐛 Bug fixes
- Handle trailing backslash in `.gitignore`. ([#1306](https://github.com/expo/eas-cli/pull/1306) by [@wkozyra95](https://github.com/wkozyra95))
- Retry ASC Api Key downloads if it has not fully propagated on Apple's infrastructure. ([#1302](https://github.com/expo/eas-cli/pull/1302) by [@quinlanj](https://github.com/quinlanj))
## [1.1.0](https://github.com/expo/eas-cli/releases/tag/v1.1.0) - 2022-08-23
### 🎉 New features
- Add support for passing platform flag to `credentials` command. ([#1277](https://github.com/expo/eas-cli/pull/1277) by [@Simek](https://github.com/Simek))
### 🐛 Bug fixes
- Prevent throwing dynamic app config write error when configuring project ID. ([#1301](https://github.com/expo/eas-cli/pull/1301) by [@wschurman](https://github.com/wschurman))
### 🧹 Chores
- Remove unused dependency and devDependency from the CLI. ([#1297](https://github.com/expo/eas-cli/pull/1297) by [@Simek](https://github.com/Simek))
## [1.0.0](https://github.com/expo/eas-cli/releases/tag/v1.0.0) - 2022-08-22
### 🛠 Breaking changes
- Remove timeout when waiting for build / submission. ([#1289](https://github.com/expo/eas-cli/pull/1289) by [@dsokal](https://github.com/dsokal))
### 🎉 New features
- Add metadata support for dynamic store.config.js files. ([#1270](https://github.com/expo/eas-cli/pull/1270) by [@byCedric](https://github.com/byCedric))
- Improve reliability of update asset presigned upload requests. ([#1278](https://github.com/expo/eas-cli/pull/1278) by [@wschurman](https://github.com/wschurman))
### 🐛 Bug fixes
- Rebind `console.info` correctly after `ora` instance stops. ([#1113](https://github.com/expo/eas-cli/pull/1113) by [@EvanBacon](https://github.com/EvanBacon))
- Fix initializing git repository in monorepo. ([#1279](https://github.com/expo/eas-cli/pull/1279) by [@dsokal](https://github.com/dsokal))
- Limit the number of SignedAssetUploadSpecifications fetched at a time. ([#1287](https://github.com/expo/eas-cli/pull/1287) by [@wschurman](https://github.com/wschurman))
### 🧹 Chores
- Remove unused install script. ([#1280](https://github.com/expo/eas-cli/pull/1280), [#1281](https://github.com/expo/eas-cli/pull/1281) by [@wkozyra95](https://github.com/wkozyra95))
- Remove "please" from output ([#1250](https://github.com/expo/eas-cli/pull/1250) by [@jonsamp](https://github.com/jonsamp))
## [0.60.0](https://github.com/expo/eas-cli/releases/tag/v0.60.0) - 2022-08-12
### 🎉 New features
- Add `appVersion` runtime policy. ([#1267](https://github.com/expo/eas-cli/pull/1267) by [@wkozyra95](https://github.com/wkozyra95))
### 🧹 Chores
- Add JSON Schema `metadataPath` to iOS submission profile. ([#1269](https://github.com/expo/eas-cli/pull/1269) by [@byCedric](https://github.com/byCedric))
- Add warning when `appVersion` runtime policy is not supported. ([#1271](https://github.com/expo/eas-cli/pull/1271) by [@wkozyra95](https://github.com/wkozyra95))
## [0.59.0](https://github.com/expo/eas-cli/releases/tag/v0.59.0) - 2022-08-10
### 🐛 Bug fixes
- Fix building Android projects locally that don't have execute permissions set for `gradlew`. ([82231](https://github.com/expo/eas-cli/commit/822313e90e94bd6ddd3872061a0c25a8aa4db7cd) by [@dsokal](https://github.com/dsokal))
- Disable `nativeVersion` policy only for remote version source. ([#1261](https://github.com/expo/eas-cli/pull/1261) by [@wkozyra95](https://github.com/wkozyra95))
### 🧹 Chores
- Improve wording on version auto-increment. ([#1260](https://github.com/expo/eas-cli/pull/1260) by [@wkozyra95](https://github.com/wkozyra95))
- Upgrade dependencies. ([#1262](https://github.com/expo/eas-cli/pull/1262) by [@dsokal](https://github.com/dsokal))
## [0.58.0](https://github.com/expo/eas-cli/releases/tag/v0.58.0) - 2022-08-09
### 🎉 New features
- Managed app versions support. ([#1209](https://github.com/expo/eas-cli/pull/1209), [#1219](https://github.com/expo/eas-cli/pull/1219), [#1232](https://github.com/expo/eas-cli/pull/1232) by [@wkozyra95](https://github.com/wkozyra95))
- Add a warning when publishing an update with too many assets. ([#1243](https://github.com/expo/eas-cli/pull/1243) by [@kgc00](https://github.com/kgc00))
- Add submission info when building with `--json` option. ([#1246](https://github.com/expo/eas-cli/pull/1246) by [@wkozyra95](https://github.com/wkozyra95))
- Set all environment variables (documented in https://docs.expo.dev/build-reference/variables/) when running local build. ([#1256](https://github.com/expo/eas-cli/pull/1256) by [@dsokal](https://github.com/dsokal))
### 🧹 Chores
- Do not include sources in `eas-json` npm package. ([#1248](https://github.com/expo/eas-cli/pull/1248) by [@wkozyra95](https://github.com/wkozyra95))
- Swallow error when unable to track file in no commit workflow. ([#1109](https://github.com/expo/eas-cli/pull/1109) by [@brentvatne](https://github.com/brentvatne))
## [0.57.0](https://github.com/expo/eas-cli/releases/tag/v0.57.0) - 2022-08-03
### 🎉 New features
- Add new option to `eas device:create` - allow importing devices from Apple Developer Portal. ([#1236](https://github.com/expo/eas-cli/pull/1236) by [@dsokal](https://github.com/dsokal))
- Add support for attaching messages to builds. ([#1237](https://github.com/expo/eas-cli/pull/1237) by [@dsokal](https://github.com/dsokal))
- Introduce interactive pagination for `branch` commands. ([#1213](https://github.com/expo/eas-cli/pull/1213) by [@kgc00](https://github.com/kgc00))
## [0.56.0](https://github.com/expo/eas-cli/releases/tag/v0.56.0) - 2022-07-28
### 🎉 New features
- Display build queue type when waiting. ([#1217](https://github.com/expo/eas-cli/pull/1217) by [@dsokal](https://github.com/dsokal))
- Add better message on how to upgrade EAS CLI. ([#1222](https://github.com/expo/eas-cli/pull/1222) by [@jeremybarbet](https://github.com/jeremybarbet), [#1231](https://github.com/expo/eas-cli/pull/1231) by [@wkozyra95](https://github.com/wkozyra95))
- Invoke export with sourcemaps on update. ([#1228](https://github.com/expo/eas-cli/pull/1228) by [@kbrandwijk](https://github.com/kbrandwijk))
### 🐛 Bug fixes
- Show spinner instead of silently timing out during asset upload. ([#1206](https://github.com/expo/eas-cli/pull/1206) by [@wschurman](https://github.com/wschurman))
- Fix build archive S3 URLs. ([#1207](https://github.com/expo/eas-cli/pull/1207) by [@dsokal](https://github.com/dsokal))
- `autoIncrement` option on iOS will update versions of all targets that depend on an application target. ([#1219](https://github.com/expo/eas-cli/pull/1219) by [@wkozyra95](https://github.com/wkozyra95))
### 🧹 Chores
- Increase max number of chained "extends" in build profiles. ([#1208](https://github.com/expo/eas-cli/pull/1208) by [@wkozyra95](https://github.com/wkozyra95))
- Change wording on build warning for bundle id/package name. ([#1211](https://github.com/expo/eas-cli/pull/1211) by [@kbrandwijk](https://github.com/kbrandwijk))
## [0.55.1](https://github.com/expo/eas-cli/releases/tag/v0.55.1) - 2022-07-12
### 🐛 Bug fixes
- Fix asset upload crash. ([#1205](https://github.com/expo/eas-cli/pull/1205) by [@wschurman](https://github.com/wschurman))
## [0.55.0](https://github.com/expo/eas-cli/releases/tag/v0.55.0) - 2022-07-11
### 🎉 New features
- Show bundler output by default for eas update command. ([#1171](https://github.com/expo/eas-cli/pull/1171) by [@kgc00](https://github.com/kgc00/))
- Allow users to skip metadata validation. ([#1175](https://github.com/expo/eas-cli/pull/1175) by [@byCedric](https://github.com/byCedric))
- Add experimental `--resource-class` flag for Build commands. ([#1138](https://github.com/expo/eas-cli/pull/1138) by [@christopherwalter](https://github.com/christopherwalter))
- Truncate long messages for `eas update` command, rather than failing. ([#1178](https://github.com/expo/eas-cli/pull/1178) by [@kgc00](https://github.com/kgc00))
- Add review details to metadata store configuration. ([#1184](https://github.com/expo/eas-cli/pull/1184) by [@byCedric](https://github.com/byCedric))
- Override applicationId via env. ([#1203](https://github.com/expo/eas-cli/pull/1203) by [@wkozyra95](https://github.com/wkozyra95))
### 🐛 Bug fixes
+5 -2
View File
@@ -3,8 +3,11 @@
packages/eas-cli/src/build @dsokal @wkozyra95
packages/eas-cli/src/commands/build @dsokal @wkozyra95
packages/eas-cli/src/submit @barthap @dsokal
packages/eas-cli/src/commands/submit.ts @barthap @dsokal
packages/eas-cli/src/submit @dsokal
packages/eas-cli/src/commands/submit.ts @dsokal
packages/eas-cli/src/update @kgc00 @wschurman
packages/eas-cli/src/commands/update @kgc00 @wschurman
packages/eas-cli/schema @bycedric
packages/eas-cli/src/metadata @bycedric
-130
View File
@@ -1,130 +0,0 @@
#!/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=$(hub release -L 1 | xargs hub release show --show-downloads | grep "${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."
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.54.1"
"version": "1.2.0"
}
+12 -12
View File
@@ -17,22 +17,22 @@
"clean": "lerna run clean && rm -rf node_modules coverage"
},
"devDependencies": {
"@types/jest": "27.5.0",
"@types/node": "17.0.32",
"eslint": "8.15.0",
"eslint-config-universe": "11.0.0",
"eslint-plugin-async-protect": "1.1.0",
"@types/jest": "28.1.6",
"@types/node": "18.6.5",
"eslint": "8.21.0",
"eslint-config-universe": "11.1.0",
"eslint-plugin-async-protect": "2.1.0",
"eslint-plugin-node": "11.1.0",
"jest": "28.1.0",
"jest-watch-typeahead": "1.1.0",
"jest": "28.1.3",
"jest-watch-typeahead": "2.0.0",
"lerna": "4.0.0",
"oclif": "3.0.1",
"prettier": "2.6.2",
"ts-jest": "28.0.2",
"oclif": "3.1.2",
"prettier": "2.7.1",
"ts-jest": "28.0.7",
"typescript": "4.7.4"
},
"volta": {
"node": "18.2.0",
"yarn": "1.22.18"
"node": "18.6.0",
"yarn": "1.22.19"
}
}
+18
View File
@@ -2,6 +2,24 @@ const rootEslintrc = require('../../.eslintrc.js');
module.exports = {
rules: {
'no-restricted-properties': [
'warn',
{
object: 'it',
property: 'only',
message: 'it.only should not be committed to main.',
},
{
object: 'test',
property: 'only',
message: 'test.only should not be committed to main.',
},
{
object: 'describe',
property: 'only',
message: 'describe.only should not be committed to main.',
},
],
'no-restricted-imports': [
'error',
{
+124 -50
View File
@@ -19,6 +19,28 @@ npm install -g eas-cli
yarn global add eas-cli
```
## Enforcing eas-cli version for your project
If you want to enforce the `eas-cli` version for your project, use the `"cli.version"` field in **eas.json**. Installing `eas-cli` to your project dependencies is strongly discouraged because it may cause dependency conflicts that are difficult to debug.
An example of **eas.json** that enforces `eas-cli` in version `1.0.0` or newer:
```json
{
"cli": {
"version": ">=1.0.0"
},
"build": {
// build profiles
}
"submit": {
// submit profiles
}
}
```
Learn more: https://docs.expo.dev/build-reference/eas-json/
# Usage
```sh
@@ -49,6 +71,8 @@ eas --help COMMAND
* [`eas build:inspect`](#eas-buildinspect)
* [`eas build:list`](#eas-buildlist)
* [`eas build:submit`](#eas-buildsubmit)
* [`eas build:version:set`](#eas-buildversionset)
* [`eas build:version:sync`](#eas-buildversionsync)
* [`eas build:view [BUILD_ID]`](#eas-buildview-build_id)
* [`eas channel:create [NAME]`](#eas-channelcreate-name)
* [`eas channel:edit [NAME]`](#eas-channeledit-name)
@@ -100,7 +124,7 @@ ALIASES
$ eas login
```
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/account/login.ts)_
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -117,7 +141,7 @@ ALIASES
$ eas logout
```
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/account/logout.ts)_
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view`
@@ -134,7 +158,7 @@ ALIASES
$ eas whoami
```
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/account/view.ts)_
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -148,7 +172,7 @@ DESCRIPTION
display or change analytics settings
```
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/analytics.ts)_
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]`
@@ -197,7 +221,7 @@ DESCRIPTION
create a branch
```
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/branch/create.ts)_
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -217,7 +241,7 @@ DESCRIPTION
delete a branch
```
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/branch/delete.ts)_
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -225,16 +249,20 @@ list all branches
```
USAGE
$ eas branch:list [--json]
$ eas branch:list [--offset <value>] [--limit <value>] [--json] [--non-interactive]
FLAGS
--json return output as JSON
--json Enable JSON output, non-JSON messages will be printed to stderr.
--limit=<value> The number of query items to list at once. The default value is 50 (the maximum is 100). Using a
lower value may help increase command speed.
--non-interactive Run the command in non-interactive mode.
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
DESCRIPTION
list all branches
```
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/branch/list.ts)_
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -253,7 +281,7 @@ DESCRIPTION
rename a branch
```
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/branch/rename.ts)_
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -261,19 +289,23 @@ view a branch
```
USAGE
$ eas branch:view [NAME] [--json]
$ eas branch:view [NAME] [--offset <value>] [--limit <value>] [--json] [--non-interactive]
ARGUMENTS
NAME Name of the branch to view
FLAGS
--json return a json with the branch's ID name and recent update groups.
--json Enable JSON output, non-JSON messages will be printed to stderr.
--limit=<value> The number of query items to list at once. The default value is 50 (the maximum is 100). Using a
lower value may help increase command speed.
--non-interactive Run the command in non-interactive mode.
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
DESCRIPTION
view a branch
```
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/branch/view.ts)_
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build`
@@ -282,9 +314,10 @@ start a build
```
USAGE
$ eas build [-p android|ios|all] [--json] [--profile <value>] [--non-interactive] [--local] [--output
<value>] [--wait] [--clear-cache] [--auto-submit | --auto-submit-with-profile <value>]
<value>] [--wait] [--clear-cache] [--auto-submit | --auto-submit-with-profile <value>] [-m <value>]
FLAGS
-m, --message=<value> A short message describing the build
-p, --platform=(android|ios|all)
--auto-submit Submit on build complete using the submit profile with the same name as the
build profile
@@ -302,7 +335,7 @@ DESCRIPTION
start a build
```
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/build/index.ts)_
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -316,7 +349,7 @@ DESCRIPTION
cancel a build
```
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/build/cancel.ts)_
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -333,7 +366,7 @@ DESCRIPTION
configure the project to support EAS Build
```
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/build/configure.ts)_
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:inspect`
@@ -368,7 +401,7 @@ DESCRIPTION
inspect the state of the project at specific build stages, useful for troubleshooting
```
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/build/inspect.ts)_
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -401,7 +434,7 @@ DESCRIPTION
list all builds for your project
```
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/build/list.ts)_
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:submit`
@@ -431,6 +464,44 @@ ALIASES
$ eas build:submit
```
## `eas build:version:set`
Update version of an app.
```
USAGE
$ eas build:version:set [-p android|ios] [--profile <value>]
FLAGS
-p, --platform=(android|ios)
--profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined in
eas.json.
DESCRIPTION
Update version of an app.
```
_See code: [src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/build/version/set.ts)_
## `eas build:version:sync`
Update a version in native code with a value stored on EAS servers
```
USAGE
$ eas build:version:sync [-p android|ios|all] [--profile <value>]
FLAGS
-p, --platform=(android|ios|all)
--profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined in
eas.json.
DESCRIPTION
Update a version in native code with a value stored on EAS servers
```
_See code: [src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]`
view a build for your project
@@ -446,7 +517,7 @@ DESCRIPTION
view a build for your project
```
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/build/view.ts)_
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -466,7 +537,7 @@ DESCRIPTION
create a channel
```
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/channel/create.ts)_
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:edit [NAME]`
@@ -487,7 +558,7 @@ DESCRIPTION
point a channel at a new branch
```
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/channel/edit.ts)_
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list`
@@ -504,7 +575,7 @@ DESCRIPTION
list all channels
```
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/channel/list.ts)_
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:view [NAME]`
@@ -524,7 +595,7 @@ DESCRIPTION
view a channel
```
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/channel/view.ts)_
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -542,7 +613,7 @@ DESCRIPTION
display project configuration (app.json + eas.json)
```
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/config.ts)_
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/config.ts)_
## `eas credentials`
@@ -550,13 +621,16 @@ manage credentials
```
USAGE
$ eas credentials
$ eas credentials [-p android|ios]
FLAGS
-p, --platform=(android|ios)
DESCRIPTION
manage credentials
```
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/credentials.ts)_
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/credentials.ts)_
## `eas device:create`
@@ -570,7 +644,7 @@ DESCRIPTION
register new Apple Devices to use for internal distribution
```
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/device/create.ts)_
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -588,7 +662,7 @@ DESCRIPTION
remove a registered device from your account
```
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/device/delete.ts)_
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -605,7 +679,7 @@ DESCRIPTION
list all registered devices for your account
```
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/device/list.ts)_
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:view [UDID]`
@@ -619,7 +693,7 @@ DESCRIPTION
view a device for your project
```
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/device/view.ts)_
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -633,7 +707,7 @@ DESCRIPTION
display environment info
```
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/diagnostics.ts)_
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas help [COMMAND]`
@@ -715,7 +789,7 @@ DESCRIPTION
generate the local store configuration from the app stores
```
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/metadata/pull.ts)_
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push`
@@ -732,7 +806,7 @@ DESCRIPTION
sync the local store configuration to the app stores
```
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/metadata/push.ts)_
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas project:info`
@@ -746,7 +820,7 @@ DESCRIPTION
information about the current project
```
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/project/info.ts)_
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -763,7 +837,7 @@ ALIASES
$ eas init
```
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/project/init.ts)_
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/project/init.ts)_
## `eas secret:create`
@@ -783,7 +857,7 @@ DESCRIPTION
create an environment secret on the current project or owner account
```
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/secret/create.ts)_
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/secret/create.ts)_
## `eas secret:delete`
@@ -800,7 +874,7 @@ DESCRIPTION
delete an environment secret by ID
```
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/secret/delete.ts)_
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/secret/delete.ts)_
## `eas secret:list`
@@ -814,7 +888,7 @@ DESCRIPTION
list environment secrets available for your current app
```
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/secret/list.ts)_
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/secret/list.ts)_
## `eas submit`
@@ -844,7 +918,7 @@ ALIASES
$ eas build:submit
```
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/submit.ts)_
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/submit.ts)_
## `eas update`
@@ -874,7 +948,7 @@ DESCRIPTION
publish an update group
```
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/update/index.ts)_
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -891,7 +965,7 @@ DESCRIPTION
configure the project to support EAS Update
```
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/update/configure.ts)_
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -911,7 +985,7 @@ DESCRIPTION
delete all the updates in an update group
```
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/update/delete.ts)_
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:list`
@@ -930,7 +1004,7 @@ DESCRIPTION
view the recent updates for a branch
```
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/update/list.ts)_
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:view GROUPID`
@@ -950,7 +1024,7 @@ DESCRIPTION
update group details
```
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/update/view.ts)_
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/update/view.ts)_
## `eas webhook:create`
@@ -970,7 +1044,7 @@ DESCRIPTION
create a webhook
```
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/webhook/create.ts)_
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -987,7 +1061,7 @@ DESCRIPTION
delete a webhook
```
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/webhook/delete.ts)_
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -1004,7 +1078,7 @@ DESCRIPTION
list webhooks
```
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/webhook/list.ts)_
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -1025,7 +1099,7 @@ DESCRIPTION
update a webhook
```
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/webhook/update.ts)_
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -1042,7 +1116,7 @@ DESCRIPTION
view a webhook
```
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/webhook/view.ts)_
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v1.2.0/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
+1 -1
View File
@@ -14,7 +14,7 @@ generates:
dedupeOperationSuffix: true
hooks:
afterOneFileWrite:
- ./annotate-graphql-codegen.sh
- 'node ./scripts/annotate-graphql-codegen.js'
./graphql.schema.json:
plugins:
- 'introspection'
+4641 -1603
View File
File diff suppressed because it is too large. Load diff
+40 -33
View File
@@ -1,38 +1,39 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "0.54.1",
"version": "1.2.0",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
},
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@expo/apple-utils": "0.0.0-alpha.33",
"@expo/apple-utils": "0.0.0-alpha.34",
"@expo/code-signing-certificates": "0.0.2",
"@expo/config": "6.0.23",
"@expo/config-plugins": "4.1.4",
"@expo/config-types": "45.0.0",
"@expo/eas-build-job": "0.2.77",
"@expo/eas-json": "0.54.0",
"@expo/config": "7.0.1",
"@expo/config-plugins": "5.0.1",
"@expo/config-types": "46.0.2",
"@expo/eas-build-job": "0.2.85",
"@expo/eas-json": "1.2.0",
"@expo/json-file": "8.2.36",
"@expo/multipart-body-parser": "1.1.0",
"@expo/package-manager": "0.0.56",
"@expo/pkcs12": "0.0.8",
"@expo/plist": "0.0.18",
"@expo/plugin-autocomplete": "1.4.0",
"@expo/plugin-help": "5.2.0",
"@expo/plugin-warn-if-update-available": "2.3.0",
"@expo/prebuild-config": "3.1.6",
"@expo/prebuild-config": "5.0.3",
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.6.0",
"@expo/spawn-async": "1.7.0",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "1.9.0",
"@urql/core": "2.4.4",
"@oclif/core": "1.13.10",
"@urql/core": "2.6.1",
"@urql/exchange-retry": "0.3.3",
"ajv": "^6.12.6",
"chalk": "4.1.2",
"cli-progress": "3.11.0",
"cli-progress": "3.11.2",
"cli-table3": "0.6.2",
"dateformat": "4.6.3",
"env-paths": "2.2.0",
@@ -54,7 +55,7 @@
"keychain": "1.3.0",
"log-symbols": "4.1.0",
"mime": "3.0.0",
"minimatch": "5.0.1",
"minimatch": "5.1.0",
"nanoid": "3.3.4",
"node-fetch": "2.6.7",
"node-forge": "1.3.1",
@@ -62,12 +63,12 @@
"ora": "5.1.0",
"pkg-dir": "4.2.0",
"promise-limit": "2.7.0",
"promise-retry": "2.0.1",
"prompts": "2.4.2",
"qrcode-terminal": "0.12.0",
"resolve-from": "5.0.0",
"semver": "7.3.7",
"slash": "3.0.0",
"strip-ansi": "6.0.0",
"tar": "6.1.11",
"terminal-link": "2.1.1",
"tslib": "2.4.0",
@@ -77,32 +78,34 @@
"wrap-ansi": "7.0.0"
},
"devDependencies": {
"@graphql-codegen/cli": "2.6.2",
"@graphql-codegen/introspection": "2.1.1",
"@graphql-codegen/typescript": "2.4.11",
"@graphql-codegen/typescript-operations": "2.4.0",
"@types/cli-progress": "3.9.2",
"@graphql-codegen/cli": "2.11.5",
"@graphql-codegen/introspection": "2.2.1",
"@graphql-codegen/typescript": "2.7.3",
"@graphql-codegen/typescript-operations": "2.5.3",
"@types/cli-progress": "3.11.0",
"@types/dateformat": "3.0.1",
"@types/envinfo": "7.8.1",
"@types/fs-extra": "9.0.13",
"@types/getenv": "^1.0.0",
"@types/mime": "2.0.3",
"@types/node-fetch": "^2.5.7",
"@types/node-forge": "1.0.2",
"@types/node-fetch": "2.6.2",
"@types/node-forge": "1.0.4",
"@types/promise-retry": "1.1.3",
"@types/prompts": "2.0.14",
"@types/semver": "7.3.9",
"@types/tar": "6.1.1",
"@types/semver": "7.3.10",
"@types/tar": "6.1.2",
"@types/tough-cookie": "4.0.2",
"@types/uuid": "8.3.4",
"@types/wrap-ansi": "3.0.0",
"axios": "0.27.2",
"eslint-plugin-graphql": "4.0.0",
"form-data": "4.0.0",
"memfs": "3.4.3",
"memfs": "3.4.7",
"mockdate": "3.0.5",
"nock": "13.2.4",
"tempy": "0.7.0",
"ts-deepmerge": "2.0.1",
"nock": "13.2.9",
"rimraf": "3.0.2",
"ts-deepmerge": "2.0.5",
"ts-mockito": "2.6.1",
"ts-node": "10.9.1",
"typescript": "4.7.4"
},
"engines": {
@@ -166,11 +169,11 @@
},
"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"
"message": "<%= chalk('★') %> <%= chalk.bold(config.name + '@' + latest) %> <%= chalk.bold('is now available.') %>\n<%= chalk.dim('To upgrade, run ') %><%= chalk.dim(chalk.bold('npm install -g eas-cli')) %><%= chalk.dim('.') %>\n<%= chalk.dim('Proceeding with outdated version.') %>\n"
},
"update": {
"node": {
"version": "12.13.0"
"version": "18.6.0"
},
"s3": {
"templates": {
@@ -190,8 +193,8 @@
},
"repository": "expo/eas-cli",
"scripts": {
"postpack": "rm -f oclif.manifest.json",
"prepack": "./scripts/prepack.sh",
"postpack": "rimraf oclif.manifest.json",
"prepack": "rimraf build && yarn build && node ./scripts/prepack.js",
"pretarball-ci": "./scripts/pretarball-ci.sh",
"build": "tsc --project tsconfig.build.json",
"watch": "yarn build --watch --preserveWatchOutput",
@@ -199,6 +202,10 @@
"test": "jest",
"version": "yarn oclif readme && node scripts/patch-readme && git add README.md",
"generate-graphql-code": "graphql-codegen --config graphql-codegen.yml",
"clean": "rm -rf dist build tmp node_modules yarn-error.log"
"clean": "rimraf dist build tmp node_modules yarn-error.log"
},
"volta": {
"node": "18.6.0",
"yarn": "1.22.19"
}
}
+39 -234
View File
@@ -3,21 +3,6 @@
"type": "object",
"definitions": {
"apple": {
"AppleLocalizedPreviewFolder": {
"anyOf": [
{
"description": "Path to folder containing preview files",
"type": "string"
},
{
"type": "array",
"uniqueItems": true,
"items": {
"type": "string"
}
}
]
},
"AppleKidsAge": {
"enum": [
"FIVE_AND_UNDER",
@@ -464,62 +449,43 @@
},
"AppleRelease": {
"type": "object",
"description": "Configure how the app is released in the app store.",
"additionalProperties": false,
"defaultSnippets": [
{
"body": {
"automaticRelease": true,
"usesThirdPartyContent": false,
"usesNonExemptEncryption": false
}
}
],
"properties": {
"autoReleaseDate": {
"description": "ISO Date to release the app automatically. Leave undefined to manually release the app to the public.",
"defaultSnippets": [
{
"body": "$CURRENT_YEAR-${1:$CURRENT_MONTH}-${2:$CURRENT_DATE}"
}
],
"anyOf": [
"automaticRelease": {
"description": "If and how the app should automatically be released when approved by App Store review.",
"markdownDescription": "If and how the app should automatically be released when approved by App Store review.\n- `false` - Manually release the app after store approval. (default behavior)\n- `true` - Automatically release after store approval.\n- `Date` - Automatically schedule release on this date, after store approval.\n\nApple does not guarantee that your app is available at the chosen scheduled release date.",
"oneOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "string",
"format": "date"
"type": "boolean"
}
],
"defaultSnippets": [
{
"label": "Manual release",
"description": "Manually release the app after store approval",
"body": false
},
{
"type": "number"
"label": "Automatic release",
"description": "Automatically release the app after store approval",
"body": true
},
{
"label": "Scheduled release",
"description": "Automatically release the app on this date, and store approval",
"body": "$CURRENT_YEAR-${1:$CURRENT_MONTH}-${2:$CURRENT_DATE}T${3:$CURRENT_HOUR}:00:00+00:00"
}
]
},
"isPhasedReleaseEnabled": {
"phasedRelease": {
"type": "boolean",
"description": "Release update over 7-day period instead of instantly.",
"meta": {
"storeInfo": "Phased release for automatic updates lets you gradually release this update over a 7-day period to users who have turned on automatic updates. Keep in mind that this version will still be available to all users as a manual update from the App Store. You can pause the phased release for up to 30 days or release this update to all users at any time. [Learn More](https://help.apple.com/app-store-connect/#/dev3d65fcee1)."
}
},
"shouldResetRatings": {
"type": "boolean",
"description": "Should App Store ratings be reset for this version of the app.",
"meta": {
"storeInfo": "You can reset your app’s summary rating for all countries or regions when you release this version. Keep in mind that once this version is released, you won’t be able to restore the rating. Your app’s existing customer reviews will still appear on the App Store."
}
},
"usesThirdPartyContent": {
"type": "boolean"
},
"automaticRelease": {
"type": "boolean",
"description": "Should the app automatically be released when approved by App Store review."
},
"usesNonExemptEncryption": {
"type": "boolean",
"description": "Alternative to setting `ITSAppUsesNonExemptEncryption` in the binary's `Info.plist`."
"description": "Phased release for automatic updates lets you gradually release this update over a 7-day period to users who have turned on automatic updates. Keep in mind that this version will still be available to all users as a manual update from the App Store. You can pause the phased release for up to 30 days or release this update to all users at any time.\n@see https://help.apple.com/app-store-connect/#/dev3d65fcee1",
"markdownDescription": "Phased release for automatic updates lets you gradually release this update over a 7-day period to users who have turned on automatic updates.\n\nKeep in mind that this version will still be available to all users as a manual update from the App Store.\n\nYou can pause the phased release for up to 30 days or release this update to all users at any time.\n\n[Learn More](https://help.apple.com/app-store-connect/#/dev3d65fcee1)"
}
}
},
@@ -606,11 +572,15 @@
"description": "Configuration that is specific to the Apple App Store.",
"additionalProperties": false,
"properties": {
"advisory": {
"$ref": "#/definitions/apple/AppleAdvisory"
},
"categories": {
"$ref": "#/definitions/apple/AppleCategory"
"version": {
"type": "string",
"description": "The version string to edit, or create, if it doesn't exist. All the metadata will be stored on this app version.",
"minLength": 1,
"defaultSnippets": [
{
"body": "${1:1}.${2:0}"
}
]
},
"copyright": {
"type": "string",
@@ -626,6 +596,12 @@
"storeInfo": "The name of the person or entity that owns the exclusive rights to your app, preceded by the year the rights were obtained (for example, \"2008 Acme Inc.\"). Do not provide a URL."
}
},
"advisory": {
"$ref": "#/definitions/apple/AppleAdvisory"
},
"categories": {
"$ref": "#/definitions/apple/AppleCategory"
},
"info": {
"description": "Localized core app info",
"type": "object",
@@ -789,177 +765,6 @@
}
}
},
"preview": {
"anyOf": [
{
"description": "Path to localized folders containing preview files",
"type": "string"
},
{
"description": "Localized previews object. Ex: `\"en-US\": [\"image.png\"]`",
"type": "object",
"additionalProperties": false,
"properties": {
"ar-SA": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Arabic"
},
"ca": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Catalan"
},
"zh-Hans": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Chinese (Simplified)"
},
"zh-Hant": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Chinese (Traditional)"
},
"hr": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Croatian"
},
"cs": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Czech"
},
"da": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Danish"
},
"nl-NL": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Dutch"
},
"en-AU": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "English (Australia)"
},
"en-CA": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "English (Canada)"
},
"en-GB": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "English (U.K.)"
},
"en-US": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "English (U.S.)"
},
"fi": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Finnish"
},
"fr-FR": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "French"
},
"fr-CA": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "French (Canada)"
},
"de-DE": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "German"
},
"el": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Greek"
},
"he": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Hebrew"
},
"hi": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Hindi"
},
"hu": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Hungarian"
},
"id": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Indonesian"
},
"it": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Italian"
},
"ja": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Japanese"
},
"ko": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Korean"
},
"ms": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Malay"
},
"no": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Norwegian"
},
"pl": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Polish"
},
"pt-BR": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Portuguese (Brazil)"
},
"pt-PT": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Portuguese (Portugal)"
},
"ro": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Romanian"
},
"ru": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Russian"
},
"sk": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Slovak"
},
"es-MX": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Spanish (Mexico)"
},
"es-ES": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Spanish (Spain)"
},
"sv": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Swedish"
},
"th": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Thai"
},
"tr": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Turkish"
},
"uk": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Ukrainian"
},
"vi": {
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
"description": "Vietnamese"
}
}
}
]
},
"release": {
"$ref": "#/definitions/apple/AppleRelease",
"description": "Release strategy"
@@ -1,13 +1,15 @@
#!/usr/bin/env sh
const fs = require('fs/promises');
OUTFILE="src/graphql/generated.ts"
const path = './src/graphql/generated.ts';
content="/**
(async () => {
const generatedCode = await fs.readFile(path, 'utf8');
const comment = `/**
* 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/
*/
"
*/\n\n`;
printf '%s\n%s\n' "$content" "$(cat $OUTFILE)" > $OUTFILE
await fs.writeFile(path, comment + generatedCode);
})();
+1 -1
View File
@@ -1,4 +1,4 @@
const { promises: fs } = require('fs');
const fs = require('fs/promises');
(async () => {
// Patch `oclif readme` path and link generation
+7
View File
@@ -0,0 +1,7 @@
const spawn = require('@expo/spawn-async');
(async () => {
if (!process.env.CLI_SIZE_CHECK) {
await spawn('yarn', ['oclif', 'manifest'], { stdio: 'inherit' });
}
})();
-10
View File
@@ -1,10 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
rm -rf build
yarn build
if [ -z "$CLI_SIZE_CHECK" ]; then
yarn oclif manifest
fi
@@ -1,17 +1,17 @@
import { fixArchiveUrl } from '../uploads';
import { fixS3Url } from '../uploads';
describe(fixArchiveUrl, () => {
describe(fixS3Url, () => {
it('fixes broken links', () => {
const input =
'https://submission-service-archives.s3.amazonaws.com/production%2Fdc98ca84-1473-4cb3-ae81-8c7b291cb27e%2F4424aa95-b985-4e2f-8755-9507b1037c1c';
const expectedOutput =
'https://submission-service-archives.s3.amazonaws.com/production/dc98ca84-1473-4cb3-ae81-8c7b291cb27e/4424aa95-b985-4e2f-8755-9507b1037c1c';
expect(fixArchiveUrl(input)).toBe(expectedOutput);
expect(fixS3Url(input)).toBe(expectedOutput);
});
it('does not change correct urls', () => {
const correctUrl =
'https://submission-service-archives.s3.amazonaws.com/production/dc98ca84-1473-4cb3-ae81-8c7b291cb27e/4424aa95-b985-4e2f-8755-9507b1037c1c';
expect(fixArchiveUrl(correctUrl)).toBe(correctUrl);
expect(fixS3Url(correctUrl)).toBe(correctUrl);
});
});
+3
View File
@@ -15,6 +15,9 @@ export enum SubmissionEvent {
SUBMIT_REQUEST_ATTEMPT = 'submit cli request attempt',
SUBMIT_REQUEST_SUCCESS = 'submit cli request success',
SUBMIT_REQUEST_FAIL = 'submit cli request fail',
API_KEY_DOWNLOAD_FAIL = 'submit cli credentials api key download fail',
API_KEY_DOWNLOAD_RETRY = 'submit cli credentials api key download fail temporary',
API_KEY_DOWNLOAD_SUCCESS = 'submit cli credentials api key download succeed',
}
export enum BuildEvent {
+109
View File
@@ -0,0 +1,109 @@
import chalk from 'chalk';
import CliTable from 'cli-table3';
import { PaginatedQueryOptions } from '../commandUtils/pagination';
import { UpdateBranchFragment } from '../graphql/generated';
import { BranchQuery } from '../graphql/queries/BranchQuery';
import Log from '../log';
import { UPDATE_COLUMNS, formatUpdate, getPlatformsForGroup } from '../update/utils';
import { printJsonOnlyOutput } from '../utils/json';
import {
paginatedQueryWithConfirmPromptAsync,
paginatedQueryWithSelectPromptAsync,
} from '../utils/queries';
export const BRANCHES_LIMIT = 50;
export async function selectBranchFromPaginatedQueryAsync(
projectId: string,
promptTitle: string,
options: PaginatedQueryOptions
): Promise<UpdateBranchFragment> {
if (options.nonInteractive) {
throw new Error('Unable to select a branch in non-interactive mode.');
}
const selectedBranch = await paginatedQueryWithSelectPromptAsync({
limit: options.limit ?? BRANCHES_LIMIT,
offset: options.offset,
queryToPerform: (limit, offset) => queryBranchesForProjectAsync(limit, offset, projectId),
promptOptions: {
title: promptTitle,
getIdentifierForQueryItem: updateBranchFragment => updateBranchFragment.id,
createDisplayTextForSelectionPromptListItem: updateBranchFragment =>
updateBranchFragment.name,
},
});
if (!selectedBranch) {
throw new Error(`Could not find any branches for project "${projectId}"`);
}
return selectedBranch;
}
export async function listAndRenderPaginatedBranchesAsync(
projectId: string,
options: PaginatedQueryOptions
): Promise<void> {
if (options.nonInteractive) {
const branches = await queryBranchesForProjectAsync(
options.limit ?? BRANCHES_LIMIT,
options.offset,
projectId
);
renderPageOfBranches(branches, options);
} else {
await paginatedQueryWithConfirmPromptAsync({
limit: options.limit ?? BRANCHES_LIMIT,
offset: options.offset,
queryToPerform: (limit, offset) => queryBranchesForProjectAsync(limit, offset, projectId),
promptOptions: {
title: 'Load more branches?',
renderListItems: branches => renderPageOfBranches(branches, options),
},
});
}
}
async function queryBranchesForProjectAsync(
limit: number,
offset: number,
projectId: string
): Promise<UpdateBranchFragment[]> {
return await BranchQuery.listBranchesAsync({
appId: projectId,
limit,
offset,
});
}
function renderPageOfBranches(
currentPage: UpdateBranchFragment[],
{ json }: PaginatedQueryOptions
): void {
if (json) {
printJsonOnlyOutput(currentPage);
} else {
const table = new CliTable({
head: ['Branch', ...UPDATE_COLUMNS],
wordWrap: true,
});
table.push(
...currentPage.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:'));
Log.addNewLineIfNone();
Log.log(table.toString());
}
}
@@ -0,0 +1,14 @@
import { truncateGitCommitMessage } from '../metadata';
describe(truncateGitCommitMessage, () => {
it('returns undefined if no message was passed', () => {
expect(truncateGitCommitMessage(undefined)).toBeUndefined();
});
it('returns commit message', () => {
expect(truncateGitCommitMessage('a'.repeat(10))).toBe('a'.repeat(10));
});
it('truncates long commit messages', () => {
expect(truncateGitCommitMessage('a'.repeat(5000))).toBe(`${'a'.repeat(4093)}...`);
});
});
@@ -89,7 +89,7 @@ describe(bumpVersionAsync, () => {
expect(resolveConfigValue(buildGradle, 'versionName')).toBe('3.0.0');
});
it('bumps expo.version and gradle versionCode when strategy = BumpStrategy.SHORT_VERSION', async () => {
it('bumps expo.version and gradle versionCode when strategy = BumpStrategy.APP_VERSION', async () => {
const nativeVersionName = '1.0.0'; // this should be overwritten by bumpVersionAsync
const fakeExp = initBareWorkflowProject({ versionName: nativeVersionName });
+18 -3
View File
@@ -1,4 +1,5 @@
import { Android, Job, Metadata, Platform, Workflow } from '@expo/eas-build-job';
import { AppVersionSource } from '@expo/eas-json';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
@@ -28,6 +29,7 @@ import { checkGoogleServicesFileAsync, checkNodeEnvVariable } from '../validate'
import { transformJob } from './graphql';
import { prepareJobAsync } from './prepareJob';
import { syncProjectConfigurationAsync } from './syncProjectConfiguration';
import { resolveRemoteVersionCodeAsync } from './version';
export async function createAndroidContextAsync(
ctx: CommonContext<Platform.ANDROID>
@@ -45,7 +47,7 @@ This means that it will most likely produce an AAB and you will not be able to i
Log.newLine();
const confirmed = await toggleConfirmAsync({ message: 'Would you like to proceed?' });
if (!confirmed) {
Log.error('Please update eas.json and come back again.');
Log.error('Update eas.json and come back again.');
process.exit(1);
}
}
@@ -60,8 +62,18 @@ This means that it will most likely produce an AAB and you will not be able to i
}
const applicationId = await getApplicationIdAsync(ctx.projectDir, ctx.exp, gradleContext);
const versionCodeOverride =
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
? await resolveRemoteVersionCodeAsync({
projectDir: ctx.projectDir,
projectId: ctx.projectId,
exp: ctx.exp,
applicationId,
buildProfile,
})
: undefined;
return { applicationId, gradleContext };
return { applicationId, gradleContext, versionCodeOverride };
}
export async function prepareAndroidBuildAsync(
@@ -76,7 +88,10 @@ export async function prepareAndroidBuildAsync(
await syncProjectConfigurationAsync({
projectDir: ctx.projectDir,
exp: ctx.exp,
buildProfile: ctx.buildProfile,
localAutoIncrement:
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
? false
: ctx.buildProfile.autoIncrement,
});
},
prepareJobAsync: async (
@@ -13,6 +13,7 @@ export function transformJob(job: Android.Job): AndroidJobInput {
secrets: job.secrets,
builderEnvironment: job.builderEnvironment,
cache: job.cache,
version: job.version?.versionCode ? { versionCode: job.version.versionCode } : undefined,
gradleCommand: job.gradleCommand,
artifactPath: job.artifactPath,
username: job.username,
@@ -75,6 +75,11 @@ export async function prepareJobAsync(
artifactPath: buildProfile.artifactPath,
buildType,
username,
...(ctx.android.versionCodeOverride && {
version: {
versionCode: ctx.android.versionCodeOverride,
},
}),
experimental: {
prebuildCommand: ctx.buildProfile.prebuildCommand,
},
@@ -1,7 +1,7 @@
import { ExpoConfig } from '@expo/config';
import { AndroidConfig } from '@expo/config-plugins';
import { Platform, Workflow } from '@expo/eas-build-job';
import { AndroidVersionAutoIncrement, BuildProfile } from '@expo/eas-json';
import { AndroidVersionAutoIncrement } from '@expo/eas-json';
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
@@ -15,15 +15,14 @@ import { BumpStrategy, bumpVersionAsync, bumpVersionInAppJsonAsync } from './ver
export async function syncProjectConfigurationAsync({
projectDir,
exp,
buildProfile,
localAutoIncrement,
}: {
projectDir: string;
exp: ExpoConfig;
buildProfile: BuildProfile<Platform.ANDROID>;
localAutoIncrement?: AndroidVersionAutoIncrement;
}): Promise<void> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
const { autoIncrement } = buildProfile;
const versionBumpStrategy = resolveVersionBumpStrategy(autoIncrement ?? false);
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
if (workflow === Workflow.GENERIC) {
await cleanUpOldEasBuildGradleScriptAsync(projectDir);
+123 -11
View File
@@ -1,16 +1,21 @@
import { ExpoConfig } from '@expo/config';
import { AndroidConfig } from '@expo/config-plugins';
import { AndroidConfig, Updates } from '@expo/config-plugins';
import { Platform, Workflow } from '@expo/eas-build-job';
import { BuildProfile } from '@expo/eas-json';
import chalk from 'chalk';
import fs from 'fs-extra';
import { AppPlatform } from '../../graphql/generated';
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
import Log from '../../log';
import { ora } from '../../ora';
import {
getAppBuildGradleAsync,
parseGradleCommand,
resolveConfigValue,
} from '../../project/android/gradleUtils';
import { getNextVersionCode } from '../../project/android/versions';
import { resolveWorkflowAsync } from '../../project/workflow';
import { updateAppJsonConfigAsync } from '../utils/appJson';
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
@@ -46,9 +51,10 @@ export async function bumpVersionAsync({
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
Log.log('Updated versions in app.json');
await writeVersionsToBuildGradleAsync({
await updateNativeVersionsAsync({
projectDir,
exp,
version: exp.version,
versionCode: exp.android?.versionCode,
});
Log.log('Synchronized versions with build gradle');
}
@@ -74,7 +80,7 @@ export async function bumpVersionInAppJsonAsync({
await bumpAppVersionAsync({ appVersion, projectDir, exp });
} else {
const versionCode = AndroidConfig.Version.getVersionCode(exp);
const bumpedVersionCode = versionCode + 1;
const bumpedVersionCode = getNextVersionCode(versionCode);
Log.log(
`Bumping ${chalk.bold('expo.android.versionCode')} from ${chalk.bold(
versionCode
@@ -116,21 +122,33 @@ export async function maybeResolveVersionsAsync(
}
}
async function writeVersionsToBuildGradleAsync({
export async function updateNativeVersionsAsync({
projectDir,
exp,
version,
versionCode,
}: {
projectDir: string;
exp: ExpoConfig;
}): Promise<string> {
version?: string;
versionCode?: number;
}): Promise<void> {
const buildGradle = await readBuildGradleAsync(projectDir);
if (!buildGradle) {
throw new Error('This project is missing a build.gradle file.');
}
let updatedBuildGradle = AndroidConfig.Version.setVersionName(exp, buildGradle);
updatedBuildGradle = AndroidConfig.Version.setVersionCode(exp, updatedBuildGradle);
let updatedBuildGradle = buildGradle;
if (version !== undefined) {
updatedBuildGradle = updatedBuildGradle.replace(
new RegExp(`versionName ".*"`),
`versionName "${version}"`
);
}
if (versionCode !== undefined) {
updatedBuildGradle = updatedBuildGradle.replace(
new RegExp(`versionCode.*`),
`versionCode ${versionCode}`
);
}
await writeBuildGradleAsync({ projectDir, buildGradle: updatedBuildGradle });
return updatedBuildGradle;
}
async function readBuildGradleAsync(projectDir: string): Promise<string | undefined> {
@@ -151,3 +169,97 @@ async function writeBuildGradleAsync({
const buildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(projectDir);
await fs.writeFile(buildGradlePath, buildGradle);
}
/**
* Returns buildNumber that will be used for the next build. If current build profile
* has an 'autoIncrement' option set, it increments the version on server.
*/
export async function resolveRemoteVersionCodeAsync({
projectDir,
projectId,
exp,
applicationId,
buildProfile,
}: {
projectDir: string;
projectId: string;
exp: ExpoConfig;
applicationId: string;
buildProfile: BuildProfile<Platform.ANDROID>;
}): Promise<string> {
const remoteVersions = await AppVersionQuery.latestVersionAsync(
projectId,
AppPlatform.Android,
applicationId
);
const localVersions = await maybeResolveVersionsAsync(projectDir, exp, buildProfile);
let currentBuildVersion: string;
if (remoteVersions?.buildVersion) {
currentBuildVersion = remoteVersions.buildVersion;
} else {
if (localVersions.appBuildVersion) {
Log.warn(
'No remote versions are configured for this project, versionCode will be initialized based on the value from the local project.'
);
currentBuildVersion = localVersions.appBuildVersion;
} else {
Log.error(
`Remote versions are not configured and EAS CLI was not able to read the current version from your project. Use "eas build:version:set" to initialize remote versions.`
);
throw new Error('Remote versions are not configured.');
}
}
if (!buildProfile.autoIncrement && remoteVersions?.buildVersion) {
return currentBuildVersion;
} else if (!buildProfile.autoIncrement && !remoteVersions?.buildVersion) {
const spinner = ora(
`Initializing versionCode with ${chalk.bold(currentBuildVersion)}.`
).start();
try {
await AppVersionMutation.createAppVersionAsync({
appId: projectId,
platform: AppPlatform.Android,
applicationIdentifier: applicationId,
storeVersion: localVersions.appVersion ?? exp.version ?? '1.0.0',
buildVersion: currentBuildVersion,
runtimeVersion: Updates.getRuntimeVersionNullable(exp, Platform.ANDROID) ?? undefined,
});
spinner.succeed(`Initialized versionCode with ${chalk.bold(currentBuildVersion)}.`);
} catch (err) {
spinner.fail(`Failed to initialize versionCode with ${chalk.bold(currentBuildVersion)}.`);
throw err;
}
return currentBuildVersion;
} else {
const nextBuildVersion = getNextVersionCode(currentBuildVersion);
const spinner = ora(
`Incrementing versionCode from ${chalk.bold(currentBuildVersion)} to ${chalk.bold(
nextBuildVersion
)}.`
).start();
try {
await AppVersionMutation.createAppVersionAsync({
appId: projectId,
platform: AppPlatform.Android,
applicationIdentifier: applicationId,
storeVersion: localVersions.appVersion ?? exp.version ?? '1.0.0',
buildVersion: String(nextBuildVersion),
runtimeVersion: Updates.getRuntimeVersionNullable(exp, Platform.ANDROID) ?? undefined,
});
spinner.succeed(
`Incremented versionCode from ${chalk.bold(currentBuildVersion)} to ${chalk.bold(
nextBuildVersion
)}.`
);
} catch (err) {
spinner.fail(
`Failed to increment versionCode from ${chalk.bold(currentBuildVersion)} to ${chalk.bold(
nextBuildVersion
)}.`
);
throw err;
}
return String(nextBuildVersion);
}
}
+16 -19
View File
@@ -25,7 +25,7 @@ import {
appPlatformEmojis,
requestedPlatformDisplayNames,
} from '../platform';
import { uploadAsync } from '../uploads';
import { uploadFileAtPathToS3Async } from '../uploads';
import { formatBytes } from '../utils/files';
import { createProgressTracker } from '../utils/progress';
import { sleepAsync } from '../utils/promise';
@@ -120,7 +120,7 @@ export async function prepareBuildRequestForPlatformAsync<
return async () => {
if (ctx.localBuildOptions.enable) {
await runLocalBuildAsync(job, ctx.localBuildOptions);
await runLocalBuildAsync(job, metadata, ctx.localBuildOptions);
return undefined;
} else {
try {
@@ -134,18 +134,18 @@ export async function prepareBuildRequestForPlatformAsync<
function handleBuildRequestError(error: any, platform: Platform): never {
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.');
Log.error('EAS Build API has changed. Upgrade to the latest eas-cli version.');
throw new Error('Build request failed.');
} else if (
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_DOWN_FOR_MAINTENANCE'
) {
Log.error(
'EAS Build is down for maintenance, please try again later. Check https://status.expo.dev/ for updates.'
'EAS Build is down for maintenance. 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.'
'EAS Build free tier is temporarily disabled. Try again later. Check https://status.expo.dev/ for updates.'
);
throw new Error('Build request failed.');
} else if (
@@ -157,7 +157,7 @@ function handleBuildRequestError(error: any, platform: Platform): never {
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.'
'Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, report the issue.'
);
}
throw error;
@@ -179,7 +179,7 @@ async function uploadProjectAsync<TPlatform extends Platform>(
const projectTarball = await makeProjectTarballAsync();
projectTarballPath = projectTarball.path;
const { bucketKey } = await uploadAsync(
const { bucketKey } = await uploadFileAtPathToS3Async(
UploadSessionType.EasBuildProjectSources,
projectTarball.path,
createProgressTracker({
@@ -243,11 +243,7 @@ type MaybeBuildFragment = BuildFragment | null;
export async function waitForBuildEndAsync(
{ buildIds, accountName }: { buildIds: string[]; accountName: string },
{
// 2 hours (max build time limit) + 10 minutes (possible queue time)
timeoutSec = 2 * 60 * 60 + 10 * 60,
intervalSec = 10,
} = {}
{ intervalSec = 10 } = {}
): Promise<MaybeBuildFragment[]> {
let spinner;
let originalSpinnerText;
@@ -259,8 +255,7 @@ export async function waitForBuildEndAsync(
originalSpinnerText = 'Waiting for builds to complete. You can press Ctrl+C to exit.';
spinner = ora('Waiting for builds to complete. You can press Ctrl+C to exit.').start();
}
const endTime = new Date().getTime() + timeoutSec * 1000;
while (new Date().getTime() <= endTime) {
while (true) {
const builds = await getBuildsSafelyAsync(buildIds);
const { refetch } =
builds.length === 1
@@ -271,10 +266,6 @@ export async function waitForBuildEndAsync(
}
await sleepAsync(intervalSec * 1000);
}
spinner.fail('Timed out');
throw new Error(
'Timeout reached! It is taking longer than expected to finish the build, aborting...'
);
}
async function getBuildsSafelyAsync(buildIds: string[]): Promise<MaybeBuildFragment[]> {
@@ -352,7 +343,7 @@ async function handleSingleBuildProgressAsync(
);
}
Log.newLine();
Log.log('Waiting in queue');
Log.log(`Waiting in ${priorityToQueueDisplayName[build.priority]}`);
queueProgressBar.start(
build.initialQueuePosition + 1,
build.initialQueuePosition - build.queuePosition + 1,
@@ -385,6 +376,12 @@ async function handleSingleBuildProgressAsync(
return { refetch: true };
}
const priorityToQueueDisplayName: Record<BuildPriority, string> = {
[BuildPriority.Normal]: 'queue',
[BuildPriority.NormalPlus]: 'queue',
[BuildPriority.High]: 'priority queue',
};
const statusToDisplayName: Record<BuildStatus, string> = {
[BuildStatus.New]: 'waiting to enter the queue (concurrency limit reached)',
[BuildStatus.InQueue]: 'in queue',
+7 -3
View File
@@ -1,7 +1,6 @@
import { ExpoConfig } from '@expo/config';
import { Platform, Workflow } from '@expo/eas-build-job';
import { BuildProfile } from '@expo/eas-json';
import type { XCBuildConfiguration } from 'xcode';
import { BuildProfile, EasJson } from '@expo/eas-json';
import { TrackingContext } from '../analytics/common';
import { CredentialsContext } from '../credentials/context';
@@ -17,18 +16,20 @@ export type CommonContext<T extends Platform> = Omit<BuildContext<T>, 'android'
export interface AndroidBuildContext {
applicationId: string;
gradleContext?: GradleBuildContext;
versionCodeOverride?: string;
}
export interface IosBuildContext {
bundleIdentifier: string;
applicationTargetBuildSettings: XCBuildConfiguration['buildSettings'];
applicationTarget: Target;
targets: Target[];
xcodeBuildContext: XcodeBuildContext;
buildNumberOverride?: string;
}
export interface BuildContext<T extends Platform> {
accountName: string;
easJsonCliConfig: EasJson['cli'];
buildProfile: BuildProfile<T>;
buildProfileName: string;
resourceClass: BuildResourceClass;
@@ -37,10 +38,13 @@ export interface BuildContext<T extends Platform> {
exp: ExpoConfig;
localBuildOptions: LocalBuildOptions;
nonInteractive: boolean;
noWait: boolean;
runFromCI: boolean;
platform: T;
projectDir: string;
projectId: string;
projectName: string;
message?: string;
trackingCtx: TrackingContext;
user: Actor;
workflow: Workflow;
+17 -2
View File
@@ -1,6 +1,7 @@
import { Platform } from '@expo/eas-build-job';
import { BuildProfile } from '@expo/eas-json';
import { BuildProfile, EasJson } from '@expo/eas-json';
import JsonFile from '@expo/json-file';
import getenv from 'getenv';
import resolveFrom from 'resolve-from';
import { v4 as uuidv4 } from 'uuid';
@@ -21,21 +22,27 @@ import { LocalBuildOptions } from './local';
export async function createBuildContextAsync<T extends Platform>({
buildProfileName,
buildProfile,
easJsonCliConfig,
clearCache = false,
localBuildOptions,
nonInteractive = false,
nonInteractive,
noWait,
platform,
projectDir,
resourceClass,
message,
}: {
buildProfileName: string;
buildProfile: BuildProfile<T>;
easJsonCliConfig: EasJson['cli'];
clearCache: boolean;
localBuildOptions: LocalBuildOptions;
nonInteractive: boolean;
noWait: boolean;
platform: T;
projectDir: string;
resourceClass: BuildResourceClass;
message?: string;
}): Promise<BuildContext<T>> {
const exp = getExpoConfig(projectDir, { env: buildProfile.env });
@@ -45,6 +52,7 @@ export async function createBuildContextAsync<T extends Platform>({
const projectId = await getProjectIdAsync(exp, { env: buildProfile.env });
const workflow = await resolveWorkflowAsync(projectDir, platform);
const accountId = findAccountByName(user.accounts, accountName)?.id;
const runFromCI = getenv.boolish('CI', false);
const credentialsCtx = new CredentialsContext({
exp,
@@ -52,6 +60,7 @@ export async function createBuildContextAsync<T extends Platform>({
projectDir,
user,
env: buildProfile.env,
easJsonCliConfig,
});
const devClientProperties = getDevClientEventProperties({
@@ -66,6 +75,8 @@ export async function createBuildContextAsync<T extends Platform>({
project_id: projectId,
project_type: workflow,
...devClientProperties,
no_wait: noWait,
run_from_ci: runFromCI,
};
Analytics.logEvent(BuildEvent.BUILD_COMMAND, trackingCtx);
@@ -74,11 +85,13 @@ export async function createBuildContextAsync<T extends Platform>({
buildProfile,
buildProfileName,
resourceClass,
easJsonCliConfig,
clearCache,
credentialsCtx,
exp,
localBuildOptions,
nonInteractive,
noWait,
platform,
projectDir,
projectId,
@@ -86,6 +99,8 @@ export async function createBuildContextAsync<T extends Platform>({
trackingCtx,
user,
workflow,
message,
runFromCI,
};
if (platform === Platform.ANDROID) {
const common = commonContext as CommonContext<Platform.ANDROID>;
@@ -3,6 +3,7 @@ import { IOSConfig } from '@expo/config-plugins';
import fs from 'fs-extra';
import { vol } from 'memfs';
import os from 'os';
import type { XCBuildConfiguration } from 'xcode';
import { readPlistAsync } from '../../../utils/plist';
import {
@@ -17,6 +18,12 @@ import {
jest.mock('fs');
const getXCBuildConfigurationFromPbxproj = jest.spyOn(
IOSConfig.Target,
'getXCBuildConfigurationFromPbxproj'
);
const getPbxproj = jest.spyOn(IOSConfig.XcodeUtils, 'getPbxproj');
afterAll(async () => {
// do not remove the following line
// this fixes a weird error with tempy in @expo/image-utils
@@ -51,14 +58,34 @@ describe(evaluateTemplateString, () => {
// bare workflow
describe(bumpVersionAsync, () => {
beforeEach(() => {
getPbxproj.mockImplementation(() => ({} as any));
});
afterEach(() => {
getXCBuildConfigurationFromPbxproj.mockReset();
getPbxproj.mockReset();
});
it('bumps expo.ios.buildNumber and CFBundleVersion when strategy = BumpStrategy.BUILD_NUMBER', async () => {
const fakeExp = initBareWorkflowProject();
getXCBuildConfigurationFromPbxproj.mockImplementation(
() =>
({
buildSettings: {
INFOPLIST_FILE: 'myproject/Info.plist',
},
} as XCBuildConfiguration)
);
await bumpVersionAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/repo',
exp: fakeExp,
buildSettings: {},
targets: [
{
targetName: 'example',
bundleIdentifier: 'example.com',
entitlements: {},
},
],
});
const appJSON = await fs.readJSON('/repo/app.json');
@@ -76,13 +103,25 @@ describe(bumpVersionAsync, () => {
it('bumps expo.ios.buildNumber and CFBundleVersion for non default Info.plist location', async () => {
const fakeExp = initBareWorkflowProject({ infoPlistName: 'Info2.plist' });
getXCBuildConfigurationFromPbxproj.mockImplementation(
() =>
({
buildSettings: {
INFOPLIST_FILE: '$(SRCROOT)/myproject/Info2.plist',
},
} as XCBuildConfiguration)
);
await bumpVersionAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/repo',
exp: fakeExp,
buildSettings: {
INFOPLIST_FILE: '$(SRCROOT)/myproject/Info2.plist',
},
targets: [
{
targetName: 'example',
bundleIdentifier: 'example.com',
entitlements: {},
},
],
});
const appJSON = await fs.readJSON('/repo/app.json');
@@ -97,14 +136,27 @@ describe(bumpVersionAsync, () => {
expect(infoPlist['CFBundleVersion']).toBe('2');
});
it('bumps expo.version and CFBundleShortVersionString when strategy = BumpStrategy.SHORT_VERSION', async () => {
it('bumps expo.version and CFBundleShortVersionString when strategy = BumpStrategy.APP_VERSION', async () => {
const fakeExp = initBareWorkflowProject();
getXCBuildConfigurationFromPbxproj.mockImplementation(
() =>
({
buildSettings: {
INFOPLIST_FILE: 'myproject/Info.plist',
},
} as XCBuildConfiguration)
);
await bumpVersionAsync({
bumpStrategy: BumpStrategy.APP_VERSION,
projectDir: '/repo',
exp: fakeExp,
buildSettings: {},
targets: [
{
targetName: 'example',
bundleIdentifier: 'example.com',
entitlements: {},
},
],
});
const appJSON = await fs.readJSON('/repo/app.json');
@@ -121,12 +173,25 @@ describe(bumpVersionAsync, () => {
it('does not bump any version when strategy = BumpStrategy.NOOP', async () => {
const fakeExp = initBareWorkflowProject();
getXCBuildConfigurationFromPbxproj.mockImplementation(
() =>
({
buildSettings: {
INFOPLIST_FILE: 'myproject/Info.plist',
},
} as XCBuildConfiguration)
);
await bumpVersionAsync({
bumpStrategy: BumpStrategy.NOOP,
projectDir: '/repo',
exp: fakeExp,
buildSettings: {},
targets: [
{
targetName: 'example',
bundleIdentifier: 'example.com',
entitlements: {},
},
],
});
const appJSON = await fs.readJSON('/repo/app.json');
@@ -288,9 +353,9 @@ describe(getInfoPlistPath, () => {
function initBareWorkflowProject({
appVersion = '1.0.0',
version = '1',
buildNumber = '1',
infoPlistName = 'Info.plist',
}: { appVersion?: string; version?: string; infoPlistName?: string } = {}): ExpoConfig {
}: { appVersion?: string; buildNumber?: string; infoPlistName?: string } = {}): ExpoConfig {
const fakeExp: ExpoConfig = {
name: 'myproject',
slug: 'myproject',
@@ -311,7 +376,7 @@ function initBareWorkflowProject({
<key>CFBundleShortVersionString</key>
<string>${appVersion}</string>
<key>CFBundleVersion</key>
<string>${version}</string>
<string>${buildNumber}</string>
</dict>
</plist>`,
},
+19 -23
View File
@@ -1,8 +1,7 @@
import { IOSConfig } from '@expo/config-plugins';
import { Ios, Job, Metadata, Platform, Workflow } from '@expo/eas-build-job';
import type { XCBuildConfiguration } from 'xcode';
import { AppVersionSource } from '@expo/eas-json';
import { IosCredentials, Target } from '../../credentials/ios/types';
import { IosCredentials } from '../../credentials/ios/types';
import { BuildParamsInput } from '../../graphql/generated';
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
import { ensureBundleIdentifierIsDefinedForManagedProjectAsync } from '../../project/ios/bundleIdentifier';
@@ -16,6 +15,7 @@ import { ensureIosCredentialsAsync } from './credentials';
import { transformJob } from './graphql';
import { prepareJobAsync } from './prepareJob';
import { syncProjectConfigurationAsync } from './syncProjectConfiguration';
import { resolveRemoteBuildNumberAsync } from './version';
export async function createIosContextAsync(
ctx: CommonContext<Platform.IOS>
@@ -44,14 +44,22 @@ export async function createIosContextAsync(
env: buildProfile.env,
});
const applicationTarget = findApplicationTarget(targets);
const applicationTargetBuildSettings = resolveBuildSettings(ctx, applicationTarget);
const buildNumberOverride =
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
? await resolveRemoteBuildNumberAsync({
projectDir: ctx.projectDir,
projectId: ctx.projectId,
exp: ctx.exp,
applicationTarget,
buildProfile,
})
: undefined;
return {
bundleIdentifier: applicationTarget.bundleIdentifier,
applicationTarget,
applicationTargetBuildSettings,
targets,
xcodeBuildContext,
buildNumberOverride,
};
}
@@ -67,8 +75,11 @@ export async function prepareIosBuildAsync(
await syncProjectConfigurationAsync({
projectDir: ctx.projectDir,
exp: ctx.exp,
buildProfile: ctx.buildProfile,
buildSettings: ctx.ios.applicationTargetBuildSettings,
targets: ctx.ios.targets,
localAutoIncrement:
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
? false
: ctx.buildProfile.autoIncrement,
});
},
prepareJobAsync: async (
@@ -97,18 +108,3 @@ export async function prepareIosBuildAsync(
},
});
}
function resolveBuildSettings(
ctx: CommonContext<Platform.IOS>,
applicationTarget: Target
): XCBuildConfiguration['buildSettings'] {
if (ctx.workflow === Workflow.MANAGED) {
return {};
}
const project = IOSConfig.XcodeUtils.getPbxproj(ctx.projectDir);
const xcBuildConfiguration = IOSConfig.Target.getXCBuildConfigurationFromPbxproj(project, {
targetName: applicationTarget.targetName,
buildConfiguration: applicationTarget.buildConfiguration,
});
return xcBuildConfiguration?.buildSettings ?? {};
}
+2 -3
View File
@@ -1,4 +1,4 @@
import { Env, Ios } from '@expo/eas-build-job';
import { Ios } from '@expo/eas-build-job';
import nullthrows from 'nullthrows';
import { IosJobInput, IosJobSecretsInput } from '../../graphql/generated';
@@ -14,6 +14,7 @@ export function transformJob(job: Ios.Job): IosJobInput {
secrets: transformIosSecrets(job.secrets),
builderEnvironment: job.builderEnvironment,
cache: job.cache,
version: job.version?.buildNumber ? { buildNumber: job.version.buildNumber } : undefined,
scheme: job.scheme,
buildConfiguration: job.buildConfiguration,
artifactPath: job.artifactPath,
@@ -26,7 +27,6 @@ export function transformJob(job: Ios.Job): IosJobInput {
function transformIosSecrets(secrets: {
buildCredentials?: Ios.BuildCredentials;
env?: Env;
}): IosJobSecretsInput {
const buildCredentials: IosJobSecretsInput['buildCredentials'] = [];
@@ -42,6 +42,5 @@ function transformIosSecrets(secrets: {
return {
buildCredentials,
environmentSecrets: secrets.env,
};
}
@@ -66,6 +66,11 @@ export async function prepareJobAsync(
buildConfiguration: ctx.buildProfile.buildConfiguration,
artifactPath: ctx.buildProfile.artifactPath,
username,
...(ctx.ios.buildNumberOverride && {
version: {
buildNumber: ctx.ios.buildNumberOverride,
},
}),
experimental: {
prebuildCommand: ctx.buildProfile.prebuildCommand,
},
@@ -1,8 +1,8 @@
import { ExpoConfig } from '@expo/config';
import { Platform, Workflow } from '@expo/eas-build-job';
import { BuildProfile, IosVersionAutoIncrement } from '@expo/eas-json';
import type { XCBuildConfiguration } from 'xcode';
import { IosVersionAutoIncrement } from '@expo/eas-json';
import { Target } from '../../credentials/ios/types';
import { isExpoUpdatesInstalled } from '../../project/projectUtils';
import { resolveWorkflowAsync } from '../../project/workflow';
import { syncUpdatesConfigurationAsync } from '../../update/ios/UpdatesModule';
@@ -11,23 +11,22 @@ import { BumpStrategy, bumpVersionAsync, bumpVersionInAppJsonAsync } from './ver
export async function syncProjectConfigurationAsync({
projectDir,
exp,
buildProfile,
buildSettings,
targets,
localAutoIncrement,
}: {
projectDir: string;
exp: ExpoConfig;
buildProfile: BuildProfile<Platform.IOS>;
buildSettings: XCBuildConfiguration['buildSettings'];
targets: Target[];
localAutoIncrement?: IosVersionAutoIncrement;
}): Promise<void> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
const { autoIncrement } = buildProfile;
const versionBumpStrategy = resolveVersionBumpStrategy(autoIncrement ?? false);
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
if (workflow === Workflow.GENERIC) {
if (isExpoUpdatesInstalled(projectDir)) {
await syncUpdatesConfigurationAsync(projectDir, exp);
}
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, buildSettings });
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, targets });
} else {
await bumpVersionInAppJsonAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
}
+180 -43
View File
@@ -1,16 +1,25 @@
import { ExpoConfig, getConfigFilePaths } from '@expo/config';
import { IOSConfig } from '@expo/config-plugins';
import { ExpoConfig } from '@expo/config';
import { IOSConfig, Updates } from '@expo/config-plugins';
import { Platform, Workflow } from '@expo/eas-build-job';
import { BuildProfile } from '@expo/eas-json';
import chalk from 'chalk';
import path from 'path';
import type { XCBuildConfiguration } from 'xcode';
import { Target } from '../../credentials/ios/types';
import { AppPlatform } from '../../graphql/generated';
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
import Log from '../../log';
import { ora } from '../../ora';
import { findApplicationTarget } from '../../project/ios/target';
import { getNextBuildNumber, isValidBuildNumber } from '../../project/ios/versions';
import { resolveWorkflowAsync } from '../../project/workflow';
import { promptAsync } from '../../prompts';
import uniqBy from '../../utils/expodash/uniqBy';
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
import { updateAppJsonConfigAsync } from '../utils/appJson';
import { bumpAppVersionAsync } from '../utils/version';
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
export enum BumpStrategy {
APP_VERSION,
@@ -22,21 +31,25 @@ export async function bumpVersionAsync({
bumpStrategy,
projectDir,
exp,
buildSettings,
targets,
}: {
projectDir: string;
exp: ExpoConfig;
bumpStrategy: BumpStrategy;
buildSettings: XCBuildConfiguration['buildSettings'];
targets: Target[];
}): Promise<void> {
if (bumpStrategy === BumpStrategy.NOOP) {
return;
}
ensureStaticConfigExists(projectDir);
const infoPlist = await readInfoPlistAsync(projectDir, buildSettings);
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
Log.log('Updated versions in app.json');
await writeVersionsToInfoPlistAsync({ projectDir, exp, infoPlist, buildSettings });
await updateNativeVersionsAsync({
projectDir,
version: exp.version,
buildNumber: exp.ios?.buildNumber,
targets,
});
Log.log('Synchronized versions with Info.plist');
}
@@ -59,10 +72,8 @@ export async function bumpVersionInAppJsonAsync({
await bumpAppVersionAsync({ appVersion, projectDir, exp });
} 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('.');
if (isValidBuildNumber(buildNumber)) {
const bumpedBuildNumber = getNextBuildNumber(buildNumber);
Log.log(
`Bumping ${chalk.bold('expo.ios.buildNumber')} from ${chalk.bold(
buildNumber
@@ -121,12 +132,21 @@ export async function readBuildNumberAsync(
export async function maybeResolveVersionsAsync(
projectDir: string,
exp: ExpoConfig,
buildSettings: XCBuildConfiguration['buildSettings']
targets: Target[]
): Promise<{ appVersion?: string; appBuildVersion?: string }> {
const applicationTarget = findApplicationTarget(targets);
try {
return {
appBuildVersion: await readBuildNumberAsync(projectDir, exp, buildSettings),
appVersion: await readShortVersionAsync(projectDir, exp, buildSettings),
appBuildVersion: await readBuildNumberAsync(
projectDir,
exp,
applicationTarget.buildSettings ?? {}
),
appVersion: await readShortVersionAsync(
projectDir,
exp,
applicationTarget.buildSettings ?? {}
),
};
} catch (err: any) {
Log.warn('Failed to read app versions.');
@@ -137,23 +157,6 @@ export async function maybeResolveVersionsAsync(
}
}
async function writeVersionsToInfoPlistAsync({
projectDir,
exp,
infoPlist,
buildSettings,
}: {
projectDir: string;
exp: ExpoConfig;
infoPlist: IOSConfig.InfoPlist;
buildSettings: XCBuildConfiguration['buildSettings'];
}): Promise<IOSConfig.InfoPlist> {
let updatedInfoPlist = IOSConfig.Version.setVersion(exp, infoPlist);
updatedInfoPlist = IOSConfig.Version.setBuildNumber(exp, updatedInfoPlist);
await writeInfoPlistAsync({ projectDir, infoPlist: updatedInfoPlist, buildSettings });
return updatedInfoPlist;
}
export function getInfoPlistPath(
projectDir: string,
buildSettings: XCBuildConfiguration['buildSettings']
@@ -181,24 +184,55 @@ async function readInfoPlistAsync(
return ((await readPlistAsync(infoPlistPath)) ?? {}) as IOSConfig.InfoPlist;
}
async function writeInfoPlistAsync({
export async function updateNativeVersionsAsync({
projectDir,
infoPlist,
buildSettings,
version,
buildNumber,
targets,
}: {
projectDir: string;
infoPlist: IOSConfig.InfoPlist;
buildSettings: XCBuildConfiguration['buildSettings'];
version?: string;
buildNumber?: string;
targets: Target[];
}): Promise<void> {
const infoPlistPath = getInfoPlistPath(projectDir, buildSettings);
await writePlistAsync(infoPlistPath, infoPlist);
const project = IOSConfig.XcodeUtils.getPbxproj(projectDir);
const iosDir = path.join(projectDir, 'ios');
const infoPlistFiles: string[] = [];
for (const target of targets) {
const { targetName, buildConfiguration } = target;
const xcBuildConfiguration = IOSConfig.Target.getXCBuildConfigurationFromPbxproj(project, {
targetName,
buildConfiguration,
});
const infoPlist = xcBuildConfiguration?.buildSettings?.INFOPLIST_FILE;
if (infoPlist) {
const evaluatedInfoPlistPath = trimQuotes(
evaluateTemplateString(infoPlist, {
SRCROOT: iosDir,
})
);
const absolutePath = path.isAbsolute(evaluatedInfoPlistPath)
? evaluatedInfoPlistPath
: path.join(iosDir, evaluatedInfoPlistPath);
infoPlistFiles.push(path.normalize(absolutePath));
}
}
const uniqueInfoPlistPaths = uniqBy(infoPlistFiles, i => i);
for (const infoPlistPath of uniqueInfoPlistPaths) {
const infoPlist = (await readPlistAsync(infoPlistPath)) as IOSConfig.InfoPlist;
if (buildNumber) {
infoPlist.CFBundleVersion = buildNumber;
}
if (version) {
infoPlist.CFBundleShortVersionString = version;
}
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');
}
function trimQuotes(s: string): string {
return s?.startsWith('"') && s.endsWith('"') ? s.slice(1, -1) : s;
}
export function evaluateTemplateString(
@@ -216,3 +250,106 @@ export function evaluateTemplateString(
}
});
}
/**
* Returns buildNumber that will be used for the next build. If current build profile
* has an 'autoIncrement' option set, it increments the version on server.
*/
export async function resolveRemoteBuildNumberAsync({
projectDir,
projectId,
exp,
applicationTarget,
buildProfile,
}: {
projectDir: string;
projectId: string;
exp: ExpoConfig;
applicationTarget: Target;
buildProfile: BuildProfile<Platform.IOS>;
}): Promise<string> {
const remoteVersions = await AppVersionQuery.latestVersionAsync(
projectId,
AppPlatform.Ios,
applicationTarget.bundleIdentifier
);
const localBuildNumber = await readBuildNumberAsync(
projectDir,
exp,
applicationTarget.buildSettings ?? {}
);
const localShortVersion = await readShortVersionAsync(
projectDir,
exp,
applicationTarget.buildSettings ?? {}
);
let currentBuildVersion: string;
if (remoteVersions?.buildVersion) {
currentBuildVersion = remoteVersions.buildVersion;
} else {
if (localBuildNumber) {
Log.warn(
'No remote versions are configured for this project, buildNumber will be initialized based on the value from the local project.'
);
currentBuildVersion = localBuildNumber;
} else {
Log.error(
`Remote versions are not configured and EAS CLI was not able to read the current version from your project. Use "eas build:version:set" to initialize remote versions.`
);
throw new Error('Remote versions are not configured.');
}
}
if (!buildProfile.autoIncrement && remoteVersions?.buildVersion) {
return currentBuildVersion;
} else if (!buildProfile.autoIncrement && !remoteVersions?.buildVersion) {
const spinner = ora(
`Initializing buildNumber with ${chalk.bold(currentBuildVersion)}.`
).start();
try {
await AppVersionMutation.createAppVersionAsync({
appId: projectId,
platform: AppPlatform.Ios,
applicationIdentifier: applicationTarget.bundleIdentifier,
storeVersion: localShortVersion ?? '1.0.0',
buildVersion: currentBuildVersion,
runtimeVersion: Updates.getRuntimeVersionNullable(exp, Platform.IOS) ?? undefined,
});
spinner.succeed(`Initialized buildNumber with ${chalk.bold(currentBuildVersion)}.`);
} catch (err) {
spinner.fail(`Failed to initialize buildNumber with ${chalk.bold(currentBuildVersion)}.`);
throw err;
}
return currentBuildVersion;
} else {
const nextBuildVersion = getNextBuildNumber(currentBuildVersion);
const spinner = ora(
`Incrementing buildNumber from ${chalk.bold(currentBuildVersion)} to ${chalk.bold(
nextBuildVersion
)}.`
).start();
try {
await AppVersionMutation.createAppVersionAsync({
appId: projectId,
platform: AppPlatform.Ios,
applicationIdentifier: applicationTarget.bundleIdentifier,
storeVersion: localShortVersion ?? '1.0.0',
buildVersion: nextBuildVersion,
runtimeVersion: Updates.getRuntimeVersionNullable(exp, Platform.IOS) ?? undefined,
});
spinner.succeed(
`Incremented buildNumber from ${chalk.bold(currentBuildVersion)} to ${chalk.bold(
nextBuildVersion
)}.`
);
} catch (err) {
spinner.fail(
`Failed to increment buildNumber from ${chalk.bold(currentBuildVersion)} to ${chalk.bold(
nextBuildVersion
)}.`
);
throw err;
}
return nextBuildVersion;
}
}
+15 -8
View File
@@ -1,4 +1,4 @@
import { Job } from '@expo/eas-build-job';
import { Job, Metadata } from '@expo/eas-build-job';
import spawnAsync from '@expo/spawn-async';
import { ChildProcess } from 'child_process';
import semver from 'semver';
@@ -6,7 +6,7 @@ import semver from 'semver';
import { ora } from '../ora';
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
const PLUGIN_PACKAGE_VERSION = '0.0.85';
const PLUGIN_PACKAGE_VERSION = '0.0.99';
export interface LocalBuildOptions {
enable: boolean;
@@ -18,8 +18,12 @@ export interface LocalBuildOptions {
verbose?: boolean;
}
export async function runLocalBuildAsync(job: Job, options: LocalBuildOptions): Promise<void> {
const { command, args } = await getCommandAndArgsAsync(job);
export async function runLocalBuildAsync(
job: Job,
metadata: Metadata,
options: LocalBuildOptions
): Promise<void> {
const { command, args } = await getCommandAndArgsAsync(job, metadata);
let spinner;
if (!options.verbose) {
spinner = ora().start(options.skipNativeBuild ? 'Preparing project' : 'Building project');
@@ -53,15 +57,18 @@ export async function runLocalBuildAsync(job: Job, options: LocalBuildOptions):
}
}
async function getCommandAndArgsAsync(job: Job): Promise<{ command: string; args: string[] }> {
const jobBase64 = Buffer.from(JSON.stringify({ job })).toString('base64');
async function getCommandAndArgsAsync(
job: Job,
metadata: Metadata
): Promise<{ command: string; args: string[] }> {
const jobAndMetadataBase64 = Buffer.from(JSON.stringify({ job, metadata })).toString('base64');
if (process.env.EAS_LOCAL_BUILD_PLUGIN_PATH) {
return {
command: process.env.EAS_LOCAL_BUILD_PLUGIN_PATH,
args: [jobBase64],
args: [jobAndMetadataBase64],
};
} else {
const args = [`${PLUGIN_PACKAGE_NAME}@${PLUGIN_PACKAGE_VERSION}`, jobBase64];
const args = [`${PLUGIN_PACKAGE_NAME}@${PLUGIN_PACKAGE_VERSION}`, jobAndMetadataBase64];
if (await isAtLeastNpm7Async()) {
// npx shipped with npm >= 7.0.0 requires the "-y" flag to run commands without
// prompting the user to install a package that is used for the first time
+41 -6
View File
@@ -24,12 +24,13 @@ import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios
export async function collectMetadataAsync<T extends Platform>(
ctx: BuildContext<T>
): Promise<Metadata> {
const vcsClient = getVcsClient();
const channelOrReleaseChannel = await resolveChannelOrReleaseChannelAsync(ctx);
const distribution =
('simulator' in ctx.buildProfile && ctx.buildProfile.simulator
? 'simulator'
: ctx.buildProfile.distribution) ?? 'store';
const metadata = {
const metadata: Metadata = {
trackingContext: ctx.trackingCtx,
...(await maybeResolveVersionsAsync(ctx)),
cliVersion: easCliVersion,
@@ -43,14 +44,20 @@ export async function collectMetadataAsync<T extends Platform>(
appName: ctx.exp.name,
appIdentifier: resolveAppIdentifier(ctx),
buildProfile: ctx.buildProfileName,
gitCommitHash: await getVcsClient().getCommitHashAsync(),
isGitWorkingTreeDirty: await getVcsClient().hasUncommittedChangesAsync(),
gitCommitHash: await vcsClient.getCommitHashAsync(),
gitCommitMessage: truncateGitCommitMessage(
(await vcsClient.getLastCommitMessageAsync()) ?? undefined
),
isGitWorkingTreeDirty: await vcsClient.hasUncommittedChangesAsync(),
username: getUsername(ctx.exp, await ensureLoggedInAsync()),
message: ctx.message,
...(ctx.platform === Platform.IOS && {
iosEnterpriseProvisioning: resolveIosEnterpriseProvisioning(
ctx as BuildContext<Platform.IOS>
),
}),
runWithNoWaitFlag: ctx.noWait,
runFromCI: ctx.runFromCI,
};
return sanitizeMetadata(metadata);
}
@@ -60,14 +67,32 @@ async function maybeResolveVersionsAsync<T extends Platform>(
): Promise<{ appBuildVersion?: string; appVersion?: string }> {
if (ctx.platform === Platform.IOS) {
const iosContext = ctx as BuildContext<Platform.IOS>;
return await maybeResolveIosVersionsAsync(
const resolvedVersion = await maybeResolveIosVersionsAsync(
ctx.projectDir,
ctx.exp,
iosContext.ios.applicationTargetBuildSettings
iosContext.ios.targets
);
if (iosContext.ios.buildNumberOverride) {
return {
...resolvedVersion,
appBuildVersion: iosContext.ios.buildNumberOverride,
};
}
return resolvedVersion;
} else if (ctx.platform === Platform.ANDROID) {
const androidCtx = ctx as BuildContext<Platform.ANDROID>;
return await maybeResolveAndroidVersionsAsync(ctx.projectDir, ctx.exp, androidCtx.buildProfile);
const resolvedVersion = await maybeResolveAndroidVersionsAsync(
ctx.projectDir,
ctx.exp,
androidCtx.buildProfile
);
if (androidCtx.android.versionCodeOverride) {
return {
...resolvedVersion,
appBuildVersion: androidCtx.android.versionCodeOverride,
};
}
return resolvedVersion;
} else {
throw new Error(`Unsupported platform ${ctx.platform}`);
}
@@ -149,3 +174,13 @@ function resolveIosEnterpriseProvisioning(
): IosEnterpriseProvisioning | undefined {
return ctx.buildProfile.enterpriseProvisioning;
}
export function truncateGitCommitMessage(
msg: string | undefined,
maxLength: number = 4096
): string | undefined {
if (msg === undefined) {
return undefined;
}
return msg.length > maxLength ? `${msg.substring(0, maxLength - 3)}...` : msg;
}
@@ -1,5 +1,11 @@
import { Platform, Workflow } from '@expo/eas-build-job';
import { BuildProfile, EasJsonReader, SubmitProfile } from '@expo/eas-json';
import {
AppVersionSource,
BuildProfile,
EasJson,
EasJsonReader,
SubmitProfile,
} from '@expo/eas-json';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
@@ -8,8 +14,10 @@ import {
BuildFragment,
BuildResourceClass,
BuildStatus,
BuildWithSubmissionsFragment,
SubmissionFragment,
} from '../graphql/generated';
import { BuildQuery } from '../graphql/queries/BuildQuery';
import { toAppPlatform, toPlatform } from '../graphql/types/AppPlatform';
import Log from '../log';
import {
@@ -20,8 +28,14 @@ import {
} from '../platform';
import { checkExpoSdkIsSupportedAsync } from '../project/expoSdk';
import { validateMetroConfigForManagedWorkflowAsync } from '../project/metroConfig';
import { validateAppVersionRuntimePolicySupportAsync } from '../project/projectUtils';
import {
validateAppConfigForRemoteVersionSource,
validateBuildProfileVersionSettings,
} from '../project/remoteVersionSource';
import { createSubmissionContextAsync } from '../submit/context';
import {
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish,
submitAsync,
waitToCompleteAsync as waitForSubmissionsToCompleteAsync,
} from '../submit/submit';
@@ -55,6 +69,7 @@ export interface BuildFlags {
submitProfile?: string;
localBuildOptions: LocalBuildOptions;
userInputResourceClass?: UserInputResourceClass;
message?: string;
}
const platformToGraphQLResourceClassMapping: Record<
@@ -80,6 +95,7 @@ export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFla
nonInteractive: flags.nonInteractive,
});
const easJsonReader = new EasJsonReader(projectDir);
const easJsonCliConfig: EasJson['cli'] = (await easJsonReader.getCliConfigAsync()) ?? {};
const platforms = toPlatforms(flags.requestedPlatform);
const buildProfiles = await getProfilesAsync({
@@ -95,8 +111,12 @@ export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFla
buildProfiles,
});
for (const buildProfile of buildProfiles) {
validateBuildProfileVersionSettings(buildProfile, easJsonCliConfig);
}
const startedBuilds: {
build: BuildFragment;
build: BuildWithSubmissionsFragment | BuildFragment;
buildProfile: ProfileData<'build'>;
}[] = [];
const buildCtxByPlatform: { [p in AppPlatform]?: BuildContext<Platform> } = {};
@@ -111,6 +131,7 @@ export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFla
platformToGraphQLResourceClassMapping[buildProfile.platform][
flags.userInputResourceClass ?? UserInputResourceClass.DEFAULT
],
easJsonCliConfig,
});
if (maybeBuild) {
startedBuilds.push({ build: maybeBuild, buildProfile });
@@ -149,6 +170,7 @@ export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFla
submitProfile,
nonInteractive: flags.nonInteractive,
});
startedBuild.build = await BuildQuery.withSubmissionsByIdAsync(startedBuild.build.id);
submissions.push(submission);
}
@@ -169,16 +191,30 @@ export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFla
buildIds: startedBuilds.map(({ build }) => build.id),
accountName,
});
printBuildResults(builds, flags.json);
if (!flags.json) {
printBuildResults(builds);
}
const haveAllBuildsFailedOrCanceled = builds.every(
build => build?.status && [BuildStatus.Errored, BuildStatus.Canceled].includes(build?.status)
);
if (haveAllBuildsFailedOrCanceled || !flags.autoSubmit) {
if (flags.json) {
printJsonOnlyOutput(builds);
}
exitWithNonZeroCodeIfSomeBuildsFailed(builds);
} else {
// the following function also exits with non zero code if any of the submissions failed
await waitForSubmissionsToCompleteAsync(submissions);
const completedSubmissions = await waitForSubmissionsToCompleteAsync(submissions);
if (flags.json) {
printJsonOnlyOutput(
await Promise.all(
builds
.filter((i): i is BuildWithSubmissionsFragment => !!i)
.map(build => BuildQuery.withSubmissionsByIdAsync(build.id))
)
);
}
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish(completedSubmissions);
}
}
@@ -188,12 +224,14 @@ async function prepareAndStartBuildAsync({
moreBuilds,
buildProfile,
resourceClass,
easJsonCliConfig,
}: {
projectDir: string;
flags: BuildFlags;
moreBuilds: boolean;
buildProfile: ProfileData<'build'>;
resourceClass: BuildResourceClass;
easJsonCliConfig: EasJson['cli'];
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
const buildCtx = await createBuildContextAsync({
buildProfileName: buildProfile.profileName,
@@ -201,9 +239,12 @@ async function prepareAndStartBuildAsync({
clearCache: flags.clearCache,
buildProfile: buildProfile.profile,
nonInteractive: flags.nonInteractive,
noWait: !flags.wait,
platform: buildProfile.platform,
projectDir,
localBuildOptions: flags.localBuildOptions,
easJsonCliConfig,
message: flags.message,
});
if (moreBuilds) {
@@ -215,7 +256,10 @@ async function prepareAndStartBuildAsync({
)}`
);
}
await validateAppVersionRuntimePolicySupportAsync(buildCtx.projectDir, buildCtx.exp);
if (easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE) {
validateAppConfigForRemoteVersionSource(buildCtx.exp, buildProfile.platform);
}
if (buildCtx.workflow === Workflow.MANAGED) {
if (!sdkVersionChecked) {
await checkExpoSdkIsSupportedAsync(buildCtx);
@@ -14,7 +14,6 @@ import {
} from '../../graphql/generated';
import Log, { learnMore } from '../../log';
import { appPlatformDisplayNames, appPlatformEmojis } from '../../platform';
import { printJsonOnlyOutput } from '../../utils/json';
import { getBuildLogsUrl, getInternalDistributionInstallUrl } from './url';
export function printLogsUrls(builds: BuildFragment[]): void {
@@ -32,18 +31,14 @@ export function printLogsUrls(builds: BuildFragment[]): void {
}
}
export function printBuildResults(builds: (BuildFragment | null)[], json: boolean): void {
if (json) {
printJsonOnlyOutput(builds);
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 {
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));
}
(builds.filter(i => i) as BuildFragment[]).forEach(build => printBuildResult(build));
}
}
@@ -99,14 +94,14 @@ export function printDeprecationWarnings(deprecationInfo?: EasBuildDeprecationIn
return;
}
if (deprecationInfo.type === EasBuildDeprecationInfoType.Internal) {
Log.warn('This command is using API that soon will be deprecated, please update eas-cli.');
Log.warn('This command is using API that soon will be deprecated. Upgrade 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.');
Log.warn(deprecationInfo.message);
} else {
Log.warn('An unexpected warning was encountered. Please report it as a bug:');
Log.warn('An unexpected warning was encountered. Report it as a bug:');
Log.warn(deprecationInfo);
}
}
@@ -28,7 +28,7 @@ export async function maybeBailOnRepoStatusAsync(): Promise<void> {
});
if (!answer) {
throw new Error('Please commit all changes. Aborting...');
throw new Error('Commit all changes. Aborting...');
}
}
@@ -39,12 +39,12 @@ export async function ensureRepoIsCleanAsync(nonInteractive = false): Promise<vo
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'
`This operation needs to be run on a clean working tree. ${chalk.bold(
'Commit all your changes before proceeding'
)}.`
);
if (nonInteractive) {
throw new Error('Please commit all changes. Aborting...');
throw new Error('Commit all changes. Aborting...');
}
const answer = await confirmAsync({
message: `Commit changes to git?`,
@@ -52,7 +52,7 @@ export async function ensureRepoIsCleanAsync(nonInteractive = false): Promise<vo
if (answer) {
await commitPromptAsync({ commitAllFiles: true });
} else {
throw new Error('Please commit all changes. Aborting...');
throw new Error('Commit all changes. Aborting...');
}
}
@@ -1,5 +1,9 @@
import { EasJsonReader } from '@expo/eas-json';
import * as PackageManagerUtils from '@expo/package-manager';
import { Command } from '@oclif/core';
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import semver from 'semver';
import {
@@ -8,6 +12,7 @@ import {
initAsync as initAnalyticsAsync,
logEvent,
} from '../analytics/rudderstackClient';
import { learnMore } from '../log';
import { findProjectRootAsync } from '../project/projectUtils';
import { getUserAsync } from '../user/User';
import { ensureLoggedInAsync } from '../user/actions';
@@ -28,8 +33,11 @@ export default abstract class EasCommand extends Command {
// eslint-disable-next-line async-protect/async-suffix
async run(): Promise<any> {
await initAnalyticsAsync();
if (this.mustBeRunInsideProject) {
await this.applyCliConfigAsync();
const projectDir = await findProjectRootAsync();
await this.applyCliConfigAsync(projectDir);
await this.ensureEasCliIsNotInDependenciesAsync(projectDir);
}
if (this.requiresAuthentication) {
@@ -53,8 +61,7 @@ export default abstract class EasCommand extends Command {
return super.finally(err);
}
private async applyCliConfigAsync(): Promise<void> {
const projectDir = await findProjectRootAsync();
private async applyCliConfigAsync(projectDir: string): Promise<void> {
const easJsonReader = new EasJsonReader(projectDir);
const config = await easJsonReader.getCliConfigAsync();
if (config?.version && !semver.satisfies(easCliVersion, config.version)) {
@@ -66,4 +73,50 @@ export default abstract class EasCommand extends Command {
setVcsClient(new GitClient());
}
}
private async ensureEasCliIsNotInDependenciesAsync(projectDir: string): Promise<void> {
let printCliVersionWarning = false;
const consoleWarn = (msg?: string): void => {
if (msg) {
// eslint-disable-next-line no-console
console.warn(chalk.yellow(msg));
} else {
// eslint-disable-next-line no-console
console.warn();
}
};
if (await this.isEasCliInDependenciesAsync(projectDir)) {
printCliVersionWarning = true;
consoleWarn(`Found ${chalk.bold('eas-cli')} in your project dependencies.`);
}
const maybeRepoRoot = PackageManagerUtils.findWorkspaceRoot(projectDir) ?? projectDir;
if (maybeRepoRoot !== projectDir && (await this.isEasCliInDependenciesAsync(maybeRepoRoot))) {
printCliVersionWarning = true;
consoleWarn(`Found ${chalk.bold('eas-cli')} in your monorepo dependencies.`);
}
if (printCliVersionWarning) {
consoleWarn(
`It's recommended to use the ${chalk.bold(
'"cli.version"'
)} field in eas.json to enforce the ${chalk.bold('eas-cli')} version for your project.`
);
consoleWarn(
learnMore('https://github.com/expo/eas-cli#enforcing-eas-cli-version-for-your-project')
);
consoleWarn();
}
}
private async isEasCliInDependenciesAsync(dir: string): Promise<boolean> {
const packageJsonPath = path.join(dir, 'package.json');
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
return (
packageJson?.dependencies?.['eas-cli'] !== undefined ||
packageJson?.devDependencies?.['eas-cli'] !== undefined
);
}
}
@@ -0,0 +1,58 @@
import { Flags } from '@oclif/core';
export const getPaginatedQueryOptions = (
flags: Record<keyof typeof EasPaginatedQueryFlags, any>
): PaginatedQueryOptions => {
return {
json: flags.json ?? false,
offset: flags.offset ?? 0,
nonInteractive: flags['non-interactive'] ?? false,
...('limit' in flags && { limit: flags.limit }),
};
};
const parseFlagInputStringAsInteger = (
input: string,
flagName: string,
lowerLimit: number,
upperLimit: number
): number => {
const inputAsNumber = Number(input);
if (isNaN(inputAsNumber)) {
throw new Error(`Unable to parse ${input} as a number`);
}
if (inputAsNumber < lowerLimit || inputAsNumber > upperLimit) {
throw new Error(`--${flagName} must be between ${lowerLimit} and ${upperLimit}`);
}
return inputAsNumber;
};
export const EasPaginatedQueryFlags = {
offset: Flags.integer({
description: 'Start queries from specified index. Use for paginating results. Defaults to 0.',
// eslint-disable-next-line async-protect/async-suffix
parse: async input =>
parseFlagInputStringAsInteger(input, 'offset', 0, Number.MAX_SAFE_INTEGER),
}),
limit: Flags.integer({
description:
'The number of query items to list at once. The default value is 50 (the maximum is 100). Using a lower value may help increase command speed.',
// eslint-disable-next-line async-protect/async-suffix
parse: async input => parseFlagInputStringAsInteger(input, 'limit', 1, 100),
}),
json: Flags.boolean({
description: 'Enable JSON output, non-JSON messages will be printed to stderr.',
}),
'non-interactive': Flags.boolean({
description: 'Run the command in non-interactive mode.',
}),
};
// options required to control a paginated query
// from user input
export type PaginatedQueryOptions = {
nonInteractive: boolean;
json: boolean;
limit?: number;
offset: number;
};
@@ -91,7 +91,7 @@ export default class BranchCreate extends EasCommand {
({ name } = await promptAsync({
type: 'text',
name: 'name',
message: 'Please name the branch:',
message: 'Provide a branch name:',
initial:
(await getVcsClient().getBranchNameAsync()) ||
`branch-${Math.random().toString(36).substr(2, 4)}`,
@@ -115,7 +115,7 @@ export default class BranchDelete extends EasCommand {
({ name } = await promptAsync({
type: 'text',
name: 'name',
message: 'Please enter the name of the branch to delete:',
message: 'Provide the name of the branch to delete:',
validate: value => (value ? true : validationMessage),
}));
}
+8 -86
View File
@@ -1,106 +1,28 @@
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import CliTable from 'cli-table3';
import { print } from 'graphql';
import gql from 'graphql-tag';
import { listAndRenderPaginatedBranchesAsync } from '../../branch/queries';
import EasCommand from '../../commandUtils/EasCommand';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import {
BranchesByAppQuery,
BranchesByAppQueryVariables,
UpdateBranchFragment,
} from '../../graphql/generated';
import { UpdateBranchFragmentNode } from '../../graphql/types/UpdateBranch';
import Log from '../../log';
import { EasPaginatedQueryFlags, getPaginatedQueryOptions } from '../../commandUtils/pagination';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { UPDATE_COLUMNS, formatUpdate, getPlatformsForGroup } from '../../update/utils';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
const BRANCHES_LIMIT = 10_000;
export async function listBranchesAsync({
projectId,
}: {
projectId: string;
}): Promise<UpdateBranchFragment[]> {
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
...UpdateBranchFragment
}
}
}
}
${print(UpdateBranchFragmentNode)}
`,
{
appId: projectId,
limit: BRANCHES_LIMIT,
},
{ additionalTypenames: ['UpdateBranch'] }
)
.toPromise()
);
return data?.app?.byId.updateBranches ?? [];
}
import { enableJsonOutput } from '../../utils/json';
export default class BranchList extends EasCommand {
static description = 'list all branches';
static flags = {
json: Flags.boolean({
description: 'return output as JSON',
default: false,
}),
...EasPaginatedQueryFlags,
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(BranchList);
if (flags.json) {
const options = getPaginatedQueryOptions(flags);
if (options.json) {
enableJsonOutput();
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const projectId = await getProjectIdAsync(exp);
const branches = await listBranchesAsync({ projectId });
if (flags.json) {
printJsonOnlyOutput(branches);
} 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.`);
}
}
await listAndRenderPaginatedBranchesAsync(projectId, options);
}
}
@@ -90,7 +90,7 @@ export default class BranchRename extends EasCommand {
({ currentName } = await promptAsync({
type: 'text',
name: 'currentName',
message: 'Please enter the current name of the branch to rename:',
message: "Provide the name of the branch you'd like to rename:",
validate: value => (value ? true : validationMessage),
}));
}
@@ -103,7 +103,7 @@ export default class BranchRename extends EasCommand {
({ newName } = await promptAsync({
type: 'text',
name: 'newName',
message: `Please rename ${currentName}`,
message: `Rename ${currentName}`,
validate: value => (value ? true : validationMessage),
}));
}
+22 -73
View File
@@ -1,17 +1,10 @@
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import Table from 'cli-table3';
import { selectBranchFromPaginatedQueryAsync } from '../../branch/queries';
import EasCommand from '../../commandUtils/EasCommand';
import { UpdateQuery } from '../../graphql/queries/UpdateQuery';
import Log from '../../log';
import { EasPaginatedQueryFlags, getPaginatedQueryOptions } from '../../commandUtils/pagination';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { UPDATE_COLUMNS, formatUpdate, getPlatformsForGroup } from '../../update/utils';
import groupBy from '../../utils/expodash/groupBy';
import formatFields from '../../utils/formatFields';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { listAndRenderUpdatesOnBranchByNameAsync } from '../../update/queries';
import { enableJsonOutput } from '../../utils/json';
export default class BranchView extends EasCommand {
static description = 'view a branch';
@@ -25,18 +18,17 @@ export default class BranchView extends EasCommand {
];
static flags = {
json: Flags.boolean({
description: `return a json with the branch's ID name and recent update groups.`,
default: false,
}),
...EasPaginatedQueryFlags,
};
async runAsync(): Promise<void> {
let {
args: { name },
flags: { json: jsonFlag },
args: { name: branchName },
flags,
} = await this.parse(BranchView);
if (jsonFlag) {
const options = getPaginatedQueryOptions(flags);
if (options.json) {
enableJsonOutput();
}
@@ -44,63 +36,20 @@ export default class BranchView extends EasCommand {
const exp = getExpoConfig(projectDir);
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 { app } = await UpdateQuery.viewBranchAsync({
appId: projectId,
name,
});
const UpdateBranch = app?.byId.updateBranchByName;
if (!UpdateBranch) {
throw new Error(`Could not find branch "${name}"`);
}
const updates = Object.values(groupBy(UpdateBranch.updates, u => u.group)).map(
group => group[0]
);
if (jsonFlag) {
printJsonOnlyOutput({ ...UpdateBranch, updates });
} else {
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,
}),
]);
// provide help to a user if they ran the command with missing args
if (!branchName) {
if (options.nonInteractive) {
throw new Error('Branch name may not be empty.');
}
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());
({ name: branchName } = await selectBranchFromPaginatedQueryAsync(
projectId,
'Which branch would you like to view?',
// discard limit and offset because this query is not those flag's intended target
{ json: options.json, nonInteractive: options.nonInteractive, offset: 0 }
));
}
await listAndRenderUpdatesOnBranchByNameAsync(projectId, branchName, options);
}
}
+60 -18
View File
@@ -4,10 +4,12 @@ import path from 'path';
import { BuildFlags, runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
import { UserInputResourceClass } from '../../build/types';
import EasCommand from '../../commandUtils/EasCommand';
import { StatuspageServiceName } from '../../graphql/generated';
import Log from '../../log';
import { RequestedPlatform, selectRequestedPlatformAsync } from '../../platform';
import { findProjectRootAsync } from '../../project/projectUtils';
import { enableJsonOutput } from '../../utils/json';
import { maybeWarnAboutEasOutagesAsync } from '../../utils/statuspageService';
interface RawBuildFlags {
platform?: string;
@@ -23,6 +25,7 @@ interface RawBuildFlags {
'auto-submit': boolean;
'auto-submit-with-profile'?: string;
'resource-class'?: UserInputResourceClass;
message?: string;
}
export default class Build extends EasCommand {
@@ -86,21 +89,38 @@ export default class Build extends EasCommand {
hidden: true,
description: 'The instance type that will be used to run this build [experimental]',
}),
message: Flags.string({
char: 'm',
description: 'A short message describing the build',
}),
};
async runAsync(): Promise<void> {
const { flags: rawFlags } = await this.parse(Build);
if (rawFlags.json) {
enableJsonOutput();
}
const flags = await this.sanitizeFlagsAsync(rawFlags);
const flags = this.sanitizeFlags(rawFlags);
const projectDir = await findProjectRootAsync();
await runBuildAndSubmitAsync(projectDir, flags);
if (!flags.localBuildOptions.enable) {
await maybeWarnAboutEasOutagesAsync(
flags.autoSubmit
? [StatuspageServiceName.EasBuild, StatuspageServiceName.EasSubmit]
: [StatuspageServiceName.EasBuild]
);
}
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
await runBuildAndSubmitAsync(projectDir, flagsWithPlatform);
}
private async sanitizeFlagsAsync(flags: RawBuildFlags): Promise<BuildFlags> {
private sanitizeFlags(
flags: RawBuildFlags
): Omit<BuildFlags, 'requestedPlatform'> & { requestedPlatform?: RequestedPlatform } {
const nonInteractive = flags['non-interactive'];
if (!flags.local && flags.output) {
Errors.error('--output is allowed only for local builds', { exit: 1 });
@@ -112,21 +132,11 @@ export default class Build extends EasCommand {
Errors.error('--json is allowed only when building in non-interactive mode', { exit: 1 });
}
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
if (flags.local) {
if (flags['auto-submit'] || flags['auto-submit-with-profile'] !== undefined) {
// TODO: implement this
Errors.error('Auto-submits are not yet supported when building locally', { exit: 1 });
}
if (requestedPlatform === RequestedPlatform.All) {
Errors.error('Builds for multiple platforms are not supported with flag --local', {
exit: 1,
});
} else if (process.platform !== 'darwin' && requestedPlatform === RequestedPlatform.Ios) {
Errors.error('Unsupported platform, macOS is required to build apps for iOS', { exit: 1 });
}
}
const requestedPlatform =
flags.platform &&
Object.values(RequestedPlatform).includes(flags.platform.toLowerCase() as RequestedPlatform)
? (flags.platform.toLowerCase() as RequestedPlatform)
: undefined;
if (flags['skip-credentials-check']) {
Log.warnDeprecatedFlag(
@@ -143,6 +153,11 @@ export default class Build extends EasCommand {
Log.newLine();
}
const message = flags['message'];
if (message && message.length > 1024) {
Errors.error('Message cannot be longer than 1024 characters.', { exit: 1 });
}
const profile = flags['profile'];
return {
requestedPlatform,
@@ -163,6 +178,33 @@ export default class Build extends EasCommand {
autoSubmit: flags['auto-submit'] || flags['auto-submit-with-profile'] !== undefined,
submitProfile: flags['auto-submit-with-profile'] ?? profile,
userInputResourceClass: flags['resource-class'] ?? UserInputResourceClass.DEFAULT,
message,
};
}
private async ensurePlatformSelectedAsync(
flags: Omit<BuildFlags, 'requestedPlatform'> & { requestedPlatform?: RequestedPlatform }
): Promise<BuildFlags> {
const requestedPlatform = await selectRequestedPlatformAsync(flags.requestedPlatform);
if (flags.localBuildOptions.enable) {
if (flags.autoSubmit) {
// TODO: implement this
Errors.error('Auto-submits are not yet supported when building locally', { exit: 1 });
}
if (requestedPlatform === RequestedPlatform.All) {
Errors.error('Builds for multiple platforms are not supported with flag --local', {
exit: 1,
});
} else if (process.platform !== 'darwin' && requestedPlatform === RequestedPlatform.Ios) {
Errors.error('Unsupported platform, macOS is required to build apps for iOS', { exit: 1 });
}
}
return {
...flags,
requestedPlatform,
};
}
}
@@ -0,0 +1,113 @@
import { getRuntimeVersionNullable } from '@expo/config-plugins/build/utils/Updates';
import { Platform } from '@expo/eas-build-job';
import { EasJsonReader } from '@expo/eas-json';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import EasCommand from '../../../commandUtils/EasCommand';
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
import { toAppPlatform } from '../../../graphql/types/AppPlatform';
import Log from '../../../log';
import { selectPlatformAsync } from '../../../platform';
import { VERSION_CODE_REQUIREMENTS, isValidVersionCode } from '../../../project/android/versions';
import { getApplicationIdentifierAsync } from '../../../project/applicationIdentifier';
import { getExpoConfig } from '../../../project/expoConfig';
import { BUILD_NUMBER_REQUIREMENTS, isValidBuildNumber } from '../../../project/ios/versions';
import {
findProjectRootAsync,
getProjectFullNameAsync,
getProjectIdAsync,
} from '../../../project/projectUtils';
import {
ensureVersionSourceIsRemoteAsync,
getBuildVersionName,
validateAppConfigForRemoteVersionSource,
} from '../../../project/remoteVersionSource';
import { promptAsync } from '../../../prompts';
export default class BuildVersionSetView extends EasCommand {
static description = 'Update version of an app.';
static flags = {
platform: Flags.enum({
char: 'p',
options: ['android', 'ios'],
}),
profile: Flags.string({
description:
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
helpValue: 'PROFILE_NAME',
}),
};
public async runAsync(): Promise<void> {
const { flags } = await this.parse(BuildVersionSetView);
const projectDir = await findProjectRootAsync();
const platform = await selectPlatformAsync(flags.platform);
const easJsonReader = new EasJsonReader(projectDir);
await ensureVersionSourceIsRemoteAsync(projectDir, easJsonReader);
const profile = await easJsonReader.getBuildProfileAsync(platform, flags.profile ?? undefined);
const exp = getExpoConfig(projectDir, { env: profile.env });
const projectId = await getProjectIdAsync(exp);
const projectFullName = await getProjectFullNameAsync(exp);
validateAppConfigForRemoteVersionSource(exp, platform);
const applicationIdentifier = await getApplicationIdentifierAsync(
projectDir,
exp,
profile,
platform
);
const remoteVersions = await AppVersionQuery.latestVersionAsync(
projectId,
toAppPlatform(platform),
applicationIdentifier
);
const currentStateMessage = remoteVersions?.buildVersion
? `Project ${chalk.bold(projectFullName)} with ${getApplicationIdentifierName(
platform
)} "${applicationIdentifier}" is configured with ${getBuildVersionName(platform)} ${
remoteVersions.buildVersion
}.`
: `Project ${chalk.bold(projectFullName)} with ${getApplicationIdentifierName(
platform
)} "${applicationIdentifier}" does not have any ${getBuildVersionName(
platform
)} configured.`;
const versionPromptMessage = remoteVersions?.buildVersion
? `What version would you like to set?`
: `What version would you like to initialize it with?`;
Log.log(currentStateMessage);
const { version } = await promptAsync({
type: platform === Platform.ANDROID ? 'number' : 'text',
name: 'version',
message: versionPromptMessage,
validate:
platform === Platform.ANDROID
? value => isValidVersionCode(value) || `Invalid value: ${VERSION_CODE_REQUIREMENTS}.`
: value => isValidBuildNumber(value) || `Invalid value: ${BUILD_NUMBER_REQUIREMENTS}.`,
});
await AppVersionMutation.createAppVersionAsync({
appId: projectId,
platform: toAppPlatform(platform),
applicationIdentifier,
storeVersion: exp.version ?? '1.0.0',
buildVersion: String(version),
runtimeVersion: getRuntimeVersionNullable(exp, platform) ?? undefined,
});
}
}
function getApplicationIdentifierName(platform: Platform): string {
if (platform === Platform.ANDROID) {
return 'application ID';
} else {
return 'bundle identifier';
}
}
@@ -0,0 +1,183 @@
import { ExpoConfig } from '@expo/config';
import { Platform, Workflow } from '@expo/eas-build-job';
import { BuildProfile, EasJsonReader } from '@expo/eas-json';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../../build/android/version';
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../../build/ios/version';
import EasCommand from '../../../commandUtils/EasCommand';
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
import { toAppPlatform } from '../../../graphql/types/AppPlatform';
import Log from '../../../log';
import {
appPlatformDisplayNames,
selectRequestedPlatformAsync,
toPlatforms,
} from '../../../platform';
import { getAppBuildGradleAsync } from '../../../project/android/gradleUtils';
import { VERSION_CODE_REQUIREMENTS, isValidVersionCode } from '../../../project/android/versions';
import { getApplicationIdentifierAsync } from '../../../project/applicationIdentifier';
import { getExpoConfig } from '../../../project/expoConfig';
import { resolveXcodeBuildContextAsync } from '../../../project/ios/scheme';
import { resolveTargetsAsync } from '../../../project/ios/target';
import { BUILD_NUMBER_REQUIREMENTS, isValidBuildNumber } from '../../../project/ios/versions';
import { findProjectRootAsync, getProjectIdAsync } from '../../../project/projectUtils';
import {
ensureVersionSourceIsRemoteAsync,
getBuildVersionName,
validateAppConfigForRemoteVersionSource,
} from '../../../project/remoteVersionSource';
import { resolveWorkflowAsync } from '../../../project/workflow';
import { getProfilesAsync } from '../../../utils/profiles';
interface SyncContext<T extends Platform> {
projectDir: string;
exp: ExpoConfig;
workflow: Workflow;
profile: BuildProfile<T>;
buildVersion: string;
}
export default class BuildVersionSyncView extends EasCommand {
public static description = 'Update a version in native code with a value stored on EAS servers';
public static flags = {
platform: Flags.enum({
char: 'p',
options: ['android', 'ios', 'all'],
}),
profile: Flags.string({
description:
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
helpValue: 'PROFILE_NAME',
}),
};
public async runAsync(): Promise<void> {
const { flags } = await this.parse(BuildVersionSyncView);
const projectDir = await findProjectRootAsync();
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
const easJsonReader = new EasJsonReader(projectDir);
await ensureVersionSourceIsRemoteAsync(projectDir, easJsonReader);
const platforms = toPlatforms(requestedPlatform);
const buildProfiles = await getProfilesAsync({
type: 'build',
easJsonReader,
platforms,
profileName: flags.profile ?? undefined,
});
for (const profileInfo of buildProfiles) {
const exp = getExpoConfig(projectDir, { env: profileInfo.profile.env });
const projectId = await getProjectIdAsync(exp);
validateAppConfigForRemoteVersionSource(exp, profileInfo.platform);
const platformDisplayName = appPlatformDisplayNames[toAppPlatform(profileInfo.platform)];
const applicationIdentifier = await getApplicationIdentifierAsync(
projectDir,
exp,
profileInfo.profile,
profileInfo.platform
);
const remoteVersions = await AppVersionQuery.latestVersionAsync(
projectId,
toAppPlatform(profileInfo.platform),
applicationIdentifier
);
const workflow = await resolveWorkflowAsync(projectDir, profileInfo.platform);
if (!remoteVersions?.buildVersion) {
Log.warn(
`Skipping versions sync for ${platformDisplayName}. There are no versions configured on Expo servers, use "eas build:version:set" or run a build to initialize it.`
);
continue;
}
if (workflow === Workflow.MANAGED) {
Log.warn(
`The remote value for the ${platformDisplayName} ${getBuildVersionName(
profileInfo.platform
)} is ${chalk.bold(
remoteVersions?.buildVersion
)}, but it was not synced to the local project. This command has no effect on projects using managed workflow.`
);
continue;
}
if (profileInfo.platform === Platform.ANDROID) {
this.syncAndroidAsync({
projectDir,
exp,
profile: profileInfo.profile as BuildProfile<Platform.ANDROID>,
workflow,
buildVersion: remoteVersions.buildVersion,
});
} else {
this.syncIosAsync({
projectDir,
exp,
profile: profileInfo.profile as BuildProfile<Platform.IOS>,
workflow,
buildVersion: remoteVersions.buildVersion,
});
}
Log.withTick(
`Successfully updated the ${platformDisplayName} ${getBuildVersionName(
profileInfo.platform
)} in native code to ${chalk.bold(remoteVersions?.buildVersion)}.`
);
}
}
private async syncIosAsync({
workflow,
projectDir,
exp,
profile,
buildVersion,
}: SyncContext<Platform.IOS>): Promise<void> {
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
{ exp, projectDir, nonInteractive: false },
profile
);
const targets = await resolveTargetsAsync({
projectDir,
exp,
xcodeBuildContext,
env: profile.env,
});
if (!isValidBuildNumber(buildVersion)) {
throw new Error(`Invalid buildNumber (${buildVersion}), ${BUILD_NUMBER_REQUIREMENTS}.`);
}
if (workflow === Workflow.GENERIC) {
await updateIosNativeVersionsAsync({
projectDir,
buildNumber: buildVersion,
targets,
});
}
}
private async syncAndroidAsync({
projectDir,
workflow,
buildVersion,
}: SyncContext<Platform.ANDROID>): Promise<void> {
if (!isValidVersionCode(buildVersion)) {
throw new Error(`Invalid versionCode (${buildVersion}), ${VERSION_CODE_REQUIREMENTS}.`);
}
if (workflow === Workflow.GENERIC) {
const buildGradle = await getAppBuildGradleAsync(projectDir);
const isMultiFlavor =
buildGradle.android?.productFlavors || buildGradle.android?.flavorDimensions;
if (isMultiFlavor) {
throw new Error(
"Versions in native code can't be automatically synced in multi-flavor Android projects. If you are using EAS Build with app version source set to remote, the correct values will be injected at the build time."
);
}
await updateAndroidNativeVersionsAsync({ projectDir, versionCode: Number(buildVersion) });
}
}
}
@@ -99,7 +99,7 @@ export default class ChannelCreate extends EasCommand {
({ name: channelName } = await promptAsync({
type: 'text',
name: 'name',
message: 'Please name the channel:',
message: 'Provide a channel name:',
validate: value => (value ? true : validationMessage),
}));
}
@@ -70,7 +70,7 @@ export default class ChannelDelete extends EasCommand {
await promptAsync({
type: 'text',
name: 'name',
message: 'Please enter the name of the channel to delete:',
message: 'Provide the name of the channel to delete:',
validate: (value: any) => (value ? true : 'Channel name may not be empty.'),
})
).name;
@@ -127,9 +127,7 @@ export default class ChannelEdit extends EasCommand {
const existingChannel = await getChannelByNameForAppAsync({ appId: projectId, channelName });
if (existingChannel.updateBranches.length > 1) {
throw new Error(
'There is a rollout in progress. Please manage it with "channel:rollout" instead.'
);
throw new Error('There is a rollout in progress. Manage it with "channel:rollout" instead.');
}
const branchName = flags.branch ?? (await promptForBranchAsync());
@@ -140,7 +138,7 @@ export default class ChannelEdit extends EasCommand {
});
if (!branch) {
throw new Error(
`Could not find a branch named "${branchName}". Please check what branches exist on this project with ${chalk.bold(
`Could not find a branch named "${branchName}". Check what branches exist on this project with ${chalk.bold(
'eas branch:list'
)}.`
);
@@ -173,7 +171,7 @@ async function promptForChannelAsync(): Promise<string> {
const { name } = await promptAsync({
type: 'text',
name: 'name',
message: 'Please enter the name of the channel to edit:',
message: 'Provide the name of the channel to edit:',
validate: value => (value ? true : 'The channel name may not be empty.'),
});
return name;
@@ -92,7 +92,7 @@ async function startRolloutAsync({
});
if (!branch) {
throw new Error(
`Could not find a branch named "${branchName}". Please check what branches exist on this project with ${chalk.bold(
`Could not find a branch named "${branchName}". Check which branches exist on this project with ${chalk.bold(
'eas branch:list'
)}.`
);
@@ -238,7 +238,7 @@ async function endRolloutAsync({
});
if (!branch) {
throw new Error(
`Could not find a branch named "${branchName}". Please check what branches exist on this project with ${chalk.bold(
`Could not find a branch named "${branchName}". Check which branches exist on this project with ${chalk.bold(
'eas branch:list'
)}.`
);
@@ -354,7 +354,7 @@ export default class ChannelRollout extends EasCommand {
});
if (!channel) {
throw new Error(
`Could not find a channel named "${channelName}". Please check what channels exist on this project with ${chalk.bold(
`Could not find a channel named "${channelName}". Check which channels exist on this project with ${chalk.bold(
'eas channel:list'
)}.`
);
@@ -176,7 +176,7 @@ export default class ChannelView extends EasCommand {
({ name: channelName } = await promptAsync({
type: 'text',
name: 'name',
message: 'Please name the channel:',
message: 'Provide a channel name:',
validate: value => (value ? true : validationMessage),
}));
}
+8 -1
View File
@@ -1,10 +1,17 @@
import { Flags } from '@oclif/core';
import EasCommand from '../commandUtils/EasCommand';
import { SelectPlatform } from '../credentials/manager/SelectPlatform';
export default class Credentials extends EasCommand {
static description = 'manage credentials';
static flags = {
platform: Flags.enum({ char: 'p', options: ['android', 'ios'] }),
};
async runAsync(): Promise<void> {
await new SelectPlatform().runAsync();
const { flags } = await this.parse(Credentials);
await new SelectPlatform(flags.platform).runAsync();
}
}
@@ -51,11 +51,11 @@ export default class MetadataPull extends EasCommand {
const relativePath = path.relative(process.cwd(), filePath);
Log.addNewLineIfNone();
Log.log(`🎉 Your store configuration is ready.
Log.log(`🎉 Your store config is ready.
- Update the ${chalk.bold(relativePath)} file to prepare the app information.
- Run ${chalk.bold('eas submit')} or manually upload a new app version to the app stores.
- Once the app is uploaded, run ${chalk.bold('eas metadata:push')} to sync the store configuration.
- Once the app is uploaded, run ${chalk.bold('eas metadata:push')} to sync the store config.
- ${learnMore('https://docs.expo.dev/eas-metadata/introduction/')}`);
} catch (error: any) {
handleMetadataError(error);
@@ -3,7 +3,11 @@ import chalk from 'chalk';
import EasCommand from '../../commandUtils/EasCommand';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, setProjectIdAsync } from '../../project/projectUtils';
import {
fetchProjectIdFromServerAsync,
findProjectRootAsync,
saveProjectIdToAppConfigAsync,
} from '../../project/projectUtils';
export default class ProjectInit extends EasCommand {
static description = 'create or link an EAS project';
@@ -20,6 +24,9 @@ export default class ProjectInit extends EasCommand {
return;
}
await setProjectIdAsync(projectDir);
const projectId = await fetchProjectIdFromServerAsync(exp);
await saveProjectIdToAppConfigAsync(projectDir, projectId);
Log.withTick(`Linked app.json to project with ID ${chalk.bold(projectId)}`);
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ export default class EnvironmentSecretList extends EasCommand {
const projectAccountName = await getProjectAccountNameAsync(exp);
if (!projectDir) {
throw new Error("Please run this command inside your project's directory");
throw new Error("Run this command inside your project's directory");
}
const secrets = await EnvironmentSecretsQuery.allAsync(projectAccountName, projectId);
+50 -19
View File
@@ -3,7 +3,7 @@ import { Errors, Flags } from '@oclif/core';
import chalk from 'chalk';
import EasCommand from '../commandUtils/EasCommand';
import { SubmissionFragment } from '../graphql/generated';
import { StatuspageServiceName, SubmissionFragment } from '../graphql/generated';
import { toAppPlatform } from '../graphql/types/AppPlatform';
import Log from '../log';
import {
@@ -16,9 +16,14 @@ import {
import { getExpoConfig } from '../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../project/projectUtils';
import { SubmitArchiveFlags, createSubmissionContextAsync } from '../submit/context';
import { submitAsync, waitToCompleteAsync } from '../submit/submit';
import {
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish,
submitAsync,
waitToCompleteAsync,
} from '../submit/submit';
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
import { getProfilesAsync } from '../utils/profiles';
import { maybeWarnAboutEasOutagesAsync } from '../utils/statuspageService';
interface RawCommandFlags {
platform?: string;
@@ -87,18 +92,23 @@ export default class Submit extends EasCommand {
async runAsync(): Promise<void> {
const { flags: rawFlags } = await this.parse(Submit);
const flags = await this.sanitizeFlagsAsync(rawFlags);
const flags = this.sanitizeFlags(rawFlags);
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const projectId = await getProjectIdAsync(exp);
const platforms = toPlatforms(flags.requestedPlatform);
await maybeWarnAboutEasOutagesAsync([StatuspageServiceName.EasSubmit]);
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
const platforms = toPlatforms(flagsWithPlatform.requestedPlatform);
const submissionProfiles = await getProfilesAsync({
type: 'submit',
easJsonReader: new EasJsonReader(projectDir),
platforms,
profileName: flags.profile,
profileName: flagsWithPlatform.profile,
});
const submissions: SubmissionFragment[] = [];
@@ -108,8 +118,8 @@ export default class Submit extends EasCommand {
projectDir,
projectId,
profile: submissionProfile.profile,
archiveFlags: flags.archiveFlags,
nonInteractive: flags.nonInteractive,
archiveFlags: flagsWithPlatform.archiveFlags,
nonInteractive: flagsWithPlatform.nonInteractive,
});
if (submissionProfiles.length > 1) {
@@ -129,12 +139,17 @@ export default class Submit extends EasCommand {
Log.newLine();
printSubmissionDetailsUrls(submissions);
if (flags.wait) {
await waitToCompleteAsync(submissions, { verbose: flags.verbose });
if (flagsWithPlatform.wait) {
const completedSubmissions = await waitToCompleteAsync(submissions, {
verbose: flagsWithPlatform.verbose,
});
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish(completedSubmissions);
}
}
private async sanitizeFlagsAsync(flags: RawCommandFlags): Promise<CommandFlags> {
private sanitizeFlags(
flags: RawCommandFlags
): Omit<CommandFlags, 'requestedPlatform'> & { requestedPlatform?: RequestedPlatform } {
const {
platform,
verbose,
@@ -148,15 +163,11 @@ export default class Submit extends EasCommand {
Errors.error('--platform is required when building in non-interactive mode', { exit: 1 });
}
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
if (requestedPlatform === RequestedPlatform.All) {
if (archiveFlags.id || archiveFlags.path || archiveFlags.url) {
Errors.error(
'--id, --path, and --url params are only supported when performing a single-platform submit',
{ exit: 1 }
);
}
}
const requestedPlatform =
flags.platform &&
Object.values(RequestedPlatform).includes(flags.platform.toLowerCase() as RequestedPlatform)
? (flags.platform.toLowerCase() as RequestedPlatform)
: undefined;
return {
archiveFlags,
@@ -167,4 +178,24 @@ export default class Submit extends EasCommand {
nonInteractive,
};
}
private async ensurePlatformSelectedAsync(
flags: Omit<CommandFlags, 'requestedPlatform'> & { requestedPlatform?: RequestedPlatform }
): Promise<CommandFlags> {
const requestedPlatform = await selectRequestedPlatformAsync(flags.requestedPlatform);
if (requestedPlatform === RequestedPlatform.All) {
if (flags.archiveFlags.id || flags.archiveFlags.path || flags.archiveFlags.url) {
Errors.error(
'--id, --path, and --url params are only supported when performing a single-platform submit',
{ exit: 1 }
);
}
}
return {
...flags,
requestedPlatform,
};
}
}
@@ -242,8 +242,8 @@ async function configureAppJSONForEASUpdateAsync({
}
function isRuntimeEqual(
runtimeVersionA: string | { policy: 'sdkVersion' | 'nativeVersion' },
runtimeVersionB: string | { policy: 'sdkVersion' | 'nativeVersion' }
runtimeVersionA: string | { policy: 'sdkVersion' | 'nativeVersion' | 'appVersion' },
runtimeVersionB: string | { policy: 'sdkVersion' | 'nativeVersion' | 'appVersion' }
): boolean {
if (typeof runtimeVersionA === 'string' && typeof runtimeVersionB === 'string') {
return runtimeVersionA === runtimeVersionB;
+47 -14
View File
@@ -9,6 +9,7 @@ import gql from 'graphql-tag';
import nullthrows from 'nullthrows';
import { getEASUpdateURL } from '../../api';
import { BRANCHES_LIMIT } from '../../branch/queries';
import { getUpdateGroupUrl } from '../../build/utils/url';
import EasCommand from '../../commandUtils/EasCommand';
import fetch from '../../fetch';
@@ -18,6 +19,7 @@ import {
PublishUpdateGroupInput,
Robot,
RootQueryUpdatesByGroupArgs,
StatuspageServiceName,
Update,
UpdateInfoGroup,
UpdatePublishMutation,
@@ -25,8 +27,9 @@ import {
ViewBranchUpdatesQuery,
} from '../../graphql/generated';
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
import { BranchQuery } from '../../graphql/queries/BranchQuery';
import { UpdateQuery } from '../../graphql/queries/UpdateQuery';
import Log, { link } from '../../log';
import Log, { learnMore, link } from '../../log';
import { ora } from '../../ora';
import { getExpoConfig } from '../../project/expoConfig';
import {
@@ -41,6 +44,7 @@ import {
buildBundlesAsync,
buildUnsortedUpdateInfoGroupAsync,
collectAssetsAsync,
isUploadedAssetCountAboveWarningThreshold,
uploadAssetsAsync,
} from '../../project/publish';
import { resolveWorkflowAsync } from '../../project/workflow';
@@ -56,9 +60,9 @@ import {
import uniqBy from '../../utils/expodash/uniqBy';
import formatFields from '../../utils/formatFields';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { maybeWarnAboutEasOutagesAsync } from '../../utils/statuspageService';
import { getVcsClient } from '../../vcs';
import { createUpdateBranchOnAppAsync } from '../branch/create';
import { listBranchesAsync } from '../branch/list';
import { createUpdateChannelOnAppAsync } from '../channel/create';
export const defaultPublishPlatforms: PublishPlatform[] = ['android', 'ios'];
@@ -128,8 +132,8 @@ export async function ensureBranchExistsAsync({
}: {
appId: string;
name: string;
limit?: number;
offset?: number;
limit: number;
offset: number;
}): Promise<{
id: string;
updates: Exclude<
@@ -226,6 +230,7 @@ export default class UpdatePublish extends EasCommand {
'non-interactive': nonInteractive,
},
} = await this.parse(UpdatePublish);
if (jsonFlag) {
enableJsonOutput();
}
@@ -243,6 +248,8 @@ export default class UpdatePublish extends EasCommand {
isPublicConfig: false,
});
await maybeWarnAboutEasOutagesAsync([StatuspageServiceName.EasUpdate]);
const codeSigningInfo = await getCodeSigningInfoAsync(expPrivate, privateKeyPath);
const hasExpoUpdates = isExpoUpdatesInstalledOrAvailable(projectDir, exp.sdkVersion);
@@ -291,12 +298,16 @@ export default class UpdatePublish extends EasCommand {
throw new Error(validationMessage);
}
const branches = await listBranchesAsync({ projectId });
const branches = await BranchQuery.listBranchesAsync({
appId: projectId,
limit: BRANCHES_LIMIT,
offset: 0,
});
if (branches.length === 0) {
({ name: branchName } = await promptAsync({
type: 'text',
name: 'name',
message: 'No branches found. Creating a new one. Please name the new branch:',
message: 'No branches found. Provide a branch name:',
initial:
(await getVcsClient().getBranchNameAsync()) ||
`branch-${Math.random().toString(36).substr(2, 4)}`,
@@ -321,6 +332,7 @@ export default class UpdatePublish extends EasCommand {
let unsortedUpdateInfoGroups: UpdateInfoGroup = {};
let oldMessage: string, oldRuntimeVersion: string;
let uploadedAssetCount = 0;
let assetLimitPerUpdateGroup = 0;
if (republish) {
// If we are republishing, we don't need to worry about building the bundle or uploading the assets.
@@ -397,7 +409,7 @@ export default class UpdatePublish extends EasCommand {
({ publishMessage: message } = await promptAsync({
type: 'text',
name: 'publishMessage',
message: `Please enter an update message.`,
message: `Provide an update message.`,
initial: `Republish "${oldMessage!}" - group: ${group}`,
validate: (value: any) => (value ? true : validationMessage),
}));
@@ -419,7 +431,7 @@ export default class UpdatePublish extends EasCommand {
({ publishMessage: message } = await promptAsync({
type: 'text',
name: 'publishMessage',
message: `Please enter an update message.`,
message: `Provide an update message.`,
initial: (await getVcsClient().getLastCommitMessageAsync())?.trim(),
validate: (value: any) => (value ? true : validationMessage),
}));
@@ -441,11 +453,17 @@ export default class UpdatePublish extends EasCommand {
try {
const platforms = platformFlag === 'all' ? defaultPublishPlatforms : [platformFlag];
const assets = await collectAssetsAsync({ inputDir: inputDir!, platforms });
const { uniqueUploadedAssetCount } = await uploadAssetsAsync(
const uploadResults = await uploadAssetsAsync(
assets,
spinnerText => (assetSpinner.text = `Uploading assets... ${spinnerText}`)
projectId,
(totalAssets, missingAssets) => {
assetSpinner.text = `Uploading assets. Finished (${
totalAssets - missingAssets
}/${totalAssets})`;
}
);
uploadedAssetCount = uniqueUploadedAssetCount;
uploadedAssetCount = uploadResults.uniqueUploadedAssetCount;
assetLimitPerUpdateGroup = uploadResults.assetLimitPerUpdateGroup;
unsortedUpdateInfoGroups = await buildUnsortedUpdateInfoGroupAsync(assets, exp);
const uploadAssetSuccessMessage = uploadedAssetCount
? `Uploaded ${uploadedAssetCount} ${uploadedAssetCount === 1 ? 'asset' : 'assets'}!`
@@ -470,6 +488,7 @@ export default class UpdatePublish extends EasCommand {
appId: projectId,
name: branchName,
limit: 0,
offset: 0,
});
// Sort the updates into different groups based on their platform specific runtime versions
@@ -564,7 +583,7 @@ export default class UpdatePublish extends EasCommand {
const newUpdatesForRuntimeVersion = newUpdates.filter(
update => update.runtimeVersion === runtime
);
if (!newUpdatesForRuntimeVersion.length) {
if (newUpdatesForRuntimeVersion.length === 0) {
throw new Error(`Publish response is missing updates with runtime ${runtime}.`);
}
const platforms = newUpdatesForRuntimeVersion.map(update => update.platform);
@@ -594,6 +613,18 @@ export default class UpdatePublish extends EasCommand {
])
);
Log.addNewLineIfNone();
if (
isUploadedAssetCountAboveWarningThreshold(uploadedAssetCount, assetLimitPerUpdateGroup)
) {
Log.warn(
`This update group contains ${uploadedAssetCount} assets and is nearing the server cap of ${assetLimitPerUpdateGroup}.\n` +
`${learnMore('https://docs.expo.dev/eas-update/optimize-assets/', {
learnMoreMessage: 'Consider optimizing your usage of assets',
dim: false,
})}.`
);
Log.addNewLineIfNone();
}
}
}
}
@@ -639,7 +670,7 @@ export async function getUpdatesToRepublishInteractiveAsync(
title: formatUpdateTitle(update),
value: update.group,
}));
if (!updateGroups.length) {
if (updateGroups.length === 0) {
throw new Error(
`There are no updates on branch "${branchName}" published for the platform(s) ${platformFlag}. Did you mean to publish a new update instead?`
);
@@ -731,7 +762,9 @@ async function checkEASUpdateURLIsSetAsync(exp: ExpoConfig): Promise<void> {
if (configuredURL !== expectedURL) {
throw new Error(
`The update URL is incorrectly configured for EAS Update. Please set updates.url to ${expectedURL} in your app.json.`
`The update URL is incorrectly configured for EAS Update. Set updates.url to ${expectedURL} in your ${chalk.bold(
'app.json'
)}.`
);
}
}
+24 -39
View File
@@ -4,24 +4,21 @@ import chalk from 'chalk';
import CliTable from 'cli-table3';
import EasCommand from '../../commandUtils/EasCommand';
import { ViewAllUpdatesQuery } from '../../graphql/generated';
import { UpdateQuery } from '../../graphql/queries/UpdateQuery';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { FormatUpdateParameter, UPDATE_COLUMNS, formatUpdate } from '../../update/utils';
import groupBy from '../../utils/expodash/groupBy';
import { UPDATES_LIMIT } from '../../update/queries';
import {
UPDATE_COLUMNS,
UpdateGroupDescription,
formatUpdate,
getUpdateGroupsWithPlatforms,
} from '../../update/utils';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { getVcsClient } from '../../vcs';
type UpdateGroupDescription = FormatUpdateParameter & {
branch: string;
group: string;
platforms: string;
runtimeVersion: string;
};
export default class BranchView extends EasCommand {
static description = 'view the recent updates for a branch';
@@ -55,10 +52,17 @@ export default class BranchView extends EasCommand {
let updateGroupDescriptions: UpdateGroupDescription[];
if (all) {
const branchesAndUpdates = await UpdateQuery.viewAllAsync({ appId: projectId });
updateGroupDescriptions = getUpdateGroupDescriptions(
branchesAndUpdates.app.byId.updateBranches
);
const branchesAndUpdates = await UpdateQuery.viewAllAsync({
appId: projectId,
limit: UPDATES_LIMIT,
offset: 0,
});
updateGroupDescriptions = getUpdateGroupsWithPlatforms(
branchesAndUpdates.app.byId.updates.map(update => ({
...update,
branch: update.branch.name,
}))
).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
} else {
let branchInteractive: string | undefined;
if (!branchFlag) {
@@ -69,7 +73,7 @@ export default class BranchView extends EasCommand {
({ name: branchInteractive } = await promptAsync({
type: 'text',
name: 'name',
message: 'Please enter the name of the branch whose updates you wish to view:',
message: 'Provide the name of the branch whose updates you wish to view:',
initial: (await getVcsClient().getBranchNameAsync()) ?? undefined,
validate: (value: any) => (value ? true : validationMessage),
}));
@@ -80,12 +84,16 @@ export default class BranchView extends EasCommand {
const branchesAndUpdates = await UpdateQuery.viewBranchAsync({
appId: projectId,
name: branch,
limit: UPDATES_LIMIT,
offset: 0,
});
const UpdateBranch = branchesAndUpdates.app?.byId.updateBranchByName;
if (!UpdateBranch) {
throw new Error(`Could not find branch "${branch}"`);
}
updateGroupDescriptions = getUpdateGroupDescriptions([UpdateBranch]);
updateGroupDescriptions = getUpdateGroupsWithPlatforms(
UpdateBranch.updates.map(update => ({ ...update, branch }))
).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
}
if (jsonFlag) {
@@ -96,29 +104,6 @@ export default class BranchView extends EasCommand {
}
}
function getUpdateGroupDescriptions(
branchesAndUpdates: ViewAllUpdatesQuery['app']['byId']['updateBranches']
): UpdateGroupDescription[] {
const flattenedBranchesAndUpdates = branchesAndUpdates.flatMap(branch =>
branch.updates.map(update => {
return { branch: branch.name, ...update };
})
);
const updateGroupDescriptions = Object.values(
groupBy(flattenedBranchesAndUpdates, update => update.group)
).map(updateGroup => {
const platforms = updateGroup
.map(update => update.platform)
.sort()
.join(', ');
return { ...updateGroup[0], platforms };
});
updateGroupDescriptions.sort(
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
return updateGroupDescriptions;
}
function logAsTable(updateGroupDescriptions: UpdateGroupDescription[]): void {
const table = new CliTable({
head: ['Branch', ...UPDATE_COLUMNS],
@@ -18,6 +18,7 @@ export const jester2: Actor = {
export const testUsername = jester.username;
export const testSlug = 'testApp';
export const testProjectId = '7ef93448-3bc7-4b57-be32-99326dcf24f0';
export const testBundleIdentifier = 'test.com.app';
export const testPackageName = 'test.com.app';
export const testExperienceName = `@${testUsername}/${testSlug}`;
@@ -35,6 +36,11 @@ export const testAppJson = {
sdkVersion: '38.0.0',
ios: { bundleIdentifier: testBundleIdentifier },
android: { package: testPackageName },
extra: {
eas: {
projectId: testProjectId,
},
},
};
export const testAppJsonWithDifferentOwner = {
@@ -54,7 +54,7 @@ export class RemoveKeystore {
)
);
Log.warn(
'Please read https://docs.expo.dev/distribution/building-standalone-apps/#if-you-choose-to-build-for-android for more info before proceeding.'
'Read https://docs.expo.dev/distribution/building-standalone-apps/#if-you-choose-to-build-for-android for more info before proceeding.'
);
Log.newLine();
Log.warn(
@@ -7,8 +7,8 @@ import {
CommonAndroidAppCredentialsFragment,
GoogleServiceAccountKeyFragment,
} from '../../../graphql/generated';
import { AppQuery } from '../../../graphql/queries/AppQuery';
import { Account } from '../../../user/Account';
import { AppQuery } from '../../ios/api/graphql/queries/AppQuery';
import { GoogleServiceAccountKey, KeystoreWithType } from '../credentials';
import { AndroidAppBuildCredentialsMutation } from './graphql/mutations/AndroidAppBuildCredentialsMutation';
import { AndroidAppCredentialsMutation } from './graphql/mutations/AndroidAppCredentialsMutation';
@@ -1,5 +1,6 @@
import { ExpoConfig } from '@expo/config';
import { Env } from '@expo/eas-build-job';
import { EasJson } from '@expo/eas-json';
import chalk from 'chalk';
import Log from '../log';
@@ -19,6 +20,7 @@ export class CredentialsContext {
public readonly nonInteractive: boolean;
public readonly projectDir: string;
public readonly user: Actor;
public readonly easJsonCliConfig?: EasJson['cli'];
private shouldAskAuthenticateAppStore: boolean = true;
private resolvedExp?: ExpoConfig;
@@ -26,12 +28,14 @@ export class CredentialsContext {
constructor(
private options: {
exp?: ExpoConfig;
easJsonCliConfig?: EasJson['cli'];
nonInteractive?: boolean;
projectDir: string;
user: Actor;
env?: Env;
}
) {
this.easJsonCliConfig = options.easJsonCliConfig;
this.projectDir = options.projectDir;
this.user = options.user;
this.nonInteractive = options.nonInteractive ?? false;
+1 -3
View File
@@ -1,8 +1,6 @@
export class MissingCredentialsNonInteractiveError extends Error {
constructor(message?: string) {
super(
message ?? 'Credentials are not set up. Please run this command again in interactive mode.'
);
super(message ?? 'Credentials are not set up. Run this command again in interactive mode.');
}
}
@@ -1,10 +1,16 @@
import { Platform } from '@expo/eas-build-job';
import { CredentialsSource, DistributionType, IosEnterpriseProvisioning } from '@expo/eas-json';
import {
CredentialsSource,
DistributionType,
EasJsonReader,
IosEnterpriseProvisioning,
} from '@expo/eas-json';
import fs from 'fs-extra';
import { CommonIosAppCredentialsFragment } from '../../graphql/generated';
import Log from '../../log';
import { findApplicationTarget } from '../../project/ios/target';
import { confirmAsync } from '../../prompts';
import { selectAsync } from '../../prompts';
import { CredentialsContext } from '../context';
import * as credentialsJsonReader from '../credentialsJson/read';
import { ensureAllTargetsAreConfigured } from '../credentialsJson/utils';
@@ -21,6 +27,12 @@ interface Options {
enterpriseProvisioning?: IosEnterpriseProvisioning;
}
enum PushNotificationSetupOption {
YES,
NO,
NO_DONT_ASK_AGAIN,
}
export default class IosCredentialsProvider {
public readonly platform = Platform.IOS;
@@ -89,13 +101,39 @@ export default class IosCredentialsProvider {
return null;
}
const confirmSetup = await confirmAsync({
message: `Would you like to set up Push Notifications for your project?`,
});
if (!confirmSetup) {
if (ctx.easJsonCliConfig?.promptToConfigurePushNotfications === false) {
return null;
}
return await setupPushKeyAction.runAsync(ctx);
const setupOption = await selectAsync(
`Would you like to set up Push Notifications for your project?`,
[
{ title: 'Yes', value: PushNotificationSetupOption.YES },
{ title: 'No', value: PushNotificationSetupOption.NO },
{
title: `No, don't ask again (preference will be saved to eas.json)`,
value: PushNotificationSetupOption.NO_DONT_ASK_AGAIN,
},
]
);
if (setupOption === PushNotificationSetupOption.YES) {
return await setupPushKeyAction.runAsync(ctx);
} else {
if (setupOption === PushNotificationSetupOption.NO_DONT_ASK_AGAIN) {
await this.disablePushNotificationsSetupInEasJsonAsync(ctx);
}
return null;
}
}
private async disablePushNotificationsSetupInEasJsonAsync(
ctx: CredentialsContext
): Promise<void> {
const easJsonPath = EasJsonReader.formatEasJsonPath(ctx.projectDir);
const easJson = await fs.readJSON(easJsonPath);
easJson.cli = { ...easJson?.cli, promptToConfigurePushNotfications: false };
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
Log.withTick('Updated eas.json');
}
private assertProvisioningProfileType(provisioningProfile: string, targetName?: string): void {
@@ -147,8 +147,8 @@ const APPLE_DIST_CERTS_TOO_MANY_GENERATED_ERROR = `
You can have only ${chalk.underline(
'three'
)} Apple Distribution Certificates generated on your Apple Developer account.
Please revoke the old ones or reuse existing from your other apps.
Please remember that Apple Distribution Certificates are not application specific!
Revoke the old ones or reuse existing from your other apps.
Remember that Apple Distribution Certificates are not application specific!
`;
export async function provideOrGenerateDistributionCertificateAsync(
@@ -45,7 +45,7 @@ export class SetUpAdhocProvisioningProfile {
return nullthrows(await getBuildCredentialsAsync(ctx, this.app, IosDistributionType.AdHoc));
} else {
throw new MissingCredentialsNonInteractiveError(
'Provisioning profile is not configured correctly. Please run this command again in interactive mode.'
'Provisioning profile is not configured correctly. Run this command again in interactive mode.'
);
}
}
@@ -254,11 +254,11 @@ export class SetUpAdhocProvisioningProfile {
ctx: CredentialsContext,
appleTeam: AppleTeamFragment
): Promise<AppleDeviceFragment[]> {
const action = new DeviceCreateAction(this.app.account, appleTeam);
const action = new DeviceCreateAction(ctx.appStore, this.app.account, appleTeam);
const method = await action.runAsync();
while (true) {
if (method !== RegistrationMethod.INPUT) {
if (method === RegistrationMethod.WEBSITE) {
Log.newLine();
Log.log(chalk.bold("Press any key if you've already finished device registration."));
await pressAnyKeyToContinueAsync();
@@ -70,7 +70,7 @@ export class SetUpInternalProvisioningProfile {
} else {
if (adhocBuildCredentialsExist && enterpriseBuildCredentialsExist) {
throw new Error(
`You're in non-interactive mode. You have set up both adhoc and universal distribution credentials. Please set the 'enterpriseProvisioning' property (to 'adhoc' or 'universal') in eas.json to choose the credentials to use.`
`You're in non-interactive mode. You have set up both adhoc and universal distribution credentials. Set the 'enterpriseProvisioning' property (to 'adhoc' or 'universal') in eas.json to choose the credentials to use.`
);
} else if (adhocBuildCredentialsExist) {
return await this.setupAdhocProvisioningProfileAsync(ctx);
@@ -78,7 +78,7 @@ export class SetUpInternalProvisioningProfile {
return await this.setupUniversalProvisioningProfileAsync(ctx);
} else {
throw new Error(
`You're in non-interactive mode. EAS CLI couldn't find any credentials suitable for internal distribution. Please run again in interactive mode.`
`You're in non-interactive mode. EAS CLI couldn't find any credentials suitable for internal distribution. Run this command again in interactive mode.`
);
}
}
@@ -95,7 +95,7 @@ export class SetUpProvisioningProfile {
}
if (ctx.nonInteractive) {
throw new MissingCredentialsNonInteractiveError(
'Provisioning profile is not configured correctly. Please run this command again in interactive mode.'
'Provisioning profile is not configured correctly. Run this command again in interactive mode.'
);
}
@@ -48,7 +48,7 @@ export class SetUpSubmissionCredentials {
async promptForAppSpecificPasswordAsync(): Promise<string> {
Log.addNewLineIfNone();
Log.log(`Please enter your Apple app-specific password.`);
Log.log(`Enter your Apple app-specific password.`);
Log.log(learnMore('https://expo.fyi/apple-app-specific-password'));
Log.warn(
wrapAnsi(
@@ -14,6 +14,7 @@ import {
IosAppBuildCredentialsFragment,
IosDistributionType,
} from '../../../graphql/generated';
import { AppQuery } from '../../../graphql/queries/AppQuery';
import { isWildcardBundleIdentifier } from '../../../project/ios/bundleIdentifier';
import { Account } from '../../../user/Account';
import { DistributionCertificate, PushKey } from '../appstore/Credentials.types';
@@ -33,7 +34,6 @@ import { ApplePushKeyMutation } from './graphql/mutations/ApplePushKeyMutation';
import { AppleTeamMutation } from './graphql/mutations/AppleTeamMutation';
import { IosAppBuildCredentialsMutation } from './graphql/mutations/IosAppBuildCredentialsMutation';
import { IosAppCredentialsMutation } from './graphql/mutations/IosAppCredentialsMutation';
import { AppQuery } from './graphql/queries/AppQuery';
import { AppStoreConnectApiKeyQuery } from './graphql/queries/AppStoreConnectApiKeyQuery';
import { AppleAppIdentifierQuery } from './graphql/queries/AppleAppIdentifierQuery';
import { AppleDeviceQuery } from './graphql/queries/AppleDeviceQuery';
@@ -1,14 +1,14 @@
import { ApiKey, ApiKeyType, UserRole } from '@expo/apple-utils';
import { ApiKey, ApiKeyType, UnexpectedResponse, UserRole } from '@expo/apple-utils';
import {
createAscApiKeyAsync,
downloadWithRetryAsync,
getAscApiKeyAsync,
listAscApiKeysAsync,
revokeAscApiKeyAsync,
} from '../ascApiKey';
import { getRequestContext } from '../authenticate';
jest.mock('@expo/apple-utils');
jest.mock('../authenticate');
jest.mock('../../../../ora');
@@ -96,3 +96,38 @@ test(`revokeAscApiKeyAsync`, async () => {
expect(mockApiKey.revokeAsync).toBeCalled();
expect(result).toEqual(mockAscApiKeyInfo);
});
test(`downloadWithRetryAsync`, async () => {
const cacheFailureMessage = `The specified resource does not exist - There is no resource of type 'apiKeys' with id 'TEST-ID'`;
// complete failure
const mockApiKeyWithDownloadError = {
...mockApiKey,
downloadAsync: jest.fn(() => {
throw new UnexpectedResponse(cacheFailureMessage);
}),
} as unknown as ApiKey;
await expect(
downloadWithRetryAsync(mockApiKeyWithDownloadError, { minTimeout: 1 }) // stay within jest timeout window
).rejects.toThrowError(cacheFailureMessage);
// expect to try once and retry 3 times = 4 total
expect(mockApiKeyWithDownloadError.downloadAsync as jest.Mock).toBeCalledTimes(7);
// one time failure
const mockApiKeyWithOneTimeDownloadError = mockApiKey as unknown as ApiKey;
(mockApiKeyWithOneTimeDownloadError.downloadAsync as jest.Mock).mockClear();
(mockApiKeyWithOneTimeDownloadError.downloadAsync as jest.Mock).mockImplementationOnce(() => {
throw new UnexpectedResponse(cacheFailureMessage);
});
const keyP8AfterOneFailure = await downloadWithRetryAsync(mockApiKeyWithOneTimeDownloadError, {
minTimeout: 1,
});
expect(keyP8AfterOneFailure).toBe('super secret');
// successful case
(mockApiKey.downloadAsync as jest.Mock).mockClear();
const keyP8NoFailure = await downloadWithRetryAsync(mockApiKeyWithOneTimeDownloadError, {
minTimeout: 1,
});
expect(keyP8NoFailure).toBe('super secret');
});
@@ -94,6 +94,30 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
'com.apple.security.application-groups': [
'group.CY-A5149AC2-49FC-11E7-B3F3-0335A16FFB8D.com.cydia.Extender',
],
'com.apple.developer.fileprovider.testing-mode': true,
'com.apple.developer.healthkit.recalibrate-estimates': true,
'com.apple.developer.maps': true,
'com.apple.developer.user-management': true,
'com.apple.developer.networking.custom-protocol': true,
'com.apple.developer.system-extension.install': true,
'com.apple.developer.push-to-talk': true,
'com.apple.developer.driverkit.transport.usb': true,
'com.apple.developer.kernel.increased-memory-limit': true,
'com.apple.developer.driverkit.communicates-with-drivers': true,
'com.apple.developer.media-device-discovery-extension': true,
'com.apple.developer.driverkit.allow-third-party-userclients': true,
'com.apple.developer.weatherkit': true,
'com.apple.developer.on-demand-install-capable': true,
'com.apple.developer.driverkit.family.scsicontroller': true,
'com.apple.developer.driverkit.family.serial': true,
'com.apple.developer.driverkit.family.networking': true,
'com.apple.developer.driverkit.family.hid.eventservice': true,
'com.apple.developer.driverkit.family.hid.device': true,
'com.apple.developer.driverkit': true,
'com.apple.developer.driverkit.transport.hid': true,
'com.apple.developer.driverkit.family.audio': true,
'com.apple.developer.shared-with-you': true,
});
expect(bundleId.updateBundleIdCapabilityAsync).toBeCalledWith([
@@ -135,36 +159,153 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
},
{ capabilityType: 'AUTOFILL_CREDENTIAL_PROVIDER', option: 'ON' },
{ capabilityType: 'APP_GROUPS', option: 'ON' },
{
capabilityType: 'FILEPROVIDER_TESTINGMODE',
option: 'ON',
},
{
capabilityType: 'HEALTHKIT_RECALIBRATE_ESTIMATES',
option: 'ON',
},
{
capabilityType: 'MAPS',
option: 'ON',
},
{
capabilityType: 'USER_MANAGEMENT',
option: 'ON',
},
{
capabilityType: 'NETWORK_CUSTOM_PROTOCOL',
option: 'ON',
},
{
capabilityType: 'SYSTEM_EXTENSION_INSTALL',
option: 'ON',
},
{
capabilityType: 'PUSH_TO_TALK',
option: 'ON',
},
{
capabilityType: 'DRIVERKIT_USBTRANSPORT_PUB',
option: 'ON',
},
{
capabilityType: 'INCREASED_MEMORY_LIMIT',
option: 'ON',
},
{
capabilityType: 'DRIVERKIT_COMMUNICATESWITHDRIVERS',
option: 'ON',
},
{
capabilityType: 'MEDIA_DEVICE_DISCOVERY',
option: 'ON',
},
{
capabilityType: 'DRIVERKIT_ALLOWTHIRDPARTY_USERCLIENTS',
option: 'ON',
},
{
capabilityType: 'WEATHERKIT',
option: 'ON',
},
{
capabilityType: 'ONDEMANDINSTALL_EXTENSIONS',
option: 'ON',
},
{
capabilityType: 'DRIVERKIT_FAMILY_SCSICONTROLLER_PUB',
option: 'ON',
},
{
capabilityType: 'DRIVERKIT_FAMILY_SERIAL_PUB',
option: 'ON',
},
{
capabilityType: 'DRIVERKIT_FAMILY_NETWORKING_PUB',
option: 'ON',
},
{
capabilityType: 'DRIVERKIT_FAMILY_HIDEVENTSERVICE_PUB',
option: 'ON',
},
{
capabilityType: 'DRIVERKIT_FAMILY_HIDDEVICE_PUB',
option: 'ON',
},
{
capabilityType: 'DRIVERKIT_PUBLIC',
option: 'ON',
},
{
capabilityType: 'DRIVERKIT_TRANSPORT_HID_PUB',
option: 'ON',
},
{
capabilityType: 'DRIVERKIT_FAMILY_AUDIO_PUB',
option: 'ON',
},
{
capabilityType: 'SHARED_WITH_YOU',
option: 'ON',
},
]);
expect(results.enabled).toStrictEqual([
'HealthKit',
'Apple Pay Payment Processing',
'iCloud',
'ClassKit',
'Data Protection',
'Network Extensions',
'Personal VPN',
'Hotspot',
'Extended Virtual Address Space',
'HomeKit',
'Multipath',
'Wireless Accessory Configuration',
'Inter-App Audio',
'Wallet',
'Fonts',
'App Attest',
'NFC Tag Reading',
'Sign In with Apple',
'SiriKit',
'Access WiFi Information',
'Communication Notifications',
'Time Sensitive Notifications',
'Group Activities',
'Family Controls',
'AutoFill Credential Provider',
'App Groups',
]);
expect(results.enabled).toMatchInlineSnapshot(`
Array [
"HealthKit",
"Apple Pay Payment Processing",
"iCloud",
"ClassKit",
"Data Protection",
"Network Extensions",
"Personal VPN",
"Hotspot",
"Extended Virtual Address Space",
"HomeKit",
"Multipath",
"Wireless Accessory Configuration",
"Inter-App Audio",
"Wallet",
"Fonts",
"App Attest",
"NFC Tag Reading",
"Sign In with Apple",
"SiriKit",
"Access WiFi Information",
"Communication Notifications",
"Time Sensitive Notifications",
"Group Activities",
"Family Controls",
"AutoFill Credential Provider",
"App Groups",
"FileProvider TestingMode",
"Recalibrate Estimates",
"Maps",
"TV Services",
"Custom Network Protocol",
"System Extension",
"Push to Talk",
"DriverKit USB Transport (development)",
"Increased Memory Limit",
"Communicates with Drivers",
"Media Device Discovery",
"DriverKit Allow Third Party UserClients",
"WeatherKit",
"On Demand Install Capable for App Clip Extensions",
"DriverKit Family SCSIController (development)",
"DriverKit Family Serial (development)",
"DriverKit Family Networking (development)",
"DriverKit Family HID EventService (development)",
"DriverKit Family HID Device (development)",
"DriverKit for Development",
"DriverKit Transport HID (development)",
"DriverKit Family Audio (development)",
"Shared with You",
]
`);
expect(results.disabled).toStrictEqual([]);
});
@@ -153,7 +153,7 @@ describe(syncCapabilityIdentifiersForEntitlementsAsync, () => {
(_ctx: any, { identifier }: { identifier: string }): any => {
// e2e test with: merchant.expodemo
throw new Error(
`There is a problem with the request entity - A Merchant ID with Identifier '${identifier}' is not available. Please enter a different string.`
`There is a problem with the request entity - A Merchant ID with Identifier '${identifier}' is not available. Enter a different string.`
);
}
);
@@ -167,6 +167,6 @@ describe(syncCapabilityIdentifiersForEntitlementsAsync, () => {
syncCapabilityIdentifiersForEntitlementsAsync(bundleId, {
'com.apple.developer.in-app-payments': ['merchant.bacon'],
})
).rejects.toThrow(/is not available. Please enter a different string/);
).rejects.toThrow(/is not available. Enter a different string/);
});
});
@@ -1,6 +1,8 @@
import { ApiKey, ApiKeyProps, ApiKeyType, UserRole } from '@expo/apple-utils';
import promiseRetry from 'promise-retry';
import Log from '../../../log';
import { Analytics, SubmissionEvent } from '../../../analytics/events';
import Log, { learnMore } from '../../../log';
import { ora } from '../../../ora';
import { AscApiKey, AscApiKeyInfo } from './Credentials.types';
import { getRequestContext } from './authenticate';
@@ -49,6 +51,73 @@ export async function getAscApiKeyAsync(
}
}
/**
* There is a bug in Apple's infrastructure that does not propagate newly created objects for a
* while. If the key has not propagated and you try to download it, Apple will error saying that
* the resource does not exist. We retry with exponential backoff until the key propagates and
* is available for download.
* */
export async function downloadWithRetryAsync(
key: ApiKey,
{
minTimeout = 1000,
retries = 6,
factor = 2,
}: { minTimeout?: number; retries?: number; factor?: number } = {}
): Promise<string | null> {
const RESOURCE_DOES_NOT_EXIST_MESSAGE =
'The specified resource does not exist - There is no resource of type';
try {
const keyP8 = await promiseRetry(
async (retry, number) => {
try {
return await key.downloadAsync();
} catch (e: any) {
if (
e.name === 'UnexpectedAppleResponse' &&
e.message.includes(RESOURCE_DOES_NOT_EXIST_MESSAGE)
) {
const secondsToRetry = Math.pow(factor, number);
Log.log(
`Received an unexpected response from Apple, retrying in ${secondsToRetry} seconds...`
);
Analytics.logEvent(SubmissionEvent.API_KEY_DOWNLOAD_RETRY, {
errorName: e.name,
reason: e.message,
retry: number,
});
return retry(e);
}
throw e;
}
},
{
retries,
factor,
minTimeout,
}
);
Analytics.logEvent(SubmissionEvent.API_KEY_DOWNLOAD_SUCCESS, {});
return keyP8;
} catch (e: any) {
if (
e.name === 'UnexpectedAppleResponse' &&
e.message.includes(RESOURCE_DOES_NOT_EXIST_MESSAGE)
) {
Log.warn(
`Unable to download Api Key from Apple at this time. Create and upload your key manually by running 'eas credentials' ${learnMore(
'https://expo.fyi/creating-asc-api-key'
)}`
);
}
Analytics.logEvent(SubmissionEvent.API_KEY_DOWNLOAD_FAIL, {
errorName: e.name,
reason: e.message,
});
throw e;
}
}
/**
* Create an App Store Connect API Key.
* **Does not support App Store Connect API (CI).**
@@ -71,7 +140,7 @@ export async function createAscApiKeyAsync(
roles: roles ?? [UserRole.ADMIN],
keyType: keyType ?? ApiKeyType.PUBLIC_API,
});
const keyP8 = await key.downloadAsync();
const keyP8 = await downloadWithRetryAsync(key);
if (!keyP8) {
const { nickname, roles } = key.attributes;
const humanReadableKey = `App Store Connect Key '${nickname}' (${
@@ -484,6 +484,7 @@ export const CapabilityMapping: CapabilityClassifier[] = [
{
// https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_networking_vpn_api
name: 'Push Notifications',
// com.apple.developer.aps-environment
entitlement: 'aps-environment',
capability: CapabilityType.PUSH_NOTIFICATIONS,
validateOptions: validateDevProdString,
@@ -522,6 +523,181 @@ export const CapabilityMapping: CapabilityClassifier[] = [
validateOptions: validateDevProdString,
getOptions: getDefinedOptions,
},
{
entitlement: 'com.apple.developer.coremedia.hls.low-latency',
name: 'Low Latency HLS',
capability: CapabilityType.HLS_LOW_LATENCY,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.associated-domains.mdm-managed',
name: 'MDM Managed Associated Domains',
capability: CapabilityType.MDM_MANAGED_ASSOCIATED_DOMAINS,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.fileprovider.testing-mode',
name: 'FileProvider TestingMode',
capability: CapabilityType.FILE_PROVIDER_TESTING_MODE,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.healthkit.recalibrate-estimates',
name: 'Recalibrate Estimates',
capability: CapabilityType.HEALTH_KIT_RECALIBRATE_ESTIMATES,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.maps',
name: 'Maps',
capability: CapabilityType.MAPS,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.user-management',
name: 'TV Services',
capability: CapabilityType.USER_MANAGEMENT,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.networking.custom-protocol',
name: 'Custom Network Protocol',
capability: CapabilityType.NETWORK_CUSTOM_PROTOCOL,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.system-extension.install',
name: 'System Extension',
capability: CapabilityType.SYSTEM_EXTENSION_INSTALL,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.push-to-talk',
name: 'Push to Talk',
capability: CapabilityType.PUSH_TO_TALK,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.driverkit.transport.usb',
name: 'DriverKit USB Transport (development)',
capability: CapabilityType.DRIVER_KIT_USB_TRANSPORT_PUB,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.kernel.increased-memory-limit',
name: 'Increased Memory Limit',
capability: CapabilityType.INCREASED_MEMORY_LIMIT,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.driverkit.communicates-with-drivers',
name: 'Communicates with Drivers',
capability: CapabilityType.DRIVER_KIT_COMMUNICATES_WITH_DRIVERS,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.media-device-discovery-extension',
name: 'Media Device Discovery',
capability: CapabilityType.MEDIA_DEVICE_DISCOVERY,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.driverkit.allow-third-party-userclients',
name: 'DriverKit Allow Third Party UserClients',
capability: CapabilityType.DRIVER_KIT_ALLOW_THIRD_PARTY_USER_CLIENTS,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.weatherkit',
name: 'WeatherKit',
capability: CapabilityType.WEATHER_KIT,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.on-demand-install-capable',
name: 'On Demand Install Capable for App Clip Extensions',
capability: CapabilityType.ON_DEMAND_INSTALL_EXTENSIONS,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.driverkit.family.scsicontroller',
name: 'DriverKit Family SCSIController (development)',
capability: CapabilityType.DRIVER_KIT_FAMILY_SCSI_CONTROLLER_PUB,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.driverkit.family.serial',
name: 'DriverKit Family Serial (development)',
capability: CapabilityType.DRIVER_KIT_FAMILY_SERIAL_PUB,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.driverkit.family.networking',
name: 'DriverKit Family Networking (development)',
capability: CapabilityType.DRIVER_KIT_FAMILY_NETWORKING_PUB,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.driverkit.family.hid.eventservice',
name: 'DriverKit Family HID EventService (development)',
capability: CapabilityType.DRIVER_KIT_FAMILY_HID_EVENT_SERVICE_PUB,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.driverkit.family.hid.device',
name: 'DriverKit Family HID Device (development)',
capability: CapabilityType.DRIVER_KIT_FAMILY_HID_DEVICE_PUB,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.driverkit',
name: 'DriverKit for Development',
capability: CapabilityType.DRIVER_KIT_PUBLIC,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.driverkit.transport.hid',
name: 'DriverKit Transport HID (development)',
capability: CapabilityType.DRIVER_KIT_TRANSPORT_HID_PUB,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.driverkit.family.audio',
name: 'DriverKit Family Audio (development)',
capability: CapabilityType.DRIVER_KIT_FAMILY_AUDIO_PUB,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
{
entitlement: 'com.apple.developer.shared-with-you',
name: 'Shared with You',
capability: CapabilityType.SHARED_WITH_YOU,
validateOptions: validateBooleanOptions,
getOptions: getBooleanOptions,
},
// These don't appear to have entitlements, so it's unclear how we can automatically enable / disable them at this time.
// TODO: Maybe add a warning about manually enabling features?
@@ -534,24 +710,9 @@ export const CapabilityMapping: CapabilityClassifier[] = [
// },
// {
// entitlement: '',
// name: 'FileProvider TestingMode',
// capability: 'FILEPROVIDER_TESTINGMODE',
// },
// {
// entitlement: '',
// name: 'HLS Interstitial Previews',
// capability: 'HLS_INTERSTITIAL_PREVIEW',
// },
// {
// entitlement: '',
// name: 'Low Latency HLS',
// capability: 'COREMEDIA_HLS_LOW_LATENCY',
// },
// {
// entitlement: '',
// name: 'MDM Managed Associated Domains',
// capability: 'MDM_MANAGED_ASSOCIATED_DOMAINS',
// },
// "Game Controllers" doesn't appear in Dev Portal but it does show up in Xcode,
// toggling in Xcode causes no network request to be sent.
@@ -67,7 +67,7 @@ async function getRequiredContractMessagesAsync(
// prompt the user to open an issue so we can address the new contract state if it ever appears.
// TODO: Maybe a silent analytic would be better
Log.error(
`\nUnexpected Apple developer contract status "${status}". Please open an issue on https://github.com/expo/eas-cli`
`\nUnexpected Apple developer contract status "${status}". Open an issue on https://github.com/expo/eas-cli`
);
Log.newLine();
return { messages: (await getContractMessagesAsync(context)) ?? [], isFatal: false };
@@ -62,7 +62,7 @@ export async function ensureBundleIdExistsWithNameAsync(
spinner.fail(
`The bundle identifier ${chalk.bold(bundleIdentifier)} is not available to team "${
authCtx.team.name
}" (${authCtx.team.id}), please change it in your app config and try again.`
}" (${authCtx.team.id}), change it in your app config and try again.`
);
} else {
spinner.fail(`Failed to register bundle identifier ${chalk.dim(bundleIdentifier)}`);
@@ -196,13 +196,13 @@ export async function ensureAppExistsAsync(
} catch (error: any) {
if (error.message.match(/An App ID with Identifier '(.*)' is not available/)) {
throw new Error(
`\nThe bundle identifier "${bundleIdentifier}" is not available to provider "${userAuthCtx.authState?.session.provider.name}. Please change it in your app config and try again.\n`
`\nThe bundle identifier "${bundleIdentifier}" is not available to provider "${userAuthCtx.authState?.session.provider.name}. Change it in your app config and try again.\n`
);
}
spinner.fail(`Failed to create App Store app ${chalk.dim(name)}`);
error.message +=
'\nPlease visit https://appstoreconnect.apple.com and resolve any warnings, then try again.';
'\nVisit https://appstoreconnect.apple.com and resolve any warnings, then try again.';
throw error;
}
} else {
@@ -12,8 +12,8 @@ const { MaxKeysCreatedError } = Keys;
export const APPLE_KEYS_TOO_MANY_GENERATED_ERROR = `
You can have only ${chalk.underline('two')} Apple Keys generated on your Apple Developer account.
Please revoke the old ones or reuse existing from your other apps.
Please remember that Apple Keys are not application specific!
Revoke the old ones or reuse existing from your other apps.
Remember that Apple Keys are not application specific!
`;
/**
@@ -1,4 +1,5 @@
import { JSONObject } from '@expo/json-file';
import type { XCBuildConfiguration } from 'xcode';
import {
CommonIosAppCredentialsFragment,
@@ -17,6 +18,7 @@ export interface Target {
bundleIdentifier: string;
parentBundleIdentifier?: string;
entitlements: JSONObject;
buildSettings?: XCBuildConfiguration['buildSettings'];
}
export interface TargetCredentials {
@@ -4,10 +4,7 @@ import assert from 'assert';
import Log, { learnMore } from '../../log';
import { GradleBuildContext, resolveGradleBuildContextAsync } from '../../project/android/gradle';
import {
getProjectAccountName,
promptToCreateProjectIfNotExistsAsync,
} from '../../project/projectUtils';
import { getProjectAccountName, getProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { findAccountByName } from '../../user/Account';
import { ensureActorHasUsername, ensureLoggedInAsync } from '../../user/actions';
@@ -80,12 +77,6 @@ export class ManageAndroid {
while (true) {
try {
if (ctx.hasProjectContext) {
const maybeProjectId = await promptToCreateProjectIfNotExistsAsync(ctx.exp);
if (!maybeProjectId) {
throw new Error(
'Your project must be registered with EAS in order to use the credentials manager.'
);
}
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx, gradleContext);
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(
appLookupParams
@@ -156,12 +147,10 @@ export class ManageAndroid {
buildProfile: BuildProfile<Platform.ANDROID>
): Promise<GradleBuildContext | undefined> {
assert(ctx.hasProjectContext, 'createProjectContextAsync: must have project context.');
const maybeProjectId = await promptToCreateProjectIfNotExistsAsync(ctx.exp);
if (!maybeProjectId) {
throw new Error(
'Your project must be registered with EAS in order to use the credentials manager.'
);
}
// ensure the project exists on the EAS server
await getProjectIdAsync(ctx.exp);
return await resolveGradleBuildContextAsync(ctx.projectDir, buildProfile);
}
@@ -11,10 +11,7 @@ import {
import Log, { learnMore } from '../../log';
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
import { resolveTargetsAsync } from '../../project/ios/target';
import {
getProjectAccountName,
promptToCreateProjectIfNotExistsAsync,
} from '../../project/projectUtils';
import { getProjectAccountName, getProjectIdAsync } from '../../project/projectUtils';
import { confirmAsync, promptAsync, selectAsync } from '../../prompts';
import { Account, findAccountByName } from '../../user/Account';
import { ensureActorHasUsername, ensureLoggedInAsync } from '../../user/actions';
@@ -175,12 +172,9 @@ export class ManageIos {
targets: Target[];
}> {
assert(ctx.hasProjectContext, 'createProjectContextAsync: must have project context.');
const maybeProjectId = await promptToCreateProjectIfNotExistsAsync(ctx.exp);
if (!maybeProjectId) {
throw new Error(
'Your project must be registered with EAS in order to use the credentials manager.'
);
}
// ensure the project exists on the EAS server
await getProjectIdAsync(ctx.exp);
const app = { account, projectName: ctx.exp.slug };
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
@@ -45,7 +45,7 @@ export class SelectIosDistributionTypeGraphqlFromBuildProfile {
if (ctx.nonInteractive) {
throw new Error(
'Unable to determine type of internal distribution. Please run this command in interactive mode.'
'Unable to determine type of internal distribution. Run this command in interactive mode.'
);
}
@@ -1,22 +1,16 @@
import { promptAsync } from '../../prompts';
import { selectPlatformAsync } from '../../platform';
import { ManageAndroid } from './ManageAndroid';
import { ManageIos } from './ManageIos';
export class SelectPlatform {
constructor(private readonly flagPlatform?: string) {}
async runAsync(): Promise<void> {
const { platform } = await promptAsync({
type: 'select',
name: 'platform',
message: 'Select platform',
choices: [
{ value: 'android', title: 'Android' },
{ value: 'ios', title: 'iOS' },
],
});
const platform = await selectPlatformAsync(this.flagPlatform);
if (platform === 'ios') {
return await new ManageIos(new SelectPlatform(), process.cwd()).runAsync();
return await new ManageIos(new SelectPlatform(platform), process.cwd()).runAsync();
}
return await new ManageAndroid(new SelectPlatform(), process.cwd()).runAsync();
return await new ManageAndroid(new SelectPlatform(platform), process.cwd()).runAsync();
}
}
@@ -28,7 +28,7 @@ const EXPERT_PROMPT = (): void => {
}
Log.warn(`
In this mode, we won't be able to make sure that your credentials are valid.
Please double check that you're uploading valid files for your app otherwise you may encounter strange errors!
Double check that you're uploading valid files for your app otherwise you may encounter strange errors!
When building for IOS make sure you've created your App ID on the Apple Developer Portal, that your App ID
is in app.json as \`bundleIdentifier\`, and that the provisioning profile you
upload matches that Team ID and App ID.
@@ -0,0 +1 @@
export const runDeveloperPortalMethodAsync = jest.fn();
@@ -1,13 +1,17 @@
import prompts from 'prompts';
import { instance, mock } from 'ts-mockito';
import AppStoreApi from '../../../../credentials/ios/appstore/AppStoreApi';
import { Account } from '../../../../user/Account';
import DeviceCreateAction, { RegistrationMethod } from '../action';
import { runDeveloperPortalMethodAsync } from '../developerPortalMethod';
import { runInputMethodAsync } from '../inputMethod';
import { runRegistrationUrlMethodAsync } from '../registrationUrlMethod';
jest.mock('prompts');
jest.mock('../registrationUrlMethod');
jest.mock('../developerPortalMethod');
jest.mock('../inputMethod');
jest.mock('../registrationUrlMethod');
beforeEach(() => {
const promptsMock = jest.mocked(prompts);
@@ -25,6 +29,8 @@ describe(DeviceCreateAction, () => {
jest.mocked(prompts).mockImplementationOnce(async () => ({
method: RegistrationMethod.WEBSITE,
}));
const appStoreApiMock = mock<AppStoreApi>();
const appStoreApi = instance(appStoreApiMock);
const account: Account = {
id: 'account_id',
@@ -35,7 +41,7 @@ describe(DeviceCreateAction, () => {
appleTeamIdentifier: 'ABC123Y',
appleTeamName: 'John Doe (Individual)',
};
const action = new DeviceCreateAction(account, appleTeam);
const action = new DeviceCreateAction(appStoreApi, account, appleTeam);
await action.runAsync();
expect(runRegistrationUrlMethodAsync).toBeCalled();
@@ -45,6 +51,8 @@ describe(DeviceCreateAction, () => {
jest.mocked(prompts).mockImplementationOnce(async () => ({
method: RegistrationMethod.INPUT,
}));
const appStoreApiMock = mock<AppStoreApi>();
const appStoreApi = instance(appStoreApiMock);
const account: Account = {
id: 'account_id',
@@ -55,10 +63,32 @@ describe(DeviceCreateAction, () => {
appleTeamIdentifier: 'ABC123Y',
appleTeamName: 'John Doe (Individual)',
};
const action = new DeviceCreateAction(account, appleTeam);
const action = new DeviceCreateAction(appStoreApi, account, appleTeam);
await action.runAsync();
expect(runInputMethodAsync).toBeCalled();
});
it('calls runDeveloperPortalMethodAsync if user chooses the developer portal method', async () => {
jest.mocked(prompts).mockImplementationOnce(async () => ({
method: RegistrationMethod.DEVELOPER_PORTAL,
}));
const appStoreApiMock = mock<AppStoreApi>();
const appStoreApi = instance(appStoreApiMock);
const account: Account = {
id: 'account_id',
name: 'foobar',
};
const appleTeam = {
id: 'apple-team-id',
appleTeamIdentifier: 'ABC123Y',
appleTeamName: 'John Doe (Individual)',
};
const action = new DeviceCreateAction(appStoreApi, account, appleTeam);
await action.runAsync();
expect(runDeveloperPortalMethodAsync).toBeCalled();
});
});
});
@@ -1,20 +1,24 @@
import chalk from 'chalk';
import AppStoreApi from '../../../credentials/ios/appstore/AppStoreApi';
import { AppleTeam } from '../../../graphql/generated';
import Log from '../../../log';
import { promptAsync } from '../../../prompts';
import { Account } from '../../../user/Account';
import { runDeveloperPortalMethodAsync } from './developerPortalMethod';
import { runInputMethodAsync } from './inputMethod';
import { runRegistrationUrlMethodAsync } from './registrationUrlMethod';
export enum RegistrationMethod {
WEBSITE,
INPUT,
DEVELOPER_PORTAL,
EXIT,
}
export default class DeviceCreateAction {
constructor(
private appStoreApi: AppStoreApi,
private account: Account,
private appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName' | 'id'>
) {}
@@ -23,6 +27,8 @@ export default class DeviceCreateAction {
const method = await this.askForRegistrationMethodAsync();
if (method === RegistrationMethod.WEBSITE) {
await runRegistrationUrlMethodAsync(this.account.id, this.appleTeam);
} else if (method === RegistrationMethod.DEVELOPER_PORTAL) {
await runDeveloperPortalMethodAsync(this.appStoreApi, this.account.id, this.appleTeam);
} else if (method === RegistrationMethod.INPUT) {
await runInputMethodAsync(this.account.id, this.appleTeam);
} else if (method === RegistrationMethod.EXIT) {
@@ -44,6 +50,12 @@ export default class DeviceCreateAction {
)} - generates a registration URL to be opened on your devices`,
value: RegistrationMethod.WEBSITE,
},
{
title: `${chalk.bold(
'Developer Portal'
)} - import devices already registered on Apple Developer Portal`,
value: RegistrationMethod.DEVELOPER_PORTAL,
},
{
title: `${chalk.bold('Input')} - allows you to type in UDIDs (advanced option)`,
value: RegistrationMethod.INPUT,
@@ -0,0 +1,131 @@
import { Device, DeviceClass } from '@expo/apple-utils';
import { AppleDeviceMutation } from '../../../credentials/ios/api/graphql/mutations/AppleDeviceMutation';
import {
AppleDeviceFragmentWithAppleTeam,
AppleDeviceQuery,
} from '../../../credentials/ios/api/graphql/queries/AppleDeviceQuery';
import AppStoreApi from '../../../credentials/ios/appstore/AppStoreApi';
import { getRequestContext } from '../../../credentials/ios/appstore/authenticate';
import { AuthCtx } from '../../../credentials/ios/appstore/authenticateTypes';
import { AppleDeviceClass, AppleTeam } from '../../../graphql/generated';
import Log from '../../../log';
import { ora } from '../../../ora';
import { promptAsync } from '../../../prompts';
import chunk from '../../../utils/expodash/chunk';
const DEVICE_IMPORT_CHUNK_SIZE = 10;
const DEVICE_CLASS_TO_GRAPHQL_TYPE: Partial<Record<DeviceClass, AppleDeviceClass>> = {
[DeviceClass.IPAD]: AppleDeviceClass.Ipad,
[DeviceClass.IPHONE]: AppleDeviceClass.Iphone,
};
export async function runDeveloperPortalMethodAsync(
appStoreApi: AppStoreApi,
accountId: string,
appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName' | 'id'>
): Promise<void> {
const appleAuthCtx = await appStoreApi.ensureAuthenticatedAsync();
const unregisteredPortalDevices = await findUnregisteredPortalDevicesAsync(
appleAuthCtx,
accountId,
appleTeam
);
if (unregisteredPortalDevices.length === 0) {
Log.log('All your devices registered on Apple Developer Portal are already imported to EAS.');
return;
}
const devicesToImport = await chooseDevicesToImportAsync(unregisteredPortalDevices);
await importDevicesAsync(accountId, appleTeam, devicesToImport);
}
async function importDevicesAsync(
accountId: string,
appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName' | 'id'>,
devices: Device[]
): Promise<void> {
const spinner = ora(
`Importing ${devices.length} Apple device${devices.length === 1 ? '' : 's'} to EAS`
).start();
const deviceChunks = chunk(devices, DEVICE_IMPORT_CHUNK_SIZE);
try {
for (const deviceChunk of deviceChunks) {
await importDeviceChunkAsync(accountId, appleTeam, deviceChunk);
}
} catch (err) {
spinner.fail();
throw err;
}
spinner.succeed();
}
async function importDeviceChunkAsync(
accountId: string,
appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName' | 'id'>,
devices: Device[]
): Promise<void> {
const promises = devices.map(device => {
return AppleDeviceMutation.createAppleDeviceAsync(
{
appleTeamId: appleTeam.id,
identifier: device.attributes.udid,
name: device.attributes.name,
deviceClass: DEVICE_CLASS_TO_GRAPHQL_TYPE[device.attributes.deviceClass] ?? undefined,
},
accountId
);
});
await Promise.all(promises);
}
async function findUnregisteredPortalDevicesAsync(
appleAuthCtx: AuthCtx,
accountId: string,
appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName' | 'id'>
): Promise<Device[]> {
const expoRegisteredDevices = await AppleDeviceQuery.getAllByAppleTeamIdentifierAsync(
accountId,
appleTeam.appleTeamIdentifier,
{ useCache: false }
);
const expoRegisteredDevicesByUdid = expoRegisteredDevices.reduce((acc, device) => {
acc[device.identifier] = device;
return acc;
}, {} as Record<string, AppleDeviceFragmentWithAppleTeam>);
const portalDevices = await Device.getAllIOSProfileDevicesAsync(getRequestContext(appleAuthCtx));
return portalDevices.filter(
portalDevice =>
!(portalDevice.attributes.udid in expoRegisteredDevicesByUdid) &&
[DeviceClass.IPAD, DeviceClass.IPHONE].includes(portalDevice.attributes.deviceClass)
);
}
async function chooseDevicesToImportAsync(devices: Device[]): Promise<Device[]> {
const { chosenDevices } = await promptAsync({
type: 'multiselect',
name: 'chosenDevices',
message: 'Which devices do you want to import to EAS?',
choices: devices.map(device => ({
value: device,
title: formatDeviceLabel(device),
selected: true,
})),
});
return chosenDevices;
}
export function formatDeviceLabel(device: Device): string {
const deviceDetails = formatDeviceDetails(device);
return `${device.attributes.name} - ${device.attributes.udid})${
deviceDetails !== '' ? ` - ${deviceDetails}` : ''
}`;
}
function formatDeviceDetails(device: Device): string {
let details: string = device.attributes.deviceClass;
if (device.attributes.model) {
details = device.attributes.model;
}
return details === '' ? details : `(${details})`;
}
+5 -5
View File
@@ -18,7 +18,7 @@ Internal distribution means that you won't need upload your app archive to App S
Your app archive (.ipa) will be installable on your equipment as long as you sign your application with an adhoc provisiong profile.
The provisioning profile needs to contain the UDIDs (unique identifiers) of your iPhones and iPads.
First of all, please choose the Expo account under which you want to register your devices.
First of all, choose the Expo account under which you want to register your devices.
Later, authenticate with Apple and choose your desired Apple Team (if your Apple ID has access to multiple teams).`;
export default class DeviceManager {
@@ -29,12 +29,12 @@ export default class DeviceManager {
Log.addNewLineIfNone();
const account = await this.resolveAccountAsync();
const { team } = await this.ctx.appStore.ensureAuthenticatedAsync();
const appleAuthCtx = await this.ctx.appStore.ensureAuthenticatedAsync();
const appleTeam = await ensureAppleTeamExistsAsync(account.id, {
appleTeamIdentifier: team.id,
appleTeamName: team.name,
appleTeamIdentifier: appleAuthCtx.team.id,
appleTeamName: appleAuthCtx.team.name,
});
const action = new DeviceCreateAction(account, appleTeam);
const action = new DeviceCreateAction(this.ctx.appStore, account, appleTeam);
await action.runAsync();
}
+11
View File
@@ -0,0 +1,11 @@
export default {
/**
* Dangerous overrides, use only if you know what you are doing
*/
/**
* Overrides applicationId from Android project, setting this option will also
* ignore failures when parsing build.gradle.
*/
overrideAndroidApplicationId: process.env.EAS_DANGEROUS_OVERRIDE_ANDROID_APPLICATION_ID,
};
+2 -2
View File
@@ -55,7 +55,7 @@ export const graphqlClient = createUrqlClient({
},
}) as StricterClient;
/* Please specify additionalTypenames in your Graphql queries */
/* Specify additionalTypenames in your Graphql queries */
export interface StricterClient extends Client {
// eslint-disable-next-line @typescript-eslint/ban-types
query<Data = any, Variables extends object = {}>(
@@ -70,7 +70,7 @@ export async function withErrorHandlingAsync<T>(promise: Promise<OperationResult
if (error) {
if (error.graphQLErrors.some(e => e?.extensions?.isTransient)) {
Log.error(`We've encountered a transient error, please try again shortly.`);
Log.error(`We've encountered a transient error. Try again shortly.`);
}
throw error;
}
+494 -36
View File
@@ -17,13 +17,9 @@ export type Scalars = {
Boolean: boolean;
Int: number;
Float: number;
/** Date custom scalar type */
DateTime: any;
/** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
JSON: any;
/** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
JSONObject: any;
/** The `Upload` scalar type represents a file upload. */
Upload: any;
};
@@ -109,6 +105,8 @@ export type Account = {
createdAt: Scalars['DateTime'];
/** Environment secrets for an account */
environmentSecrets: Array<EnvironmentSecret>;
/** GitHub App installations for an account */
githubAppInstallations: Array<GitHubAppInstallation>;
/** Android credentials for account */
googleServiceAccountKeys: Array<GoogleServiceAccountKey>;
id: Scalars['ID'];
@@ -123,6 +121,8 @@ export type Account = {
requiresAccessTokenForPushSecurity: Scalars['Boolean'];
/** Snacks associated with this account */
snacks: Array<Snack>;
/** SSO configuration for this account */
ssoConfiguration?: Maybe<AccountSsoConfiguration>;
/** Subscription info visible to members that have VIEWER role */
subscription?: Maybe<SubscriptionDetails>;
/** @deprecated No longer needed */
@@ -280,8 +280,8 @@ export type AccountMutation = {
grantActorPermissions: Account;
/** Rename this account and the primary user's username if this account is a personal account */
rename: Account;
/** Requests a refund for the specified charge. Returns true if auto-refund was possible, otherwise requests a manual refund from support and returns false. */
requestRefund: Scalars['Boolean'];
/** Requests a refund for the specified charge by requesting a manual refund from support */
requestRefund?: Maybe<Scalars['Boolean']>;
/** Revoke specified Permissions for Actor. Actor must already have at least one permission on the account. */
revokeActorPermissions: Account;
/**
@@ -393,6 +393,90 @@ export type AccountQueryByNameArgs = {
accountName: Scalars['String'];
};
/** Auth configuration data for an SSO account. */
export type AccountSsoConfiguration = {
__typename?: 'AccountSSOConfiguration';
authEndpoint?: Maybe<Scalars['String']>;
authProtocol: AuthProtocolType;
authProviderIdentifier: Scalars['String'];
clientIdentifier: Scalars['String'];
clientSecret: Scalars['String'];
createdAt: Scalars['DateTime'];
id: Scalars['ID'];
issuer?: Maybe<Scalars['String']>;
jwksEndpoint?: Maybe<Scalars['String']>;
redirectUri: Scalars['String'];
tokenEndpoint?: Maybe<Scalars['String']>;
updatedAt: Scalars['DateTime'];
userInfoEndpoint?: Maybe<Scalars['String']>;
};
export type AccountSsoConfigurationData = {
authEndpoint?: InputMaybe<Scalars['String']>;
authProtocol: AuthProtocolType;
authProviderIdentifier: Scalars['String'];
clientIdentifier: Scalars['String'];
clientSecret: Scalars['String'];
issuer?: InputMaybe<Scalars['String']>;
jwksEndpoint?: InputMaybe<Scalars['String']>;
redirectUri: Scalars['String'];
tokenEndpoint?: InputMaybe<Scalars['String']>;
userInfoEndpoint?: InputMaybe<Scalars['String']>;
};
export type AccountSsoConfigurationMutation = {
__typename?: 'AccountSSOConfigurationMutation';
/** Create an AccountSSOConfiguration for an Account */
createAccountSSOConfiguration: AccountSsoConfiguration;
/** Delete an AccountSSOConfiguration */
deleteAccountSSOConfiguration: DeleteAccountSsoConfigurationResult;
/** Update an AccountSSOConfiguration */
updateAccountSSOConfiguration: AccountSsoConfiguration;
};
export type AccountSsoConfigurationMutationCreateAccountSsoConfigurationArgs = {
accountId: Scalars['ID'];
accountSSOConfigurationData: AccountSsoConfigurationData;
};
export type AccountSsoConfigurationMutationDeleteAccountSsoConfigurationArgs = {
id: Scalars['ID'];
};
export type AccountSsoConfigurationMutationUpdateAccountSsoConfigurationArgs = {
accountSSOConfigurationData: AccountSsoConfigurationData;
id: Scalars['ID'];
};
/** Public auth configuration data for an SSO account. */
export type AccountSsoConfigurationPublicData = {
__typename?: 'AccountSSOConfigurationPublicData';
authEndpoint?: Maybe<Scalars['String']>;
authProtocol: AuthProtocolType;
authProviderIdentifier: Scalars['String'];
clientIdentifier: Scalars['String'];
id: Scalars['ID'];
issuer?: Maybe<Scalars['String']>;
jwksEndpoint?: Maybe<Scalars['String']>;
redirectUri: Scalars['String'];
tokenEndpoint?: Maybe<Scalars['String']>;
userInfoEndpoint?: Maybe<Scalars['String']>;
};
export type AccountSsoConfigurationPublicDataQuery = {
__typename?: 'AccountSSOConfigurationPublicDataQuery';
/** Get AccountSSOConfiguration public data by account name */
publicDataByAccountName: AccountSsoConfigurationPublicData;
};
export type AccountSsoConfigurationPublicDataQueryPublicDataByAccountNameArgs = {
accountName: Scalars['String'];
};
export type AccountUsageMetric = {
__typename?: 'AccountUsageMetric';
id: Scalars['ID'];
@@ -427,6 +511,7 @@ export type AccountUsageMetricsByBillingPeriodArgs = {
export type AccountUsageMetricsMetricsForServiceMetricArgs = {
filterParams?: InputMaybe<Scalars['JSONObject']>;
granularity: UsageMetricsGranularity;
serviceMetric: EasServiceMetric;
timespan: UsageMetricsTimespan;
@@ -708,6 +793,7 @@ export type AndroidJobInput = {
type: BuildWorkflow;
updates?: InputMaybe<BuildUpdatesInput>;
username?: InputMaybe<Scalars['String']>;
version?: InputMaybe<AndroidJobVersionInput>;
};
export type AndroidJobKeystoreInput = {
@@ -717,9 +803,27 @@ export type AndroidJobKeystoreInput = {
keystorePassword: Scalars['String'];
};
export type AndroidJobOverridesInput = {
artifactPath?: InputMaybe<Scalars['String']>;
buildType?: InputMaybe<AndroidBuildType>;
builderEnvironment?: InputMaybe<AndroidBuilderEnvironmentInput>;
cache?: InputMaybe<BuildCacheInput>;
developmentClient?: InputMaybe<Scalars['Boolean']>;
experimental?: InputMaybe<Scalars['JSONObject']>;
gradleCommand?: InputMaybe<Scalars['String']>;
releaseChannel?: InputMaybe<Scalars['String']>;
secrets?: InputMaybe<AndroidJobSecretsInput>;
updates?: InputMaybe<BuildUpdatesInput>;
username?: InputMaybe<Scalars['String']>;
version?: InputMaybe<AndroidJobVersionInput>;
};
export type AndroidJobSecretsInput = {
buildCredentials?: InputMaybe<AndroidJobBuildCredentialsInput>;
environmentSecrets?: InputMaybe<Scalars['JSONObject']>;
};
export type AndroidJobVersionInput = {
versionCode: Scalars['String'];
};
export type AndroidKeystore = {
@@ -802,6 +906,7 @@ export type App = Project & {
androidAppCredentials: Array<AndroidAppCredentials>;
/** ios.appStoreUrl field from most recent classic update manifest */
appStoreUrl?: Maybe<Scalars['String']>;
assetLimitPerUpdateGroup: Scalars['Int'];
buildJobs: Array<BuildJob>;
/**
* Coalesced Build (EAS) or BuildJob (Classic) items for this app.
@@ -834,6 +939,7 @@ export type App = Project & {
isLikedByMe: Scalars['Boolean'];
/** @deprecated No longer supported */
lastPublishedTime: Scalars['DateTime'];
latestAppVersionByPlatformAndApplicationIdentifier?: Maybe<AppVersion>;
latestReleaseForReleaseChannel?: Maybe<AppRelease>;
/** ID of latest classic update release */
latestReleaseId: Scalars['ID'];
@@ -956,6 +1062,13 @@ export type AppIosAppCredentialsArgs = {
};
/** Represents an Exponent App (or Experience in legacy terms) */
export type AppLatestAppVersionByPlatformAndApplicationIdentifierArgs = {
applicationIdentifier: Scalars['String'];
platform: AppPlatform;
};
/** Represents an Exponent App (or Experience in legacy terms) */
export type AppLatestReleaseForReleaseChannelArgs = {
platform: AppPlatform;
@@ -1198,6 +1311,52 @@ export enum AppStoreConnectUserRole {
Unknown = 'UNKNOWN'
}
/** Represents Play Store/App Store version of an application */
export type AppVersion = {
__typename?: 'AppVersion';
/**
* Store identifier for an application
* - Android - applicationId
* - iOS - bundle identifier
*/
applicationIdentifier: Scalars['String'];
/**
* Value that identifies build in a store (it's visible to developers, but not to end users)
* - Android - versionCode in build.gradle ("android.versionCode" field in app.json)
* - iOS - CFBundleVersion in Info.plist ("ios.buildNumber" field in app.json)
*/
buildVersion: Scalars['String'];
id: Scalars['ID'];
platform: AppPlatform;
runtimeVersion?: Maybe<Scalars['String']>;
/**
* User-facing version in a store
* - Android - versionName in build.gradle ("version" field in app.json)
* - iOS - CFBundleShortVersionString in Info.plist ("version" field in app.json)
*/
storeVersion: Scalars['String'];
};
export type AppVersionInput = {
appId: Scalars['ID'];
applicationIdentifier: Scalars['String'];
buildVersion: Scalars['String'];
platform: AppPlatform;
runtimeVersion?: InputMaybe<Scalars['String']>;
storeVersion: Scalars['String'];
};
export type AppVersionMutation = {
__typename?: 'AppVersionMutation';
/** Create an app version */
createAppVersion: AppVersion;
};
export type AppVersionMutationCreateAppVersionArgs = {
appVersionInput: AppVersionInput;
};
export type AppleAppIdentifier = {
__typename?: 'AppleAppIdentifier';
account: Account;
@@ -1414,7 +1573,7 @@ export type ApplePushKey = {
};
export type ApplePushKeyInput = {
appleTeamId?: InputMaybe<Scalars['ID']>;
appleTeamId: Scalars['ID'];
keyIdentifier: Scalars['String'];
keyP8: Scalars['String'];
};
@@ -1539,6 +1698,10 @@ export type AssetQueryMetadataArgs = {
storageKeys: Array<Scalars['String']>;
};
export enum AuthProtocolType {
Oidc = 'OIDC'
}
export type Billing = {
__typename?: 'Billing';
/** History of invoices */
@@ -1567,6 +1730,7 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
canRetry: Scalars['Boolean'];
cancelingActor?: Maybe<Actor>;
channel?: Maybe<Scalars['String']>;
childBuild?: Maybe<Build>;
completedAt?: Maybe<Scalars['DateTime']>;
createdAt: Scalars['DateTime'];
distribution?: Maybe<DistributionType>;
@@ -1575,6 +1739,7 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
estimatedWaitTimeLeftSeconds?: Maybe<Scalars['Int']>;
expirationDate?: Maybe<Scalars['DateTime']>;
gitCommitHash?: Maybe<Scalars['String']>;
gitCommitMessage?: Maybe<Scalars['String']>;
id: Scalars['ID'];
/** Queue position is 1-indexed */
initialQueuePosition?: Maybe<Scalars['Int']>;
@@ -1584,6 +1749,10 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
iosEnterpriseProvisioning?: Maybe<BuildIosEnterpriseProvisioning>;
isGitWorkingTreeDirty?: Maybe<Scalars['Boolean']>;
logFiles: Array<Scalars['String']>;
maxBuildTimeSeconds: Scalars['Int'];
/** Retry time starts after completedAt */
maxRetryTimeMinutes: Scalars['Int'];
message?: Maybe<Scalars['String']>;
metrics?: Maybe<BuildMetrics>;
parentBuild?: Maybe<Build>;
platform: AppPlatform;
@@ -1595,6 +1764,7 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
reactNativeVersion?: Maybe<Scalars['String']>;
releaseChannel?: Maybe<Scalars['String']>;
resourceClass?: Maybe<BuildResourceClass>;
runFromCI?: Maybe<Scalars['Boolean']>;
runtimeVersion?: Maybe<Scalars['String']>;
sdkVersion?: Maybe<Scalars['String']>;
status: BuildStatus;
@@ -1739,10 +1909,14 @@ export type BuildMetadataInput = {
credentialsSource?: InputMaybe<BuildCredentialsSource>;
distribution?: InputMaybe<DistributionType>;
gitCommitHash?: InputMaybe<Scalars['String']>;
gitCommitMessage?: InputMaybe<Scalars['String']>;
iosEnterpriseProvisioning?: InputMaybe<BuildIosEnterpriseProvisioning>;
isGitWorkingTreeDirty?: InputMaybe<Scalars['Boolean']>;
message?: InputMaybe<Scalars['String']>;
reactNativeVersion?: InputMaybe<Scalars['String']>;
releaseChannel?: InputMaybe<Scalars['String']>;
runFromCI?: InputMaybe<Scalars['Boolean']>;
runWithNoWaitFlag?: InputMaybe<Scalars['Boolean']>;
runtimeVersion?: InputMaybe<Scalars['String']>;
sdkVersion?: InputMaybe<Scalars['String']>;
trackingContext?: InputMaybe<Scalars['JSONObject']>;
@@ -1772,8 +1946,15 @@ export type BuildMutation = {
createIosBuild: CreateBuildResult;
/** Delete an EAS Build build */
deleteBuild: Build;
/** Retry an EAS Build build */
/** Retry an Android EAS Build */
retryAndroidBuild: Build;
/**
* Retry an EAS Build build
* @deprecated Use retryAndroidBuild and retryIosBuild instead
*/
retryBuild: Build;
/** Retry an iOS EAS Build */
retryIosBuild: Build;
};
@@ -1803,10 +1984,22 @@ export type BuildMutationDeleteBuildArgs = {
};
export type BuildMutationRetryAndroidBuildArgs = {
buildId: Scalars['ID'];
jobOverrides?: InputMaybe<AndroidJobOverridesInput>;
};
export type BuildMutationRetryBuildArgs = {
buildId: Scalars['ID'];
};
export type BuildMutationRetryIosBuildArgs = {
buildId: Scalars['ID'];
jobOverrides?: InputMaybe<IosJobOverridesInput>;
};
export type BuildOrBuildJob = {
id: Scalars['ID'];
};
@@ -1817,7 +2010,8 @@ export type BuildParamsInput = {
export enum BuildPriority {
High = 'HIGH',
Normal = 'NORMAL'
Normal = 'NORMAL',
NormalPlus = 'NORMAL_PLUS'
}
/** Publicly visible data for a Build. */
@@ -2005,6 +2199,11 @@ export type CreateEnvironmentSecretInput = {
value: Scalars['String'];
};
export type CreateGitHubAppInstallationInput = {
accountId: Scalars['ID'];
installationIdentifier: Scalars['Int'];
};
export type CreateIosSubmissionInput = {
appId: Scalars['ID'];
archiveUrl?: InputMaybe<Scalars['String']>;
@@ -2012,6 +2211,12 @@ export type CreateIosSubmissionInput = {
submittedBuildId?: InputMaybe<Scalars['ID']>;
};
export type CreateServerlessFunctionUploadUrlResult = {
__typename?: 'CreateServerlessFunctionUploadUrlResult';
formDataFields: Scalars['JSONObject'];
url: Scalars['String'];
};
export type CreateSubmissionResult = {
__typename?: 'CreateSubmissionResult';
/** Created submission */
@@ -2028,6 +2233,11 @@ export type DeleteAccountResult = {
id: Scalars['ID'];
};
export type DeleteAccountSsoConfigurationResult = {
__typename?: 'DeleteAccountSSOConfigurationResult';
id: Scalars['ID'];
};
export type DeleteAndroidKeystoreResult = {
__typename?: 'DeleteAndroidKeystoreResult';
id: Scalars['ID'];
@@ -2123,7 +2333,9 @@ export enum EasBuildDeprecationInfoType {
export enum EasServiceMetric {
AssetsRequests = 'ASSETS_REQUESTS',
BandwidthUsage = 'BANDWIDTH_USAGE',
Builds = 'BUILDS',
ManifestRequests = 'MANIFEST_REQUESTS',
UniqueUpdaters = 'UNIQUE_UPDATERS',
UniqueUsers = 'UNIQUE_USERS'
}
@@ -2216,11 +2428,52 @@ export enum Feature {
Teams = 'TEAMS'
}
export type FutureSubscription = {
__typename?: 'FutureSubscription';
createdAt: Scalars['DateTime'];
id: Scalars['ID'];
meteredBillingStatus: MeteredBillingStatus;
planId: Scalars['String'];
startDate: Scalars['DateTime'];
};
export type GetSignedAssetUploadSpecificationsResult = {
__typename?: 'GetSignedAssetUploadSpecificationsResult';
specifications: Array<Scalars['String']>;
};
export type GitHubAppInstallation = {
__typename?: 'GitHubAppInstallation';
account: Account;
actor?: Maybe<Actor>;
id: Scalars['ID'];
installationIdentifier: Scalars['Int'];
};
export type GitHubAppInstallationMutation = {
__typename?: 'GitHubAppInstallationMutation';
/** Create a GitHub App installation for an Account */
createGitHubAppInstallationForAccount: GitHubAppInstallation;
/** Delete a GitHub App installation by ID */
deleteGitHubAppInstallation: GitHubAppInstallation;
};
export type GitHubAppInstallationMutationCreateGitHubAppInstallationForAccountArgs = {
githubAppInstallationData: CreateGitHubAppInstallationInput;
};
export type GitHubAppInstallationMutationDeleteGitHubAppInstallationArgs = {
githubAppInstallationId: Scalars['ID'];
};
export type GitHubAppQuery = {
__typename?: 'GitHubAppQuery';
appIdentifier: Scalars['String'];
clientIdentifier: Scalars['String'];
};
export type GoogleServiceAccountKey = {
__typename?: 'GoogleServiceAccountKey';
account: Account;
@@ -2488,11 +2741,31 @@ export type IosJobInput = {
type: BuildWorkflow;
updates?: InputMaybe<BuildUpdatesInput>;
username?: InputMaybe<Scalars['String']>;
version?: InputMaybe<IosJobVersionInput>;
};
export type IosJobOverridesInput = {
artifactPath?: InputMaybe<Scalars['String']>;
buildConfiguration?: InputMaybe<Scalars['String']>;
/** @deprecated */
buildType?: InputMaybe<IosBuildType>;
builderEnvironment?: InputMaybe<IosBuilderEnvironmentInput>;
cache?: InputMaybe<BuildCacheInput>;
developmentClient?: InputMaybe<Scalars['Boolean']>;
/** @deprecated */
distribution?: InputMaybe<DistributionType>;
experimental?: InputMaybe<Scalars['JSONObject']>;
releaseChannel?: InputMaybe<Scalars['String']>;
scheme?: InputMaybe<Scalars['String']>;
secrets?: InputMaybe<IosJobSecretsInput>;
simulator?: InputMaybe<Scalars['Boolean']>;
updates?: InputMaybe<BuildUpdatesInput>;
username?: InputMaybe<Scalars['String']>;
version?: InputMaybe<IosJobVersionInput>;
};
export type IosJobSecretsInput = {
buildCredentials?: InputMaybe<Array<InputMaybe<IosJobTargetCredentialsInput>>>;
environmentSecrets?: InputMaybe<Scalars['JSONObject']>;
};
export type IosJobTargetCredentialsInput = {
@@ -2501,6 +2774,10 @@ export type IosJobTargetCredentialsInput = {
targetName: Scalars['String'];
};
export type IosJobVersionInput = {
buildNumber: Scalars['String'];
};
/** @deprecated Use developmentClient option instead. */
export enum IosManagedBuildType {
DevelopmentClient = 'DEVELOPMENT_CLIENT',
@@ -2682,6 +2959,11 @@ export type MeMutationUpdateProfileArgs = {
userData: UserDataInput;
};
export type MeteredBillingStatus = {
__typename?: 'MeteredBillingStatus';
EAS_UPDATE: Scalars['Boolean'];
};
export type Offer = {
__typename?: 'Offer';
features?: Maybe<Array<Maybe<Feature>>>;
@@ -2898,6 +3180,8 @@ export type RootMutation = {
accessToken: AccessTokenMutation;
/** Mutations that modify an Account */
account: AccountMutation;
/** Mutations that create, update, and delete an AccountSSOConfiguration */
accountSSOConfiguration: AccountSsoConfigurationMutation;
/** Mutations that modify the build credentials for an Android app */
androidAppBuildCredentials: AndroidAppBuildCredentialsMutation;
/** Mutations that modify the credentials for an Android app */
@@ -2910,6 +3194,8 @@ export type RootMutation = {
app?: Maybe<AppMutation>;
/** Mutations that modify an App Store Connect Api Key */
appStoreConnectApiKey: AppStoreConnectApiKeyMutation;
/** Mutations that modify an AppVersion */
appVersion: AppVersionMutation;
/** Mutations that modify an Identifier for an iOS App */
appleAppIdentifier: AppleAppIdentifierMutation;
/** Mutations that modify an Apple Device */
@@ -2933,6 +3219,8 @@ export type RootMutation = {
emailSubscription: EmailSubscriptionMutation;
/** Mutations that create and delete EnvironmentSecrets */
environmentSecret: EnvironmentSecretMutation;
/** Mutations for GitHub App installations */
githubAppInstallation: GitHubAppInstallationMutation;
/** Mutations that modify a Google Service Account Key */
googleServiceAccountKey: GoogleServiceAccountKeyMutation;
/** Mutations that modify the build credentials for an iOS app */
@@ -2944,6 +3232,7 @@ export type RootMutation = {
me: MeMutation;
/** Mutations that create, update, and delete Robots */
robot: RobotMutation;
serverlessFunction: ServerlessFunctionMutation;
/** Mutations that modify an EAS Submit submission */
submission: SubmissionMutation;
update: UpdateMutation;
@@ -2985,6 +3274,8 @@ export type RootQuery = {
_doNotUse?: Maybe<Scalars['String']>;
/** Top-level query object for querying Accounts. */
account: AccountQuery;
/** Top-level query object for querying AccountSSOConfigurationPublicData */
accountSSOConfigurationPublicData: AccountSsoConfigurationPublicDataQuery;
/** Top-level query object for querying Actors. */
actor: ActorQuery;
/**
@@ -3010,6 +3301,8 @@ export type RootQuery = {
clientBuilds: ClientBuildQuery;
/** Top-level query object for querying Experimentation configuration. */
experimentation: ExperimentationQuery;
/** Top-level query object for querying GitHub App information and resources it has access to. */
githubApp: GitHubAppQuery;
/** Top-level query object for querying Stripe Invoices. */
invoice: InvoiceQuery;
/**
@@ -3024,6 +3317,8 @@ export type RootQuery = {
meActor?: Maybe<Actor>;
project: ProjectQuery;
snack: SnackQuery;
/** Top-level query object for querying Expo status page services. */
statuspageService: StatuspageServiceQuery;
submissions: SubmissionQuery;
/** fetch all updates in a group */
updatesByGroup: Array<Update>;
@@ -3109,6 +3404,21 @@ export type SecondFactorRegenerateBackupCodesResult = {
plaintextBackupCodes: Array<Scalars['String']>;
};
export type ServerlessFunctionIdentifierInput = {
gitCommitSHA1: Scalars['String'];
};
export type ServerlessFunctionMutation = {
__typename?: 'ServerlessFunctionMutation';
createUploadPresignedUrl: CreateServerlessFunctionUploadUrlResult;
};
export type ServerlessFunctionMutationCreateUploadPresignedUrlArgs = {
appId: Scalars['ID'];
serverlessFunctionIdentifierInput: ServerlessFunctionIdentifierInput;
};
export type Snack = Project & {
__typename?: 'Snack';
/** Description of the Snack */
@@ -3168,6 +3478,110 @@ export enum StandardOffer {
YearlySub = 'YEARLY_SUB'
}
/** Incident for a given component from Expo status page API. */
export type StatuspageIncident = {
__typename?: 'StatuspageIncident';
createdAt: Scalars['DateTime'];
id: Scalars['ID'];
/** Impact of an incident from Expo status page. */
impact: StatuspageIncidentImpact;
name: Scalars['String'];
resolvedAt?: Maybe<Scalars['DateTime']>;
/** Shortlink to the incident from Expo status page. */
shortlink: Scalars['String'];
/** Current status of an incident from Expo status page. */
status: StatuspageIncidentStatus;
updatedAt: Scalars['DateTime'];
/** List of all updates for an incident from Expo status page. */
updates: Array<StatuspageIncidentUpdate>;
};
/** Possible Incident impact values from Expo status page API. */
export enum StatuspageIncidentImpact {
Critical = 'CRITICAL',
Maintenance = 'MAINTENANCE',
Major = 'MAJOR',
Minor = 'MINOR',
None = 'NONE'
}
/** Possible Incident statuses from Expo status page API. */
export enum StatuspageIncidentStatus {
Completed = 'COMPLETED',
Identified = 'IDENTIFIED',
Investigating = 'INVESTIGATING',
InProgress = 'IN_PROGRESS',
Monitoring = 'MONITORING',
Resolved = 'RESOLVED',
Scheduled = 'SCHEDULED',
Verifying = 'VERIFYING'
}
/** Update for an Incident from Expo status page API. */
export type StatuspageIncidentUpdate = {
__typename?: 'StatuspageIncidentUpdate';
/** Text of an update from Expo status page. */
body: Scalars['String'];
createdAt: Scalars['DateTime'];
id: Scalars['ID'];
/** Status set at the moment of update. */
status: StatuspageIncidentStatus;
};
/** Service monitored by Expo status page. */
export type StatuspageService = {
__typename?: 'StatuspageService';
/** Description of a service from Expo status page. */
description?: Maybe<Scalars['String']>;
id: Scalars['ID'];
/**
* List of last inicdents for a service from Expo status page (we always query for 50 latest incidents for all services)
* sorted by createdAt field in descending order.
*/
incidents: Array<StatuspageIncident>;
/** Name of a service monitored by Expo status page. */
name: StatuspageServiceName;
/** Current status of a service from Expo status page. */
status: StatuspageServiceStatus;
};
/** Name of a service monitored by Expo status page. */
export enum StatuspageServiceName {
EasBuild = 'EAS_BUILD',
EasSubmit = 'EAS_SUBMIT',
EasUpdate = 'EAS_UPDATE'
}
export type StatuspageServiceQuery = {
__typename?: 'StatuspageServiceQuery';
/** Query services from Expo status page by names. */
byServiceNames: Array<StatuspageService>;
};
export type StatuspageServiceQueryByServiceNamesArgs = {
serviceNames: Array<StatuspageServiceName>;
};
/** Possible statuses for a service. */
export enum StatuspageServiceStatus {
DegradedPerformance = 'DEGRADED_PERFORMANCE',
MajorOutage = 'MAJOR_OUTAGE',
Operational = 'OPERATIONAL',
PartialOutage = 'PARTIAL_OUTAGE',
UnderMaintenance = 'UNDER_MAINTENANCE'
}
export type StripeCoupon = {
__typename?: 'StripeCoupon';
amountOff?: Maybe<Scalars['String']>;
appliesTo?: Maybe<Scalars['String']>;
id: Scalars['ID'];
name: Scalars['String'];
percentOff?: Maybe<Scalars['Float']>;
valid: Scalars['Boolean'];
};
/** Represents an EAS Submission */
export type Submission = ActivityTimelineProjectActivity & {
__typename?: 'Submission';
@@ -3178,12 +3592,16 @@ export type Submission = ActivityTimelineProjectActivity & {
archiveUrl?: Maybe<Scalars['String']>;
canRetry: Scalars['Boolean'];
cancelingActor?: Maybe<Actor>;
childSubmission?: Maybe<Submission>;
completedAt?: Maybe<Scalars['DateTime']>;
createdAt: Scalars['DateTime'];
error?: Maybe<SubmissionError>;
id: Scalars['ID'];
initiatingActor?: Maybe<Actor>;
iosConfig?: Maybe<IosSubmissionConfig>;
logsUrl?: Maybe<Scalars['String']>;
/** Retry time starts after completedAt */
maxRetryTimeMinutes: Scalars['Int'];
parentSubmission?: Maybe<Submission>;
platform: AppPlatform;
status: SubmissionStatus;
@@ -3278,9 +3696,12 @@ export type SubscriptionDetails = {
addons: Array<AddonDetails>;
cancelledAt?: Maybe<Scalars['DateTime']>;
concurrencies?: Maybe<Concurrencies>;
coupon?: Maybe<StripeCoupon>;
endedAt?: Maybe<Scalars['DateTime']>;
futureSubscription?: Maybe<FutureSubscription>;
id: Scalars['ID'];
isDowngrading?: Maybe<Scalars['Boolean']>;
meteredBillingStatus: MeteredBillingStatus;
name?: Maybe<Scalars['String']>;
nextInvoice?: Maybe<Scalars['DateTime']>;
planId?: Maybe<Scalars['String']>;
@@ -3475,7 +3896,9 @@ export type UsageMetricTotal = {
export enum UsageMetricType {
Bandwidth = 'BANDWIDTH',
Build = 'BUILD',
Request = 'REQUEST',
Update = 'UPDATE',
User = 'USER'
}
@@ -3846,14 +4269,6 @@ export type DeleteUpdateBranchMutationVariables = Exact<{
export type DeleteUpdateBranchMutation = { __typename?: 'RootMutation', updateBranch: { __typename?: 'UpdateBranchMutation', deleteUpdateBranch: { __typename?: 'DeleteUpdateBranchResult', id: string } } };
export type BranchesByAppQueryVariables = Exact<{
appId: Scalars['String'];
limit: Scalars['Int'];
}>;
export type BranchesByAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, createdAt: any, message?: string | null, runtimeVersion: string, group: string, platform: string, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null }> }> } } };
export type EditUpdateBranchMutationVariables = Exact<{
input: EditUpdateBranchInput;
}>;
@@ -4204,13 +4619,6 @@ export type SetAppStoreConnectApiKeyForSubmissionsMutationVariables = Exact<{
export type SetAppStoreConnectApiKeyForSubmissionsMutation = { __typename?: 'RootMutation', iosAppCredentials: { __typename?: 'IosAppCredentialsMutation', setAppStoreConnectApiKeyForSubmissions: { __typename?: 'IosAppCredentials', id: string, iosAppBuildCredentialsList: Array<{ __typename?: 'IosAppBuildCredentials', id: string, iosDistributionType: IosDistributionType, distributionCertificate?: { __typename?: 'AppleDistributionCertificate', id: string, certificateP12?: string | null, certificatePassword?: string | null, serialNumber: string, developerPortalIdentifier?: string | null, validityNotBefore: any, validityNotAfter: any, updatedAt: any, appleTeam?: { __typename?: 'AppleTeam', id: string, appleTeamIdentifier: string, appleTeamName?: string | null } | null, iosAppBuildCredentialsList: Array<{ __typename?: 'IosAppBuildCredentials', id: string, iosAppCredentials: { __typename?: 'IosAppCredentials', id: string, app: { __typename?: 'App', id: string, fullName: string, slug: string }, appleAppIdentifier: { __typename?: 'AppleAppIdentifier', id: string, bundleIdentifier: string } }, provisioningProfile?: { __typename?: 'AppleProvisioningProfile', id: string, developerPortalIdentifier?: string | null } | null }> } | null, provisioningProfile?: { __typename?: 'AppleProvisioningProfile', id: string, expiration: any, developerPortalIdentifier?: string | null, provisioningProfile?: string | null, updatedAt: any, status: string, appleTeam?: { __typename?: 'AppleTeam', id: string, appleTeamIdentifier: string, appleTeamName?: string | null } | null, appleDevices: Array<{ __typename?: 'AppleDevice', id: string, identifier: string, name?: string | null, model?: string | null, deviceClass?: AppleDeviceClass | null }> } | null }>, app: { __typename?: 'App', id: string, fullName: string, slug: string }, appleTeam?: { __typename?: 'AppleTeam', id: string, appleTeamIdentifier: string, appleTeamName?: string | null } | null, appleAppIdentifier: { __typename?: 'AppleAppIdentifier', id: string, bundleIdentifier: string }, pushKey?: { __typename?: 'ApplePushKey', id: string, keyIdentifier: string, updatedAt: any, appleTeam?: { __typename?: 'AppleTeam', id: string, appleTeamIdentifier: string, appleTeamName?: string | null } | null, iosAppCredentialsList: Array<{ __typename?: 'IosAppCredentials', id: string, app: { __typename?: 'App', id: string, fullName: string, slug: string }, appleAppIdentifier: { __typename?: 'AppleAppIdentifier', id: string, bundleIdentifier: string } }> } | null, appStoreConnectApiKeyForSubmissions?: { __typename?: 'AppStoreConnectApiKey', id: string, issuerIdentifier: string, keyIdentifier: string, name?: string | null, roles?: Array<AppStoreConnectUserRole> | null, createdAt: any, updatedAt: any, appleTeam?: { __typename?: 'AppleTeam', id: string, appleTeamIdentifier: string, appleTeamName?: string | null } | null } | null } } };
export type AppByFullNameQueryVariables = Exact<{
fullName: Scalars['String'];
}>;
export type AppByFullNameQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byFullName: { __typename?: 'App', id: string, fullName: string, slug: string } } };
export type AppStoreConnectApiKeyByAccountQueryVariables = Exact<{
accountName: Scalars['String'];
}>;
@@ -4330,6 +4738,13 @@ export type CreateAppMutationVariables = Exact<{
export type CreateAppMutation = { __typename?: 'RootMutation', app?: { __typename?: 'AppMutation', createApp: { __typename?: 'App', id: string } } | null };
export type CreateAppVersionMutationVariables = Exact<{
appVersionInput: AppVersionInput;
}>;
export type CreateAppVersionMutation = { __typename?: 'RootMutation', appVersion: { __typename?: 'AppVersionMutation', createAppVersion: { __typename?: 'AppVersion', id: string } } };
export type CreateAndroidBuildMutationVariables = Exact<{
appId: Scalars['ID'];
job: AndroidJobInput;
@@ -4448,6 +4863,22 @@ export type DeleteWebhookMutationVariables = Exact<{
export type DeleteWebhookMutation = { __typename?: 'RootMutation', webhook: { __typename?: 'WebhookMutation', deleteWebhook: { __typename?: 'DeleteWebhookResult', id: string } } };
export type AppByFullNameQueryVariables = Exact<{
fullName: Scalars['String'];
}>;
export type AppByFullNameQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byFullName: { __typename?: 'App', id: string, fullName: string, slug: string } } };
export type LatestAppVersionQueryVariables = Exact<{
appId: Scalars['String'];
platform: AppPlatform;
applicationIdentifier: Scalars['String'];
}>;
export type LatestAppVersionQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, latestAppVersionByPlatformAndApplicationIdentifier?: { __typename?: 'AppVersion', id: string, storeVersion: string, buildVersion: string } | null } } };
export type ViewBranchQueryVariables = Exact<{
appId: Scalars['String'];
name: Scalars['String'];
@@ -4456,6 +4887,15 @@ export type ViewBranchQueryVariables = Exact<{
export type ViewBranchQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranchByName?: { __typename?: 'UpdateBranch', id: string, name: string } | null } } };
export type BranchesByAppQueryVariables = Exact<{
appId: Scalars['String'];
limit: Scalars['Int'];
offset: Scalars['Int'];
}>;
export type BranchesByAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, createdAt: any, message?: string | null, runtimeVersion: string, group: string, platform: string, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null }> }> } } };
export type BuildsByIdQueryVariables = Exact<{
buildId: Scalars['ID'];
}>;
@@ -4463,6 +4903,13 @@ export type BuildsByIdQueryVariables = Exact<{
export type BuildsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
export type BuildsWithSubmissionsByIdQueryVariables = Exact<{
buildId: Scalars['ID'];
}>;
export type BuildsWithSubmissionsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, submissions: Array<{ __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logsUrl?: string | null, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null }>, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
export type GetAllBuildsForAppQueryVariables = Exact<{
appId: Scalars['String'];
offset: Scalars['Int'];
@@ -4495,14 +4942,6 @@ export type EnvironmentSecretsByAppIdQueryVariables = Exact<{
export type EnvironmentSecretsByAppIdQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, environmentSecrets: Array<{ __typename?: 'EnvironmentSecret', id: string, name: string, createdAt: any }> } } };
export type ProjectByUsernameAndSlugQueryVariables = Exact<{
username: Scalars['String'];
slug: Scalars['String'];
}>;
export type ProjectByUsernameAndSlugQuery = { __typename?: 'RootQuery', project: { __typename?: 'ProjectQuery', byUsernameAndSlug: { __typename?: 'App', id: string } | { __typename?: 'Snack', id: string } } };
export type GetAssetMetadataQueryVariables = Exact<{
storageKeys: Array<Scalars['String']> | Scalars['String'];
}>;
@@ -4510,6 +4949,20 @@ export type GetAssetMetadataQueryVariables = Exact<{
export type GetAssetMetadataQuery = { __typename?: 'RootQuery', asset: { __typename?: 'AssetQuery', metadata: Array<{ __typename?: 'AssetMetadataResult', storageKey: string, status: AssetMetadataStatus }> } };
export type GetAssetLimitPerUpdateGroupForAppQueryVariables = Exact<{
appId: Scalars['String'];
}>;
export type GetAssetLimitPerUpdateGroupForAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, assetLimitPerUpdateGroup: number } } };
export type StatuspageServiceByServiceNamesQueryVariables = Exact<{
serviceNames: Array<StatuspageServiceName> | StatuspageServiceName;
}>;
export type StatuspageServiceByServiceNamesQuery = { __typename?: 'RootQuery', statuspageService: { __typename?: 'StatuspageServiceQuery', byServiceNames: Array<{ __typename?: 'StatuspageService', id: string, name: StatuspageServiceName, status: StatuspageServiceStatus, incidents: Array<{ __typename?: 'StatuspageIncident', id: string, status: StatuspageIncidentStatus, name: string, impact: StatuspageIncidentImpact, shortlink: string }> }> } };
export type SubmissionsByIdQueryVariables = Exact<{
submissionId: Scalars['ID'];
}>;
@@ -4531,10 +4984,11 @@ export type GetAllSubmissionsForAppQuery = { __typename?: 'RootQuery', app: { __
export type ViewAllUpdatesQueryVariables = Exact<{
appId: Scalars['String'];
limit: Scalars['Int'];
offset: Scalars['Int'];
}>;
export type ViewAllUpdatesQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, actor?: { __typename?: 'Robot', firstName?: string | null, id: string } | { __typename?: 'User', username: string, id: string } | null }> }> } } };
export type ViewAllUpdatesQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, actor?: { __typename?: 'Robot', firstName?: string | null, id: string } | { __typename?: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } }> } } };
export type ViewBranchUpdatesQueryVariables = Exact<{
appId: Scalars['String'];
@@ -4544,7 +4998,7 @@ export type ViewBranchUpdatesQueryVariables = Exact<{
}>;
export type ViewBranchUpdatesQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranchByName?: { __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename?: 'Robot', firstName?: string | null, id: string } | { __typename?: 'User', username: string, id: string } | null }> } | null } } };
export type ViewBranchUpdatesQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranchByName?: { __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename?: 'Robot', firstName?: string | null, id: string } | { __typename?: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } }> } | null } } };
export type CurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
@@ -4570,8 +5024,12 @@ export type AppFragment = { __typename?: 'App', id: string, fullName: string, sl
export type BuildFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
export type BuildWithSubmissionsFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, submissions: Array<{ __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logsUrl?: string | null, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null }>, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
export type EnvironmentSecretFragment = { __typename?: 'EnvironmentSecret', id: string, name: string, createdAt: any };
export type StatuspageServiceFragment = { __typename?: 'StatuspageService', id: string, name: StatuspageServiceName, status: StatuspageServiceStatus, incidents: Array<{ __typename?: 'StatuspageIncident', id: string, status: StatuspageIncidentStatus, name: string, impact: StatuspageIncidentImpact, shortlink: string }> };
export type SubmissionFragment = { __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logsUrl?: string | null, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null };
export type UpdateBranchFragment = { __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, createdAt: any, message?: string | null, runtimeVersion: string, group: string, platform: string, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null }> };
Loaded 100 of 193 files, more files were not shown because too many files have changed in this diff. Show more