mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68e552010e | ||
|
|
c5143fabad | ||
|
|
64efdad61d | ||
|
|
8bc07ca452 | ||
|
|
ea4d28e7d9 | ||
|
|
e1fce4a066 | ||
|
|
76d75e9c17 | ||
|
|
9278b3c17d | ||
|
|
ef9fbe84c0 | ||
|
|
b3a69e8863 | ||
|
|
5e4663ac44 | ||
|
|
ca18a555c1 | ||
|
|
c446bbb016 | ||
|
|
d5254434c6 | ||
|
|
e2277b9482 | ||
|
|
739502aef9 | ||
|
|
de42624992 | ||
|
|
0ccfbe5dd6 | ||
|
|
3ffad12681 | ||
|
|
565867b5a8 | ||
|
|
303cc82a06 | ||
|
|
4c2abc0693 | ||
|
|
b63167fbae | ||
|
|
8adb6434bf | ||
|
|
4c9e407fef | ||
|
|
ef566b3f9c | ||
|
|
895231ea98 | ||
|
|
c7000a754e | ||
|
|
9ac8404681 | ||
|
|
6c0b7d5951 | ||
|
|
ddfe60a7b0 | ||
|
|
7dedeb904b | ||
|
|
4c02e0102b | ||
|
|
e91516fcc7 | ||
|
|
d01f6c0d8e | ||
|
|
1ce69cd7c9 | ||
|
|
83c10793ac | ||
|
|
a344c11ba1 | ||
|
|
c7d5a9a819 | ||
|
|
2f43ed94ab | ||
|
|
4a4ebf3cd6 | ||
|
|
18b832dbda | ||
|
|
1200e7fecd | ||
|
|
3cc6b73d3a | ||
|
|
4831ff186a | ||
|
|
a2d60a4a76 | ||
|
|
9f604777b9 | ||
|
|
fce9bda9bb | ||
|
|
4cc6745a0d | ||
|
|
67f0b806f4 | ||
|
|
9280dfb3ad | ||
|
|
2befbb0eba | ||
|
|
41359b7194 | ||
|
|
c57861a67d | ||
|
|
d31ece8793 | ||
|
|
4f7148b756 | ||
|
|
bc8a781d86 | ||
|
|
153a70fb85 | ||
|
|
2f93492fc3 | ||
|
|
0b39ff1457 | ||
|
|
f6817037ae | ||
|
|
216f97ea8a | ||
|
|
11c194a168 | ||
|
|
3c74c111a7 | ||
|
|
b69b8b87a7 | ||
|
|
825baa9f68 | ||
|
|
9f5736cfaa | ||
|
|
ae24ffbe86 | ||
|
|
487316370b | ||
|
|
8bc3565270 | ||
|
|
07450f6bf7 | ||
|
|
71d48c3cf2 | ||
|
|
7343e81065 | ||
|
|
1f1310bef6 | ||
|
|
27c69ec8e1 | ||
|
|
b81b896bc6 | ||
|
|
ef79db2ca5 | ||
|
|
88565eec62 | ||
|
|
e8a520e55f | ||
|
|
9a33f7bd93 | ||
|
|
23c70e237b | ||
|
|
7fcf41db78 | ||
|
|
36f4a4bd38 | ||
|
|
87541db80d | ||
|
|
80112c682f | ||
|
|
eb29d3dadb | ||
|
|
558c367038 | ||
|
|
4b88d00379 | ||
|
|
aad3b5ccd5 | ||
|
|
8b50806093 | ||
|
|
45c9bda2e9 | ||
|
|
23237210d1 | ||
|
|
e34382e6a3 | ||
|
|
b8ad2ee29a | ||
|
|
6bd62bac64 | ||
|
|
8b959fc6d8 | ||
|
|
6e615e20d6 | ||
|
|
efbc5fc4bc | ||
|
|
79c77ae835 | ||
|
|
618b6b1caf | ||
|
|
864658df80 | ||
|
|
4510e86b64 | ||
|
|
4af4490abc | ||
|
|
37c4040203 | ||
|
|
55f9be3b5c | ||
|
|
152b25b93c | ||
|
|
ea0d0bb4dc | ||
|
|
37e360aead | ||
|
|
20211e6265 | ||
|
|
226c888cc9 | ||
|
|
0e9f8ec272 | ||
|
|
8eb5d8028d | ||
|
|
2fb296d116 | ||
|
|
010c1ee439 | ||
|
|
dddc4a0e95 | ||
|
|
679ccf19d9 | ||
|
|
5ba3cc6623 | ||
|
|
c1a4731762 | ||
|
|
767577e386 | ||
|
|
d2f620dbc6 | ||
|
|
0aa556c29e | ||
|
|
10cf003758 | ||
|
|
0e6254453a | ||
|
|
587a29892f | ||
|
|
db8245d954 | ||
|
|
0088306bca | ||
|
|
b6fc718713 | ||
|
|
18e55dafd4 | ||
|
|
8e013ab793 | ||
|
|
b88e79f0f3 | ||
|
|
35b505120d | ||
|
|
6807d3080b | ||
|
|
0d1cfb3286 | ||
|
|
0b9a5621fe | ||
|
|
9bd15af02a | ||
|
|
aab3bf1330 | ||
|
|
9e95218523 | ||
|
|
bb98e6de03 | ||
|
|
2604228233 | ||
|
|
a7035d3469 | ||
|
|
f6dce0e17c | ||
|
|
ccfa6f9fc3 | ||
|
|
0ee8e2f967 | ||
|
|
94dc4c6e93 | ||
|
|
4144e387d6 | ||
|
|
de4e05e1e0 | ||
|
|
36118b700f | ||
|
|
2fb947b47a | ||
|
|
0f1156e951 | ||
|
|
e9f8be92b7 | ||
|
|
6360ea9f8e | ||
|
|
ad2d8e80f0 | ||
|
|
bc0a5fdaa6 | ||
|
|
d6cfc887c3 | ||
|
|
ce41c436aa | ||
|
|
ec096eaaa2 | ||
|
|
9c32dd5308 | ||
|
|
69d7ee9547 | ||
|
|
e2e4cb67ca | ||
|
|
4a08ad4997 | ||
|
|
107a1c12bd | ||
|
|
8fe5268a73 | ||
|
|
faba1ad8e3 | ||
|
|
ea4885c710 | ||
|
|
4d85002d2b | ||
|
|
535b17f592 | ||
|
|
e556b45d4d | ||
|
|
48130dcbdc | ||
|
|
1419ca6325 | ||
|
|
8513dc0645 | ||
|
|
70e8f8370b | ||
|
|
135c90a8b8 | ||
|
|
fa588c2d24 | ||
|
|
b4ce7a91c2 | ||
|
|
8ca9d66c32 | ||
|
|
a86fabde59 | ||
|
|
40a896675f | ||
|
|
cf7848e3c5 | ||
|
|
3d4394a9bc | ||
|
|
05f4c08d09 | ||
|
|
a4107a86f9 | ||
|
|
a5c695dd88 | ||
|
|
7576d9195d | ||
|
|
838c1508be | ||
|
|
7941683435 | ||
|
|
91f2311a50 | ||
|
|
45d10d9b83 | ||
|
|
f5d5093922 | ||
|
|
fa4687b0f8 | ||
|
|
e78c475118 | ||
|
|
def91af01f | ||
|
|
5eb0fb75ac | ||
|
|
de8c69f766 | ||
|
|
5879ac424d | ||
|
|
e30f62ec12 | ||
|
|
dc4e36e8a8 | ||
|
|
12ded8c9a9 | ||
|
|
7378c6fd0e | ||
|
|
77a8b08834 | ||
|
|
79db9eb482 | ||
|
|
59f6d36114 | ||
|
|
c36c26f5a9 | ||
|
|
d6692f20b5 | ||
|
|
6e37f596df | ||
|
|
d326891ef8 | ||
|
|
46a34327d9 | ||
|
|
97242af30f | ||
|
|
ca0a8f79cd | ||
|
|
67eac59585 | ||
|
|
8cf1f0dc22 | ||
|
|
51702e4bda | ||
|
|
426a3d1ef3 | ||
|
|
e8d71a52ec | ||
|
|
ccd0cb19a4 | ||
|
|
9f85178d71 | ||
|
|
085ee52305 | ||
|
|
22a71275b2 | ||
|
|
5a730d70d6 | ||
|
|
41f58e5e52 | ||
|
|
34af57f585 | ||
|
|
4674cbebc5 | ||
|
|
69add6f523 | ||
|
|
b70e874135 | ||
|
|
03fa29dd5a | ||
|
|
9e467b900e | ||
|
|
6e1c7dd91e | ||
|
|
9812406c18 | ||
|
|
cf11ae93af | ||
|
|
14756ad39b | ||
|
|
1486b481f6 | ||
|
|
84d82e9a59 | ||
|
|
c7ce4a8670 | ||
|
|
fe0a3b4c41 | ||
|
|
0bce278bd4 | ||
|
|
fedfdb3364 | ||
|
|
028f0ab74b | ||
|
|
cd278155e2 | ||
|
|
1e419ab49d | ||
|
|
2f57fd199d | ||
|
|
5932775cd4 | ||
|
|
d68d6c38d6 | ||
|
|
d690e10caf | ||
|
|
99ea613b39 | ||
|
|
13715317bd | ||
|
|
18d1ed441b | ||
|
|
db067c4757 | ||
|
|
51388f6adb | ||
|
|
fcbf363538 | ||
|
|
b4d7795006 | ||
|
|
2364ed74d7 | ||
|
|
87fb74dd5a | ||
|
|
eef0c6ae6e | ||
|
|
5ee905a977 | ||
|
|
ca3e083995 | ||
|
|
b7ae91892c | ||
|
|
508ca745c1 | ||
|
|
95e8a38c85 | ||
|
|
441f3eaf4c | ||
|
|
9edb10ca55 | ||
|
|
7f094bc605 | ||
|
|
2463e958aa | ||
|
|
390e6fef9e | ||
|
|
6b416c3dfb | ||
|
|
60d2daf4c8 | ||
|
|
9528f25ff2 | ||
|
|
5da03dfca7 | ||
|
|
8b3b0376c3 | ||
|
|
5f3c4756d2 | ||
|
|
3b34a64893 | ||
|
|
243cf8d630 | ||
|
|
671ce30ed4 | ||
|
|
9021887072 | ||
|
|
6a80d4c8e9 | ||
|
|
471e26817a | ||
|
|
14bbd1ee0f | ||
|
|
22b06b444d | ||
|
|
cebd29e40d | ||
|
|
acf7e55b5b | ||
|
|
7c29aa3b38 | ||
|
|
dee5b4cf60 | ||
|
|
896e14b1e4 | ||
|
|
42c20d6ef5 | ||
|
|
0c94f80f68 | ||
|
|
95f308737e | ||
|
|
2a2a0392cd | ||
|
|
bcbdae8278 | ||
|
|
805aee67ee | ||
|
|
0477984d7a | ||
|
|
4e96196e07 | ||
|
|
25ee2abdaa | ||
|
|
75b71ae457 | ||
|
|
7548fc35fd | ||
|
|
5bc14b5b30 | ||
|
|
bee98ce25e | ||
|
|
0e6a5315ab | ||
|
|
91b51528ef | ||
|
|
5e3962afc2 | ||
|
|
f07c52d2fb | ||
|
|
50846a1a96 | ||
|
|
4cb726f374 | ||
|
|
da9d1b4984 | ||
|
|
64ad52dc45 | ||
|
|
6169e5ff5b | ||
|
|
ef33973f7f | ||
|
|
33a5254f2a | ||
|
|
0d737ecf6d | ||
|
|
951eea2396 | ||
|
|
75968bf449 | ||
|
|
32aa46ac69 | ||
|
|
339cae2532 | ||
|
|
e6ec3bfe6a | ||
|
|
012518c375 | ||
|
|
cc8c8584d9 | ||
|
|
d1910b3c42 | ||
|
|
4a1fa3d4fa | ||
|
|
61178c9d06 | ||
|
|
094c04c7f0 | ||
|
|
37cca4878d | ||
|
|
2b40e1ec05 | ||
|
|
6e95d3afcc | ||
|
|
6b9b9f0be2 | ||
|
|
f14b963b29 | ||
|
|
ddd2814e8f | ||
|
|
36f15a81a9 | ||
|
|
ece93dac20 | ||
|
|
0226dbba5a | ||
|
|
e773e77185 | ||
|
|
893cbb2ec5 | ||
|
|
1898de2609 | ||
|
|
fe03cc28c4 | ||
|
|
5309b7b6ce | ||
|
|
bc9b8442d7 | ||
|
|
a84762b00d | ||
|
|
2d117e1d7e | ||
|
|
986674942d | ||
|
|
13c224589b | ||
|
|
20b091fbfa | ||
|
|
168b6afdfa | ||
|
|
3c6320b929 | ||
|
|
4f1dc582bd | ||
|
|
daf2ef1e30 | ||
|
|
f703bd8b5a | ||
|
|
997cd7058a | ||
|
|
e896c2f6c5 | ||
|
|
3d723ca426 | ||
|
|
c17e1b3fa2 | ||
|
|
d70fc36485 | ||
|
|
47784ca6d3 | ||
|
|
3e5a3aca27 | ||
|
|
89b0227594 | ||
|
|
b98a30e7c6 | ||
|
|
5c7862a685 | ||
|
|
82833a9eb4 | ||
|
|
66171c3c4b | ||
|
|
179def91c7 | ||
|
|
9f63ad4527 | ||
|
|
878028271a | ||
|
|
d4e15bbcbf | ||
|
|
13bb056df1 | ||
|
|
14d56904d2 | ||
|
|
c2ffd05763 | ||
|
|
46e394fbef | ||
|
|
2613f0c2f9 | ||
|
|
e5fd7c01f4 | ||
|
|
73a0dd6e23 | ||
|
|
a9dc7f9eeb | ||
|
|
c3836f23d1 | ||
|
|
bfd73cbb19 | ||
|
|
6a1753edaa | ||
|
|
558a73086d | ||
|
|
15966fbd26 | ||
|
|
1e8b55383e | ||
|
|
6920ebb0ba | ||
|
|
55f35949e1 | ||
|
|
ccb998a221 | ||
|
|
30bc27b4c4 | ||
|
|
da3c95ccaf | ||
|
|
bbcbe1dceb | ||
|
|
f15beea454 | ||
|
|
16e384be76 | ||
|
|
e74e6d8fc5 | ||
|
|
b6f4603107 | ||
|
|
0e7d7f765a | ||
|
|
33b4d8d719 | ||
|
|
0d765748e3 | ||
|
|
93953f1424 | ||
|
|
e9b67db05c | ||
|
|
09bf912b9e | ||
|
|
e701bacdaa | ||
|
|
cc22542031 | ||
|
|
6c53262b5b | ||
|
|
7d7d4995d8 | ||
|
|
977546c8c9 | ||
|
|
53dcd4cb43 | ||
|
|
9e18e738eb | ||
|
|
252c7187d5 | ||
|
|
90852b9557 | ||
|
|
0df16236a2 | ||
|
|
ecaf5a4318 | ||
|
|
86e5e1a7b1 | ||
|
|
ab10094071 | ||
|
|
2d536d73fe | ||
|
|
80ea8ee50b | ||
|
|
b49c5f47d1 | ||
|
|
a9da78946b | ||
|
|
b6d9accea4 | ||
|
|
4611a65bce | ||
|
|
db29b3fc18 | ||
|
|
2534a46bd3 | ||
|
|
7def4b502e | ||
|
|
4ea26d7ca3 | ||
|
|
c4c8075b1f |
No files matched your search
+40
-13
@@ -1,15 +1,9 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['universe/node'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/__tests__/*'],
|
||||
},
|
||||
],
|
||||
globals: {
|
||||
jasmine: false,
|
||||
},
|
||||
plugins: ['async-protect', 'node'],
|
||||
rules: {
|
||||
'no-console': 'warn',
|
||||
'no-constant-condition': ['warn', { checkLoops: false }],
|
||||
'sort-imports': [
|
||||
'warn',
|
||||
@@ -17,11 +11,44 @@ module.exports = {
|
||||
ignoreDeclarationSort: true,
|
||||
},
|
||||
],
|
||||
'flowtype/no-types-missing-file-annotation': 'off',
|
||||
curly: 'warn',
|
||||
'import/no-cycle': 'error',
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{ devDependencies: ['**/__tests__/**/*', '**/__mocks__/**/*'] },
|
||||
],
|
||||
'import/no-relative-packages': 'error',
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: 'lodash',
|
||||
message: "Don't use lodash, it's heavy!",
|
||||
},
|
||||
{
|
||||
name: 'node-fetch',
|
||||
message: 'Use got instead, node-fetch is used only for the GraphQL client.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
'no-underscore-dangle': ['error', { allow: ['__typename'] }],
|
||||
'@typescript-eslint/explicit-function-return-type': [
|
||||
'warn',
|
||||
{
|
||||
allowExpressions: true,
|
||||
},
|
||||
],
|
||||
'async-protect/async-suffix': 'error',
|
||||
'node/no-sync': 'error',
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/__tests__/**/*.ts', '**/__mocks__/**/*.ts'],
|
||||
rules: {
|
||||
'async-protect/async-suffix': 'off',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 192 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 61 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 153 KiB |
@@ -0,0 +1 @@
|
||||
**/yarn.lock linguist-generated
|
||||
@@ -0,0 +1,54 @@
|
||||
name: "\U0001F41B Bug Report in EAS CLI"
|
||||
labels: 'bug'
|
||||
description: 'You want to report a reproducible bug or regression in EAS CLI.'
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "We're looking to keep questions on our forums and bug reports on the GitHub repo. For questions and help using EAS CLI, post in our [community forums](https://forums.expo.dev/c/help-expo-application-services) (log in with your Expo developer account)."
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Found a bug in the Expo SDK? Open a bug report [here](https://github.com/expo/expo/issues/new?assignees=&labels=needs+review&template=bug_report.yml) instead.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thanks for taking the time to file a bug report! Please fill out this form as completely as possible.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: If you leave out sections there is a high likelihood your issue will be closed. If you have a question, not a bug report, please post it on our [forums](https://forums.expo.dev/) instead.
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the issue in 1 or 2 sentences
|
||||
placeholder: Clearly describe what the expected behavior is vs. what is actually happening. This should be as short as possible, while still communicating all the necessary information. If your summary is just 'X library/method isn't working', then you need to continue debugging yourself and provide more information.
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Managed or bare?
|
||||
description: See the [Workflows](https://docs.expo.dev/introduction/managed-vs-bare/) page for more information.
|
||||
placeholder: Is your project managed or bare? Bare - Android and/or iOS native projects exist in your repo; Managed - you don't have native code (custom or 3rd party) in your project.
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Environment
|
||||
placeholder: Run `eas diagnostics` and paste the output here
|
||||
- type: textarea
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: Error output
|
||||
placeholder: If you're running into an error message, please run the same command with `EXPO_DEBUG=true <your command here>` and paste output here.
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Reproducible demo or steps to reproduce from a blank project
|
||||
description: 'This should include as little code as possible, do not simply link your entire project. Sharing a link to a [Snack](https://snack.expo.dev/) is a GREAT way to provide a reproducible demo :) If a reproducible demo, or a complete list of steps from blank project to bug, are not provided, it is very likely your issue will be closed. Read [here more guidance](https://stackoverflow.com/help/mcve).'
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Please make sure contributors can follow the steps your provided in order to reproduce the bug.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "**Realize that it is up to you to debug your code and be as certain as possible that the bug is with EAS CLI, not with your own app.** [Here's an excellent guide to debugging you can follow](https://github.com/expo/fyi/blob/master/manual-debugging.md#how-to-narrow-down-the-source-of-an-error-a-step-by-step-guide-to-good-old-fashioned-manual-debugging)."
|
||||
@@ -0,0 +1,14 @@
|
||||
name: "\U0001F64B Enhancement"
|
||||
labels: 'enhancement'
|
||||
description: 'Want us to add something to EAS CLI?'
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thanks for helping us make EAS even better! 😀 Unfortunately, this is not the right place for feature requests. Please find and upvote your feature (or create a new one) at https://expo.canny.io instead.
|
||||
- type: dropdown
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Do you understand that any feature requests opened in this repository will be closed?
|
||||
options:
|
||||
- 'Yes'
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
# Checklist
|
||||
|
||||
- [ ] I've added an entry to [CHANGELOG.md](https://github.com/expo/eas-cli/blob/main/CHANGELOG.md#main) if necessary.
|
||||
- [ ] I've tagged the changelog entry with `[EAS BUILD API]` if it's a part of a breaking change to EAS Build API (only possible when updating `@expo/eas-build-job` package).
|
||||
- [ ] I've added an entry to [CHANGELOG.md](https://github.com/expo/eas-cli/blob/main/CHANGELOG.md#main) if necessary. You can comment this pull request with `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]` and CHANGELOG.md will be updated automatically.
|
||||
|
||||
# Why
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
name: Changelog
|
||||
|
||||
jobs:
|
||||
pr-commented:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.issue.pull_request && github.event.issue.user.login == github.event.comment.user.login && startsWith(github.event.comment.body, '/changelog-entry ') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Checkout Pull Request
|
||||
run: hub pr checkout ${{ github.event.issue.number }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install dependencies
|
||||
working-directory: ./scripts
|
||||
run: yarn install --frozen-lockfile --check-files
|
||||
- name: Add changelog entry
|
||||
working-directory: ./scripts
|
||||
run: |
|
||||
PARAMS=${PR_COMMENT_BODY#/changelog-entry }
|
||||
if [[ ! "$PARAMS" =~ " " ]]; then
|
||||
echo "Invalid /changelog-entry command"
|
||||
exit 1
|
||||
fi
|
||||
CATEGORY=$( cut -d ' ' -f 1 <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
|
||||
MESSAGE=$( cut -d ' ' -f 2- <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
|
||||
yarn changelog-entry "$CATEGORY" "$MESSAGE"
|
||||
env:
|
||||
GITHUB_PR_AUTHOR: ${{ github.event.issue.user.login }}
|
||||
GITHUB_PR_NUMBER: ${{ github.event.issue.number }}
|
||||
PR_COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
- name: Commit and push
|
||||
run: |
|
||||
# use the last commit author name and email
|
||||
git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)"
|
||||
git config --global user.email "$(git --no-pager log --format=format:'%ae' -n 1)"
|
||||
|
||||
git add CHANGELOG.md
|
||||
git commit -m "update CHANGELOG.md"
|
||||
git push
|
||||
+101
-19
@@ -15,7 +15,7 @@ jobs:
|
||||
- id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
draft: true
|
||||
@@ -25,22 +25,30 @@ jobs:
|
||||
needs: release
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '16'
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: yarn build
|
||||
- name: Build tarballs
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli
|
||||
run: yarn oclif-dev pack --targets linux-x64,linux-arm
|
||||
run: |
|
||||
yarn pretarball-ci
|
||||
yarn oclif pack:tarballs --no-xz --targets linux-x64,linux-arm
|
||||
- id: x64
|
||||
run: echo "::set-output name=filename::$(ls eas-*-x64.tar.gz)"
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist
|
||||
- id: arm
|
||||
run: echo "::set-output name=filename::$(ls eas-*-arm.tar.gz)"
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist
|
||||
- name: Upload linux-x64 tarball
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
||||
asset_path: packages/eas-cli/dist/eas-linux-x64.tar.gz
|
||||
asset_name: eas-linux-x64.tar.gz
|
||||
asset_path: packages/eas-cli/dist/${{ steps.x64.outputs.filename }}
|
||||
asset_name: ${{ steps.x64.outputs.filename }}
|
||||
asset_content_type: application/gzip
|
||||
- name: Upload linux-arm tarball
|
||||
uses: actions/upload-release-asset@v1
|
||||
@@ -48,8 +56,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
||||
asset_path: packages/eas-cli/dist/eas-linux-arm.tar.gz
|
||||
asset_name: eas-linux-arm.tar.gz
|
||||
asset_path: packages/eas-cli/dist/${{ steps.arm.outputs.filename }}
|
||||
asset_name: ${{ steps.arm.outputs.filename }}
|
||||
asset_content_type: application/gzip
|
||||
build-mac:
|
||||
name: Build for macOS
|
||||
@@ -58,22 +66,27 @@ jobs:
|
||||
needs: release
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '16'
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: yarn build
|
||||
- name: Build tarballs
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli
|
||||
run: yarn oclif-dev pack --targets darwin-x64
|
||||
run: |
|
||||
yarn pretarball-ci
|
||||
yarn oclif pack:tarballs --no-xz --targets darwin-x64
|
||||
- id: x64
|
||||
run: echo "::set-output name=filename::$(ls eas-*-x64.tar.gz)"
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist
|
||||
- name: Upload darwin-x64 tarball
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
||||
asset_path: packages/eas-cli/dist/eas-darwin-x64.tar.gz
|
||||
asset_name: eas-darwin-x64.tar.gz
|
||||
asset_path: packages/eas-cli/dist/${{ steps.x64.outputs.filename }}
|
||||
asset_name: ${{ steps.x64.outputs.filename }}
|
||||
asset_content_type: application/gzip
|
||||
build-windows:
|
||||
name: Build for Windows
|
||||
@@ -81,20 +94,22 @@ jobs:
|
||||
needs: release
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '16'
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: sudo apt-get install nsis
|
||||
- run: yarn build
|
||||
- run: yarn oclif-dev pack:win
|
||||
- run: |
|
||||
yarn pretarball-ci
|
||||
yarn oclif pack:win
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli
|
||||
- id: x64
|
||||
run: echo "::set-output name=filename::$(ls eas-*-x64.exe)"
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist/win
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist/win32
|
||||
- id: x86
|
||||
run: echo "::set-output name=filename::$(ls eas-*-x86.exe)"
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist/win
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist/win32
|
||||
- name: Upload x64 installer
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
@@ -112,4 +127,71 @@ jobs:
|
||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
||||
asset_path: packages/eas-cli/dist/win/${{ steps.x86.outputs.filename }}
|
||||
asset_name: ${{ steps.x86.outputs.filename }}
|
||||
asset_content_type: application/gzip
|
||||
asset_content_type: application/gzip
|
||||
publish-to-npm:
|
||||
name: Publish to npm
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-linux, build-mac, build-windows]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
scope: 'expo'
|
||||
node-version: '16'
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile --check-files
|
||||
- name: Build
|
||||
run: yarn build
|
||||
- name: Publish packages to npm
|
||||
run: yarn lerna publish from-package --yes --no-verify-access
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
||||
release-changelog:
|
||||
name: Update changelog and publish release
|
||||
runs-on: ubuntu-latest
|
||||
needs: publish-to-npm
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Install dependencies
|
||||
working-directory: ./scripts
|
||||
run: yarn install --frozen-lockfile --check-files
|
||||
- name: Resolve version
|
||||
run: echo "EAS_CLI_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
- name: Update changelog
|
||||
working-directory: ./scripts
|
||||
run: yarn --silent release-changelog $EAS_CLI_VERSION > /tmp/current-changelog
|
||||
- name: Publish release
|
||||
run: hub release edit v$EAS_CLI_VERSION --draft=false -F /tmp/current-changelog
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Commit and push
|
||||
run: |
|
||||
git fetch
|
||||
git checkout main
|
||||
git config --global user.email "[email protected]"
|
||||
git config --global user.name "Expo CI"
|
||||
git add CHANGELOG.md
|
||||
git commit -m "update CHANGELOG.md"
|
||||
git push
|
||||
- name: Prepare changelog for Slack
|
||||
run: |
|
||||
tail -n +3 /tmp/current-changelog > /tmp/changelog.md
|
||||
npx slackmd /tmp/changelog.md
|
||||
echo 'SLACK_CHANGELOG<<EOF' >> $GITHUB_ENV
|
||||
cat /tmp/changelog.slack.md >> $GITHUB_ENV
|
||||
echo 'EOF' >> $GITHUB_ENV
|
||||
- name: Slack Notification
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
env:
|
||||
SLACK_CHANNEL: eas-cli
|
||||
SLACK_COLOR: ${{ job.status }}
|
||||
SLACK_ICON: https://github.com/expo-ci.png?size=48
|
||||
SLACK_TITLE: 'Released eas-cli@${{ env.EAS_CLI_VERSION }}'
|
||||
SLACK_MESSAGE: ${{ env.SLACK_CHANGELOG }}
|
||||
SLACK_USERNAME: 'Expo CI'
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
SLACK_FOOTER: ''
|
||||
@@ -7,12 +7,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: 16
|
||||
- uses: preactjs/compressed-size-action@v2
|
||||
with:
|
||||
build-script: 'build:tarballs:linux'
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
pattern: './packages/eas-cli/dist/*.tar.gz'
|
||||
compression: 'none'
|
||||
strip-hash: "v\\d+.\\d+.\\d+-\\w+-"
|
||||
clean-script: 'clean'
|
||||
@@ -10,15 +10,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: ['12', '14']
|
||||
node: ['12', '14', '16']
|
||||
include:
|
||||
- node: '14'
|
||||
- node: '16'
|
||||
coverage: true
|
||||
name: Test with Node ${{ matrix.node }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
|
||||
+15
-9
@@ -1,13 +1,19 @@
|
||||
# Build directories
|
||||
packages/*/build
|
||||
packages/eas-cli/dist
|
||||
packages/eas-cli/tmp
|
||||
|
||||
# Code editors
|
||||
.idea
|
||||
.history
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Node
|
||||
node_modules
|
||||
*-debug.log
|
||||
*-error.log
|
||||
|
||||
**/build/**
|
||||
!packages/eas-cli/src/commands/build/**
|
||||
!packages/eas-cli/src/build/**
|
||||
|
||||
dist
|
||||
/package-lock.json
|
||||
tmp
|
||||
node_modules
|
||||
# Tests
|
||||
coverage
|
||||
.history
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"jsxBracketSameLine": true,
|
||||
"bracketSameLine": true,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
+591
-2
@@ -10,10 +10,599 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix CLI UI getting blocked on credentials migration, leading to partially migrated state. ([#477](https://github.com/expo/eas-cli/pull/477) by [@brentvatne](https://github.com/brentvatne))
|
||||
- Allow select eas commands to be run outside of a project. ([#851](https://github.com/expo/eas-cli/pull/851) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Replace all oclif dependencies with `@oclif/core`. ([#858](https://github.com/expo/eas-cli/pull/858) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.41.1](https://github.com/expo/eas-cli/releases/tag/v0.41.1) - 2021-12-16
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Adds commands for EAS Update, which is now in preview for subscribers. EAS Update makes fixing small bugs and pushing quick fixes a snap in between app store submissions. It accomplishes this by allowing an end-user's app to swap out the non-native parts of their app (for example, JS, styling, and image changes) with a new update that contains bug fixes and other updates.
|
||||
- Adds `eas update`, which can bundle and publish updates on a branch.
|
||||
- Adds `eas branch`, which manages branches. Branches contain a list of updates and are linked to channels.
|
||||
- Adds `eas channel`, which manages channels. Channels are specified inside builds and are linked to branches, allowing us to link specific updates with specific builds.
|
||||
- Read more in our [feature preview docs](https://docs.expo.dev/eas-update/introduction/).
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix `eas submit` displaying a prompt in non-interactive mode when some ASC API credentials are missing in `eas.json`. ([#841](https://github.com/expo/eas-cli/pull/841) by [@barthap](https://github.com/barthap))
|
||||
|
||||
## [0.41.0](https://github.com/expo/eas-cli/releases/tag/v0.41.0) - 2021-12-13
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Ask user to select profile if `release` does not exist. ([#829](https://github.com/expo/eas-cli/pull/829) by [@dsokal](https://github.com/dsokal))
|
||||
- Add `build:inspect` command. ([#834](https://github.com/expo/eas-cli/pull/834) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix validating tool versions in eas.json. ([#832](https://github.com/expo/eas-cli/pull/832) by [@dsokal](https://github.com/dsokal))
|
||||
- Fix submission URL. ([#845](https://github.com/expo/eas-cli/pull/845) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Warn when building managed project with SDK < 41. ([#833](https://github.com/expo/eas-cli/pull/833) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.40.0](https://github.com/expo/eas-cli/releases/tag/v0.40.0) - 2021-12-08
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- [eas-cli] change eas update:publish to eas update. ([#830](https://github.com/expo/eas-cli/pull/830) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Pass build profile environment to credentials command. ([#828](https://github.com/expo/eas-cli/pull/828) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.39.0](https://github.com/expo/eas-cli/releases/tag/v0.39.0) - 2021-12-06
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Set runtime version in `eas update:configure`. ([#811](https://github.com/expo/eas-cli/pull/811) by [@jkhales](https://github.com/jkhales))
|
||||
- Print project workflow in `eas diagnostics`. ([#822](https://github.com/expo/eas-cli/pull/822) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix submit for multi-target projects where ascAppId is not specified. ([#809](https://github.com/expo/eas-cli/pull/809) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Fix building iOS projects with Apple Watch companion app. ([#812](https://github.com/expo/eas-cli/pull/812), [#817](https://github.com/expo/eas-cli/pull/817), [#821](https://github.com/expo/eas-cli/pull/821) by [@jkhales](https://github.com/jkhales) + [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Improve error message for missing ascAppId. ([#813](https://github.com/expo/eas-cli/pull/813) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Add `react-native` version to metadata. ([#823](https://github.com/expo/eas-cli/pull/823) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
## [0.38.3](https://github.com/expo/eas-cli/releases/tag/v0.38.3) - 2021-11-29
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Print warning about outdated version to stderr. ([#805](https://github.com/expo/eas-cli/pull/805) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Fix Apple sign in capabilities. ([#806](https://github.com/expo/eas-cli/pull/806) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.38.2](https://github.com/expo/eas-cli/releases/tag/v0.38.2) - 2021-11-26
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add support for extending submit profiles. ([#794](https://github.com/expo/eas-cli/pull/794) by [@dsokal](https://github.com/dsokal))
|
||||
- Add command: `eas update:configure`. ([#800](https://github.com/expo/eas-cli/pull/800) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix reading app version and app build version of iOS projects. ([#798](https://github.com/expo/eas-cli/pull/798) by [@dsokal](https://github.com/dsokal))
|
||||
- Fix push notifications entitlements. ([#801](https://github.com/expo/eas-cli/pull/801) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.38.1](https://github.com/expo/eas-cli/releases/tag/v0.38.1) - 2021-11-25
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix building iOS projects with whitespace in the project name. ([#792](https://github.com/expo/eas-cli/pull/792) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.38.0](https://github.com/expo/eas-cli/releases/tag/v0.38.0) - 2021-11-24
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Improve unknown capability syncing error message. ([#775](https://github.com/expo/eas-cli/pulls/775) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Add submit webhooks. ([#777](https://github.com/expo/eas-cli/pull/777) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Compute runtime version policies. ([#783](https://github.com/expo/eas-cli/pull/783), [#785](https://github.com/expo/eas-cli/pull/785) by [@jkhales](https://github.com/jkhales))
|
||||
- Fix local builds with npm < 7. ([#787](https://github.com/expo/eas-cli/pull/787) by [@dsokal](https://github.com/dsokal))
|
||||
- Override `applicationId`/`bundleIdentifier` when auto-submitting after build. ([#780](https://github.com/expo/eas-cli/pull/780) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade `typescript` to 4.5.2. Upgrade oclif dependencies. ([#781](https://github.com/expo/eas-cli/pull/781) by [@dsokal](https://github.com/dsokal))
|
||||
- Make missing `git` command error message more descriptive. ([#784](https://github.com/expo/eas-cli/pull/784) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.37.0](https://github.com/expo/eas-cli/releases/tag/v0.37.0) - 2021-11-18
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Require explicitly defined applicationId/bundleIdentifier for EAS Submit in case of bare projects consisting of multiple product flavors on Android or multiple schemes/targets on iOS. ([#765](https://github.com/expo/eas-cli/pull/765) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Auto create channel on publish. ([#766](https://github.com/expo/eas-cli/pull/766) by [@jkhales](https://github.com/jkhales))
|
||||
- Interactively configure Git `user.name` and `user.email`. ([#772](https://github.com/expo/eas-cli/pull/772) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Validate release channel in eas.json. ([#764](https://github.com/expo/eas-cli/pull/764) by [@dsokal](https://github.com/dsokal))
|
||||
- Make the missing profile error message more descriptive. ([#761](https://github.com/expo/eas-cli/pull/761) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Better error message when missing eas.json. ([#770](https://github.com/expo/eas-cli/pull/770) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.36.1](https://github.com/expo/eas-cli/releases/tag/v0.36.1) - 2021-11-15
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix `dateformat` import issue when building with `--local` flag. ([a520d](https://github.com/expo/eas-cli/commit/a520d1750736d61d6489da6487879f35ed3deb23) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.36.0](https://github.com/expo/eas-cli/releases/tag/v0.36.0) - 2021-11-15
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Do not require manual `eas-cli-local-build-plugin` installation. An existing global installation (either with `yarn global add` or `npm install -g`) will not be used anymore. ([#753](https://github.com/expo/eas-cli/pull/753) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Use Keystore Service when `keytool` is not installed. ([#754](https://github.com/expo/eas-cli/pull/754) by [@dsokal](https://github.com/dsokal))
|
||||
- Add applicationId/bundleIdentifier fields to the submit profile. ([#765](https://github.com/expo/eas-cli/pull/765) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix auto-submitting with `--auto-submit-with-profile`. ([#748](https://github.com/expo/eas-cli/pull/748) by [@dsokal](https://github.com/dsokal))
|
||||
- Pass env from build profile when resolving entitlements. ([#751](https://github.com/expo/eas-cli/pull/751) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Only prompt for Apple Id username if authenticating with an App Specific Password. ([#745](https://github.com/expo/eas-cli/pull/745) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Use same URL paths for local development of EAS CLI as production. ([#750](https://github.com/expo/eas-cli/pull/750) by [@ide](https://github.com/ide))
|
||||
- Grammar: Api -> API. ([#755](https://github.com/expo/eas-cli/pull/755) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add analytics for EAS Submit. ([#752](https://github.com/expo/eas-cli/pull/752) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add an 'attempt' event to build and submit analytics. ([#757](https://github.com/expo/eas-cli/pull/757) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.35.0](https://github.com/expo/eas-cli/releases/tag/v0.35.0) - 2021-11-08
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add ASC API Key generation workflow. ([#718](https://github.com/expo/eas-cli/pull/718) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add support for removal of ASC API Keys. ([#721](https://github.com/expo/eas-cli/pull/721) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Allow users to assign an ASC API Key to their project. ([#719](https://github.com/expo/eas-cli/pull/719) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add setup support for ASC API Keys. ([#733](https://github.com/expo/eas-cli/pull/733) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Show initiating user display name when selecting a build to submit. ([#730](https://github.com/expo/eas-cli/pull/730) by [@barthap](https://github.com/barthap))
|
||||
- Handle Apple servers maintenance error in `eas submit`. ([#738](https://github.com/expo/eas-cli/pull/738) by [@barthap](https://github.com/barthap))
|
||||
- Integrate ASC API Key with submissions workflow. ([#737](https://github.com/expo/eas-cli/pull/737) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Change EAS API server domain. ([#744](https://github.com/expo/eas-cli/pull/744) by [@ide](https://github.com/ide))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Clean up credentials prompt method. ([#728](https://github.com/expo/eas-cli/pull/728) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Bump `@expo/apple-utils` to 0.0.0-alpha.26. ([#723](https://github.com/expo/eas-cli/pull/723) by [@brentvatne](https://github.com/brentvatne))
|
||||
- Grammar: replace setup with set up. ([#735](https://github.com/expo/eas-cli/pull/735) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Improve VCS workflow migration experience. ([#732](https://github.com/expo/eas-cli/pull/732) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
## [0.34.1](https://github.com/expo/eas-cli/releases/tag/v0.34.1) - 2021-11-02
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Don't show commit prompt in no-commit workflow after installing `expo-dev-client`. ([#722](https://github.com/expo/eas-cli/pull/722) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add App Store Connect API Key graphql type and print support. ([#717](https://github.com/expo/eas-cli/pull/717) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.34.0](https://github.com/expo/eas-cli/releases/tag/v0.34.0) - 2021-11-01
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Use new build job format. ([#701](https://github.com/expo/eas-cli/pull/701) and [#711](https://github.com/expo/eas-cli/pull/711) by [@dsokal](https://github.com/dsokal))
|
||||
- Don't print logs to `stderr`. ([#708](https://github.com/expo/eas-cli/pull/708) by [@dsokal](https://github.com/dsokal))
|
||||
- Remove automatic migration for legacy `eas.json` format introduced in [v0.22.2](https://github.com/expo/eas-cli/releases/tag/v0.22.2). ([#695](https://github.com/expo/eas-cli/pull/695) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Implement no-commit build workflow. Add required `cli` field in root of `eas.json`. ([#695](https://github.com/expo/eas-cli/pull/695) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Better error message when eas.json is invalid. ([#707](https://github.com/expo/eas-cli/pull/707) by [@dsokal](https://github.com/dsokal))
|
||||
- Fix credentials workflow for new users: add additionalTypenames for credentials. ([#703](https://github.com/expo/eas-cli/pull/703) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add additionalTypenames for other queries. ([#704](https://github.com/expo/eas-cli/pull/704) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Fix errors after pulling down most recent graphql schema changes. ([#713](https://github.com/expo/eas-cli/pull/713) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Refactor credentials manager. ([#712](https://github.com/expo/eas-cli/pull/712) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Remove unneeded parameters. ([#710](https://github.com/expo/eas-cli/pull/710) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Enforce additionalTypenames for graphql queries. ([#709](https://github.com/expo/eas-cli/pull/709) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.33.1](https://github.com/expo/eas-cli/releases/tag/v0.33.1) - 2021-10-22
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Compute runtime version using config-plugins. ([#697](https://github.com/expo/eas-cli/pull/697) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- [eas-cli] Skip validating updates scripts integration, which no longer exist in SDK 43+ ([#706](https://github.com/expo/eas-cli/pull/706) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.33.0](https://github.com/expo/eas-cli/releases/tag/v0.33.0) - 2021-10-20
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Make "production" the default profile for building and submitting. ([#677](https://github.com/expo/eas-cli/pull/677)) by [@jonsamp](https://github.com/jonsamp)
|
||||
- Previously, the default profile when running `eas build` or `eas submit` was "release". We're changing it to a more recognizable name that is consistent with our docs, which is now "production". You can always specify a profile manually with the `--profile` flag. For this major version, if you do not have a profile named "production", and still have a profile named "release", we will fall back to the "release" profile as the default, however you'll see a warning as we're going to remove that behavior in the next major release of EAS CLI.
|
||||
- To upgrade, update **eas.json** to have a "production" profile in both the `build` and `submit` objects. If you already have a project set up, this will replace the existing "release" profile. After the change, eas.json should have the following profiles:
|
||||
```json
|
||||
{
|
||||
"build": {
|
||||
"production": { ... }
|
||||
},
|
||||
"submit": {
|
||||
"production": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Skip the second prompt for Apple ID if the user is already signed in with Apple. ([#691](https://github.com/expo/eas-cli/pull/691) by [@dsokal](https://github.com/dsokal))
|
||||
- Unify reading `app.json`. ([#692](https://github.com/expo/eas-cli/pull/692) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.32.0](https://github.com/expo/eas-cli/releases/tag/v0.32.0) - 2021-10-15
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add App Store Connect API Key support to iOS submissions. ([#678](https://github.com/expo/eas-cli/pull/678) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Create/list/revoke App Store Connect Api keys via Apple apis. ([#687](https://github.com/expo/eas-cli/pull/687) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add ability to select a build from a list in `eas submit` interactive mode. ([#688](https://github.com/expo/eas-cli/pull/688) by [@barthap](https://github.com/barthap))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix printing App Store Connect URL after submission. ([#683](https://github.com/expo/eas-cli/pull/683) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add App Store Connect API Key fields to `eas.json`. ([#684](https://github.com/expo/eas-cli/pull/684) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Enable no-underscore-dangle eslint rule. ([#686](https://github.com/expo/eas-cli/pull/686) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.31.1](https://github.com/expo/eas-cli/releases/tag/v0.31.1) - 2021-10-08
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Google Service Account Keys: Fix non-interactive bug in credentials service workflow ([#682](https://github.com/expo/eas-cli/pull/682) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.31.0](https://github.com/expo/eas-cli/releases/tag/v0.31.0) - 2021-10-08
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Don't resolve the iOS builder image on the client side. EAS Build will use the appropriate iOS image for a given Expo SDK version unless the `image` is defined. This only applies to managed projects. ([#675](https://github.com/expo/eas-cli/pull/675) by [@wkozyra95](https://github.com/wkozyra95) + [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Integrate credentials service with Android submissions. ([#664](https://github.com/expo/eas-cli/pull/664) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add option to review ad-hoc devices when reusing provisioning profile. ([#673](https://github.com/expo/eas-cli/pull/673) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.30.1](https://github.com/expo/eas-cli/releases/tag/v0.30.1) - 2021-10-06
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix `--json` flag when running EAS CLI on GitHub actions. ([#669](https://github.com/expo/eas-cli/pull/669) by [@dsokal](https://github.com/dsokal))
|
||||
- Fix `"ios: mods.ios.infoPlist: Failed to find Info.plist linked to Xcode project."` warning when running `eas build` in a managed project.([#670](https://github.com/expo/eas-cli/pull/670) by [@brentvatne](https://github.com/brentvatne))
|
||||
- Fix building monorepo projects on Windows. ([#671](https://github.com/expo/eas-cli/pull/671) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.30.0](https://github.com/expo/eas-cli/releases/tag/v0.30.0) - 2021-10-05
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Enable auto-incrementing on Android. ([#645](https://github.com/expo/eas-cli/pull/645) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Ports detection for Google Service Account Keys into the credentials service. ([#660](https://github.com/expo/eas-cli/pull/660) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Improve iOS credentials printing. ([#657](https://github.com/expo/eas-cli/pull/657) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Automate `eas-cli` releases. ([#668](https://github.com/expo/eas-cli/pull/668) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.29.1](https://github.com/expo/eas-cli/releases/tag/v0.29.1) - 2021-09-29
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- More upload support for Google Service Account Keys. ([#649](https://github.com/expo/eas-cli/pull/649) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Allow the user to assign an existing Google Service Account Key to their project. ([#650](https://github.com/expo/eas-cli/pull/650) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Allow the user to remove a Google Service Account Key from their account. ([#658](https://github.com/expo/eas-cli/pull/658) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Adds setup support for Google Service Account Keys. ([#659](https://github.com/expo/eas-cli/pull/659) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix double prompt for Apple credentials when running `eas build --auto-submit`. ([#654](https://github.com/expo/eas-cli/pull/654) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Always use async `fs` functions. ([#652](https://github.com/expo/eas-cli/pull/652) by [@dsokal](https://github.com/dsokal))
|
||||
- Improve Android credentials printing. ([#656](https://github.com/expo/eas-cli/pull/656) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.29.0](https://github.com/expo/eas-cli/releases/tag/v0.29.0) - 2021-09-28
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Added upload support for Google Service Account Keys. ([#642](https://github.com/expo/eas-cli/pull/642) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add Xcode 13 image. ([#651](https://github.com/expo/eas-cli/pull/651) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Credentials manager: stop prompting for an Android build profile every iteration. ([#641](https://github.com/expo/eas-cli/pull/641) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Don't display prompt in non-interactive mode when the metro config seems invalid. ([#644](https://github.com/expo/eas-cli/pull/644) by [@dsokal](https://github.com/dsokal))
|
||||
- Read versions from correct `Info.plist`. ([#635](https://github.com/expo/eas-cli/pull/635) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Move away from weird convention of exports. ([#647](https://github.com/expo/eas-cli/pull/647) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.28.2](https://github.com/expo/eas-cli/releases/tag/v0.28.2) - 2021-09-23
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Use `log-symbols` to unify green ticks style. ([#639](https://github.com/expo/eas-cli/pull/639) by [@dsokal](https://github.com/dsokal))
|
||||
- Fix `eas build --auto-submit` in fresh projects. ([#640](https://github.com/expo/eas-cli/pull/640) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.28.1](https://github.com/expo/eas-cli/releases/tag/v0.28.1) - 2021-09-22
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Prevent ora spinners breaking when debug logs are enabled. ([#575](https://github.com/expo/eas-cli/pull/575) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Do not fail `eas build` when transient network errors occur. ([#638](https://github.com/expo/eas-cli/pull/638) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add GraphQL support for GoogleServiceAccountKey. ([#632](https://github.com/expo/eas-cli/pull/632) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Remove GraphQL support for the App Specific Password. ([#633](https://github.com/expo/eas-cli/pull/633) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.28.0](https://github.com/expo/eas-cli/releases/tag/v0.28.0) - 2021-09-20
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Use envs from build profile to resolve app config when auto-submitting. ([#614](https://github.com/expo/eas-cli/pull/614) by [@dsokal](https://github.com/dsokal))
|
||||
- Support multi flavor Android projects. ([#595](https://github.com/expo/eas-cli/pull/595) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Improve experience when using the build details page as a build artifact URL in `eas submit`. ([#620](https://github.com/expo/eas-cli/pull/620) by [@dsokal](https://github.com/dsokal))
|
||||
- Better error message when eas.json is invalid JSON. ([#618](https://github.com/expo/eas-cli/pull/618) by [@dsokal](https://github.com/dsokal))
|
||||
- Add warning about the legacy build service IDs in `eas submit`. ([#624](https://github.com/expo/eas-cli/pull/624) by [@dsokal](https://github.com/dsokal))
|
||||
- Validate that project includes `expo-dev-client` when building with `developmentClient` flag. ([#627](https://github.com/expo/eas-cli/pull/627) by [@dsokal](https://github.com/dsokal))
|
||||
- Better experience when not logged in in non-interactive mode. ([#628](https://github.com/expo/eas-cli/pull/628) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Reduce `eas-cli` size by almost getting rid of `lodash` from dependencies. ([#605](https://github.com/expo/eas-cli/pull/605) by [@dsokal](https://github.com/dsokal))
|
||||
- Enforce explicit return type in all functions. ([#619](https://github.com/expo/eas-cli/pull/619) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Enforce `Async` suffix for async functions. ([#623](https://github.com/expo/eas-cli/pull/623) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.27.1](https://github.com/expo/eas-cli/releases/tag/v0.27.1) - 2021-09-10
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fixed force-creation, app secret creation, and secret deletion mutations. ([#606](https://github.com/expo/eas-cli/pull/606) by [@fiberjw](https://github.com/fiberjw))
|
||||
|
||||
## [0.27.0](https://github.com/expo/eas-cli/releases/tag/v0.27.0) - 2021-09-10
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Implement support for `--platform all` in `eas submit`. ([#598](https://github.com/expo/eas-cli/pull/598) by [@dsokal](https://github.com/dsokal))
|
||||
- Implement support for `--non-interactive` in `eas submit`. ([#600](https://github.com/expo/eas-cli/pull/600) by [@dsokal](https://github.com/dsokal))
|
||||
- Add auto-submit feature. Run `eas build --auto-submit` to submit automatically on build complete. ([#603](https://github.com/expo/eas-cli/pull/603) by [@dsokal](https://github.com/dsokal))
|
||||
- Allow using env var for `android.serviceAccountKeyPath` in submit profiles. ([#604](https://github.com/expo/eas-cli/pull/604) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix detecting `googleServicesFile` on Windows. ([#602](https://github.com/expo/eas-cli/pull/602) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
## [0.26.0](https://github.com/expo/eas-cli/releases/tag/v0.26.0) - 2021-09-08
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Require `eas submit` to be configured with eas.json submit profiles (see [https://docs.expo.dev/submit/eas-json/](https://docs.expo.dev/submit/eas-json/) for details). Drop support for CLI params. ([#590](https://github.com/expo/eas-cli/pull/590) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `eas diagnostics` command. ([#594](https://github.com/expo/eas-cli/pull/594) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix `eas build` from throwing an exception when detecting the git `core.ignorecase` setting. ([#592](https://github.com/expo/eas-cli/pull/592) by [@mwillbanks](https://github.com/mwillbanks))
|
||||
- Support app names that consist only of unicode characters. ([#596](https://github.com/expo/eas-cli/pull/596) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
## [0.25.0](https://github.com/expo/eas-cli/releases/tag/v0.25.0) - 2021-09-01
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Drop support for node < 12. ([#581](https://github.com/expo/eas-cli/pull/581) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Support `--no-wait` in `eas submit`. ([#578](https://github.com/expo/eas-cli/pull/578) by [@dsokal](https://github.com/dsokal))
|
||||
- Detect changes when `core.ignorecase` is set to true. ([#570](https://github.com/expo/eas-cli/pull/570) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix wrong warning for `metro.config.js` check on Windows. ([#587](https://github.com/expo/eas-cli/pull/588) by [@louisgv](https://github.com/louisgv))
|
||||
- Fix detecting `googleServicesFile` with `EAS_NO_VCS=1`. ([#583](https://github.com/expo/eas-cli/pull/583) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Replace `@hapi/joi` with `joi`. Upgrade typescript to 4.4.2. Upgrade dependencies. ([#582](https://github.com/expo/eas-cli/pull/582) by [@dsokal](https://github.com/dsokal))
|
||||
- Change typescript target from `ES2017` to `ES2019`. ([#584](https://github.com/expo/eas-cli/pull/584) by [@dsokal](https://github.com/dsokal))
|
||||
- Use `ts-jest` instead of `babel-jest`. ([#585](https://github.com/expo/eas-cli/pull/585) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.24.1](https://github.com/expo/eas-cli/releases/tag/v0.24.1) - 2021-08-25
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix wrong `EasJsonReader` import causing `eas submit` to fail. ([951ee](https://github.com/expo/eas-cli/commit/951eea2396591bd612aa7cb3038da35e45c33bb8) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.24.0](https://github.com/expo/eas-cli/releases/tag/v0.24.0) - 2021-08-25
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Allow publishing of multiple update groups. ([#566](https://github.com/expo/eas-cli/pull/566) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Retry status code 500-600 server errors from Apple servers with cookies auth. ([#574](https://github.com/expo/eas-cli/pull/574) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Throw internal server errors when Apple servers fail to authenticate. ([#574](https://github.com/expo/eas-cli/pull/574) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Add --auto flag for eas branch:publish. ([#549](https://github.com/expo/eas-cli/pull/549) by [@jkhales](https://github.com/jkhales))
|
||||
- Add submissions link in output of `eas submit`. ([#553](https://github.com/expo/eas-cli/pull/553) by [@ajsmth](https://github.com/ajsmth))
|
||||
- Add submit profiles. ([#555](https://github.com/expo/eas-cli/pull/555) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Add `changesNotSentForReview` option to android submissions. ([#560](https://github.com/expo/eas-cli/pull/560) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Support `--json` flag in build commands. ([#567](https://github.com/expo/eas-cli/pull/567) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Add link to https://expo.fyi/eas-build-archive in `eas build` output to make it easier to understand what is going on in the archive/upload phase of the build. ([#562](https://github.com/expo/eas-cli/pull/562) by [@brentvatne](https://github.com/brentvatne))
|
||||
- Update QR code URL for Android internal distribution build and generate smaller QR code. ([#573](https://github.com/expo/eas-cli/pull/573) by [@axeldelafosse](https://github.com/axeldelafosse))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix `--sku` flag being ignored when running `eas submit -p ios`. ([#559](https://github.com/expo/eas-cli/pull/559) by [@barthap](https://github.com/barthap))
|
||||
- Set correct distribution for simulator builds. ([#556](https://github.com/expo/eas-cli/pull/556) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add warnings around common issues (`NODE_ENV=production`, git ignored `googleServicesFile`). ([#569](https://github.com/expo/eas-cli/pull/569) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
## [0.23.0](https://github.com/expo/eas-cli/releases/tag/v0.23.0) - 2021-08-05
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Stop including deprecated storageBucket field when publishing. ([#547](https://github.com/expo/eas-cli/pull/547) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Pass through metadata context to app version resolver. ([#550](https://github.com/expo/eas-cli/pull/550) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.22.4](https://github.com/expo/eas-cli/releases/tag/v0.22.4) - 2021-08-02
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix build profile schema validation when `simulator` key is present for iOS. ([#546](https://github.com/expo/eas-cli/pull/546) by [@sallar](https://github.com/sallar))
|
||||
|
||||
## [0.22.2](https://github.com/expo/eas-cli/releases/tag/v0.22.2) - 2021-08-02
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Introduce new format of eas.json (old eas.json will be migrated automatically). ([#537](https://github.com/expo/eas-cli/pull/537) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Validate metro config for managed workflow projects. ([#534](https://github.com/expo/eas-cli/pull/534) by [@dsokal](https://github.com/dsokal))
|
||||
- Add more filter params to `build:list`. ([#540](https://github.com/expo/eas-cli/pull/540) by [@dsokal](https://github.com/dsokal))
|
||||
- Evaluate interpolated iOS version strings for metadata. ([#541](https://github.com/expo/eas-cli/pull/541) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Validate metadata client side to print better errors. ([#542](https://github.com/expo/eas-cli/pull/542) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Fix `new` build status when building both paltforms. ([#543](https://github.com/expo/eas-cli/pull/543) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Fix link to build details page where previously we used `project.name` instead of `project.slug` - leave out project segment entirely and depend on redirect. ([#554](https://github.com/expo/eas-cli/pull/554) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.22.1](https://github.com/expo/eas-cli/releases/tag/v0.22.1) - 2021-07-28
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Include the file extension for update's assets in the manifest fragment. ([#532](https://github.com/expo/eas-cli/pull/535) by [@jkhales](https://github.com/jkhales))
|
||||
- Use env variables from `eas.json` when evaluating `app.config.js`. ([#532](https://github.com/expo/eas-cli/pull/532) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix workflow detection when xcodeproj is an empty directory. ([#531](https://github.com/expo/eas-cli/pull/531) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Fix Android Keystore upload. ([#538](https://github.com/expo/eas-cli/pull/538) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade typescript to 4.3.5. ([#533](https://github.com/expo/eas-cli/pull/533) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.22.0](https://github.com/expo/eas-cli/releases/tag/v0.22.0) - 2021-07-23
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Added support for automatically creating and linking iOS capability identifiers (Apple Pay, iCloud Containers, App Groups). ([#524](https://github.com/expo/eas-cli/pull/524) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Add `secret:create --force` command to overwrite existing secrets. ([#513](https://github.com/expo/eas-cli/pull/513) by [@bycedric](https://github.com/bycedric))
|
||||
- Fall back to APK when building for internal distribution. ([#527](https://github.com/expo/eas-cli/pull/527) by [@dsokal](https://github.com/dsokal))
|
||||
- Add `iosEnterpriseProvisioning` to build metadata. ([#522](https://github.com/expo/eas-cli/pull/522) by [@dsokal](https://github.com/dsokal))
|
||||
- Autodetect Google Services JSON key path in `eas submit -p android`. ([#520](https://github.com/expo/eas-cli/pull/297) by [@barthap](https://github.com/barthap))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix iOS capability syncing on build. ([#521](https://github.com/expo/eas-cli/pull/521) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Fix unhandled error when amplitude domains are blocked. ([#512](https://github.com/expo/eas-cli/pull/512) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Use default value for `appBuildVersion` in build metadata when building an Android managed project. ([#526](https://github.com/expo/eas-cli/pull/526) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.21.0](https://github.com/expo/eas-cli/releases/tag/v0.21.0) - 2021-07-12
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `project:*` commands. ([#500](https://github.com/expo/eas-cli/pull/500) by [@jkhales](https://github.com/jkhales))
|
||||
- Added support for iOS 15 capabilities: Communication Notifications, Time Sensitive Notifications, Group Activities, and Family Controls. ([#499](https://github.com/expo/eas-cli/pull/499) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Show more build metadata in `build:view` and `build:list`. ([#504](https://github.com/expo/eas-cli/pull/504) and [#508](https://github.com/expo/eas-cli/pull/508) by [@dsokal](https://github.com/dsokal))
|
||||
- Add runtime version to build metadata. ([#493](https://github.com/expo/eas-cli/pull/493) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.20.0](https://github.com/expo/eas-cli/releases/tag/v0.20.0) - 2021-07-09
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Unifify generic and managed workflow, deprecate `workflow` field. ([#497](https://github.com/expo/eas-cli/pull/497) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix runtime version checks. ([#495](https://github.com/expo/eas-cli/pull/495) by [@dsokal](https://github.com/dsokal))
|
||||
- Resolve `--android-package` correctly in `eas submit` command. ([#494](https://github.com/expo/eas-cli/pull/494) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
## [0.19.1](https://github.com/expo/eas-cli/releases/tag/v0.19.1) - 2021-07-02
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Improve project workflow detection (fixes the case where the `android` and/or `ios` directories are git-ignored). ([#490](https://github.com/expo/eas-cli/pull/490) by [@dsokal](https://github.com/dsokal))
|
||||
- Improve credentials workflow with project creation and current working directories ([#491](https://github.com/expo/eas-cli/pull/491) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Better debugging experience ([#492](https://github.com/expo/eas-cli/pull/492) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.19.0](https://github.com/expo/eas-cli/releases/tag/v0.19.0) - 2021-07-01
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Auto-suggest application id and bundle identifier when running `eas build:configure` for a managed project. ([#487](https://github.com/expo/eas-cli/pull/487) by [@dsokal](https://github.com/dsokal))
|
||||
- Support configuring one platform at a time when running `eas build`. ([#483](https://github.com/expo/eas-cli/pull/483) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.18.3](https://github.com/expo/eas-cli/releases/tag/v0.18.3) - 2021-06-28
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Support bundle identifiers that begin with `.`. ([#459](https://github.com/expo/eas-cli/pull/459) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Push Key setup integrated with ios build command ([#473](https://github.com/expo/eas-cli/pull/473) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix android builds for PKCS12 keystores. ([#484](https://github.com/expo/eas-cli/pull/484) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.18.2](https://github.com/expo/eas-cli/releases/tag/v0.18.2) - 2021-06-25
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Change default iOS image for projects with Expo SDK <= 41 (SDK 41 won't build with Xcode 12.5). ([#479](https://github.com/expo/eas-cli/pull/479) by [@dsokal](https://github.com/dsokal))
|
||||
- Make Apple team optional in appropriate cases. ([#468](https://github.com/expo/eas-cli/pull/468) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Show projects that depend on a push key. ([#472](https://github.com/expo/eas-cli/pull/472) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix inverted conditional so we actually only prompt about commit when index is dirty. ([#481](https://github.com/expo/eas-cli/pull/481) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.18.1](https://github.com/expo/eas-cli/releases/tag/v0.18.1) - 2021-06-24
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add basic printing support for Apple App Specific Password. ([#474](https://github.com/expo/eas-cli/pull/474) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix CLI UI getting blocked on credentials migration, leading to partially migrated state. ([#477](https://github.com/expo/eas-cli/pull/477) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.18.0](https://github.com/expo/eas-cli/releases/tag/v0.18.0) - 2021-06-24
|
||||
|
||||
### 🛠 Breaking changes
|
||||
@@ -75,7 +664,7 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- `[EAS BUILD API]` Remove "Auto" option for `schemeBuildConfiguration` and make the old "Auto" behaviour the default. ([#394](https://github.com/expo/eas-cli/pull/394) by [@randomhajile](https://github.com/randomhajile))
|
||||
- Remove "Auto" option for `schemeBuildConfiguration` and make the old "Auto" behaviour the default. ([#394](https://github.com/expo/eas-cli/pull/394) by [@randomhajile](https://github.com/randomhajile))
|
||||
- Remove `experimental.disableIosBundleIdentifierValidation` flag from eas.json. ([#407](https://github.com/expo/eas-cli/pull/407) by [@dsokal](https://github.com/dsokal))
|
||||
- Deprecate `android.package` and `ios.bundleIdentifier` in app config for generic projects. EAS CLI depends on the values in native code now. ([#407](https://github.com/expo/eas-cli/pull/407) by [@dsokal](https://github.com/dsokal))
|
||||
- Remove application id synchronization (`android.package` and `ios.bundleIdentifier`) between app.json and native code when running builds. ([#407](https://github.com/expo/eas-cli/pull/407) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
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-json @dsokal @wkozyra95
|
||||
@@ -0,0 +1,7 @@
|
||||
# Community Code of Conduct
|
||||
|
||||
The Expo team is committed to fostering an open and welcoming environment.
|
||||
|
||||
**Our Community Guidelines can be found here:**
|
||||
|
||||
https://expo.dev/guidelines
|
||||
+2
-37
@@ -1,39 +1,4 @@
|
||||
## Releasing a new version
|
||||
|
||||
1. Run `yarn release` in the repository root folder. This will create a new tag like `v1.0.0`.
|
||||
1. **Wait until the `release` GitHub workflow has finished.**
|
||||

|
||||
1. Run `yarn lerna publish from-git` in the repository root folder. This will publish the packages to npm.
|
||||
|
||||
- This command works only if `HEAD` is on the release commit, e.g. when `git log -1` prints:
|
||||
|
||||
```
|
||||
commit cbb2de9e118bfbcb5a597b261d2dbef0bb5d4b0b (tag: v0.11.1)
|
||||
Author: Brent Vatne <[email protected]>
|
||||
Date: Tue Apr 20 15:00:15 2021 -0700
|
||||
|
||||
v0.11.1
|
||||
```
|
||||
|
||||
- If you run `yarn lerna publish from-git` on any other commit, it'll print something like:
|
||||
|
||||
```
|
||||
lerna notice from-git No tagged release found
|
||||
lerna success No changed packages to publish
|
||||
```
|
||||
|
||||
- If you ever run into the above issue, use `git checkout RELEASE_COMMIT_HASH` to checkout to the release commit. Later, when you've published the packages to npm, run `git checkout main` to go back to the latest commit.
|
||||
|
||||
1. Update `CHANGELOG.md`.
|
||||
- Add the new section with the release version in the title.
|
||||
- Leave the `main` section empty (without any changelog entries).
|
||||
1. Once the release is ready to go, you can go to https://github.com/expo/eas-cli/releases and mark it as not draft.
|
||||
- Remember to copy the changelog entries from `CHANGELOG.md`.
|
||||
- Before hitting the submit button, make sure there are 5 assets available under the release entry.
|
||||

|
||||
1. If any of the changelog entries have been tagged with `[EAS BUILD API]`
|
||||
- Open `package.json` and note the version of `@expo/eas-build-job` used in the eas-cli.
|
||||
- Find the `Publish` commit in [expo/eas-build](https://github.com/expo/eas-build/commit/) for that version. In most cases, it will be the latest `Publish` commit.
|
||||
- Find the `eas-cli-local-build-plugin` version published in that commit.
|
||||

|
||||
- Run `npm dist-tags add eas-cli-local-build-plugin@VERSION latest` with the version from the previous step.
|
||||
1. Run `yarn release` in the repository root folder.
|
||||
2. That's it! GitHub Actions is going to take care of the rest. Watch the #eas-cli Slack channel for a successful release notification.
|
||||
@@ -1,4 +1,3 @@
|
||||
comment: false
|
||||
coverage:
|
||||
round: up
|
||||
precision: 2
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['@expo/babel-preset-cli'],
|
||||
};
|
||||
};
|
||||
+1
-3
@@ -1,9 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testRegex: '/__tests__/.*(test|spec)\\.[jt]sx?$',
|
||||
transform: {
|
||||
'^.+\\.[jt]sx?$': ['babel-jest', { configFile: require.resolve('./babel.config.js') }],
|
||||
},
|
||||
watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
|
||||
collectCoverageFrom: ['<rootDir>/src/**/*.{ts,tsx,js,jsx}'],
|
||||
};
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.18.1"
|
||||
"version": "0.42.2"
|
||||
}
|
||||
+23
-13
@@ -5,24 +5,34 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "lerna run build",
|
||||
"build:tarballs:linux": "yarn build && yarn workspace eas-cli oclif-dev pack --targets linux-x64",
|
||||
"build:tarballs:linux": "yarn build-for-size-check",
|
||||
"build-for-size-check": "yarn build && yarn workspace eas-cli pretarball-ci && CLI_SIZE_CHECK=1 yarn workspace eas-cli oclif pack:tarballs --no-xz --targets linux-x64",
|
||||
"typecheck": "lerna run typecheck",
|
||||
"start": "lerna run watch --parallel",
|
||||
"watch": "yarn start",
|
||||
"eas": "packages/eas-cli/bin/run",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"release": "lerna version",
|
||||
"test": "jest"
|
||||
"lint": "eslint .",
|
||||
"release": "./scripts/bin/run update-local-plugin && lerna version --exact",
|
||||
"test": "jest",
|
||||
"clean": "lerna run clean && rm -rf node_modules coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/oclif-dev-cli": "1.24.0-expo",
|
||||
"@types/jest": "^26.0.13",
|
||||
"eslint": "^6.6.0",
|
||||
"eslint-config-universe": "^4.0.0",
|
||||
"eslint-plugin-graphql": "^4.0.0",
|
||||
"jest": "^26.4.2",
|
||||
"jest-watch-typeahead": "^0.6.1",
|
||||
"lerna": "^3.22.1",
|
||||
"prettier": "^2.0.5"
|
||||
"@types/jest": "27.0.1",
|
||||
"@types/node": "16.7.8",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-universe": "8.0.0",
|
||||
"eslint-plugin-async-protect": "1.1.0",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"jest": "27.1.0",
|
||||
"jest-watch-typeahead": "0.6.4",
|
||||
"lerna": "4.0.0",
|
||||
"oclif": "2.0.1",
|
||||
"prettier": "2.4.1",
|
||||
"ts-jest": "27.0.5",
|
||||
"typescript": "4.5.2"
|
||||
},
|
||||
"volta": {
|
||||
"node": "16.10.0",
|
||||
"yarn": "1.22.15"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,18 @@
|
||||
const rootEslintrc = require('../../.eslintrc.js');
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
'import/no-cycle': 'error',
|
||||
'import/no-extraneous-dependencies': [
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{ devDependencies: ['**/__tests__/**/*', '**/__mocks__/**/*'] },
|
||||
{
|
||||
paths: [
|
||||
...rootEslintrc.rules['no-restricted-imports'][1].paths,
|
||||
{
|
||||
name: 'ora',
|
||||
message: 'Import ora from src/ora.ts instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
'graphql/template-strings': [
|
||||
'error',
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
graphql.schema.json linguist-generated
|
||||
src/graphql/generated.ts linguist-generated
|
||||
+555
-146
@@ -7,9 +7,9 @@ EAS command line tool
|
||||
[](https://npmjs.org/package/eas-cli)
|
||||
[](https://github.com/expo/eas-cli/blob/main/package.json)
|
||||
|
||||
* [Installation](#installation)
|
||||
* [Usage](#usage)
|
||||
* [Commands](#commands)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Commands](#commands)
|
||||
|
||||
# Installation
|
||||
|
||||
@@ -37,20 +37,38 @@ eas --help COMMAND
|
||||
* [`eas account:logout`](#eas-accountlogout)
|
||||
* [`eas account:view`](#eas-accountview)
|
||||
* [`eas analytics [STATUS]`](#eas-analytics-status)
|
||||
* [`eas branch:create [NAME]`](#eas-branchcreate-name)
|
||||
* [`eas branch:delete [NAME]`](#eas-branchdelete-name)
|
||||
* [`eas branch:list`](#eas-branchlist)
|
||||
* [`eas branch:rename`](#eas-branchrename)
|
||||
* [`eas branch:view [NAME]`](#eas-branchview-name)
|
||||
* [`eas build`](#eas-build)
|
||||
* [`eas build:cancel [BUILD_ID]`](#eas-buildcancel-build_id)
|
||||
* [`eas build:configure`](#eas-buildconfigure)
|
||||
* [`eas build:inspect`](#eas-buildinspect)
|
||||
* [`eas build:list`](#eas-buildlist)
|
||||
* [`eas build:view [BUILD_ID]`](#eas-buildview-build_id)
|
||||
* [`eas channel:create [NAME]`](#eas-channelcreate-name)
|
||||
* [`eas channel:edit [NAME]`](#eas-channeledit-name)
|
||||
* [`eas channel:list`](#eas-channellist)
|
||||
* [`eas channel:view [NAME]`](#eas-channelview-name)
|
||||
* [`eas config`](#eas-config)
|
||||
* [`eas credentials`](#eas-credentials)
|
||||
* [`eas device:create`](#eas-devicecreate)
|
||||
* [`eas device:list`](#eas-devicelist)
|
||||
* [`eas device:view [UDID]`](#eas-deviceview-udid)
|
||||
* [`eas diagnostics`](#eas-diagnostics)
|
||||
* [`eas help [COMMAND]`](#eas-help-command)
|
||||
* [`eas project:info`](#eas-projectinfo)
|
||||
* [`eas project:init`](#eas-projectinit)
|
||||
* [`eas secret:create`](#eas-secretcreate)
|
||||
* [`eas secret:delete`](#eas-secretdelete)
|
||||
* [`eas secret:list`](#eas-secretlist)
|
||||
* [`eas submit --platform=(android|ios)`](#eas-submit---platformandroidios)
|
||||
* [`eas submit`](#eas-submit)
|
||||
* [`eas update`](#eas-update)
|
||||
* [`eas update:configure`](#eas-updateconfigure)
|
||||
* [`eas update:delete GROUPID`](#eas-updatedelete-groupid)
|
||||
* [`eas update:view GROUPID`](#eas-updateview-groupid)
|
||||
* [`eas webhook:create`](#eas-webhookcreate)
|
||||
* [`eas webhook:delete [ID]`](#eas-webhookdelete-id)
|
||||
* [`eas webhook:list`](#eas-webhooklist)
|
||||
@@ -65,11 +83,14 @@ log in with your Expo account
|
||||
USAGE
|
||||
$ eas account:login
|
||||
|
||||
DESCRIPTION
|
||||
log in with your Expo account
|
||||
|
||||
ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/account/login.ts)_
|
||||
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -79,11 +100,14 @@ log out
|
||||
USAGE
|
||||
$ eas account:logout
|
||||
|
||||
DESCRIPTION
|
||||
log out
|
||||
|
||||
ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -93,22 +117,124 @@ show the username you are logged in as
|
||||
USAGE
|
||||
$ eas account:view
|
||||
|
||||
DESCRIPTION
|
||||
show the username you are logged in as
|
||||
|
||||
ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/account/view.ts)_
|
||||
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
View or change analytics settings
|
||||
view or change analytics settings
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas analytics [STATUS]
|
||||
|
||||
DESCRIPTION
|
||||
view or change analytics settings
|
||||
```
|
||||
|
||||
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas branch:create [NAME]`
|
||||
|
||||
Create a branch on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:create [NAME] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the branch to create
|
||||
|
||||
FLAGS
|
||||
--json return a json with the new branch ID and name.
|
||||
|
||||
DESCRIPTION
|
||||
Create a branch on the current project.
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
Delete a branch on the current project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:delete [NAME] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the branch to delete
|
||||
|
||||
FLAGS
|
||||
--json return JSON with the edited branch's ID and name.
|
||||
|
||||
DESCRIPTION
|
||||
Delete a branch on the current project
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
List all branches on this project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:list [--json]
|
||||
|
||||
FLAGS
|
||||
--json return output as JSON
|
||||
|
||||
DESCRIPTION
|
||||
List all branches on this project.
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
Rename a branch.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:rename [--from <value>] [--to <value>] [--json]
|
||||
|
||||
FLAGS
|
||||
--from=<value> current name of the branch.
|
||||
--json return a json with the edited branch's ID and name.
|
||||
--to=<value> new name of the branch.
|
||||
|
||||
DESCRIPTION
|
||||
Rename a branch.
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
View a branch.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:view [NAME] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the branch to view
|
||||
|
||||
FLAGS
|
||||
--json return a json with the branch's ID name and recent update groups.
|
||||
|
||||
DESCRIPTION
|
||||
View a branch.
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -116,19 +242,28 @@ Start a build
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build
|
||||
$ eas build [-p android|ios|all] [--json] [--skip-project-configuration] [--profile <value>]
|
||||
[--non-interactive] [--local] [--wait] [--clear-cache] [--auto-submit | --auto-submit-with-profile <value>]
|
||||
|
||||
OPTIONS
|
||||
FLAGS
|
||||
-p, --platform=(android|ios|all)
|
||||
--clear-cache Clear cache before the build
|
||||
--local Run build locally [experimental]
|
||||
--non-interactive Run command in --non-interactive mode
|
||||
--profile=profile [default: release] Name of the build profile from eas.json
|
||||
--skip-project-configuration Skip project configuration
|
||||
--[no-]wait Wait for build(s) to complete
|
||||
--auto-submit Submit on build complete using the submit profile with the same name as the
|
||||
build profile
|
||||
--auto-submit-with-profile=PROFILE_NAME Submit on build complete using the submit profile with provided name
|
||||
--clear-cache Clear cache before the build
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
--local Run build locally [experimental]
|
||||
--non-interactive Run command in non-interactive mode
|
||||
--profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined
|
||||
in eas.json.
|
||||
--skip-project-configuration Skip project configuration
|
||||
--[no-]wait Wait for build(s) to complete
|
||||
|
||||
DESCRIPTION
|
||||
Start a build
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/build/index.ts)_
|
||||
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -137,9 +272,12 @@ Cancel a build.
|
||||
```
|
||||
USAGE
|
||||
$ eas build:cancel [BUILD_ID]
|
||||
|
||||
DESCRIPTION
|
||||
Cancel a build.
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -147,13 +285,52 @@ Configure the project to support EAS Build.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:configure
|
||||
$ eas build:configure [-p android|ios|all]
|
||||
|
||||
OPTIONS
|
||||
FLAGS
|
||||
-p, --platform=(android|ios|all) Platform to configure
|
||||
|
||||
DESCRIPTION
|
||||
Configure the project to support EAS Build.
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
Inspect the state of the project at specific build stages. Useful for troubleshooting.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:inspect -p android|ios -s archive|pre-build|post-build --output <value> [--profile <value>] [--force]
|
||||
[--verbose]
|
||||
|
||||
FLAGS
|
||||
-p, --platform=(android|ios)
|
||||
(required)
|
||||
|
||||
-s, --stage=(archive|pre-build|post-build)
|
||||
(required) Stage of the build you want to inspect.
|
||||
archive - builds the project archive that would be uploaded to EAS when building
|
||||
pre-build - prepares the project to be built with Gradle/Xcode. Does not run the native build.
|
||||
post-build - builds the native project and leaves the output directory for inspection
|
||||
|
||||
--force
|
||||
Delete OUTPUT_DIRECTORY if it already exists.
|
||||
|
||||
--output=OUTPUT_DIRECTORY
|
||||
(required) Output directory.
|
||||
|
||||
--profile=PROFILE_NAME
|
||||
Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.
|
||||
|
||||
--verbose
|
||||
|
||||
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.42.2/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -161,15 +338,32 @@ list all builds for your project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:list
|
||||
$ eas build:list [--platform all|android|ios] [--json] [--status
|
||||
new|in-queue|in-progress|errored|finished|canceled] [--distribution store|internal|simulator] [--channel <value>]
|
||||
[--appVersion <value>] [--appBuildVersion <value>] [--sdkVersion <value>] [--runtimeVersion <value>]
|
||||
[--appIdentifier <value>] [--buildProfile <value>] [--gitCommitHash <value>] [--limit <value>]
|
||||
|
||||
OPTIONS
|
||||
--limit=limit
|
||||
FLAGS
|
||||
--appBuildVersion=<value>
|
||||
--appIdentifier=<value>
|
||||
--appVersion=<value>
|
||||
--buildProfile=<value>
|
||||
--channel=<value>
|
||||
--distribution=(store|internal|simulator)
|
||||
--gitCommitHash=<value>
|
||||
--json Enable JSON output, non-JSON messages will be printed
|
||||
to stderr
|
||||
--limit=<value>
|
||||
--platform=(all|android|ios)
|
||||
--runtimeVersion=<value>
|
||||
--sdkVersion=<value>
|
||||
--status=(new|in-queue|in-progress|errored|finished|canceled)
|
||||
|
||||
DESCRIPTION
|
||||
list all builds for your project
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/build/list.ts)_
|
||||
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -177,21 +371,126 @@ view a build for your project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:view [BUILD_ID]
|
||||
$ eas build:view [BUILD_ID] [--json]
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
|
||||
DESCRIPTION
|
||||
view a build for your project
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/build/view.ts)_
|
||||
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
Create a channel on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas channel:create [NAME] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the channel to create
|
||||
|
||||
FLAGS
|
||||
--json print output as a JSON object with the new channel ID, name and branch mapping.
|
||||
|
||||
DESCRIPTION
|
||||
Create a channel on the current project.
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
Point a channel at a new branch.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas channel:edit [NAME] [--branch <value>] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the channel to edit
|
||||
|
||||
FLAGS
|
||||
--branch=<value> Name of the branch to point to
|
||||
--json print output as a JSON object with the channel ID, name and branch mapping.
|
||||
|
||||
DESCRIPTION
|
||||
Point a channel at a new branch.
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
List all channels on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas channel:list [--json]
|
||||
|
||||
FLAGS
|
||||
--json print output as a JSON object with the channel ID, name and branch mapping.
|
||||
|
||||
DESCRIPTION
|
||||
List all channels on the current project.
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
View a channel on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas channel:view [NAME] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the channel to view
|
||||
|
||||
FLAGS
|
||||
--json print output as a JSON object with the channel ID, name and branch mapping.
|
||||
|
||||
DESCRIPTION
|
||||
View a channel on the current project.
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
show the eas.json config
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas config [-p android|ios] [--profile <value>]
|
||||
|
||||
FLAGS
|
||||
-p, --platform=(android|ios)
|
||||
--profile=<value>
|
||||
|
||||
DESCRIPTION
|
||||
show the eas.json config
|
||||
```
|
||||
|
||||
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
Manage your credentials
|
||||
manage your credentials
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas credentials
|
||||
|
||||
DESCRIPTION
|
||||
manage your credentials
|
||||
```
|
||||
|
||||
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/credentials.ts)_
|
||||
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/credentials.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -200,9 +499,12 @@ register new Apple Devices to use for internal distribution
|
||||
```
|
||||
USAGE
|
||||
$ eas device:create
|
||||
|
||||
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.18.1/packages/eas-cli/src/commands/device/create.ts)_
|
||||
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -210,13 +512,16 @@ list all registered devices for your account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas device:list
|
||||
$ eas device:list [--apple-team-id <value>]
|
||||
|
||||
OPTIONS
|
||||
--apple-team-id=apple-team-id
|
||||
FLAGS
|
||||
--apple-team-id=<value>
|
||||
|
||||
DESCRIPTION
|
||||
list all registered devices for your account
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/device/list.ts)_
|
||||
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -225,26 +530,77 @@ view a device for your project
|
||||
```
|
||||
USAGE
|
||||
$ eas device:view [UDID]
|
||||
|
||||
DESCRIPTION
|
||||
view a device for your project
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/device/view.ts)_
|
||||
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
## `eas diagnostics`
|
||||
|
||||
display help for eas
|
||||
log environment info to the console
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas help [COMMAND]
|
||||
$ eas diagnostics
|
||||
|
||||
ARGUMENTS
|
||||
COMMAND command to show help for
|
||||
|
||||
OPTIONS
|
||||
--all see all commands in CLI
|
||||
DESCRIPTION
|
||||
log environment info to the console
|
||||
```
|
||||
|
||||
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.0/src/commands/help.ts)_
|
||||
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
Display help for eas.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas help [COMMAND] [-n]
|
||||
|
||||
ARGUMENTS
|
||||
COMMAND Command to show help for.
|
||||
|
||||
FLAGS
|
||||
-n, --nested-commands Include all nested commands in the output.
|
||||
|
||||
DESCRIPTION
|
||||
Display help for eas.
|
||||
```
|
||||
|
||||
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.1.9/src/commands/help.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
information about the current project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas project:info
|
||||
|
||||
DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
create or link an EAS project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas project:init
|
||||
|
||||
DESCRIPTION
|
||||
create or link an EAS project
|
||||
|
||||
ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
@@ -252,15 +608,19 @@ Create an environment secret on the current project or owner account.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas secret:create
|
||||
$ eas secret:create [--scope account|project] [--name <value>] [--value <value>] [--force]
|
||||
|
||||
OPTIONS
|
||||
--name=name Name of the secret
|
||||
FLAGS
|
||||
--force Delete and recreate existing secrets
|
||||
--name=<value> Name of the secret
|
||||
--scope=(account|project) [default: project] Scope for the secret
|
||||
--value=value Value of the secret
|
||||
--value=<value> Value of the secret
|
||||
|
||||
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.18.1/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -268,16 +628,18 @@ Delete an environment secret by ID.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas secret:delete
|
||||
$ eas secret:delete [--id <value>]
|
||||
|
||||
OPTIONS
|
||||
--id=id ID of the secret to delete
|
||||
FLAGS
|
||||
--id=<value> ID of the secret to delete
|
||||
|
||||
DESCRIPTION
|
||||
Unsure where to find the secret's ID? Run eas secrets:list
|
||||
Delete an environment secret by ID.
|
||||
|
||||
Unsure where to find the secret's ID? Run eas secret:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -286,88 +648,124 @@ Lists environment secrets available for your current app
|
||||
```
|
||||
USAGE
|
||||
$ eas secret:list
|
||||
|
||||
DESCRIPTION
|
||||
Lists environment secrets available for your current app
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
|
||||
## `eas submit --platform=(android|ios)`
|
||||
## `eas submit`
|
||||
|
||||
Submits build artifact to app store
|
||||
Submit build archive to App Store Connect
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas submit --platform=(android|ios)
|
||||
$ eas submit [-p android|ios|all] [--profile <value>] [--latest | --id <value> | --path <value> | --url
|
||||
<value>] [--verbose] [--wait] [--non-interactive]
|
||||
|
||||
OPTIONS
|
||||
-p, --platform=(android|ios) For which platform you want to submit a build
|
||||
FLAGS
|
||||
-p, --platform=(android|ios|all)
|
||||
--id=<value> ID of the build to submit
|
||||
--latest Submit the latest build for specified platform
|
||||
--non-interactive Run command in non-interactive mode
|
||||
--path=<value> Path to the .apk/.aab/.ipa file
|
||||
--profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in
|
||||
eas.json.
|
||||
--url=<value> App archive url
|
||||
--verbose Always print logs from Submission Service
|
||||
--[no-]wait Wait for submission to complete
|
||||
|
||||
Android specific options=android-package Android package name (default: expo.android.package
|
||||
from app config)
|
||||
DESCRIPTION
|
||||
Submit build archive to App Store Connect
|
||||
|
||||
iOS specific options=app-name The name of your app as it will appear on the App
|
||||
Store (default: expo.name from app config)
|
||||
|
||||
iOS specific options=apple-id Your Apple ID username (you can also set
|
||||
EXPO_APPLE_ID env variable)
|
||||
|
||||
iOS specific options=apple-team-id Your Apple Developer Team ID
|
||||
|
||||
iOS specific options=asc-app-id App Store Connect unique application Apple ID
|
||||
number. Providing this param results in skipping
|
||||
app creation step. Learn more.
|
||||
|
||||
iOS specific options=bundle-identifier Your iOS Bundle Identifier (default:
|
||||
expo.ios.bundleIdentifier from app config)
|
||||
|
||||
iOS specific options=company-name The name of your company, needed only for the first
|
||||
upload of any app to App Store
|
||||
|
||||
--id=id ID of the build to submit
|
||||
|
||||
Android specific options=key Path to the JSON key used to authenticate with
|
||||
Google Play
|
||||
|
||||
iOS specific options=language [default: en-US] Primary language (e.g. English,
|
||||
German, ...)
|
||||
|
||||
--latest Submit the latest build for specified platform
|
||||
|
||||
--path=path Path to the .apk/.aab file
|
||||
|
||||
Android specific options=(completed|draft|halted|inProgress) [default: completed] Release status (used when
|
||||
uploading new APKs/AABs)
|
||||
|
||||
iOS specific options=sku An unique ID for your app that is not visible on
|
||||
the App Store, will be generated unless provided
|
||||
|
||||
Android specific options=(production|beta|alpha|internal|rollout) [default: internal] The track of the application to
|
||||
use
|
||||
|
||||
Android specific options=(apk|aab) Android archive type
|
||||
|
||||
--url=url App archive url
|
||||
|
||||
--verbose Always print logs from Submission Service
|
||||
See how to configure submits with eas.json: https://docs.expo.dev/submit/eas-json/
|
||||
|
||||
ALIASES
|
||||
$ eas build:submit
|
||||
|
||||
EXAMPLES
|
||||
$ eas submit --platform=ios
|
||||
- Fully interactive iOS submission
|
||||
|
||||
$ eas submit --platform=android
|
||||
- Fully interactive Android submission
|
||||
|
||||
$ eas submit -p android --latest --key=/path/to/google-services.json
|
||||
- Minimal non-interactive Android submission, however it can ask you for other params if not specified
|
||||
|
||||
$ EXPO_APPLE_APP_SPECIFIC_PASSWORD=xxx eas submit -p ios --latest [email protected] --asc-app-id=1234567890,
|
||||
- Minimal non-interactive iOS submission, assuming you already have an app in App Store Connect
|
||||
and provide its App ID
|
||||
```
|
||||
|
||||
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
Publish an update group.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update [--branch <value>] [--message <value>] [--republish | --input-dir <value> | --skip-bundler]
|
||||
[--group <value> | | ] [-p android|ios|all] [--json] [--auto]
|
||||
|
||||
FLAGS
|
||||
-p, --platform=(android|ios|all) [default: all]
|
||||
--auto Use the current git branch and commit message for the EAS branch and update message
|
||||
--branch=<value> Branch to publish the update group on
|
||||
--group=<value> Update group to republish
|
||||
--input-dir=<value> [default: dist] Location of the bundle
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
--message=<value> A short message describing the update
|
||||
--republish Republish an update group
|
||||
--skip-bundler Skip running Expo CLI to bundle the app before publishing
|
||||
|
||||
DESCRIPTION
|
||||
Publish an update group.
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
Configure the project to support EAS Update.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:configure
|
||||
|
||||
DESCRIPTION
|
||||
Configure the project to support EAS Update.
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
Delete all the updates in an update Group.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:delete [GROUPID] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
GROUPID The ID of an update group to delete.
|
||||
|
||||
FLAGS
|
||||
--json Return a json with the group ID of the deleted updates.
|
||||
|
||||
DESCRIPTION
|
||||
Delete all the updates in an update Group.
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
Update group details.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:view [GROUPID] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
GROUPID The ID of an update group.
|
||||
|
||||
FLAGS
|
||||
--json Return a json with the updates belonging to the group.
|
||||
|
||||
DESCRIPTION
|
||||
Update group details.
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -375,18 +773,19 @@ Create a webhook on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:create
|
||||
$ eas webhook:create [--event BUILD|SUBMIT] [--url <value>] [--secret <value>]
|
||||
|
||||
OPTIONS
|
||||
--event=(BUILD) [default: BUILD] Event type that triggers the webhook
|
||||
FLAGS
|
||||
--event=(BUILD|SUBMIT) Event type that triggers the webhook
|
||||
--secret=<value> Secret used to create a hash signature of the request payload, provided in the
|
||||
'Expo-Signature' header.
|
||||
--url=<value> Webhook URL
|
||||
|
||||
--secret=secret Secret used to create a hash signature of the request payload, provided in the 'Expo-Signature'
|
||||
header.
|
||||
|
||||
--url=url Webhook URL
|
||||
DESCRIPTION
|
||||
Create a webhook on the current project.
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -398,9 +797,12 @@ USAGE
|
||||
|
||||
ARGUMENTS
|
||||
ID ID of the webhook to delete
|
||||
|
||||
DESCRIPTION
|
||||
Delete a webhook on the current project.
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -408,13 +810,16 @@ List webhooks on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:list
|
||||
$ eas webhook:list [--event BUILD|SUBMIT]
|
||||
|
||||
OPTIONS
|
||||
--event=(BUILD) Event type that triggers the webhook
|
||||
FLAGS
|
||||
--event=(BUILD|SUBMIT) Event type that triggers the webhook
|
||||
|
||||
DESCRIPTION
|
||||
List webhooks on the current project.
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -422,19 +827,20 @@ Create a webhook on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:update
|
||||
$ eas webhook:update --id <value> [--event BUILD|SUBMIT] [--url <value>] [--secret <value>]
|
||||
|
||||
OPTIONS
|
||||
--event=(BUILD) [default: BUILD] Event type that triggers the webhook
|
||||
--id=id (required) Webhook ID
|
||||
FLAGS
|
||||
--event=(BUILD|SUBMIT) Event type that triggers the webhook
|
||||
--id=<value> (required) Webhook ID
|
||||
--secret=<value> Secret used to create a hash signature of the request payload, provided in the
|
||||
'Expo-Signature' header.
|
||||
--url=<value> Webhook URL
|
||||
|
||||
--secret=secret Secret used to create a hash signature of the request payload, provided in the 'Expo-Signature'
|
||||
header.
|
||||
|
||||
--url=url Webhook URL
|
||||
DESCRIPTION
|
||||
Create a webhook on the current project.
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -442,11 +848,14 @@ View a webhook on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:view ID
|
||||
$ eas webhook:view [ID]
|
||||
|
||||
ARGUMENTS
|
||||
ID ID of the webhook to view
|
||||
|
||||
DESCRIPTION
|
||||
View a webhook on the current project.
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.18.1/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.42.2/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
<!-- commandsstop -->
|
||||
@@ -1,2 +1,14 @@
|
||||
import { fs } from 'memfs';
|
||||
|
||||
// needed because of a weird bug with tempy (dependency of @expo/config-plugins)
|
||||
// eslint-disable-next-line node/no-sync
|
||||
fs.mkdirSync('/tmp');
|
||||
if (process.env.TMPDIR) {
|
||||
// eslint-disable-next-line node/no-sync
|
||||
fs.mkdirSync(process.env.TMPDIR, { recursive: true });
|
||||
}
|
||||
|
||||
// fs-extra@10 is not compatible with memfs, the following line fixes tests
|
||||
(fs.realpath as any).native = jest.fn();
|
||||
|
||||
module.exports = fs;
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('@oclif/command')
|
||||
.run()
|
||||
.then(require('@oclif/command/flush'))
|
||||
.catch(require('@oclif/errors/handle'));
|
||||
const oclif = require('@oclif/core')
|
||||
|
||||
oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle'))
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = async () => {
|
||||
process.env.TZ = 'UTC';
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
overwrite: true
|
||||
schema: 'https://staging.exp.host/--/graphql'
|
||||
schema: 'https://staging-api.expo.dev/graphql'
|
||||
documents:
|
||||
- 'src/graphql/**/!(*.d).{ts,tsx}'
|
||||
- 'src/credentials/ios/api/graphql/**/!(*.d).{ts,tsx}'
|
||||
|
||||
Generated
+5844
-2537
File diff suppressed because it is too large.
Load diff
@@ -2,5 +2,6 @@ module.exports = {
|
||||
...require('../../jest/jest.config.js'),
|
||||
rootDir: __dirname,
|
||||
roots: ['src', '__mocks__'],
|
||||
globalSetup: './global-setup.ts',
|
||||
globalSetup: './jest/global-setup.ts',
|
||||
setupFilesAfterEnv: ['./jest/setup-after-env.ts'],
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = async () => {
|
||||
process.env.TZ = 'UTC';
|
||||
process.env.EAS_NO_VCS = '1';
|
||||
process.env.EAS_PROJECT_ROOT = '/app';
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
/* eslint-disable no-console */
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
console.error = jest.fn();
|
||||
});
|
||||
@@ -1,113 +1,102 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.18.1",
|
||||
"author": "Expo <support@expo.io>",
|
||||
"version": "0.42.2",
|
||||
"author": "Expo <support@expo.dev>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
},
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@amplitude/identify": "1.5.0",
|
||||
"@amplitude/node": "1.5.0",
|
||||
"@expo/apple-utils": "0.0.0-alpha.20",
|
||||
"@expo/config": "3.3.42",
|
||||
"@expo/config-plugins": "2.0.2",
|
||||
"@expo/eas-build-job": "0.2.37",
|
||||
"@expo/eas-json": "^0.18.0",
|
||||
"@expo/json-file": "8.2.25",
|
||||
"@expo/pkcs12": "0.0.4",
|
||||
"@expo/plist": "0.0.11",
|
||||
"@expo/plugin-warn-if-update-available": "1.7.0",
|
||||
"@expo/apple-utils": "0.0.0-alpha.26",
|
||||
"@expo/config": "6.0.11",
|
||||
"@expo/config-plugins": "4.0.11",
|
||||
"@expo/eas-build-job": "0.2.61",
|
||||
"@expo/eas-json": "0.42.2",
|
||||
"@expo/json-file": "8.2.34",
|
||||
"@expo/pkcs12": "0.0.6",
|
||||
"@expo/plist": "0.0.16",
|
||||
"@expo/plugin-warn-if-update-available": "2.0.1",
|
||||
"@expo/prebuild-config": "3.0.11",
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/sdk-runtime-versions": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.5.0",
|
||||
"@hapi/joi": "17.1.1",
|
||||
"@oclif/command": "1.8.0",
|
||||
"@oclif/config": "1.17.0",
|
||||
"@oclif/errors": "1.3.3",
|
||||
"@oclif/plugin-help": "3.2.0",
|
||||
"@urql/core": "2.0.0",
|
||||
"@urql/exchange-retry": "0.2.0",
|
||||
"chalk": "4.1.0",
|
||||
"@expo/timeago.js": "1.0.0",
|
||||
"@oclif/core": "1.0.10",
|
||||
"@urql/core": "2.3.1",
|
||||
"@urql/exchange-retry": "0.3.0",
|
||||
"chalk": "4.1.2",
|
||||
"cli-table3": "0.6.0",
|
||||
"dateformat": "3.0.3",
|
||||
"dateformat": "4.5.1",
|
||||
"env-paths": "2.2.0",
|
||||
"fast-glob": "3.2.4",
|
||||
"envinfo": "7.8.1",
|
||||
"fast-glob": "3.2.7",
|
||||
"figures": "3.2.0",
|
||||
"form-data": "3.0.0",
|
||||
"fs-extra": "9.0.1",
|
||||
"fs-extra": "10.0.0",
|
||||
"getenv": "1.0.0",
|
||||
"got": "11.5.2",
|
||||
"graphql": "15.3.0",
|
||||
"graphql-tag": "2.11.0",
|
||||
"got": "11.8.2",
|
||||
"gradle-to-js": "2.0.0",
|
||||
"graphql": "15.5.1",
|
||||
"graphql-tag": "2.12.5",
|
||||
"ignore": "5.1.8",
|
||||
"indent-string": "4.0.0",
|
||||
"jks-js": "1.0.0",
|
||||
"jks-js": "1.0.1",
|
||||
"joi": "17.4.2",
|
||||
"keychain": "1.3.0",
|
||||
"lodash": "4.17.20",
|
||||
"mime": "2.4.7",
|
||||
"log-symbols": "4.1.0",
|
||||
"mime": "2.5.2",
|
||||
"minimatch": "3.0.4",
|
||||
"nanoid": "3.1.23",
|
||||
"nanoid": "3.1.25",
|
||||
"node-fetch": "2.6.1",
|
||||
"node-forge": "0.10.0",
|
||||
"nullthrows": "1.1.1",
|
||||
"ora": "5.1.0",
|
||||
"pkg-dir": "4.2.0",
|
||||
"progress": "2.0.3",
|
||||
"prompts": "2.3.2",
|
||||
"qrcode-terminal": "0.12.0",
|
||||
"resolve-from": "5.0.0",
|
||||
"semver": "7.3.4",
|
||||
"semver": "7.3.5",
|
||||
"slash": "3.0.0",
|
||||
"strip-ansi": "6.0.0",
|
||||
"tar": "6.0.5",
|
||||
"tempy": "0.7.0",
|
||||
"tar": "6.1.11",
|
||||
"terminal-link": "2.1.1",
|
||||
"timeago.js": "4.0.2",
|
||||
"tslib": "1.14.1",
|
||||
"turndown": "7.0.0",
|
||||
"tslib": "2.3.1",
|
||||
"turndown": "7.1.1",
|
||||
"untildify": "4.0.0",
|
||||
"uuid": "8.3.0",
|
||||
"uuid": "8.3.2",
|
||||
"wrap-ansi": "7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/preset-env": "^7.11.5",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@expo/babel-preset-cli": "^0.2.17",
|
||||
"@graphql-codegen/cli": "^1.19.2",
|
||||
"@graphql-codegen/introspection": "^1.18.0",
|
||||
"@graphql-codegen/typescript": "^1.17.11",
|
||||
"@graphql-codegen/typescript-operations": "^1.17.10",
|
||||
"@types/color-string": "^1.5.0",
|
||||
"@types/dateformat": "^3.0.1",
|
||||
"@types/dateformat": "3.0.1",
|
||||
"@types/envinfo": "7.8.1",
|
||||
"@types/form-data": "^2.5.0",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/fs-extra": "9.0.12",
|
||||
"@types/getenv": "^1.0.0",
|
||||
"@types/lodash": "^4.14.161",
|
||||
"@types/mime": "^2.0.3",
|
||||
"@types/node": "^12",
|
||||
"@types/mime": "2.0.3",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/node-forge": "^0.9.5",
|
||||
"@types/progress": "^2.0.3",
|
||||
"@types/prompts": "^2.0.9",
|
||||
"@types/semver": "^7.3.4",
|
||||
"@types/tar": "^4.0.3",
|
||||
"@types/tough-cookie": "^4.0.0",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"@types/wrap-ansi": "^3.0.0",
|
||||
"@vercel/ncc": "^0.23.0",
|
||||
"axios": "^0.21.0",
|
||||
"babel-jest": "^26.3.0",
|
||||
"memfs": "^3.2.0",
|
||||
"@types/semver": "7.3.8",
|
||||
"@types/tar": "4.0.5",
|
||||
"@types/tough-cookie": "4.0.1",
|
||||
"@types/uuid": "8.3.1",
|
||||
"@types/wrap-ansi": "3.0.0",
|
||||
"axios": "0.21.4",
|
||||
"eslint-plugin-graphql": "4.0.0",
|
||||
"memfs": "3.2.2",
|
||||
"mockdate": "^3.0.2",
|
||||
"nock": "^13.0.5",
|
||||
"ts-node": "^8",
|
||||
"typescript": "^3.9.7",
|
||||
"wonka": "^4.0.14"
|
||||
"tempy": "0.7.0",
|
||||
"ts-deepmerge": "1.0.8",
|
||||
"typescript": "4.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"files": [
|
||||
"/bin",
|
||||
@@ -121,7 +110,6 @@
|
||||
"cli"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "build/index.js",
|
||||
"oclif": {
|
||||
"bin": "eas",
|
||||
"commands": "./build/commands",
|
||||
@@ -129,17 +117,28 @@
|
||||
"@oclif/plugin-help",
|
||||
"@expo/plugin-warn-if-update-available"
|
||||
],
|
||||
"helpClass": "./build/help",
|
||||
"topics": {
|
||||
"account": {
|
||||
"description": "manage your account"
|
||||
},
|
||||
"build": {
|
||||
"description": "build app binaries"
|
||||
},
|
||||
"channel": {
|
||||
"description": "manage update channels"
|
||||
},
|
||||
"device": {
|
||||
"description": "manage your Apple devices for internal distribution"
|
||||
},
|
||||
"project": {
|
||||
"description": "manage your project"
|
||||
},
|
||||
"release": {
|
||||
"description": "manage update releases"
|
||||
},
|
||||
"secret": {
|
||||
"description": "manage project and account secrets"
|
||||
},
|
||||
"update": {
|
||||
"description": "manage individual updates"
|
||||
},
|
||||
@@ -163,19 +162,25 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalHelpFlags": [
|
||||
"-h"
|
||||
],
|
||||
"additionalVersionFlags": [
|
||||
"-v"
|
||||
]
|
||||
},
|
||||
"repository": "expo/eas-cli",
|
||||
"scripts": {
|
||||
"postpack": "rm -f oclif.manifest.json",
|
||||
"prepack": "rm -rf build && yarn build && yarn oclif-dev manifest && yarn oclif-dev readme && node patch-readme",
|
||||
"prepack": "./scripts/prepack.sh",
|
||||
"pretarball-ci": "./scripts/pretarball-ci.sh",
|
||||
"build": "tsc --project tsconfig.build.json",
|
||||
"watch": "yarn build --watch --preserveWatchOutput",
|
||||
"typecheck": "tsc",
|
||||
"test": "jest",
|
||||
"version": "yarn oclif-dev readme && node patch-readme && git add README.md",
|
||||
"generate-graphql-code": "graphql-codegen --config graphql-codegen.yml"
|
||||
},
|
||||
"types": "build/index.d.ts",
|
||||
"gitHead": "161808bc37c255fdea6801d69d23dbc725c056c3"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
// Patch `oclif-dev readme` path and link generation
|
||||
let readmeContent = fs.readFileSync('README.md', 'utf8');
|
||||
readmeContent = readmeContent.replace(/\[build\//g, '[src/');
|
||||
readmeContent = readmeContent.replace(/build\/commands/g, 'packages/eas-cli/src/commands');
|
||||
fs.writeFileSync('README.md', readmeContent);
|
||||
|
||||
console.log('Patched README path generation');
|
||||
@@ -0,0 +1,12 @@
|
||||
const { promises: fs } = require('fs');
|
||||
|
||||
(async () => {
|
||||
// Patch `oclif readme` path and link generation
|
||||
let readmeContent = await fs.readFile('README.md', 'utf8');
|
||||
readmeContent = readmeContent.replace(/\[build\//g, '[src/');
|
||||
readmeContent = readmeContent.replace(/build\/commands/g, 'packages/eas-cli/src/commands');
|
||||
await fs.writeFile('README.md', readmeContent);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Patched README path generation');
|
||||
})();
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
rm -rf build
|
||||
yarn build
|
||||
|
||||
if [ -z "$CLI_SIZE_CHECK" ]; then
|
||||
yarn oclif manifest
|
||||
fi
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
EAS_CLI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
PACKAGE_JSON_PATH="$EAS_CLI_DIR/package.json"
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' "s/\\\"@expo\/eas-json\\\": \\\".*\\\"/\\\"@expo\/eas-json\\\": \\\"file:..\/..\/..\/eas-json\\\"/g" $PACKAGE_JSON_PATH
|
||||
else
|
||||
sed -i "s/\\\"@expo\/eas-json\\\": \\\".*\\\"/\\\"@expo\/eas-json\\\": \\\"file:..\/..\/..\/eas-json\\\"/g" $PACKAGE_JSON_PATH
|
||||
fi
|
||||
@@ -1,7 +1,9 @@
|
||||
export default jest.fn().mockReturnValue({
|
||||
export const ora = jest.fn().mockReturnValue({
|
||||
start: jest.fn().mockReturnValue({
|
||||
fail: jest.fn(),
|
||||
succeed: jest.fn(),
|
||||
}),
|
||||
stop: jest.fn(),
|
||||
stopAndPersist: jest.fn(),
|
||||
succeed: jest.fn(),
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import { apiClient, getExpoApiBaseUrl } from '../api';
|
||||
describe('apiClient', () => {
|
||||
it('converts Expo APIv2 error to ApiV2Error', async () => {
|
||||
nock(getExpoApiBaseUrl())
|
||||
.post('/--/api/v2/test')
|
||||
.post('/v2/test')
|
||||
.reply(400, {
|
||||
errors: [
|
||||
{
|
||||
@@ -24,7 +24,7 @@ describe('apiClient', () => {
|
||||
let error: Error | null = null;
|
||||
try {
|
||||
await apiClient.post('test');
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
@@ -39,12 +39,12 @@ describe('apiClient', () => {
|
||||
});
|
||||
|
||||
it('does not convert non-APIv2 error to ApiV2Error', async () => {
|
||||
nock(getExpoApiBaseUrl()).post('/--/api/v2/test').reply(500, 'Something went wrong');
|
||||
nock(getExpoApiBaseUrl()).post('/v2/test').reply(500, 'Something went wrong');
|
||||
|
||||
let error: Error | null = null;
|
||||
try {
|
||||
await apiClient.post('test');
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeInstanceOf(RequestError);
|
||||
|
||||
@@ -25,7 +25,7 @@ interface AppJSON {
|
||||
};
|
||||
}
|
||||
|
||||
function createTestProject(
|
||||
export function createTestProject(
|
||||
user: Actor,
|
||||
appJSONExtraData?: Record<string, object | string>
|
||||
): MockProject {
|
||||
@@ -58,5 +58,3 @@ function createTestProject(
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { createTestProject };
|
||||
@@ -1,106 +0,0 @@
|
||||
import { Identify } from '@amplitude/identify';
|
||||
import * as Amplitude from '@amplitude/node';
|
||||
import os from 'os';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import UserSettings from './user/UserSettings';
|
||||
|
||||
/**
|
||||
* We use require() to exclude package.json from TypeScript's analysis since it lives outside
|
||||
* the src directory and would change the directory structure of the emitted files
|
||||
* under the build directory
|
||||
*/
|
||||
const packageJSON = require('../package.json');
|
||||
|
||||
const PLATFORM_TO_ANALYTICS_PLATFORM: { [platform: string]: string } = {
|
||||
darwin: 'Mac',
|
||||
win32: 'Windows',
|
||||
linux: 'Linux',
|
||||
};
|
||||
|
||||
let client: Amplitude.NodeClient | null = null;
|
||||
let userIdentifyCalled = false;
|
||||
let identifyData: {
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
identify: Identify;
|
||||
} | null = null;
|
||||
|
||||
export async function initAsync(): Promise<void> {
|
||||
if (process.env.DISABLE_EAS_ANALYTICS) {
|
||||
await UserSettings.setAsync('amplitudeEnabled', false);
|
||||
}
|
||||
const amplitudeEnabled = await UserSettings.getAsync('amplitudeEnabled', true);
|
||||
if (amplitudeEnabled) {
|
||||
const apiKey =
|
||||
process.env.EXPO_STAGING || process.env.EXPO_LOCAL
|
||||
? 'cdebbc678931403439486c4750781544'
|
||||
: '4ac443afd5073c0df6169291db1d3495';
|
||||
client = Amplitude.init(apiKey);
|
||||
}
|
||||
}
|
||||
|
||||
export async function setUserDataAsync(
|
||||
userId: string,
|
||||
properties: Record<string, any>
|
||||
): Promise<void> {
|
||||
const identify = new Identify();
|
||||
Object.entries(properties).forEach(([property, value]) => {
|
||||
identify.set(property, value);
|
||||
});
|
||||
const savedDeviceId = await UserSettings.getAsync('amplitudeDeviceId', null);
|
||||
const deviceId = savedDeviceId ?? uuidv4();
|
||||
if (!savedDeviceId) {
|
||||
await UserSettings.setAsync('amplitudeDeviceId', deviceId);
|
||||
}
|
||||
identifyData = {
|
||||
userId,
|
||||
deviceId,
|
||||
identify,
|
||||
};
|
||||
|
||||
ensureUserIdentified();
|
||||
}
|
||||
|
||||
export async function flushAsync(): Promise<void> {
|
||||
if (client) {
|
||||
await client.flush();
|
||||
}
|
||||
}
|
||||
|
||||
export function logEvent(name: string, properties: Record<string, any> = {}) {
|
||||
if (client) {
|
||||
ensureUserIdentified();
|
||||
const { userId, deviceId } = identifyData ?? {};
|
||||
client.logEvent({
|
||||
event_type: name,
|
||||
event_properties: properties,
|
||||
...(userId && { user_id: userId }),
|
||||
...(deviceId && { device_id: deviceId }),
|
||||
...getContext(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function ensureUserIdentified() {
|
||||
if (client && !userIdentifyCalled && identifyData) {
|
||||
client.identify(identifyData.userId, identifyData.deviceId, identifyData.identify);
|
||||
userIdentifyCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function getContext() {
|
||||
const platform = PLATFORM_TO_ANALYTICS_PLATFORM[os.platform()] || os.platform();
|
||||
return {
|
||||
os_name: platform,
|
||||
os_version: os.release(),
|
||||
device_brand: platform,
|
||||
device_model: platform,
|
||||
source_version: packageJSON?.version,
|
||||
source: 'eas cli',
|
||||
};
|
||||
}
|
||||
|
||||
export enum AnalyticsEvent {
|
||||
ACTION = 'action', // generic event type which is used to determine the 'daily active user' stat, include an `action: eas ${subcommand}` property inside of the event properties object
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Analytics, Event } from './events';
|
||||
|
||||
export type TrackingContext = Record<string, string | number | boolean>;
|
||||
|
||||
export async function withAnalyticsAsync<Result>(
|
||||
fn: () => Promise<Result>,
|
||||
analytics: {
|
||||
attemptEvent: Event;
|
||||
successEvent: Event;
|
||||
failureEvent: Event;
|
||||
trackingCtx: TrackingContext;
|
||||
}
|
||||
): Promise<Result> {
|
||||
try {
|
||||
const result = await fn();
|
||||
Analytics.logEvent(analytics.successEvent, analytics.trackingCtx);
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
Analytics.logEvent(analytics.failureEvent, {
|
||||
...analytics.trackingCtx,
|
||||
reason: error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { logEvent } from './rudderstackClient';
|
||||
export type Event = BuildEvent | SubmissionEvent;
|
||||
|
||||
export enum SubmissionEvent {
|
||||
SUBMIT_COMMAND = 'submit cli submit command',
|
||||
SUBMIT_COMMAND_ATTEMPT = 'submit cli attempt',
|
||||
SUBMIT_COMMAND_SUCCESS = 'submit cli success',
|
||||
SUBMIT_COMMAND_FAIL = 'submit cli fail',
|
||||
GATHER_CREDENTIALS_ATTEMPT = 'submit cli gather credentials attempt',
|
||||
GATHER_CREDENTIALS_SUCCESS = 'submit cli gather credentials success',
|
||||
GATHER_CREDENTIALS_FAIL = 'submit cli gather credentials fail',
|
||||
GATHER_ARCHIVE_ATTEMPT = 'submit cli gather archive attempt',
|
||||
GATHER_ARCHIVE_SUCCESS = 'submit cli gather archive success',
|
||||
GATHER_ARCHIVE_FAIL = 'submit cli gather archive fail',
|
||||
SUBMIT_REQUEST_ATTEMPT = 'submit cli request attempt',
|
||||
SUBMIT_REQUEST_SUCCESS = 'submit cli request success',
|
||||
SUBMIT_REQUEST_FAIL = 'submit cli request fail',
|
||||
}
|
||||
|
||||
export enum BuildEvent {
|
||||
BUILD_COMMAND = 'build cli build command',
|
||||
PROJECT_UPLOAD_ATTEMPT = 'build cli project upload attempt',
|
||||
PROJECT_UPLOAD_SUCCESS = 'build cli project upload success',
|
||||
PROJECT_UPLOAD_FAIL = 'build cli project upload fail',
|
||||
GATHER_CREDENTIALS_ATTEMPT = 'build cli gather credentials attempt',
|
||||
GATHER_CREDENTIALS_SUCCESS = 'build cli gather credentials success',
|
||||
GATHER_CREDENTIALS_FAIL = 'build cli gather credentials fail',
|
||||
CONFIGURE_PROJECT_ATTEMPT = 'build cli configure project attempt',
|
||||
CONFIGURE_PROJECT_SUCCESS = 'build cli configure project success',
|
||||
CONFIGURE_PROJECT_FAIL = 'build cli configure project fail',
|
||||
BUILD_REQUEST_ATTEMPT = 'build cli build request attempt',
|
||||
BUILD_REQUEST_SUCCESS = 'build cli build request success',
|
||||
BUILD_REQUEST_FAIL = 'build cli build request fail',
|
||||
|
||||
BUILD_STATUS_COMMAND = 'build cli build status',
|
||||
|
||||
CREDENTIALS_SYNC_COMMAND = 'build cli credentials sync command',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_ATTEMPT = 'build cli credentials sync update local attempt',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_SUCCESS = 'build cli credentials sync update local success',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_FAIL = 'build cli credentials sync update local fail',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_ATTEMPT = 'build cli credentials sync update remote attempt',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_SUCCESS = 'build cli credentials sync update remote success',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_FAIL = 'build cli credentials sync update remote fail',
|
||||
|
||||
ANDROID_KEYSTORE_CREATE = 'build cli credentials keystore create',
|
||||
}
|
||||
|
||||
export class Analytics {
|
||||
static logEvent(name: Event, properties: Record<string, any>): void {
|
||||
logEvent(name, properties);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import RudderAnalytics from '@expo/rudder-sdk-node';
|
||||
import os from 'os';
|
||||
import { URL } from 'url';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import UserSettings from '../user/UserSettings';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
|
||||
const PLATFORM_TO_ANALYTICS_PLATFORM: { [platform: string]: string } = {
|
||||
darwin: 'Mac',
|
||||
win32: 'Windows',
|
||||
linux: 'Linux',
|
||||
};
|
||||
|
||||
let rudderstackClient: RudderAnalytics | null = null;
|
||||
let userIdentified = false;
|
||||
let identifyData: {
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
traits: Record<string, any>;
|
||||
} | null = null;
|
||||
|
||||
export async function initAsync(): Promise<void> {
|
||||
// TODO: remove after some time
|
||||
const amplitudeEnabled = await UserSettings.getAsync('amplitudeEnabled', null);
|
||||
if (amplitudeEnabled !== null) {
|
||||
await UserSettings.setAsync('analyticsEnabled', amplitudeEnabled);
|
||||
await UserSettings.deleteKeyAsync('amplitudeEnabled');
|
||||
}
|
||||
const amplitudeDeviceId = await UserSettings.getAsync('amplitudeDeviceId', null);
|
||||
if (amplitudeDeviceId !== null) {
|
||||
await UserSettings.setAsync('analyticsDeviceId', amplitudeDeviceId);
|
||||
await UserSettings.deleteKeyAsync('amplitudeDeviceId');
|
||||
}
|
||||
// TODO: cut here
|
||||
if (process.env.DISABLE_EAS_ANALYTICS) {
|
||||
await UserSettings.setAsync('analyticsEnabled', false);
|
||||
}
|
||||
|
||||
const analyticsEnabled = await UserSettings.getAsync('analyticsEnabled', true);
|
||||
if (analyticsEnabled) {
|
||||
const config =
|
||||
process.env.EXPO_STAGING || process.env.EXPO_LOCAL
|
||||
? {
|
||||
// staging environment
|
||||
rudderstackWriteKey: '1wpX20Da4ltFGSXbPFYUL00Chb7',
|
||||
rudderstackDataPlaneURL: 'https://cdp.expo.dev',
|
||||
}
|
||||
: {
|
||||
// prod environment
|
||||
rudderstackWriteKey: '1wpXLFxmujq86etH6G6cc90hPcC',
|
||||
rudderstackDataPlaneURL: 'https://cdp.expo.dev',
|
||||
};
|
||||
|
||||
rudderstackClient = new RudderAnalytics(
|
||||
config.rudderstackWriteKey,
|
||||
new URL('/v1/batch', config.rudderstackDataPlaneURL).toString(),
|
||||
{
|
||||
flushInterval: 300,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function setUserDataAsync(userId: string, traits: Record<string, any>): Promise<void> {
|
||||
const savedDeviceId = await UserSettings.getAsync('analyticsDeviceId', null);
|
||||
const deviceId = savedDeviceId ?? uuidv4();
|
||||
if (!savedDeviceId) {
|
||||
await UserSettings.setAsync('analyticsDeviceId', deviceId);
|
||||
}
|
||||
|
||||
identifyData = {
|
||||
userId,
|
||||
deviceId,
|
||||
traits,
|
||||
};
|
||||
|
||||
ensureUserIdentified();
|
||||
}
|
||||
|
||||
export async function flushAsync(): Promise<void> {
|
||||
if (rudderstackClient) {
|
||||
await rudderstackClient.flush();
|
||||
}
|
||||
}
|
||||
|
||||
export function logEvent(name: string, properties: Record<string, any> = {}): void {
|
||||
if (!rudderstackClient) {
|
||||
return;
|
||||
}
|
||||
ensureUserIdentified();
|
||||
|
||||
const { userId, deviceId } = identifyData ?? {};
|
||||
const commonEventProperties = { source_version: easCliVersion, source: 'eas cli' };
|
||||
|
||||
const identity = { userId: userId ?? undefined, anonymousId: deviceId ?? uuidv4() };
|
||||
rudderstackClient.track({
|
||||
event: name,
|
||||
properties: { ...properties, ...commonEventProperties },
|
||||
...identity,
|
||||
context: getRudderStackContext(),
|
||||
});
|
||||
}
|
||||
|
||||
function ensureUserIdentified(): void {
|
||||
if (!rudderstackClient || userIdentified || !identifyData) {
|
||||
return;
|
||||
}
|
||||
|
||||
rudderstackClient.identify({
|
||||
userId: identifyData.userId,
|
||||
anonymousId: identifyData.deviceId,
|
||||
traits: identifyData.traits,
|
||||
});
|
||||
userIdentified = true;
|
||||
}
|
||||
|
||||
function getRudderStackContext(): Record<string, any> {
|
||||
const platform = PLATFORM_TO_ANALYTICS_PLATFORM[os.platform()] || os.platform();
|
||||
return {
|
||||
os: { name: platform, version: os.release() },
|
||||
device: { type: platform, model: platform },
|
||||
app: { name: 'eas cli', version: easCliVersion ?? undefined },
|
||||
};
|
||||
}
|
||||
|
||||
export enum AnalyticsEvent {
|
||||
ACTION = 'action', // generic event type which is used to determine the 'daily active user' stat, include an `action: eas ${subcommand}` property inside of the event properties object
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import ApiV2Error from './ApiV2Error';
|
||||
import { getAccessToken, getSessionSecret } from './user/sessionStorage';
|
||||
|
||||
export const apiClient = got.extend({
|
||||
prefixUrl: getExpoApiBaseUrl() + '/--/api/v2/',
|
||||
prefixUrl: getExpoApiBaseUrl() + '/v2/',
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
(options: NormalizedOptions) => {
|
||||
@@ -40,20 +40,30 @@ export const apiClient = got.extend({
|
||||
|
||||
export function getExpoApiBaseUrl(): string {
|
||||
if (process.env.EXPO_STAGING) {
|
||||
return `https://staging.exp.host`;
|
||||
return `https://staging-api.expo.dev`;
|
||||
} else if (process.env.EXPO_LOCAL) {
|
||||
return `http://127.0.0.1:3000`;
|
||||
} else {
|
||||
return `https://exp.host`;
|
||||
return `https://api.expo.dev`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getExpoWebsiteBaseUrl(): string {
|
||||
if (process.env.EXPO_STAGING) {
|
||||
return `https://staging.expo.io`;
|
||||
return `https://staging.expo.dev`;
|
||||
} else if (process.env.EXPO_LOCAL) {
|
||||
return `http://expo.test`;
|
||||
} else {
|
||||
return `https://expo.io`;
|
||||
return `https://expo.dev`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getEASUpdateURL(projectId: string): string {
|
||||
if (process.env.EXPO_STAGING) {
|
||||
return new URL(projectId, `https://staging-u.expo.dev`).href;
|
||||
} else if (process.env.EXPO_LOCAL) {
|
||||
return new URL(`expo-updates/${projectId}`, `http://127.0.0.1:3000`).href;
|
||||
} else {
|
||||
return new URL(projectId, `https://u.expo.dev`).href;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { AndroidConfig, AndroidManifest } from '@expo/config-plugins';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import Log from '../../log';
|
||||
import { getProjectAccountName } from '../../project/projectUtils';
|
||||
@@ -10,16 +9,6 @@ import { ensureValidVersions } from '../utils/updates';
|
||||
export async function configureUpdatesAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
|
||||
ensureValidVersions(exp);
|
||||
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
|
||||
const buildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(projectDir);
|
||||
const buildGradleContents = await fs.readFile(buildGradlePath, 'utf8');
|
||||
|
||||
if (!AndroidConfig.Updates.isBuildGradleConfigured(projectDir, buildGradleContents)) {
|
||||
const updatedBuildGradleContents = AndroidConfig.Updates.ensureBuildGradleContainsConfigurationScript(
|
||||
projectDir,
|
||||
buildGradleContents
|
||||
);
|
||||
await fs.writeFile(buildGradlePath, updatedBuildGradleContents);
|
||||
}
|
||||
|
||||
const androidManifestPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectDir);
|
||||
const androidManifest = await getAndroidManifestAsync(projectDir);
|
||||
@@ -38,7 +27,7 @@ export async function syncUpdatesConfigurationAsync(
|
||||
ensureValidVersions(exp);
|
||||
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
|
||||
try {
|
||||
await ensureUpdatesConfiguredAsync(projectDir, exp);
|
||||
await ensureUpdatesConfiguredAsync(projectDir);
|
||||
} catch (error) {
|
||||
Log.error(
|
||||
'expo-updates module is not configured. Please run "eas build:configure" first to configure the project'
|
||||
@@ -61,15 +50,9 @@ export async function syncUpdatesConfigurationAsync(
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureUpdatesConfiguredAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
|
||||
const buildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(projectDir);
|
||||
const buildGradleContents = await fs.readFile(buildGradlePath, 'utf8');
|
||||
|
||||
if (!AndroidConfig.Updates.isBuildGradleConfigured(projectDir, buildGradleContents)) {
|
||||
const gradleScriptApply = AndroidConfig.Updates.formatApplyLineForBuildGradle(projectDir);
|
||||
throw new Error(`Missing ${gradleScriptApply} in ${buildGradlePath}`);
|
||||
}
|
||||
|
||||
// Note: we assume here that Expo modules are properly configured in the project. Aside from that,
|
||||
// all that is needed on Expo SDK 43+ to configure expo-updates configuration in AndroidManifest.xml
|
||||
async function ensureUpdatesConfiguredAsync(projectDir: string): Promise<void> {
|
||||
const androidManifest = await getAndroidManifestAsync(projectDir);
|
||||
|
||||
if (!AndroidConfig.Updates.isMainApplicationMetaDataSet(androidManifest)) {
|
||||
@@ -94,14 +77,13 @@ export async function readChannelSafelyAsync(projectDir: string): Promise<string
|
||||
const androidManifest = await getAndroidManifestAsync(projectDir);
|
||||
const stringifiedRequestHeaders = AndroidConfig.Manifest.getMainApplicationMetaDataValue(
|
||||
androidManifest,
|
||||
'expo.modules.updates.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY' //TODO-JJ AndroidConfig.Updates.Config.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY once https://github.com/expo/expo-cli/pull/3571 is published
|
||||
AndroidConfig.Updates.Config.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY
|
||||
);
|
||||
if (!stringifiedRequestHeaders) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(stringifiedRequestHeaders)['expo-channel-name'] ?? null;
|
||||
} catch (err) {
|
||||
console.log({ err });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,17 @@ import { readChannelSafelyAsync } from '../UpdatesModule';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
afterAll(() => {
|
||||
afterAll(async () => {
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.removeSync(os.tmpdir());
|
||||
await fs.remove(os.tmpdir());
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
vol.reset();
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.mkdirpSync(os.tmpdir());
|
||||
await fs.mkdirp(os.tmpdir());
|
||||
});
|
||||
|
||||
const testChannel = 'test-channel';
|
||||
|
||||
@@ -1,114 +1,280 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
import assert from 'assert';
|
||||
import fs from 'fs-extra';
|
||||
import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import { readVersionCode, readVersionName } from '../version';
|
||||
import { getAppBuildGradleAsync, resolveConfigValue } from '../../../project/android/gradleUtils';
|
||||
import {
|
||||
BumpStrategy,
|
||||
bumpVersionAsync,
|
||||
bumpVersionInAppJsonAsync,
|
||||
maybeResolveVersionsAsync,
|
||||
} from '../version';
|
||||
|
||||
const fsReal = jest.requireActual('fs').promises as typeof fs;
|
||||
jest.mock('fs');
|
||||
|
||||
afterAll(() => {
|
||||
afterAll(async () => {
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.removeSync(os.tmpdir());
|
||||
await fs.remove(os.tmpdir());
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
vol.reset();
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.mkdirpSync(os.tmpdir());
|
||||
await fs.mkdirp(os.tmpdir());
|
||||
});
|
||||
|
||||
describe(readVersionCode, () => {
|
||||
describe('generic project', () => {
|
||||
it('reads the version code from native code', () => {
|
||||
const exp = initGenericProject();
|
||||
const versionCode = readVersionCode('/repo', exp);
|
||||
expect(versionCode).toBe(123);
|
||||
// bare workflow
|
||||
describe(bumpVersionAsync, () => {
|
||||
it('throws an informative error when multiple flavors are detected in the android project', async () => {
|
||||
await initProjectWithGradleFileAsync(
|
||||
/*buildGradlePath*/ path.join(
|
||||
__dirname,
|
||||
'../../../project/android/__tests__/fixtures/multiflavor-build.gradle'
|
||||
)
|
||||
);
|
||||
await expect(
|
||||
bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.VERSION_CODE,
|
||||
projectDir: '/multiflavor',
|
||||
exp: {} as any,
|
||||
})
|
||||
).rejects.toThrow(
|
||||
'Automatic version bumping is not supported for multi-flavor Android projects.'
|
||||
);
|
||||
});
|
||||
it('throws an informative error when multiple flavor dimensions are defined in an android project', async () => {
|
||||
await initProjectWithGradleFileAsync(
|
||||
/*buildGradlePath*/ path.join(
|
||||
__dirname,
|
||||
'../../../project/android/__tests__/fixtures/multiflavor-with-dimensions-build.gradle'
|
||||
)
|
||||
);
|
||||
await expect(
|
||||
bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.VERSION_CODE,
|
||||
projectDir: '/multiflavor',
|
||||
exp: {} as any,
|
||||
})
|
||||
).rejects.toThrow(
|
||||
'Automatic version bumping is not supported for multi-flavor Android projects.'
|
||||
);
|
||||
});
|
||||
it('bumps expo.android.versionCode and buildGradle versionCode when strategy = BumpStrategy.VERSION_CODE', async () => {
|
||||
const nativeVersionCode = 100; // this should be overwritten by bumpVersionAsync
|
||||
const fakeExp = initBareWorkflowProject({ versionCode: nativeVersionCode });
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.VERSION_CODE,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
expect(fakeExp.version).toBe('3.0.0');
|
||||
expect(fakeExp.android?.versionCode).toBe(124);
|
||||
expect(appJSON.expo.version).toBe('3.0.0');
|
||||
expect(appJSON.expo.android.versionCode).toBe(124);
|
||||
|
||||
const buildGradle = await getAppBuildGradleAsync('/repo');
|
||||
assert(buildGradle);
|
||||
expect(resolveConfigValue(buildGradle, 'versionCode')).toBe('124');
|
||||
expect(resolveConfigValue(buildGradle, 'versionName')).toBe('3.0.0');
|
||||
});
|
||||
|
||||
it('bumps expo.version and gradle versionCode when strategy = BumpStrategy.SHORT_VERSION', async () => {
|
||||
const nativeVersionName = '1.0.0'; // this should be overwritten by bumpVersionAsync
|
||||
const fakeExp = initBareWorkflowProject({ versionName: nativeVersionName });
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.APP_VERSION,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
expect(fakeExp.version).toBe('3.0.1');
|
||||
expect(fakeExp.android?.versionCode).toBe(123);
|
||||
expect(appJSON.expo.version).toBe('3.0.1');
|
||||
expect(appJSON.expo.android.versionCode).toBe(123);
|
||||
|
||||
const buildGradle = await getAppBuildGradleAsync('/repo');
|
||||
assert(buildGradle);
|
||||
expect(resolveConfigValue(buildGradle, 'versionCode')).toBe('123');
|
||||
expect(resolveConfigValue(buildGradle, 'versionName')).toBe('3.0.1');
|
||||
});
|
||||
|
||||
it('does not bump any version when strategy = BumpStrategy.NOOP', async () => {
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.NOOP,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
expect(fakeExp.version).toBe('3.0.0');
|
||||
expect(fakeExp.android?.versionCode).toBe(123);
|
||||
expect(appJSON.expo.version).toBe('3.0.0');
|
||||
expect(appJSON.expo.android.versionCode).toBe(123);
|
||||
|
||||
const buildGradle = await getAppBuildGradleAsync('/repo');
|
||||
assert(buildGradle);
|
||||
expect(resolveConfigValue(buildGradle, 'versionCode')).toBe('123');
|
||||
expect(resolveConfigValue(buildGradle, 'versionName')).toBe('3.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
// managed workflow
|
||||
describe(bumpVersionInAppJsonAsync, () => {
|
||||
it('bumps expo.android.versionCode when strategy = BumpStrategy.VERSION_CODE', async () => {
|
||||
const fakeExp = initManagedProject();
|
||||
|
||||
await bumpVersionInAppJsonAsync({
|
||||
bumpStrategy: BumpStrategy.VERSION_CODE,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
expect(fakeExp.version).toBe('5.0.0');
|
||||
expect(fakeExp.android?.versionCode).toBe(127);
|
||||
expect(appJSON.expo.version).toBe('5.0.0');
|
||||
expect(appJSON.expo.android.versionCode).toBe(127);
|
||||
});
|
||||
|
||||
it('bumps expo.version when strategy = BumpStrategy.SHORT_VERSION', async () => {
|
||||
const fakeExp = initManagedProject();
|
||||
|
||||
await bumpVersionInAppJsonAsync({
|
||||
bumpStrategy: BumpStrategy.APP_VERSION,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
expect(fakeExp.version).toBe('5.0.1');
|
||||
expect(fakeExp.android?.versionCode).toBe(126);
|
||||
expect(appJSON.expo.version).toBe('5.0.1');
|
||||
expect(appJSON.expo.android.versionCode).toBe(126);
|
||||
});
|
||||
|
||||
it('does not bump any version when strategy = BumpStrategy.NOOP', async () => {
|
||||
const fakeExp = initManagedProject();
|
||||
|
||||
await bumpVersionInAppJsonAsync({
|
||||
bumpStrategy: BumpStrategy.NOOP,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
expect(fakeExp.version).toBe('5.0.0');
|
||||
expect(fakeExp.android?.versionCode).toBe(126);
|
||||
expect(appJSON.expo.version).toBe('5.0.0');
|
||||
expect(appJSON.expo.android.versionCode).toBe(126);
|
||||
});
|
||||
});
|
||||
|
||||
describe(maybeResolveVersionsAsync, () => {
|
||||
describe('bare project', () => {
|
||||
it('reads the versions from native code', async () => {
|
||||
const exp = initBareWorkflowProject();
|
||||
const { appVersion, appBuildVersion } = await maybeResolveVersionsAsync(
|
||||
'/repo',
|
||||
exp,
|
||||
{} as BuildProfile<Platform.ANDROID>
|
||||
);
|
||||
expect(appVersion).toBe('3.0.0');
|
||||
expect(appBuildVersion).toBe('123');
|
||||
});
|
||||
});
|
||||
describe('managed project', () => {
|
||||
it('reads the version code from expo config', () => {
|
||||
it('reads the versions from expo config', async () => {
|
||||
const exp = initManagedProject();
|
||||
const versionCode = readVersionCode('/repo', exp);
|
||||
expect(versionCode).toBe(123);
|
||||
const { appVersion, appBuildVersion } = await maybeResolveVersionsAsync(
|
||||
'/repo',
|
||||
exp,
|
||||
{} as BuildProfile<Platform.ANDROID>
|
||||
);
|
||||
expect(appVersion).toBe('5.0.0');
|
||||
expect(appBuildVersion).toBe('126');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(readVersionName, () => {
|
||||
describe('generic project', () => {
|
||||
it('reads the version name from native code', () => {
|
||||
const exp = initGenericProject();
|
||||
const versionName = readVersionName('/repo', exp);
|
||||
expect(versionName).toBe('1.0');
|
||||
});
|
||||
});
|
||||
describe('managed project', () => {
|
||||
it('reads the version from expo config', () => {
|
||||
const exp = initManagedProject();
|
||||
const versionName = readVersionName('/repo', exp);
|
||||
expect(versionName).toBe('1.0.0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function initGenericProject(): ExpoConfig {
|
||||
function initBareWorkflowProject({
|
||||
versionCode = 123,
|
||||
versionName = '3.0.0',
|
||||
}: {
|
||||
versionCode?: number;
|
||||
versionName?: string;
|
||||
} = {}): ExpoConfig {
|
||||
const fakeExp: ExpoConfig = {
|
||||
name: 'myproject',
|
||||
slug: 'myproject',
|
||||
version: '3.0.0',
|
||||
android: {
|
||||
versionCode: 123,
|
||||
},
|
||||
};
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./app.json': JSON.stringify({
|
||||
expo: {
|
||||
version: '1.0.0',
|
||||
android: {
|
||||
versionCode: 1,
|
||||
},
|
||||
},
|
||||
expo: fakeExp,
|
||||
}),
|
||||
'./android/app/build.gradle': `android {
|
||||
defaultConfig {
|
||||
applicationId "com.expo.testapp"
|
||||
versionCode 123
|
||||
versionName "1.0"
|
||||
versionCode ${versionCode}
|
||||
versionName "${versionName}"
|
||||
}
|
||||
}`,
|
||||
'./android/app/src/main/AndroidManifest.xml': 'fake',
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
|
||||
const fakeExp: ExpoConfig = {
|
||||
name: 'myproject',
|
||||
slug: 'myproject',
|
||||
version: '1.0.0',
|
||||
android: {
|
||||
versionCode: 123,
|
||||
},
|
||||
};
|
||||
return fakeExp;
|
||||
}
|
||||
|
||||
function initManagedProject(): ExpoConfig {
|
||||
const fakeExp: ExpoConfig = {
|
||||
name: 'myproject',
|
||||
slug: 'myproject',
|
||||
version: '5.0.0',
|
||||
android: {
|
||||
versionCode: 126,
|
||||
},
|
||||
};
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./app.json': JSON.stringify({
|
||||
expo: {
|
||||
version: '1.0.0',
|
||||
android: {
|
||||
versionCode: 123,
|
||||
},
|
||||
},
|
||||
expo: fakeExp,
|
||||
}),
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
|
||||
const fakeExp: ExpoConfig = {
|
||||
name: 'myproject',
|
||||
slug: 'myproject',
|
||||
version: '1.0.0',
|
||||
android: {
|
||||
versionCode: 123,
|
||||
},
|
||||
};
|
||||
return fakeExp;
|
||||
}
|
||||
|
||||
async function initProjectWithGradleFileAsync(gradleFilePath: string): Promise<void> {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'android/app/build.gradle': await fsReal.readFile(gradleFilePath, 'utf-8'),
|
||||
'./app.json': JSON.stringify({
|
||||
expo: {} as any,
|
||||
}),
|
||||
},
|
||||
'/multiflavor'
|
||||
);
|
||||
}
|
||||
@@ -1,58 +1,39 @@
|
||||
import { Android, Metadata, Workflow } from '@expo/eas-build-job';
|
||||
import { EasConfig } from '@expo/eas-json';
|
||||
import { Android, Job, Metadata, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import nullthrows from 'nullthrows';
|
||||
import path from 'path';
|
||||
|
||||
import AndroidCredentialsProvider, {
|
||||
AndroidCredentials,
|
||||
} from '../../credentials/android/AndroidCredentialsProvider';
|
||||
import { createCredentialsContextAsync } from '../../credentials/context';
|
||||
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
ensureApplicationIdIsDefinedForManagedProjectAsync,
|
||||
getApplicationId,
|
||||
getApplicationIdAsync,
|
||||
} from '../../project/android/applicationId';
|
||||
import { resolveGradleBuildContextAsync } from '../../project/android/gradle';
|
||||
import { toggleConfirmAsync } from '../../prompts';
|
||||
import { findAccountByName } from '../../user/Account';
|
||||
import { CredentialsResult, prepareBuildRequestForPlatformAsync } from '../build';
|
||||
import { BuildContext, CommandContext, createBuildContext } from '../context';
|
||||
import {
|
||||
BuildRequestSender,
|
||||
CredentialsResult,
|
||||
JobData,
|
||||
prepareBuildRequestForPlatformAsync,
|
||||
} from '../build';
|
||||
import { AndroidBuildContext, BuildContext, CommonContext } from '../context';
|
||||
import { transformMetadata } from '../graphql';
|
||||
import { Platform } from '../types';
|
||||
import { logCredentialsSource } from '../utils/credentials';
|
||||
import { checkGoogleServicesFileAsync, checkNodeEnvVariable } from '../validate';
|
||||
import { validateAndSyncProjectConfigurationAsync } from './configure';
|
||||
import { transformGenericJob, transformManagedJob } from './graphql';
|
||||
import { transformJob } from './graphql';
|
||||
import { prepareJobAsync } from './prepareJob';
|
||||
|
||||
export async function prepareAndroidBuildAsync(
|
||||
commandCtx: CommandContext,
|
||||
easConfig: EasConfig
|
||||
): Promise<() => Promise<string | undefined>> {
|
||||
const buildCtx = createBuildContext<Platform.ANDROID>({
|
||||
commandCtx,
|
||||
platform: Platform.ANDROID,
|
||||
easConfig,
|
||||
});
|
||||
const { buildProfile } = buildCtx;
|
||||
export async function createAndroidContextAsync(
|
||||
ctx: CommonContext<Platform.ANDROID>
|
||||
): Promise<AndroidBuildContext> {
|
||||
const { buildProfile } = ctx;
|
||||
|
||||
if (
|
||||
buildProfile.workflow === Workflow.GENERIC &&
|
||||
!(await fs.pathExists(path.join(commandCtx.projectDir, 'android')))
|
||||
) {
|
||||
throw new Error(
|
||||
`"android" directory not found. If you're trying to build a managed project, set ${chalk.bold(
|
||||
`builds.android.${commandCtx.profile}.workflow`
|
||||
)} in "eas.json" to "managed".`
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
buildProfile.workflow === Workflow.GENERIC &&
|
||||
buildProfile.distribution === 'internal' &&
|
||||
buildProfile.gradleCommand?.match(/bundle/)
|
||||
) {
|
||||
if (buildProfile.distribution === 'internal' && buildProfile.gradleCommand?.match(/bundle/)) {
|
||||
Log.addNewLineIfNone();
|
||||
Log.warn(
|
||||
`You're building your Android app for internal distribution. However, we've detected that the Gradle command you defined (${chalk.underline(
|
||||
@@ -68,49 +49,59 @@ This means that it will most likely produce an AAB and you will not be able to i
|
||||
}
|
||||
}
|
||||
|
||||
if (buildCtx.buildProfile.workflow === Workflow.MANAGED) {
|
||||
await ensureApplicationIdIsDefinedForManagedProjectAsync(commandCtx.projectDir, commandCtx.exp);
|
||||
checkNodeEnvVariable(ctx);
|
||||
await checkGoogleServicesFileAsync(ctx);
|
||||
|
||||
const gradleContext = await resolveGradleBuildContextAsync(ctx.projectDir, buildProfile);
|
||||
|
||||
if (ctx.workflow === Workflow.MANAGED) {
|
||||
await ensureApplicationIdIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp);
|
||||
}
|
||||
|
||||
const applicationId = await getApplicationIdAsync(ctx.projectDir, ctx.exp, gradleContext);
|
||||
|
||||
return { applicationId, gradleContext };
|
||||
}
|
||||
|
||||
export async function prepareAndroidBuildAsync(
|
||||
ctx: BuildContext<Platform.ANDROID>
|
||||
): Promise<BuildRequestSender> {
|
||||
return await prepareBuildRequestForPlatformAsync({
|
||||
ctx: buildCtx,
|
||||
ensureCredentialsAsync: ensureAndroidCredentialsAsync,
|
||||
ctx,
|
||||
ensureCredentialsAsync: async (ctx: BuildContext<Platform.ANDROID>) => {
|
||||
return await ensureAndroidCredentialsAsync(ctx);
|
||||
},
|
||||
ensureProjectConfiguredAsync: async () => {
|
||||
if (buildCtx.buildProfile.workflow === Workflow.GENERIC) {
|
||||
await validateAndSyncProjectConfigurationAsync(commandCtx.projectDir, commandCtx.exp);
|
||||
}
|
||||
await validateAndSyncProjectConfigurationAsync({
|
||||
projectDir: ctx.projectDir,
|
||||
exp: ctx.exp,
|
||||
buildProfile: ctx.buildProfile,
|
||||
});
|
||||
},
|
||||
prepareJobAsync: async (
|
||||
ctx: BuildContext<Platform.ANDROID>,
|
||||
jobData: JobData<AndroidCredentials>
|
||||
): Promise<Job> => {
|
||||
return await prepareJobAsync(ctx, jobData);
|
||||
},
|
||||
prepareJobAsync,
|
||||
sendBuildRequestAsync: async (
|
||||
appId: string,
|
||||
job: Android.Job,
|
||||
metadata: Metadata
|
||||
): Promise<BuildResult> => {
|
||||
const graphqlMetadata = transformMetadata(metadata);
|
||||
if (job.type === Workflow.GENERIC) {
|
||||
const graphqlJob = transformGenericJob(job);
|
||||
return await BuildMutation.createAndroidGenericBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
});
|
||||
} else {
|
||||
const graphqlJob = transformManagedJob(job);
|
||||
return await BuildMutation.createAndroidManagedBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
});
|
||||
}
|
||||
const graphqlJob = transformJob(job);
|
||||
return await BuildMutation.createAndroidBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function shouldProvideCredentials(ctx: BuildContext<Platform.ANDROID>): boolean {
|
||||
return (
|
||||
ctx.buildProfile.workflow === Workflow.MANAGED ||
|
||||
(ctx.buildProfile.workflow === Workflow.GENERIC && !ctx.buildProfile.withoutCredentials)
|
||||
);
|
||||
return !ctx.buildProfile.withoutCredentials;
|
||||
}
|
||||
|
||||
async function ensureAndroidCredentialsAsync(
|
||||
@@ -119,25 +110,21 @@ async function ensureAndroidCredentialsAsync(
|
||||
if (!shouldProvideCredentials(ctx)) {
|
||||
return;
|
||||
}
|
||||
const androidApplicationIdentifier = getApplicationId(
|
||||
ctx.commandCtx.projectDir,
|
||||
ctx.commandCtx.exp
|
||||
);
|
||||
const provider = new AndroidCredentialsProvider(
|
||||
await createCredentialsContextAsync(ctx.commandCtx.projectDir, {
|
||||
nonInteractive: ctx.commandCtx.nonInteractive,
|
||||
}),
|
||||
{
|
||||
app: {
|
||||
account: nullthrows(
|
||||
findAccountByName(ctx.commandCtx.user.accounts, ctx.commandCtx.accountName),
|
||||
`You do not have access to account: ${ctx.commandCtx.accountName}`
|
||||
),
|
||||
projectName: ctx.commandCtx.projectName,
|
||||
androidApplicationIdentifier,
|
||||
},
|
||||
}
|
||||
const androidApplicationIdentifier = await getApplicationIdAsync(
|
||||
ctx.projectDir,
|
||||
ctx.exp,
|
||||
ctx.android.gradleContext
|
||||
);
|
||||
const provider = new AndroidCredentialsProvider(ctx.credentialsCtx, {
|
||||
app: {
|
||||
account: nullthrows(
|
||||
findAccountByName(ctx.user.accounts, ctx.accountName),
|
||||
`You do not have access to account: ${ctx.accountName}`
|
||||
),
|
||||
projectName: ctx.projectName,
|
||||
androidApplicationIdentifier,
|
||||
},
|
||||
});
|
||||
const { credentialsSource } = ctx.buildProfile;
|
||||
logCredentialsSource(credentialsSource, Platform.ANDROID);
|
||||
return {
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
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 Log from '../../log';
|
||||
import {
|
||||
ensureApplicationIdIsDefinedForManagedProjectAsync,
|
||||
warnIfAndroidPackageDefinedInAppConfigForGenericProject,
|
||||
warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject,
|
||||
} from '../../project/android/applicationId';
|
||||
import vcs from '../../vcs';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { ConfigureContext } from '../context';
|
||||
import { isExpoUpdatesInstalled } from '../utils/updates';
|
||||
import { configureUpdatesAsync, syncUpdatesConfigurationAsync } from './UpdatesModule';
|
||||
import { BumpStrategy, bumpVersionAsync, bumpVersionInAppJsonAsync } from './version';
|
||||
|
||||
export async function configureAndroidAsync(ctx: ConfigureContext): Promise<void> {
|
||||
if (!ctx.hasAndroidNativeProject) {
|
||||
@@ -17,12 +21,12 @@ export async function configureAndroidAsync(ctx: ConfigureContext): Promise<void
|
||||
return;
|
||||
}
|
||||
|
||||
warnIfAndroidPackageDefinedInAppConfigForGenericProject(ctx.projectDir, ctx.exp);
|
||||
warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject(ctx.projectDir, ctx.exp);
|
||||
|
||||
await AndroidConfig.EasBuild.configureEasBuildAsync(ctx.projectDir);
|
||||
|
||||
const easGradlePath = AndroidConfig.EasBuild.getEasBuildGradlePath(ctx.projectDir);
|
||||
await vcs.trackFileAsync(easGradlePath);
|
||||
await getVcsClient().trackFileAsync(easGradlePath);
|
||||
|
||||
if (isExpoUpdatesInstalled(ctx.projectDir)) {
|
||||
await configureUpdatesAsync(ctx.projectDir, ctx.exp);
|
||||
@@ -30,16 +34,42 @@ export async function configureAndroidAsync(ctx: ConfigureContext): Promise<void
|
||||
Log.withTick('Android project configured');
|
||||
}
|
||||
|
||||
export async function validateAndSyncProjectConfigurationAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
): Promise<void> {
|
||||
if (!(await AndroidConfig.EasBuild.isEasBuildGradleConfiguredAsync(projectDir))) {
|
||||
throw new Error(
|
||||
'Project is not configured. Please run "eas build:configure" to configure the project.'
|
||||
);
|
||||
export async function validateAndSyncProjectConfigurationAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
buildProfile,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
buildProfile: BuildProfile<Platform.ANDROID>;
|
||||
}): Promise<void> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
const { autoIncrement } = buildProfile;
|
||||
const versionBumpStrategy = resolveVersionBumpStrategy(autoIncrement ?? false);
|
||||
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
if (!(await AndroidConfig.EasBuild.isEasBuildGradleConfiguredAsync(projectDir))) {
|
||||
throw new Error(
|
||||
'Project is not configured. Please run "eas build:configure" to configure the project.'
|
||||
);
|
||||
}
|
||||
if (isExpoUpdatesInstalled(projectDir)) {
|
||||
await syncUpdatesConfigurationAsync(projectDir, exp);
|
||||
}
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
|
||||
} else {
|
||||
await bumpVersionInAppJsonAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
|
||||
}
|
||||
if (isExpoUpdatesInstalled(projectDir)) {
|
||||
await syncUpdatesConfigurationAsync(projectDir, exp);
|
||||
}
|
||||
|
||||
function resolveVersionBumpStrategy(autoIncrement: AndroidVersionAutoIncrement): BumpStrategy {
|
||||
if (autoIncrement === true) {
|
||||
return BumpStrategy.VERSION_CODE;
|
||||
} else if (autoIncrement === false) {
|
||||
return BumpStrategy.NOOP;
|
||||
} else if (autoIncrement === 'versionCode') {
|
||||
return BumpStrategy.VERSION_CODE;
|
||||
} else {
|
||||
return BumpStrategy.APP_VERSION;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
import { Android } from '@expo/eas-build-job';
|
||||
|
||||
import {
|
||||
AndroidGenericJobInput,
|
||||
AndroidManagedBuildType,
|
||||
AndroidManagedJobInput,
|
||||
} from '../../graphql/generated';
|
||||
import { transformProjectArchive } from '../graphql';
|
||||
import { AndroidBuildType, AndroidJobInput } from '../../graphql/generated';
|
||||
import { transformProjectArchive, transformWorkflow } from '../graphql';
|
||||
|
||||
export function transformGenericJob(job: Android.GenericJob): AndroidGenericJobInput {
|
||||
export function transformJob(job: Android.Job): AndroidJobInput {
|
||||
return {
|
||||
type: transformWorkflow(job.type),
|
||||
projectArchive: transformProjectArchive(job.projectArchive),
|
||||
projectRootDirectory: job.projectRootDirectory,
|
||||
releaseChannel: job.releaseChannel,
|
||||
@@ -18,29 +15,16 @@ export function transformGenericJob(job: Android.GenericJob): AndroidGenericJobI
|
||||
cache: job.cache,
|
||||
gradleCommand: job.gradleCommand,
|
||||
artifactPath: job.artifactPath,
|
||||
};
|
||||
}
|
||||
|
||||
export function transformManagedJob(job: Android.ManagedJob): AndroidManagedJobInput {
|
||||
return {
|
||||
projectArchive: transformProjectArchive(job.projectArchive),
|
||||
projectRootDirectory: job.projectRootDirectory,
|
||||
releaseChannel: job.releaseChannel,
|
||||
updates: job.updates,
|
||||
secrets: job.secrets,
|
||||
builderEnvironment: job.builderEnvironment,
|
||||
cache: job.cache,
|
||||
username: job.username,
|
||||
buildType: transformBuildType(job.buildType),
|
||||
buildType: job.buildType && transformBuildType(job.buildType),
|
||||
developmentClient: job.developmentClient,
|
||||
};
|
||||
}
|
||||
|
||||
function transformBuildType(buildType: Android.ManagedBuildType): AndroidManagedBuildType {
|
||||
if (buildType === Android.ManagedBuildType.APK) {
|
||||
return AndroidManagedBuildType.Apk;
|
||||
} else if (buildType === Android.ManagedBuildType.APP_BUNDLE) {
|
||||
return AndroidManagedBuildType.AppBundle;
|
||||
function transformBuildType(buildType: Android.BuildType): AndroidBuildType {
|
||||
if (buildType === Android.BuildType.APK) {
|
||||
return AndroidBuildType.Apk;
|
||||
} else {
|
||||
return AndroidManagedBuildType.DevelopmentClient;
|
||||
return AndroidBuildType.AppBundle;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,33 @@
|
||||
import { Android, ArchiveSource, Cache, Job, Workflow, sanitizeJob } from '@expo/eas-build-job';
|
||||
import { AndroidGenericBuildProfile, AndroidManagedBuildProfile } from '@expo/eas-json';
|
||||
import { Android, ArchiveSource, Job, Platform, sanitizeJob } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
import path from 'path';
|
||||
import slash from 'slash';
|
||||
|
||||
import { AndroidCredentials } from '../../credentials/android/AndroidCredentialsProvider';
|
||||
import { getUsername } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import vcs from '../../vcs';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { BuildContext } from '../context';
|
||||
import { Platform } from '../types';
|
||||
|
||||
interface JobData {
|
||||
projectArchive: ArchiveSource;
|
||||
credentials?: AndroidCredentials;
|
||||
}
|
||||
|
||||
const cacheDefaults = {
|
||||
disabled: false,
|
||||
customPaths: [],
|
||||
cacheDefaultPaths: true,
|
||||
};
|
||||
|
||||
export async function prepareJobAsync(
|
||||
ctx: BuildContext<Platform.ANDROID>,
|
||||
jobData: JobData
|
||||
): Promise<Job> {
|
||||
if (ctx.buildProfile.workflow === Workflow.GENERIC) {
|
||||
const partialJob = await prepareGenericJobAsync(ctx, jobData, ctx.buildProfile);
|
||||
return sanitizeJob(partialJob);
|
||||
} else if (ctx.buildProfile.workflow === Workflow.MANAGED) {
|
||||
const partialJob = await prepareManagedJobAsync(ctx, jobData, ctx.buildProfile);
|
||||
return sanitizeJob(partialJob);
|
||||
} else {
|
||||
throw new Error("Unknown workflow. Shouldn't happen");
|
||||
}
|
||||
}
|
||||
|
||||
interface CommonJobProperties {
|
||||
platform: Platform.ANDROID;
|
||||
projectArchive: ArchiveSource;
|
||||
builderEnvironment: Android.BuilderEnvironment;
|
||||
cache: Cache;
|
||||
secrets: {
|
||||
buildCredentials?: {
|
||||
keystore: Android.Keystore;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareJobCommonAsync(
|
||||
ctx: BuildContext<Platform.ANDROID>,
|
||||
jobData: JobData
|
||||
): Promise<Partial<CommonJobProperties>> {
|
||||
const username = getUsername(ctx.exp, await ensureLoggedInAsync());
|
||||
const buildProfile: BuildProfile<Platform.ANDROID> = ctx.buildProfile;
|
||||
const projectRootDirectory =
|
||||
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
const { credentials } = jobData;
|
||||
const buildCredentials = credentials
|
||||
? {
|
||||
@@ -59,60 +42,40 @@ async function prepareJobCommonAsync(
|
||||
}
|
||||
: {};
|
||||
|
||||
return {
|
||||
let buildType = ctx.buildProfile.buildType;
|
||||
if (!buildType && !buildProfile.gradleCommand && ctx.buildProfile.distribution === 'internal') {
|
||||
buildType = Android.BuildType.APK;
|
||||
}
|
||||
|
||||
const job: Android.Job = {
|
||||
type: ctx.workflow,
|
||||
platform: Platform.ANDROID,
|
||||
projectRootDirectory,
|
||||
projectArchive: jobData.projectArchive,
|
||||
builderEnvironment: {
|
||||
image: ctx.buildProfile.image,
|
||||
node: ctx.buildProfile.node,
|
||||
yarn: ctx.buildProfile.yarn,
|
||||
ndk: ctx.buildProfile.ndk,
|
||||
expoCli: ctx.buildProfile.expoCli,
|
||||
env: ctx.buildProfile.env,
|
||||
image: buildProfile.image,
|
||||
node: buildProfile.node,
|
||||
yarn: buildProfile.yarn,
|
||||
ndk: buildProfile.ndk,
|
||||
expoCli: buildProfile.expoCli,
|
||||
env: buildProfile.env,
|
||||
},
|
||||
cache: {
|
||||
...ctx.buildProfile.cache,
|
||||
clear: ctx.commandCtx.clearCache,
|
||||
...cacheDefaults,
|
||||
...buildProfile.cache,
|
||||
clear: ctx.clearCache,
|
||||
},
|
||||
secrets: {
|
||||
...buildCredentials,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareGenericJobAsync(
|
||||
ctx: BuildContext<Platform.ANDROID>,
|
||||
jobData: JobData,
|
||||
buildProfile: AndroidGenericBuildProfile
|
||||
): Promise<Partial<Android.GenericJob>> {
|
||||
const projectRootDirectory =
|
||||
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
|
||||
return {
|
||||
...(await prepareJobCommonAsync(ctx, jobData)),
|
||||
type: Workflow.GENERIC,
|
||||
releaseChannel: ctx.buildProfile.releaseChannel,
|
||||
updates: { channel: ctx.buildProfile.channel },
|
||||
developmentClient: buildProfile.developmentClient,
|
||||
gradleCommand: buildProfile.gradleCommand,
|
||||
artifactPath: buildProfile.artifactPath,
|
||||
releaseChannel: buildProfile.releaseChannel,
|
||||
updates: { channel: buildProfile.channel },
|
||||
projectRootDirectory,
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareManagedJobAsync(
|
||||
ctx: BuildContext<Platform.ANDROID>,
|
||||
jobData: JobData,
|
||||
buildProfile: AndroidManagedBuildProfile
|
||||
): Promise<Partial<Android.ManagedJob>> {
|
||||
const projectRootDirectory =
|
||||
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
|
||||
const username = getUsername(ctx.commandCtx.exp, await ensureLoggedInAsync());
|
||||
return {
|
||||
...(await prepareJobCommonAsync(ctx, jobData)),
|
||||
type: Workflow.MANAGED,
|
||||
buildType,
|
||||
username,
|
||||
buildType: buildProfile.buildType,
|
||||
releaseChannel: buildProfile.releaseChannel,
|
||||
updates: { channel: buildProfile.channel },
|
||||
projectRootDirectory,
|
||||
};
|
||||
|
||||
return sanitizeJob(job);
|
||||
}
|
||||
@@ -1,44 +1,153 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { AndroidConfig } 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 { resolveWorkflow } from '../../project/workflow';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
getAppBuildGradleAsync,
|
||||
parseGradleCommand,
|
||||
resolveConfigValue,
|
||||
} from '../../project/android/gradleUtils';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
|
||||
|
||||
export function readVersionCode(projectDir: string, exp: ExpoConfig): number | undefined {
|
||||
const workflow = resolveWorkflow(projectDir, Platform.ANDROID);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const buildGradle = readBuildGradle(projectDir);
|
||||
const matchResult = buildGradle?.match(/versionCode (.*)/);
|
||||
if (matchResult) {
|
||||
return Number(matchResult[1]);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
export enum BumpStrategy {
|
||||
APP_VERSION,
|
||||
VERSION_CODE,
|
||||
NOOP,
|
||||
}
|
||||
|
||||
export async function bumpVersionAsync({
|
||||
bumpStrategy,
|
||||
projectDir,
|
||||
exp,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
bumpStrategy: BumpStrategy;
|
||||
}): Promise<void> {
|
||||
if (bumpStrategy === BumpStrategy.NOOP) {
|
||||
return;
|
||||
}
|
||||
ensureStaticConfigExists(projectDir);
|
||||
|
||||
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
||||
const isMultiFlavor =
|
||||
buildGradle.android?.productFlavors || buildGradle.android?.flavorDimensions;
|
||||
if (isMultiFlavor) {
|
||||
throw new Error(
|
||||
'Automatic version bumping is not supported for multi-flavor Android projects.'
|
||||
);
|
||||
}
|
||||
|
||||
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
|
||||
Log.log('Updated versions in app.json');
|
||||
await writeVersionsToBuildGradleAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
});
|
||||
Log.log('Synchronized versions with build gradle');
|
||||
}
|
||||
|
||||
export async function bumpVersionInAppJsonAsync({
|
||||
bumpStrategy,
|
||||
projectDir,
|
||||
exp,
|
||||
}: {
|
||||
bumpStrategy: BumpStrategy;
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
}): Promise<void> {
|
||||
if (bumpStrategy === BumpStrategy.NOOP) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureStaticConfigExists(projectDir);
|
||||
Log.addNewLineIfNone();
|
||||
|
||||
if (bumpStrategy === BumpStrategy.APP_VERSION) {
|
||||
const appVersion = AndroidConfig.Version.getVersionName(exp) ?? '1.0.0';
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp });
|
||||
} else {
|
||||
return AndroidConfig.Version.getVersionCode(exp) ?? undefined;
|
||||
const versionCode = AndroidConfig.Version.getVersionCode(exp);
|
||||
const bumpedVersionCode = versionCode + 1;
|
||||
Log.log(
|
||||
`Bumping ${chalk.bold('expo.android.versionCode')} from ${chalk.bold(
|
||||
versionCode
|
||||
)} to ${chalk.bold(bumpedVersionCode)}`
|
||||
);
|
||||
await updateAppJsonConfigAsync({ projectDir, exp }, config => {
|
||||
config.android = { ...config.android, versionCode: bumpedVersionCode };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function readVersionName(projectDir: string, exp: ExpoConfig): string | undefined {
|
||||
const workflow = resolveWorkflow(projectDir, Platform.ANDROID);
|
||||
export async function maybeResolveVersionsAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig,
|
||||
buildProfile: BuildProfile<Platform.ANDROID>
|
||||
): Promise<{ appVersion?: string; appBuildVersion?: string }> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const buildGradle = readBuildGradle(projectDir);
|
||||
const matchResult = buildGradle?.match(/versionName ["'](.*)["']/);
|
||||
if (matchResult) {
|
||||
return matchResult[1];
|
||||
} else {
|
||||
return undefined;
|
||||
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
||||
try {
|
||||
const parsedGradleCommand = buildProfile.gradleCommand
|
||||
? parseGradleCommand(buildProfile.gradleCommand, buildGradle)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
appVersion:
|
||||
resolveConfigValue(buildGradle, 'versionName', parsedGradleCommand?.flavor) ?? '1.0.0',
|
||||
appBuildVersion:
|
||||
resolveConfigValue(buildGradle, 'versionCode', parsedGradleCommand?.flavor) ?? '1',
|
||||
};
|
||||
} catch (err: any) {
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
return exp.version;
|
||||
return {
|
||||
appBuildVersion: String(AndroidConfig.Version.getVersionCode(exp)),
|
||||
appVersion: exp.version,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function readBuildGradle(projectDir: string): string | undefined {
|
||||
async function writeVersionsToBuildGradleAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
}): Promise<string> {
|
||||
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);
|
||||
await writeBuildGradleAsync({ projectDir, buildGradle: updatedBuildGradle });
|
||||
return updatedBuildGradle;
|
||||
}
|
||||
|
||||
async function readBuildGradleAsync(projectDir: string): Promise<string | undefined> {
|
||||
const buildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(projectDir);
|
||||
if (!fs.pathExistsSync(buildGradlePath)) {
|
||||
if (!(await fs.pathExists(buildGradlePath))) {
|
||||
return undefined;
|
||||
}
|
||||
return fs.readFileSync(buildGradlePath, 'utf8');
|
||||
return await fs.readFile(buildGradlePath, 'utf8');
|
||||
}
|
||||
|
||||
async function writeBuildGradleAsync({
|
||||
projectDir,
|
||||
buildGradle,
|
||||
}: {
|
||||
projectDir: string;
|
||||
buildGradle: string;
|
||||
}): Promise<void> {
|
||||
const buildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(projectDir);
|
||||
await fs.writeFile(buildGradlePath, buildGradle);
|
||||
}
|
||||
+163
-113
@@ -1,23 +1,26 @@
|
||||
import { ArchiveSource, ArchiveSourceType, Job, Metadata } from '@expo/eas-build-job';
|
||||
import { ArchiveSource, ArchiveSourceType, Job, Metadata, Platform } from '@expo/eas-build-job';
|
||||
import { CredentialsSource } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { UploadSessionType } from '../graphql/generated';
|
||||
import { withAnalyticsAsync } from '../analytics/common';
|
||||
import { BuildEvent } from '../analytics/events';
|
||||
import { BuildFragment, BuildStatus, UploadSessionType } from '../graphql/generated';
|
||||
import { BuildResult } from '../graphql/mutations/BuildMutation';
|
||||
import Log from '../log';
|
||||
import { promptAsync } from '../prompts';
|
||||
import { BuildQuery } from '../graphql/queries/BuildQuery';
|
||||
import Log, { learnMore } from '../log';
|
||||
import { ora } from '../ora';
|
||||
import { requestedPlatformDisplayNames } from '../platform';
|
||||
import { uploadAsync } from '../uploads';
|
||||
import { formatBytes } from '../utils/files';
|
||||
import { createProgressTracker } from '../utils/progress';
|
||||
import vcs from '../vcs';
|
||||
import { requestedPlatformDisplayNames } from './constants';
|
||||
import { sleepAsync } from '../utils/promise';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import { BuildContext } from './context';
|
||||
import { runLocalBuildAsync } from './local';
|
||||
import { collectMetadata } from './metadata';
|
||||
import { Platform, TrackingContext } from './types';
|
||||
import Analytics, { Event } from './utils/analytics';
|
||||
import { collectMetadataAsync } from './metadata';
|
||||
import { printDeprecationWarnings } from './utils/printBuildInfo';
|
||||
import { commitPromptAsync, makeProjectTarballAsync } from './utils/repository';
|
||||
import { makeProjectTarballAsync, reviewAndCommitChangesAsync } from './utils/repository';
|
||||
|
||||
export interface CredentialsResult<Credentials> {
|
||||
source: CredentialsSource.LOCAL | CredentialsSource.REMOTE;
|
||||
@@ -40,68 +43,81 @@ interface Builder<TPlatform extends Platform, Credentials, TJob extends Job> {
|
||||
sendBuildRequestAsync(appId: string, job: TJob, metadata: Metadata): Promise<BuildResult>;
|
||||
}
|
||||
|
||||
export type BuildRequestSender = () => Promise<BuildFragment | undefined>;
|
||||
|
||||
export async function prepareBuildRequestForPlatformAsync<
|
||||
TPlatform extends Platform,
|
||||
Credentials,
|
||||
TJob extends Job
|
||||
>(builder: Builder<TPlatform, Credentials, TJob>): Promise<() => Promise<string | undefined>> {
|
||||
>(builder: Builder<TPlatform, Credentials, TJob>): Promise<BuildRequestSender> {
|
||||
const { ctx } = builder;
|
||||
const credentialsResult = await withAnalyticsAsync(
|
||||
async () => await builder.ensureCredentialsAsync(builder.ctx),
|
||||
async () => await builder.ensureCredentialsAsync(ctx),
|
||||
{
|
||||
successEvent: Event.GATHER_CREDENTIALS_SUCCESS,
|
||||
failureEvent: Event.GATHER_CREDENTIALS_FAIL,
|
||||
trackingCtx: builder.ctx.trackingCtx,
|
||||
attemptEvent: BuildEvent.GATHER_CREDENTIALS_ATTEMPT,
|
||||
successEvent: BuildEvent.GATHER_CREDENTIALS_SUCCESS,
|
||||
failureEvent: BuildEvent.GATHER_CREDENTIALS_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
}
|
||||
);
|
||||
if (!builder.ctx.commandCtx.skipProjectConfiguration) {
|
||||
await withAnalyticsAsync(async () => await builder.ensureProjectConfiguredAsync(builder.ctx), {
|
||||
successEvent: Event.CONFIGURE_PROJECT_SUCCESS,
|
||||
failureEvent: Event.CONFIGURE_PROJECT_FAIL,
|
||||
trackingCtx: builder.ctx.trackingCtx,
|
||||
if (!ctx.skipProjectConfiguration) {
|
||||
await withAnalyticsAsync(async () => await builder.ensureProjectConfiguredAsync(ctx), {
|
||||
attemptEvent: BuildEvent.CONFIGURE_PROJECT_ATTEMPT,
|
||||
successEvent: BuildEvent.CONFIGURE_PROJECT_SUCCESS,
|
||||
failureEvent: BuildEvent.CONFIGURE_PROJECT_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
});
|
||||
}
|
||||
|
||||
if (await vcs.hasUncommittedChangesAsync()) {
|
||||
if (await getVcsClient().isCommitRequiredAsync()) {
|
||||
Log.addNewLineIfNone();
|
||||
await reviewAndCommitChangesAsync(
|
||||
`[EAS Build] Run EAS Build for ${
|
||||
requestedPlatformDisplayNames[builder.ctx.platform as Platform]
|
||||
}`,
|
||||
{
|
||||
nonInteractive: builder.ctx.commandCtx.nonInteractive,
|
||||
}
|
||||
`[EAS Build] Run EAS Build for ${requestedPlatformDisplayNames[ctx.platform as Platform]}`,
|
||||
{ nonInteractive: ctx.nonInteractive }
|
||||
);
|
||||
}
|
||||
|
||||
const projectArchive = builder.ctx.commandCtx.local
|
||||
const projectArchive = ctx.localBuildOptions.enable
|
||||
? ({
|
||||
type: ArchiveSourceType.PATH,
|
||||
path: (await makeProjectTarballAsync()).path,
|
||||
} as const)
|
||||
: ({
|
||||
type: ArchiveSourceType.S3,
|
||||
bucketKey: await uploadProjectAsync(builder.ctx),
|
||||
bucketKey: await uploadProjectAsync(ctx),
|
||||
} as const);
|
||||
|
||||
const metadata = await collectMetadata(builder.ctx, {
|
||||
credentialsSource: credentialsResult?.source,
|
||||
});
|
||||
const job = await builder.prepareJobAsync(builder.ctx, {
|
||||
const metadata = await collectMetadataAsync(ctx);
|
||||
const job = await builder.prepareJobAsync(ctx, {
|
||||
projectArchive,
|
||||
credentials: credentialsResult?.credentials,
|
||||
});
|
||||
|
||||
return async () => {
|
||||
if (builder.ctx.commandCtx.local) {
|
||||
await runLocalBuildAsync(job);
|
||||
if (ctx.localBuildOptions.enable) {
|
||||
await runLocalBuildAsync(job, ctx.localBuildOptions);
|
||||
return undefined;
|
||||
} else {
|
||||
try {
|
||||
return await sendBuildRequestAsync(builder, job, metadata);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
if (error?.graphQLErrors?.[0]?.extensions?.errorCode === 'TURTLE_DEPRECATED_JOB_FORMAT') {
|
||||
Log.error('EAS Build API has changed, please upgrade to the latest eas-cli version.');
|
||||
throw new Error('Build request failed.');
|
||||
} else if (
|
||||
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_DOWN_FOR_MAINTENANCE'
|
||||
) {
|
||||
Log.error(
|
||||
'EAS Build is down for maintenance, please try again later. Check https://status.expo.dev/ for updates.'
|
||||
);
|
||||
throw new Error('Build request failed.');
|
||||
} else if (
|
||||
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_FREE_TIER_DISABLED'
|
||||
) {
|
||||
Log.error(
|
||||
'EAS Build free tier is temporarily disabled, please try again later. Check https://status.expo.dev/ for updates.'
|
||||
);
|
||||
throw new Error('Build request failed.');
|
||||
} else if (
|
||||
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_TOO_MANY_PENDING_BUILDS'
|
||||
) {
|
||||
@@ -141,14 +157,18 @@ async function uploadProjectAsync<TPlatform extends Platform>(
|
||||
`Uploading to EAS Build (${formatBytes(projectTarball.size * ratio)} / ${formatBytes(
|
||||
projectTarball.size
|
||||
)})`,
|
||||
completedMessage: `Uploaded to EAS`,
|
||||
completedMessage: (duration: string) =>
|
||||
`Uploaded to EAS ${chalk.dim(duration)} ${learnMore(
|
||||
'https://expo.fyi/eas-build-archive'
|
||||
)}`,
|
||||
})
|
||||
);
|
||||
return bucketKey;
|
||||
},
|
||||
{
|
||||
successEvent: Event.PROJECT_UPLOAD_SUCCESS,
|
||||
failureEvent: Event.PROJECT_UPLOAD_FAIL,
|
||||
attemptEvent: BuildEvent.PROJECT_UPLOAD_ATTEMPT,
|
||||
successEvent: BuildEvent.PROJECT_UPLOAD_SUCCESS,
|
||||
failureEvent: BuildEvent.PROJECT_UPLOAD_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
}
|
||||
);
|
||||
@@ -163,7 +183,7 @@ async function sendBuildRequestAsync<TPlatform extends Platform, Credentials, TJ
|
||||
builder: Builder<TPlatform, Credentials, TJob>,
|
||||
job: TJob,
|
||||
metadata: Metadata
|
||||
): Promise<string> {
|
||||
): Promise<BuildFragment> {
|
||||
const { ctx } = builder;
|
||||
return await withAnalyticsAsync(
|
||||
async () => {
|
||||
@@ -172,91 +192,121 @@ async function sendBuildRequestAsync<TPlatform extends Platform, Credentials, TJ
|
||||
}
|
||||
|
||||
const { build, deprecationInfo } = await builder.sendBuildRequestAsync(
|
||||
ctx.commandCtx.projectId,
|
||||
ctx.projectId,
|
||||
job,
|
||||
metadata
|
||||
);
|
||||
|
||||
printDeprecationWarnings(deprecationInfo);
|
||||
return build.id;
|
||||
return build;
|
||||
},
|
||||
{
|
||||
successEvent: Event.BUILD_REQUEST_SUCCESS,
|
||||
failureEvent: Event.BUILD_REQUEST_FAIL,
|
||||
attemptEvent: BuildEvent.BUILD_REQUEST_ATTEMPT,
|
||||
successEvent: BuildEvent.BUILD_REQUEST_SUCCESS,
|
||||
failureEvent: BuildEvent.BUILD_REQUEST_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function withAnalyticsAsync<Result>(
|
||||
fn: () => Promise<Result>,
|
||||
analytics: {
|
||||
successEvent: Event;
|
||||
failureEvent: Event;
|
||||
trackingCtx: TrackingContext;
|
||||
}
|
||||
): Promise<Result> {
|
||||
try {
|
||||
const result = await fn();
|
||||
Analytics.logEvent(analytics.successEvent, analytics.trackingCtx);
|
||||
return result;
|
||||
} catch (error) {
|
||||
Analytics.logEvent(analytics.failureEvent, {
|
||||
...analytics.trackingCtx,
|
||||
reason: error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
enum ShouldCommitChanges {
|
||||
Yes,
|
||||
ShowDiffFirst,
|
||||
Abort,
|
||||
}
|
||||
|
||||
async function reviewAndCommitChangesAsync(
|
||||
initialCommitMessage: string,
|
||||
{ nonInteractive, askedFirstTime = true }: { nonInteractive: boolean; askedFirstTime?: boolean }
|
||||
): Promise<void> {
|
||||
if (process.env.EAS_BUILD_AUTOCOMMIT) {
|
||||
await vcs.commitAsync({ commitMessage: initialCommitMessage, commitAllFiles: false });
|
||||
Log.withTick('Committed changes.');
|
||||
return;
|
||||
}
|
||||
if (nonInteractive) {
|
||||
throw new Error(
|
||||
'Cannot commit changes when --non-interactive is specified. Run the command in interactive mode or set EAS_BUILD_AUTOCOMMIT=1 in your environment.'
|
||||
export async function waitForBuildEndAsync(
|
||||
buildIds: string[],
|
||||
{ timeoutSec = 3600, intervalSec = 30 } = {}
|
||||
): Promise<(BuildFragment | null)[]> {
|
||||
Log.log(
|
||||
`Waiting for build${buildIds.length > 1 ? 's' : ''} to complete. You can press Ctrl+C to exit.`
|
||||
);
|
||||
const spinner = ora().start();
|
||||
let time = new Date().getTime();
|
||||
const endTime = time + timeoutSec * 1000;
|
||||
while (time <= endTime) {
|
||||
const builds: (BuildFragment | null)[] = await Promise.all(
|
||||
buildIds.map(async buildId => {
|
||||
try {
|
||||
return await BuildQuery.byIdAsync(buildId, { useCache: false });
|
||||
} catch (err) {
|
||||
Log.debug('Failed to fetch the build status', err);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
if (builds.length === 1) {
|
||||
const [build] = builds;
|
||||
if (build !== null) {
|
||||
switch (build.status) {
|
||||
case BuildStatus.Finished:
|
||||
spinner.succeed('Build finished');
|
||||
return builds;
|
||||
case BuildStatus.New:
|
||||
spinner.text = 'Build created';
|
||||
break;
|
||||
case BuildStatus.InQueue:
|
||||
spinner.text = 'Build queued...';
|
||||
break;
|
||||
case BuildStatus.Canceled:
|
||||
spinner.text = 'Build canceled';
|
||||
spinner.stopAndPersist();
|
||||
return builds;
|
||||
case BuildStatus.InProgress:
|
||||
spinner.text = 'Build in progress...';
|
||||
break;
|
||||
case BuildStatus.Errored:
|
||||
spinner.fail('Build failed');
|
||||
if (build.error) {
|
||||
return builds;
|
||||
} else {
|
||||
throw new Error(`Standalone build failed!`);
|
||||
}
|
||||
default:
|
||||
spinner.warn('Unknown status');
|
||||
throw new Error(`Unknown build status: ${build.status} - aborting!`);
|
||||
}
|
||||
} else {
|
||||
if (!spinner.text) {
|
||||
spinner.text = 'Could not fetch the build status. Check your network connection.';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (builds.filter(build => build?.status === BuildStatus.Finished).length === builds.length) {
|
||||
spinner.succeed('All builds have finished');
|
||||
return builds;
|
||||
} else if (
|
||||
builds.filter(build =>
|
||||
build?.status
|
||||
? [BuildStatus.Finished, BuildStatus.Errored, BuildStatus.Canceled].includes(
|
||||
build.status
|
||||
)
|
||||
: false
|
||||
).length === builds.length
|
||||
) {
|
||||
spinner.fail('Some of the builds were canceled or failed.');
|
||||
return builds;
|
||||
} else {
|
||||
const newBuilds = builds.filter(build => build?.status === BuildStatus.New).length;
|
||||
const inQueue = builds.filter(build => build?.status === BuildStatus.InQueue).length;
|
||||
const inProgress = builds.filter(build => build?.status === BuildStatus.InProgress).length;
|
||||
const errored = builds.filter(build => build?.status === BuildStatus.Errored).length;
|
||||
const finished = builds.filter(build => build?.status === BuildStatus.Finished).length;
|
||||
const canceled = builds.filter(build => build?.status === BuildStatus.Canceled).length;
|
||||
const unknown = builds.length - newBuilds - inQueue - inProgress - errored - finished;
|
||||
spinner.text = [
|
||||
newBuilds && `Builds created: ${newBuilds}`,
|
||||
inQueue && `Builds in queue: ${inQueue}`,
|
||||
inProgress && `Builds in progress: ${inProgress}`,
|
||||
canceled && `Builds canceled: ${canceled}`,
|
||||
errored && chalk.red(`Builds failed: ${errored}`),
|
||||
finished && chalk.green(`Builds finished: ${finished}`),
|
||||
unknown && chalk.red(`Builds with unknown status: ${unknown}`),
|
||||
]
|
||||
.filter(i => i)
|
||||
.join('\t');
|
||||
}
|
||||
}
|
||||
time = new Date().getTime();
|
||||
await sleepAsync(intervalSec * 1000);
|
||||
}
|
||||
const { selected } = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'selected',
|
||||
message: 'Can we commit these changes to git for you?',
|
||||
choices: [
|
||||
{ title: 'Yes', value: ShouldCommitChanges.Yes },
|
||||
...(askedFirstTime
|
||||
? [{ title: 'Show the diff and ask me again', value: ShouldCommitChanges.ShowDiffFirst }]
|
||||
: []),
|
||||
{
|
||||
title: 'Abort build process',
|
||||
value: ShouldCommitChanges.Abort,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (selected === ShouldCommitChanges.Abort) {
|
||||
throw new Error(
|
||||
"Aborting, run the command again once you're ready. Make sure to commit any changes you've made."
|
||||
);
|
||||
} else if (selected === ShouldCommitChanges.Yes) {
|
||||
await commitPromptAsync({ initialCommitMessage });
|
||||
Log.withTick('Committed changes.');
|
||||
} else if (selected === ShouldCommitChanges.ShowDiffFirst) {
|
||||
await vcs.showDiffAsync();
|
||||
await reviewAndCommitChangesAsync(initialCommitMessage, {
|
||||
nonInteractive,
|
||||
askedFirstTime: false,
|
||||
});
|
||||
}
|
||||
spinner.warn('Timed out');
|
||||
throw new Error(
|
||||
'Timeout reached! It is taking longer than expected to finish the build, aborting...'
|
||||
);
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { EasJson, EasJsonReader } from '@expo/eas-json';
|
||||
import { Errors } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import Log from '../log';
|
||||
import { promptAsync } from '../prompts';
|
||||
import Log, { learnMore } from '../log';
|
||||
import { RequestedPlatform } from '../platform';
|
||||
import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { confirmAsync, promptAsync } from '../prompts';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import vcs from '../vcs';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import { configureAndroidAsync } from './android/configure';
|
||||
import { ConfigureContext } from './context';
|
||||
import { configureIosAsync } from './ios/configure';
|
||||
import { RequestedPlatform } from './types';
|
||||
import { commitPromptAsync, maybeBailOnRepoStatusAsync } from './utils/repository';
|
||||
|
||||
const configureCommitMessage = {
|
||||
@@ -19,11 +23,41 @@ const configureCommitMessage = {
|
||||
[RequestedPlatform.All]: 'Configure EAS Build',
|
||||
};
|
||||
|
||||
export async function ensureProjectConfiguredAsync(
|
||||
projectDir: string,
|
||||
requestedPlatform: RequestedPlatform
|
||||
): Promise<void> {
|
||||
if (await fs.pathExists(EasJsonReader.formatEasJsonPath(projectDir))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = 'This project is not configured to build with EAS. Set it up now?';
|
||||
const confirm = await confirmAsync({ message });
|
||||
if (confirm) {
|
||||
await configureAsync({
|
||||
projectDir,
|
||||
platform: requestedPlatform,
|
||||
});
|
||||
if (await getVcsClient().isCommitRequiredAsync()) {
|
||||
Errors.error(
|
||||
'Build process requires clean working tree, please commit all your changes and run `eas build` again',
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Errors.error(
|
||||
`Aborting, please run ${chalk.bold('eas build:configure')} or create eas.json (${learnMore(
|
||||
'https://docs.expo.dev/build/eas-json'
|
||||
)})`,
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureAsync(options: {
|
||||
platform: RequestedPlatform;
|
||||
projectDir: string;
|
||||
}): Promise<void> {
|
||||
await vcs.ensureRepoExistsAsync();
|
||||
await maybeBailOnRepoStatusAsync();
|
||||
|
||||
const { exp } = getConfig(options.projectDir, { skipSDKVersionRequirement: true });
|
||||
@@ -37,8 +71,10 @@ export async function configureAsync(options: {
|
||||
options.platform
|
||||
),
|
||||
shouldConfigureIos: [RequestedPlatform.All, RequestedPlatform.Ios].includes(options.platform),
|
||||
hasAndroidNativeProject: await fs.pathExists(path.join(options.projectDir, 'android')),
|
||||
hasIosNativeProject: await fs.pathExists(path.join(options.projectDir, 'ios')),
|
||||
hasAndroidNativeProject:
|
||||
(await resolveWorkflowAsync(options.projectDir, Platform.ANDROID)) === Workflow.GENERIC,
|
||||
hasIosNativeProject:
|
||||
(await resolveWorkflowAsync(options.projectDir, Platform.IOS)) === Workflow.GENERIC,
|
||||
};
|
||||
|
||||
Log.newLine();
|
||||
@@ -50,94 +86,77 @@ export async function configureAsync(options: {
|
||||
await configureIosAsync(ctx);
|
||||
}
|
||||
|
||||
if (await vcs.hasUncommittedChangesAsync()) {
|
||||
if (await getVcsClient().isCommitRequiredAsync()) {
|
||||
Log.newLine();
|
||||
await reviewAndCommitChangesAsync(configureCommitMessage[options.platform]);
|
||||
} else {
|
||||
} else if (!(await getVcsClient().hasUncommittedChangesAsync())) {
|
||||
Log.newLine();
|
||||
Log.withTick('No changes were necessary, the project is already configured correctly.');
|
||||
}
|
||||
}
|
||||
|
||||
const MANAGED_DEFAULTS = {
|
||||
release: {
|
||||
workflow: 'managed',
|
||||
const EAS_JSON_MANAGED_DEFAULT: EasJson = {
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
},
|
||||
development: {
|
||||
workflow: 'managed',
|
||||
buildType: 'development-client',
|
||||
distribution: 'internal',
|
||||
build: {
|
||||
development: {
|
||||
developmentClient: true,
|
||||
distribution: 'internal',
|
||||
},
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
},
|
||||
production: {},
|
||||
},
|
||||
submit: {
|
||||
production: {},
|
||||
},
|
||||
};
|
||||
const ANDROID_GENERIC_DEFAULTS = {
|
||||
release: {
|
||||
workflow: 'generic',
|
||||
|
||||
const EAS_JSON_BARE_DEFAULT: EasJson = {
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
},
|
||||
development: {
|
||||
workflow: 'generic',
|
||||
gradleCommand: ':app:assembleDebug',
|
||||
distribution: 'internal',
|
||||
build: {
|
||||
development: {
|
||||
distribution: 'internal',
|
||||
android: {
|
||||
gradleCommand: ':app:assembleDebug',
|
||||
},
|
||||
ios: {
|
||||
buildConfiguration: 'Debug',
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
},
|
||||
production: {},
|
||||
},
|
||||
};
|
||||
const IOS_GENERIC_DEFAULTS = {
|
||||
release: {
|
||||
workflow: 'generic',
|
||||
},
|
||||
development: {
|
||||
workflow: 'generic',
|
||||
schemeBuildConfiguration: 'Debug',
|
||||
distribution: 'internal',
|
||||
submit: {
|
||||
production: {},
|
||||
},
|
||||
};
|
||||
|
||||
export async function ensureEasJsonExistsAsync(ctx: ConfigureContext): Promise<void> {
|
||||
const easJsonPath = path.join(ctx.projectDir, 'eas.json');
|
||||
let existingEasJson;
|
||||
const easJsonPath = EasJsonReader.formatEasJsonPath(ctx.projectDir);
|
||||
|
||||
if (await fs.pathExists(easJsonPath)) {
|
||||
const reader = new EasJsonReader(ctx.projectDir, ctx.requestedPlatform);
|
||||
await reader.validateAsync();
|
||||
const reader = new EasJsonReader(ctx.projectDir);
|
||||
await reader.readAsync();
|
||||
|
||||
existingEasJson = await reader.readRawAsync();
|
||||
Log.withTick('Validated eas.json');
|
||||
|
||||
// If we have already populated eas.json with the default fields for the
|
||||
// platform then proceed
|
||||
if (ctx.requestedPlatform !== 'all' && existingEasJson.builds[ctx.requestedPlatform]) {
|
||||
return;
|
||||
} else if (
|
||||
ctx.requestedPlatform === 'all' &&
|
||||
existingEasJson.builds.ios &&
|
||||
existingEasJson.builds.android
|
||||
) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldInitIOS =
|
||||
['all', 'ios'].includes(ctx.requestedPlatform) && !existingEasJson?.builds.ios;
|
||||
const shouldInitAndroid =
|
||||
['all', 'android'].includes(ctx.requestedPlatform) && !existingEasJson?.builds.android;
|
||||
|
||||
const easJson = {
|
||||
builds: {
|
||||
...existingEasJson,
|
||||
...(shouldInitAndroid
|
||||
? {
|
||||
android: ctx.hasAndroidNativeProject ? ANDROID_GENERIC_DEFAULTS : MANAGED_DEFAULTS,
|
||||
}
|
||||
: null),
|
||||
...(shouldInitIOS
|
||||
? {
|
||||
ios: ctx.hasIosNativeProject ? IOS_GENERIC_DEFAULTS : MANAGED_DEFAULTS,
|
||||
}
|
||||
: null),
|
||||
},
|
||||
};
|
||||
const easJson =
|
||||
ctx.hasAndroidNativeProject && ctx.hasIosNativeProject
|
||||
? EAS_JSON_BARE_DEFAULT
|
||||
: EAS_JSON_MANAGED_DEFAULT;
|
||||
|
||||
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
|
||||
await vcs.trackFileAsync(easJsonPath);
|
||||
Log.withTick(`${existingEasJson ? 'Updated' : 'Generated'} eas.json`);
|
||||
await getVcsClient().trackFileAsync(easJsonPath);
|
||||
Log.withTick('Generated eas.json');
|
||||
}
|
||||
|
||||
enum ShouldCommitChanges {
|
||||
@@ -170,7 +189,7 @@ async function reviewAndCommitChangesAsync(
|
||||
await commitPromptAsync({ initialCommitMessage });
|
||||
Log.withTick('Committed changes');
|
||||
} else if (selected === ShouldCommitChanges.ShowDiffFirst) {
|
||||
await vcs.showDiffAsync();
|
||||
await getVcsClient().showDiffAsync();
|
||||
await reviewAndCommitChangesAsync(initialCommitMessage, false);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { AppPlatform } from '../graphql/generated';
|
||||
import { RequestedPlatform } from './types';
|
||||
|
||||
export const requestedPlatformDisplayNames: Record<RequestedPlatform, string> = {
|
||||
[RequestedPlatform.Ios]: 'iOS',
|
||||
[RequestedPlatform.Android]: 'Android',
|
||||
[RequestedPlatform.All]: 'Android and iOS',
|
||||
};
|
||||
|
||||
export const appPlatformDisplayNames: Record<AppPlatform, string> = {
|
||||
[AppPlatform.Android]: 'Android',
|
||||
[AppPlatform.Ios]: 'iOS',
|
||||
};
|
||||
|
||||
export const appPlatformEmojis = {
|
||||
[AppPlatform.Ios]: '🍎',
|
||||
[AppPlatform.Android]: '🤖',
|
||||
};
|
||||
@@ -1,76 +1,16 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Workflow } from '@expo/eas-build-job';
|
||||
import { AndroidBuildProfile, EasConfig, IosBuildProfile } from '@expo/eas-json';
|
||||
import JsonFile from '@expo/json-file';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { getProjectAccountName } from '../project/projectUtils';
|
||||
import { findAccountByName } from '../user/Account';
|
||||
import { TrackingContext } from '../analytics/common';
|
||||
import { CredentialsContext } from '../credentials/context';
|
||||
import { Target } from '../credentials/ios/types';
|
||||
import { RequestedPlatform } from '../platform';
|
||||
import { GradleBuildContext } from '../project/android/gradle';
|
||||
import { XcodeBuildContext } from '../project/ios/scheme';
|
||||
import { Actor } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { Platform, RequestedPlatform, TrackingContext } from './types';
|
||||
import Analytics, { Event } from './utils/analytics';
|
||||
|
||||
export interface CommandContext {
|
||||
requestedPlatform: RequestedPlatform;
|
||||
profile: string;
|
||||
projectDir: string;
|
||||
projectId: string;
|
||||
user: Actor;
|
||||
accountName: string;
|
||||
projectName: string;
|
||||
exp: ExpoConfig;
|
||||
nonInteractive: boolean;
|
||||
local: boolean;
|
||||
clearCache: boolean;
|
||||
skipProjectConfiguration: boolean;
|
||||
waitForBuildEnd: boolean;
|
||||
}
|
||||
|
||||
export async function createCommandContextAsync({
|
||||
requestedPlatform,
|
||||
profile,
|
||||
exp,
|
||||
projectDir,
|
||||
projectId,
|
||||
nonInteractive = false,
|
||||
local,
|
||||
clearCache = false,
|
||||
skipProjectConfiguration = false,
|
||||
waitForBuildEnd,
|
||||
}: {
|
||||
requestedPlatform: RequestedPlatform;
|
||||
profile: string;
|
||||
exp: ExpoConfig;
|
||||
projectId: string;
|
||||
projectDir: string;
|
||||
nonInteractive: boolean;
|
||||
local: boolean;
|
||||
clearCache: boolean;
|
||||
skipProjectConfiguration: boolean;
|
||||
waitForBuildEnd: boolean;
|
||||
}): Promise<CommandContext> {
|
||||
const user = await ensureLoggedInAsync();
|
||||
const accountName = getProjectAccountName(exp, user);
|
||||
const projectName = exp.slug;
|
||||
|
||||
return {
|
||||
requestedPlatform,
|
||||
profile,
|
||||
projectDir,
|
||||
projectId,
|
||||
user,
|
||||
accountName,
|
||||
projectName,
|
||||
exp,
|
||||
nonInteractive,
|
||||
local,
|
||||
clearCache,
|
||||
skipProjectConfiguration,
|
||||
waitForBuildEnd,
|
||||
};
|
||||
}
|
||||
import { LocalBuildOptions } from './local';
|
||||
|
||||
export interface ConfigureContext {
|
||||
user: Actor;
|
||||
@@ -83,80 +23,38 @@ export interface ConfigureContext {
|
||||
hasIosNativeProject: boolean;
|
||||
}
|
||||
|
||||
type PlatformBuildProfile<T extends Platform> = T extends Platform.ANDROID
|
||||
? AndroidBuildProfile
|
||||
: IosBuildProfile;
|
||||
export type CommonContext<T extends Platform> = Omit<BuildContext<T>, 'android' | 'ios'>;
|
||||
|
||||
export interface AndroidBuildContext {
|
||||
applicationId: string;
|
||||
gradleContext?: GradleBuildContext;
|
||||
}
|
||||
|
||||
export interface IosBuildContext {
|
||||
bundleIdentifier: string;
|
||||
applicationTargetBuildSettings: XCBuildConfiguration['buildSettings'];
|
||||
applicationTarget: Target;
|
||||
targets: Target[];
|
||||
xcodeBuildContext: XcodeBuildContext;
|
||||
}
|
||||
|
||||
export interface BuildContext<T extends Platform> {
|
||||
commandCtx: CommandContext;
|
||||
accountName: string;
|
||||
buildProfile: BuildProfile<T>;
|
||||
buildProfileName: string;
|
||||
clearCache: boolean;
|
||||
credentialsCtx: CredentialsContext;
|
||||
exp: ExpoConfig;
|
||||
localBuildOptions: LocalBuildOptions;
|
||||
nonInteractive: boolean;
|
||||
platform: T;
|
||||
projectDir: string;
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
skipProjectConfiguration: boolean;
|
||||
trackingCtx: TrackingContext;
|
||||
platform: T;
|
||||
buildProfile: PlatformBuildProfile<T>;
|
||||
}
|
||||
|
||||
export function createBuildContext<T extends Platform>({
|
||||
platform,
|
||||
easConfig,
|
||||
commandCtx,
|
||||
}: {
|
||||
platform: T;
|
||||
easConfig: EasConfig;
|
||||
commandCtx: CommandContext;
|
||||
}): BuildContext<T> {
|
||||
const buildProfile = easConfig.builds[platform] as PlatformBuildProfile<T> | undefined;
|
||||
if (!buildProfile) {
|
||||
throw new Error(`${platform} build profile does not exist`);
|
||||
}
|
||||
|
||||
const accountId = findAccountByName(commandCtx.user.accounts, commandCtx.accountName)?.id;
|
||||
const devClientProperties = getDevClientEventProperties(platform, commandCtx, buildProfile);
|
||||
const trackingCtx = {
|
||||
tracking_id: uuidv4(),
|
||||
platform,
|
||||
...(accountId && { account_id: accountId }),
|
||||
account_name: commandCtx.accountName,
|
||||
project_id: commandCtx.projectId,
|
||||
project_type: buildProfile.workflow,
|
||||
...devClientProperties,
|
||||
};
|
||||
Analytics.logEvent(Event.BUILD_COMMAND, trackingCtx);
|
||||
return {
|
||||
commandCtx,
|
||||
trackingCtx,
|
||||
platform,
|
||||
buildProfile,
|
||||
};
|
||||
}
|
||||
|
||||
function getDevClientEventProperties(
|
||||
platform: Platform,
|
||||
commandCtx: CommandContext,
|
||||
buildProfile: AndroidBuildProfile | IosBuildProfile
|
||||
): Partial<TrackingContext> {
|
||||
let includesDevClient;
|
||||
const version = tryGetDevClientVersion(commandCtx.projectDir);
|
||||
if (buildProfile.workflow === Workflow.MANAGED) {
|
||||
includesDevClient = buildProfile.buildType === 'development-client';
|
||||
} else if (platform === Platform.ANDROID && 'gradleCommand' in buildProfile) {
|
||||
includesDevClient = Boolean(version && buildProfile.gradleCommand?.includes('Debug'));
|
||||
} else if (platform === Platform.IOS && 'schemeBuildConfiguration' in buildProfile) {
|
||||
includesDevClient = Boolean(version && buildProfile.schemeBuildConfiguration === 'Debug');
|
||||
} else {
|
||||
includesDevClient = false;
|
||||
}
|
||||
|
||||
if (version) {
|
||||
return { dev_client: includesDevClient, dev_client_version: version };
|
||||
} else {
|
||||
return { dev_client: includesDevClient };
|
||||
}
|
||||
}
|
||||
|
||||
function tryGetDevClientVersion(projectDir: string): string | null {
|
||||
try {
|
||||
const pkg = JsonFile.read(resolveFrom(projectDir, 'expo-dev-client/package.json'));
|
||||
return pkg.version?.toString() ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
user: Actor;
|
||||
workflow: Workflow;
|
||||
android: T extends Platform.ANDROID ? AndroidBuildContext : undefined;
|
||||
ios: T extends Platform.IOS ? IosBuildContext : undefined;
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
import ora from 'ora';
|
||||
|
||||
import { BuildFragment, BuildStatus } from '../graphql/generated';
|
||||
import { BuildQuery } from '../graphql/queries/BuildQuery';
|
||||
import Log from '../log';
|
||||
import { sleep } from '../utils/promise';
|
||||
import vcs from '../vcs';
|
||||
import { prepareAndroidBuildAsync } from './android/build';
|
||||
import { CommandContext } from './context';
|
||||
import { prepareIosBuildAsync } from './ios/build';
|
||||
import { Platform, RequestedPlatform } from './types';
|
||||
import { printBuildResults, printLogsUrls } from './utils/printBuildInfo';
|
||||
import { ensureRepoIsCleanAsync } from './utils/repository';
|
||||
|
||||
export async function buildAsync(commandCtx: CommandContext): Promise<void> {
|
||||
await vcs.ensureRepoExistsAsync();
|
||||
await ensureRepoIsCleanAsync(commandCtx.nonInteractive);
|
||||
|
||||
const scheduledBuilds = await startBuildsAsync(commandCtx);
|
||||
if (commandCtx.local) {
|
||||
return;
|
||||
}
|
||||
Log.newLine();
|
||||
printLogsUrls(commandCtx.accountName, scheduledBuilds);
|
||||
Log.newLine();
|
||||
|
||||
if (commandCtx.waitForBuildEnd) {
|
||||
const builds = await waitForBuildEndAsync(scheduledBuilds.map(i => i.buildId));
|
||||
printBuildResults(commandCtx.accountName, builds);
|
||||
exitWithNonZeroCodeIfSomeBuildsFailed(builds);
|
||||
}
|
||||
}
|
||||
|
||||
async function startBuildsAsync(
|
||||
commandCtx: CommandContext
|
||||
): Promise<{ platform: Platform; buildId: string }[]> {
|
||||
const shouldBuildAndroid = [RequestedPlatform.Android, RequestedPlatform.All].includes(
|
||||
commandCtx.requestedPlatform
|
||||
);
|
||||
const shouldBuildIos = [RequestedPlatform.Ios, RequestedPlatform.All].includes(
|
||||
commandCtx.requestedPlatform
|
||||
);
|
||||
const easConfig = await new EasJsonReader(
|
||||
commandCtx.projectDir,
|
||||
commandCtx.requestedPlatform
|
||||
).readAsync(commandCtx.profile);
|
||||
|
||||
const builds: {
|
||||
platform: Platform;
|
||||
sendBuildRequestAsync: () => Promise<string | undefined>;
|
||||
}[] = [];
|
||||
if (shouldBuildAndroid) {
|
||||
const sendBuildRequestAsync = await prepareAndroidBuildAsync(commandCtx, easConfig);
|
||||
builds.push({ platform: Platform.ANDROID, sendBuildRequestAsync });
|
||||
}
|
||||
if (shouldBuildIos) {
|
||||
const sendBuildRequestAsync = await prepareIosBuildAsync(commandCtx, easConfig);
|
||||
builds.push({ platform: Platform.IOS, sendBuildRequestAsync });
|
||||
}
|
||||
return (
|
||||
await Promise.all(
|
||||
builds.map(async ({ platform, sendBuildRequestAsync }) => ({
|
||||
platform,
|
||||
buildId: await sendBuildRequestAsync(),
|
||||
}))
|
||||
)
|
||||
).filter((build): build is { platform: Platform; buildId: string } => !!build.buildId);
|
||||
}
|
||||
|
||||
async function waitForBuildEndAsync(
|
||||
buildIds: string[],
|
||||
{ timeoutSec = 3600, intervalSec = 30 } = {}
|
||||
): Promise<(BuildFragment | null)[]> {
|
||||
Log.log('Waiting for build to complete. You can press Ctrl+C to exit.');
|
||||
const spinner = ora().start();
|
||||
let time = new Date().getTime();
|
||||
const endTime = time + timeoutSec * 1000;
|
||||
while (time <= endTime) {
|
||||
const builds: (BuildFragment | null)[] = await Promise.all(
|
||||
buildIds.map(async buildId => {
|
||||
try {
|
||||
return await BuildQuery.byIdAsync(buildId, { useCache: false });
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
if (builds.length === 1) {
|
||||
const build = nullthrows(builds[0]);
|
||||
switch (build.status) {
|
||||
case BuildStatus.Finished:
|
||||
spinner.succeed('Build finished');
|
||||
return builds;
|
||||
case BuildStatus.New:
|
||||
spinner.text = 'Build created';
|
||||
break;
|
||||
case BuildStatus.InQueue:
|
||||
spinner.text = 'Build queued...';
|
||||
break;
|
||||
case BuildStatus.Canceled:
|
||||
spinner.text = 'Build canceled';
|
||||
spinner.stopAndPersist();
|
||||
return builds;
|
||||
case BuildStatus.InProgress:
|
||||
spinner.text = 'Build in progress...';
|
||||
break;
|
||||
case BuildStatus.Errored:
|
||||
spinner.fail('Build failed');
|
||||
if (build.error) {
|
||||
return builds;
|
||||
} else {
|
||||
throw new Error(`Standalone build failed!`);
|
||||
}
|
||||
default:
|
||||
spinner.warn('Unknown status.');
|
||||
throw new Error(`Unknown status: ${builds} - aborting!`);
|
||||
}
|
||||
} else {
|
||||
if (builds.filter(build => build?.status === BuildStatus.Finished).length === builds.length) {
|
||||
spinner.succeed('All builds have finished');
|
||||
return builds;
|
||||
} else if (
|
||||
builds.filter(build =>
|
||||
build?.status
|
||||
? [BuildStatus.Finished, BuildStatus.Errored, BuildStatus.Canceled].includes(
|
||||
build.status
|
||||
)
|
||||
: false
|
||||
).length === builds.length
|
||||
) {
|
||||
spinner.fail('Some of the builds were canceled or failed.');
|
||||
return builds;
|
||||
} else {
|
||||
const newBuilds = builds.filter(build => build?.status === BuildStatus.New).length;
|
||||
const inQueue = builds.filter(build => build?.status === BuildStatus.InQueue).length;
|
||||
const inProgress = builds.filter(build => build?.status === BuildStatus.InProgress).length;
|
||||
const errored = builds.filter(build => build?.status === BuildStatus.Errored).length;
|
||||
const finished = builds.filter(build => build?.status === BuildStatus.Finished).length;
|
||||
const canceled = builds.filter(build => build?.status === BuildStatus.Canceled).length;
|
||||
const unknownState = builds.length - inQueue - inProgress - errored - finished;
|
||||
spinner.text = [
|
||||
newBuilds && `Builds created: ${newBuilds}`,
|
||||
inQueue && `Builds in queue: ${inQueue}`,
|
||||
inProgress && `Builds in progress: ${inProgress}`,
|
||||
canceled && `Builds canceled: ${canceled}`,
|
||||
errored && chalk.red(`Builds failed: ${errored}`),
|
||||
finished && chalk.green(`Builds finished: ${finished}`),
|
||||
unknownState && chalk.red(`Builds in unknown state: ${unknownState}`),
|
||||
]
|
||||
.filter(i => i)
|
||||
.join('\t');
|
||||
}
|
||||
}
|
||||
time = new Date().getTime();
|
||||
await sleep(intervalSec * 1000);
|
||||
}
|
||||
spinner.warn('Timed out');
|
||||
throw new Error(
|
||||
'Timeout reached! It is taking longer than expected to finish the build, aborting...'
|
||||
);
|
||||
}
|
||||
|
||||
function exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds: (BuildFragment | null)[]): void {
|
||||
const failedBuilds = (maybeBuilds.filter(i => i) as BuildFragment[]).filter(
|
||||
i => i.status === BuildStatus.Errored
|
||||
);
|
||||
if (failedBuilds.length > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
import JsonFile from '@expo/json-file';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { TrackingContext } from '../analytics/common';
|
||||
import { Analytics, BuildEvent } from '../analytics/events';
|
||||
import { CredentialsContext } from '../credentials/context';
|
||||
import { getExpoConfig } from '../project/expoConfig';
|
||||
import { getProjectAccountName, getProjectIdAsync } from '../project/projectUtils';
|
||||
import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { findAccountByName } from '../user/Account';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { createAndroidContextAsync } from './android/build';
|
||||
import { BuildContext, CommonContext } from './context';
|
||||
import { createIosContextAsync } from './ios/build';
|
||||
import { LocalBuildOptions } from './local';
|
||||
|
||||
export async function createBuildContextAsync<T extends Platform>({
|
||||
buildProfileName,
|
||||
buildProfile,
|
||||
clearCache = false,
|
||||
localBuildOptions,
|
||||
nonInteractive = false,
|
||||
platform,
|
||||
projectDir,
|
||||
skipProjectConfiguration = false,
|
||||
}: {
|
||||
buildProfileName: string;
|
||||
buildProfile: BuildProfile<T>;
|
||||
clearCache: boolean;
|
||||
localBuildOptions: LocalBuildOptions;
|
||||
nonInteractive: boolean;
|
||||
platform: T;
|
||||
projectDir: string;
|
||||
skipProjectConfiguration: boolean;
|
||||
}): Promise<BuildContext<T>> {
|
||||
const exp = getExpoConfig(projectDir, { env: buildProfile.env });
|
||||
|
||||
const user = await ensureLoggedInAsync();
|
||||
const accountName = getProjectAccountName(exp, user);
|
||||
const projectName = exp.slug;
|
||||
const projectId = await getProjectIdAsync(exp, { env: buildProfile.env });
|
||||
const workflow = await resolveWorkflowAsync(projectDir, platform);
|
||||
const accountId = findAccountByName(user.accounts, accountName)?.id;
|
||||
|
||||
const credentialsCtx = new CredentialsContext({
|
||||
exp,
|
||||
nonInteractive,
|
||||
projectDir,
|
||||
user,
|
||||
env: buildProfile.env,
|
||||
});
|
||||
|
||||
const devClientProperties = getDevClientEventProperties({
|
||||
platform,
|
||||
projectDir,
|
||||
buildProfile,
|
||||
});
|
||||
const trackingCtx = {
|
||||
tracking_id: uuidv4(),
|
||||
platform,
|
||||
...(accountId && { account_id: accountId }),
|
||||
project_id: projectId,
|
||||
project_type: workflow,
|
||||
...devClientProperties,
|
||||
};
|
||||
Analytics.logEvent(BuildEvent.BUILD_COMMAND, trackingCtx);
|
||||
|
||||
const commonContext: CommonContext<T> = {
|
||||
accountName,
|
||||
buildProfile,
|
||||
buildProfileName,
|
||||
clearCache,
|
||||
credentialsCtx,
|
||||
exp,
|
||||
localBuildOptions,
|
||||
nonInteractive,
|
||||
platform,
|
||||
projectDir,
|
||||
projectId,
|
||||
projectName,
|
||||
skipProjectConfiguration,
|
||||
trackingCtx,
|
||||
user,
|
||||
workflow,
|
||||
};
|
||||
if (platform === Platform.ANDROID) {
|
||||
const common = commonContext as CommonContext<Platform.ANDROID>;
|
||||
return {
|
||||
...common,
|
||||
android: await createAndroidContextAsync(common),
|
||||
} as BuildContext<T>;
|
||||
} else {
|
||||
const common = commonContext as CommonContext<Platform.IOS>;
|
||||
return {
|
||||
...common,
|
||||
ios: await createIosContextAsync(common),
|
||||
} as BuildContext<T>;
|
||||
}
|
||||
}
|
||||
|
||||
function getDevClientEventProperties({
|
||||
platform,
|
||||
projectDir,
|
||||
buildProfile,
|
||||
}: {
|
||||
platform: Platform;
|
||||
projectDir: string;
|
||||
buildProfile: BuildProfile;
|
||||
}): Partial<TrackingContext> {
|
||||
let includesDevClient;
|
||||
const version = tryGetDevClientVersion(projectDir);
|
||||
if (platform === Platform.ANDROID && 'gradleCommand' in buildProfile) {
|
||||
includesDevClient = Boolean(version && buildProfile.gradleCommand?.includes('Debug'));
|
||||
} else if (platform === Platform.IOS && 'buildConfiguration' in buildProfile) {
|
||||
includesDevClient = Boolean(version && buildProfile.buildConfiguration === 'Debug');
|
||||
} else if (buildProfile.developmentClient) {
|
||||
includesDevClient = true;
|
||||
} else {
|
||||
includesDevClient = false;
|
||||
}
|
||||
|
||||
if (version) {
|
||||
return { dev_client: includesDevClient, dev_client_version: version };
|
||||
} else {
|
||||
return { dev_client: includesDevClient };
|
||||
}
|
||||
}
|
||||
|
||||
function tryGetDevClientVersion(projectDir: string): string | null {
|
||||
try {
|
||||
const pkg = JsonFile.read(resolveFrom(projectDir, 'expo-dev-client/package.json'));
|
||||
return pkg.version?.toString() ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { ArchiveSource, ArchiveSourceType, Metadata, Workflow } from '@expo/eas-
|
||||
|
||||
import {
|
||||
BuildCredentialsSource,
|
||||
BuildIosEnterpriseProvisioning,
|
||||
BuildMetadataInput,
|
||||
BuildWorkflow,
|
||||
DistributionType,
|
||||
@@ -32,6 +33,9 @@ export function transformMetadata(metadata: Metadata): BuildMetadataInput {
|
||||
metadata.credentialsSource && transformCredentialsSource(metadata.credentialsSource),
|
||||
distribution: metadata.distribution && transformDistribution(metadata.distribution),
|
||||
workflow: metadata.workflow && transformWorkflow(metadata.workflow),
|
||||
iosEnterpriseProvisioning:
|
||||
metadata.iosEnterpriseProvisioning &&
|
||||
transformIosEnterpriseProvisioning(metadata.iosEnterpriseProvisioning),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,10 +59,20 @@ function transformDistribution(distribution: Metadata['distribution']): Distribu
|
||||
}
|
||||
}
|
||||
|
||||
function transformWorkflow(workflow: Metadata['workflow']): BuildWorkflow {
|
||||
export function transformWorkflow(workflow: Workflow): BuildWorkflow {
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
return BuildWorkflow.Generic;
|
||||
} else {
|
||||
return BuildWorkflow.Managed;
|
||||
}
|
||||
}
|
||||
|
||||
export function transformIosEnterpriseProvisioning(
|
||||
enterpriseProvisioning: Metadata['iosEnterpriseProvisioning']
|
||||
): BuildIosEnterpriseProvisioning {
|
||||
if (enterpriseProvisioning === 'adhoc') {
|
||||
return BuildIosEnterpriseProvisioning.Adhoc;
|
||||
} else {
|
||||
return BuildIosEnterpriseProvisioning.Universal;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,16 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { IOSConfig } from '@expo/config-plugins';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import Log from '../../log';
|
||||
import { getProjectAccountName } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import vcs from '../../vcs';
|
||||
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { ensureValidVersions } from '../utils/updates';
|
||||
import { readPlistAsync, writePlistAsync } from './plist';
|
||||
|
||||
export async function configureUpdatesAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
|
||||
ensureValidVersions(exp);
|
||||
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
|
||||
let xcodeProject = IOSConfig.XcodeUtils.getPbxproj(projectDir);
|
||||
|
||||
if (!IOSConfig.Updates.isShellScriptBuildPhaseConfigured(projectDir, xcodeProject)) {
|
||||
xcodeProject = IOSConfig.Updates.ensureBundleReactNativePhaseContainsConfigurationScript(
|
||||
projectDir,
|
||||
xcodeProject
|
||||
);
|
||||
await fs.writeFile(IOSConfig.Paths.getPBXProjectPath(projectDir), xcodeProject.writeSync());
|
||||
}
|
||||
|
||||
let expoPlist = await readExpoPlistAsync(projectDir);
|
||||
if (!IOSConfig.Updates.isPlistConfigurationSynced(exp, expoPlist, accountName)) {
|
||||
@@ -37,7 +27,7 @@ export async function syncUpdatesConfigurationAsync(
|
||||
ensureValidVersions(exp);
|
||||
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
|
||||
try {
|
||||
await ensureUpdatesConfiguredAsync(projectDir, exp);
|
||||
await ensureUpdatesConfiguredAsync(projectDir);
|
||||
} catch (error) {
|
||||
Log.error(
|
||||
'expo-updates module is not configured. Please run "eas build:configure" first to configure the project'
|
||||
@@ -58,15 +48,9 @@ export async function syncUpdatesConfigurationAsync(
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureUpdatesConfiguredAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
|
||||
const xcodeProject = IOSConfig.XcodeUtils.getPbxproj(projectDir);
|
||||
|
||||
if (!IOSConfig.Updates.isShellScriptBuildPhaseConfigured(projectDir, xcodeProject)) {
|
||||
const script = 'expo-updates/scripts/create-manifest-ios.sh';
|
||||
const buildPhase = '"Bundle React Native code and images"';
|
||||
throw new Error(`Path to ${script} is missing in a ${buildPhase} build phase.`);
|
||||
}
|
||||
|
||||
// Note: we assume here that Expo modules are properly configured in the project. Aside from that,
|
||||
// all that is needed on Expo SDK 43+ to configure expo-updates configuration in Expo.plist
|
||||
async function ensureUpdatesConfiguredAsync(projectDir: string): Promise<void> {
|
||||
const expoPlist = await readExpoPlistAsync(projectDir);
|
||||
if (!IOSConfig.Updates.isPlistConfigurationSet(expoPlist)) {
|
||||
throw new Error('Missing values in Expo.plist');
|
||||
@@ -75,7 +59,7 @@ async function ensureUpdatesConfiguredAsync(projectDir: string, exp: ExpoConfig)
|
||||
|
||||
async function readExpoPlistAsync(projectDir: string): Promise<IOSConfig.ExpoPlist> {
|
||||
const expoPlistPath = IOSConfig.Paths.getExpoPlistPath(projectDir);
|
||||
return await readPlistAsync(expoPlistPath);
|
||||
return ((await readPlistAsync(expoPlistPath)) ?? {}) as IOSConfig.ExpoPlist;
|
||||
}
|
||||
|
||||
async function writeExpoPlistAsync(
|
||||
@@ -84,7 +68,7 @@ async function writeExpoPlistAsync(
|
||||
): Promise<void> {
|
||||
const expoPlistPath = IOSConfig.Paths.getExpoPlistPath(projectDir);
|
||||
await writePlistAsync(expoPlistPath, expoPlist);
|
||||
await vcs.trackFileAsync(expoPlistPath);
|
||||
await getVcsClient().trackFileAsync(expoPlistPath);
|
||||
}
|
||||
|
||||
export async function readReleaseChannelSafelyAsync(projectDir: string): Promise<string | null> {
|
||||
|
||||
@@ -6,17 +6,17 @@ import { readChannelSafelyAsync } from '../UpdatesModule';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
afterAll(() => {
|
||||
afterAll(async () => {
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.removeSync(os.tmpdir());
|
||||
await fs.remove(os.tmpdir());
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
vol.reset();
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.mkdirpSync(os.tmpdir());
|
||||
await fs.mkdirp(os.tmpdir());
|
||||
});
|
||||
|
||||
const testChannel = 'test-channel';
|
||||
|
||||
@@ -4,44 +4,61 @@ import fs from 'fs-extra';
|
||||
import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
|
||||
import { readPlistAsync } from '../plist';
|
||||
import { readPlistAsync } from '../../../utils/plist';
|
||||
import {
|
||||
BumpStrategy,
|
||||
bumpVersionAsync,
|
||||
bumpVersionInAppJsonAsync,
|
||||
evaluateTemplateString,
|
||||
getInfoPlistPath,
|
||||
readBuildNumberAsync,
|
||||
readShortVersionAsync,
|
||||
} from '../version';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
afterAll(async () => {
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.removeSync(os.tmpdir());
|
||||
await fs.remove(os.tmpdir());
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
vol.reset();
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.mkdirpSync(os.tmpdir());
|
||||
await fs.mkdirp(os.tmpdir());
|
||||
});
|
||||
|
||||
// generic workflow
|
||||
describe(evaluateTemplateString, () => {
|
||||
it('evaluates the template string when value is a number', () => {
|
||||
expect(evaluateTemplateString('$(BLAH_BLAH)', { BLAH_BLAH: 123 })).toBe('123');
|
||||
});
|
||||
it('evaluates the template string when value is a string', () => {
|
||||
expect(evaluateTemplateString('$(BLAH_BLAH)', { BLAH_BLAH: '123' })).toBe('123');
|
||||
});
|
||||
it('evaluates the template string when template is not the only element', () => {
|
||||
expect(evaluateTemplateString('before$(BLAH_BLAH)after', { BLAH_BLAH: '123' })).toBe(
|
||||
'before123after'
|
||||
);
|
||||
});
|
||||
it('evaluates the template string when template value is double quoted', () => {
|
||||
expect(evaluateTemplateString('before$(BLAH_BLAH)after', { BLAH_BLAH: '"123"' })).toBe(
|
||||
'before123after'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// bare workflow
|
||||
describe(bumpVersionAsync, () => {
|
||||
it('bumps expo.ios.buildNumber and CFBundleVersion when strategy = BumpStrategy.BUILD_NUMBER', async () => {
|
||||
const fakeExp = initGenericProject();
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.BUILD_NUMBER,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
buildSettings: {},
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
@@ -56,13 +73,38 @@ describe(bumpVersionAsync, () => {
|
||||
expect(infoPlist['CFBundleVersion']).toBe('2');
|
||||
});
|
||||
|
||||
it('bumps expo.version and CFBundleShortVersionString when strategy = BumpStrategy.SHORT_VERSION', async () => {
|
||||
const fakeExp = initGenericProject();
|
||||
it('bumps expo.ios.buildNumber and CFBundleVersion for non default Info.plist location', async () => {
|
||||
const fakeExp = initBareWorkflowProject({ infoPlistName: 'Info2.plist' });
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.SHORT_VERSION,
|
||||
bumpStrategy: BumpStrategy.BUILD_NUMBER,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
buildSettings: {
|
||||
INFOPLIST_FILE: '$(SRCROOT)/myproject/Info2.plist',
|
||||
},
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
const infoPlist = (await readPlistAsync(
|
||||
'/repo/ios/myproject/Info2.plist'
|
||||
)) as IOSConfig.InfoPlist;
|
||||
expect(fakeExp.version).toBe('1.0.0');
|
||||
expect(fakeExp.ios?.buildNumber).toBe('2');
|
||||
expect(appJSON.expo.version).toBe('1.0.0');
|
||||
expect(appJSON.expo.ios.buildNumber).toBe('2');
|
||||
expect(infoPlist['CFBundleShortVersionString']).toBe('1.0.0');
|
||||
expect(infoPlist['CFBundleVersion']).toBe('2');
|
||||
});
|
||||
|
||||
it('bumps expo.version and CFBundleShortVersionString when strategy = BumpStrategy.SHORT_VERSION', async () => {
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.APP_VERSION,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
buildSettings: {},
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
@@ -78,12 +120,13 @@ describe(bumpVersionAsync, () => {
|
||||
});
|
||||
|
||||
it('does not bump any version when strategy = BumpStrategy.NOOP', async () => {
|
||||
const fakeExp = initGenericProject();
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.NOOP,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
buildSettings: {},
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
@@ -121,7 +164,7 @@ describe(bumpVersionInAppJsonAsync, () => {
|
||||
const fakeExp = initManagedProject();
|
||||
|
||||
await bumpVersionInAppJsonAsync({
|
||||
bumpStrategy: BumpStrategy.SHORT_VERSION,
|
||||
bumpStrategy: BumpStrategy.APP_VERSION,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
});
|
||||
@@ -151,10 +194,10 @@ describe(bumpVersionInAppJsonAsync, () => {
|
||||
});
|
||||
|
||||
describe(readBuildNumberAsync, () => {
|
||||
describe('generic project', () => {
|
||||
describe('bare project', () => {
|
||||
it('reads the build number from native code', async () => {
|
||||
const exp = initGenericProject();
|
||||
const buildNumber = await readBuildNumberAsync('/repo', exp);
|
||||
const exp = initBareWorkflowProject();
|
||||
const buildNumber = await readBuildNumberAsync('/repo', exp, {});
|
||||
expect(buildNumber).toBe('1');
|
||||
});
|
||||
});
|
||||
@@ -162,55 +205,123 @@ describe(readBuildNumberAsync, () => {
|
||||
describe('managed project', () => {
|
||||
it('reads the build number from expo config', async () => {
|
||||
const exp = initManagedProject();
|
||||
const buildNumber = await readBuildNumberAsync('/repo', exp);
|
||||
const buildNumber = await readBuildNumberAsync('/repo', exp, {});
|
||||
expect(buildNumber).toBe('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(readShortVersionAsync, () => {
|
||||
describe('generic project', () => {
|
||||
describe('bare project', () => {
|
||||
it('reads the short version from native code', async () => {
|
||||
const exp = initGenericProject();
|
||||
const shortVersion = await readShortVersionAsync('/repo', exp);
|
||||
expect(shortVersion).toBe('1.0.0');
|
||||
const exp = initBareWorkflowProject();
|
||||
const appVersion = await readShortVersionAsync('/repo', exp, {});
|
||||
expect(appVersion).toBe('1.0.0');
|
||||
});
|
||||
it('evaluates interpolated build number', async () => {
|
||||
const exp = initBareWorkflowProject({
|
||||
appVersion: '$(CURRENT_PROJECT_VERSION)',
|
||||
});
|
||||
const buildNumber = await readShortVersionAsync('/repo', exp, {
|
||||
CURRENT_PROJECT_VERSION: '1.0.0',
|
||||
});
|
||||
expect(buildNumber).toBe('1.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('managed project', () => {
|
||||
it('reads the version from app config', async () => {
|
||||
const exp = initGenericProject();
|
||||
const shortVersion = await readShortVersionAsync('/repo', exp);
|
||||
expect(shortVersion).toBe('1.0.0');
|
||||
const exp = initBareWorkflowProject();
|
||||
const appVersion = await readShortVersionAsync('/repo', exp, {});
|
||||
expect(appVersion).toBe('1.0.0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function initGenericProject(): ExpoConfig {
|
||||
describe(getInfoPlistPath, () => {
|
||||
it('returns default path if INFOPLIST_FILE is not specified', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/testapp/Info.plist': '',
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
const plistPath = getInfoPlistPath('/repo', {});
|
||||
expect(plistPath).toBe('/repo/ios/testapp/Info.plist');
|
||||
});
|
||||
it('returns INFOPLIST_FILE if specified', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/testapp/Info.plist': '',
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
const plistPath = getInfoPlistPath('/repo', { INFOPLIST_FILE: './qwert/NotInfo.plist' });
|
||||
expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist');
|
||||
});
|
||||
it('evaluates SRCROOT in Info.plist', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/testapp/Info.plist': '',
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
const plistPath = getInfoPlistPath('/repo', {
|
||||
INFOPLIST_FILE: '$(SRCROOT)/qwert/NotInfo.plist',
|
||||
});
|
||||
expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist');
|
||||
});
|
||||
it('evaluates BuildSettings in Info.plist', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/testapp/Info.plist': '',
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
const plistPath = getInfoPlistPath('/repo', {
|
||||
INFOPLIST_FILE: '$(SRCROOT)/qwert/$(TARGET_NAME).plist',
|
||||
TARGET_NAME: 'NotInfo',
|
||||
});
|
||||
expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist');
|
||||
});
|
||||
});
|
||||
|
||||
function initBareWorkflowProject({
|
||||
appVersion = '1.0.0',
|
||||
version = '1',
|
||||
infoPlistName = 'Info.plist',
|
||||
}: { appVersion?: string; version?: string; infoPlistName?: string } = {}): ExpoConfig {
|
||||
const fakeExp: ExpoConfig = {
|
||||
name: 'myproject',
|
||||
slug: 'myproject',
|
||||
version: '1.0.0',
|
||||
ios: {
|
||||
buildNumber: '1',
|
||||
},
|
||||
};
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./app.json': JSON.stringify({
|
||||
expo: {
|
||||
version: '1.0.0',
|
||||
ios: {
|
||||
buildNumber: '1',
|
||||
},
|
||||
},
|
||||
expo: fakeExp,
|
||||
}),
|
||||
'./ios/myproject/Info.plist': `<?xml version="1.0" encoding="UTF-8"?>
|
||||
[`./ios/myproject/${infoPlistName}`]: `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<string>${appVersion}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>${version}</string>
|
||||
</dict>
|
||||
</plist>`,
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
|
||||
return fakeExp;
|
||||
}
|
||||
|
||||
function initManagedProject(): ExpoConfig {
|
||||
const fakeExp: ExpoConfig = {
|
||||
name: 'myproject',
|
||||
slug: 'myproject',
|
||||
@@ -219,31 +330,15 @@ function initGenericProject(): ExpoConfig {
|
||||
buildNumber: '1',
|
||||
},
|
||||
};
|
||||
return fakeExp;
|
||||
}
|
||||
|
||||
function initManagedProject(): ExpoConfig {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./app.json': JSON.stringify({
|
||||
expo: {
|
||||
version: '1.0.0',
|
||||
ios: {
|
||||
buildNumber: '1',
|
||||
},
|
||||
},
|
||||
expo: fakeExp,
|
||||
}),
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
|
||||
const fakeExp: ExpoConfig = {
|
||||
name: 'myproject',
|
||||
slug: 'myproject',
|
||||
version: '1.0.0',
|
||||
ios: {
|
||||
buildNumber: '1',
|
||||
},
|
||||
};
|
||||
return fakeExp;
|
||||
}
|
||||
@@ -1,85 +1,84 @@
|
||||
import { Ios, Job, Metadata, Workflow } from '@expo/eas-build-job';
|
||||
import { EasConfig } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { IOSConfig } from '@expo/config-plugins';
|
||||
import { Ios, Job, Metadata, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { IosCredentials } from '../../credentials/ios/types';
|
||||
import { IosCredentials, Target } from '../../credentials/ios/types';
|
||||
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
|
||||
import { ensureBundleIdentifierIsDefinedForManagedProjectAsync } from '../../project/ios/bundleIdentifier';
|
||||
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
|
||||
import { resolveTargetsAsync } from '../../project/ios/target';
|
||||
import { JobData, prepareBuildRequestForPlatformAsync } from '../build';
|
||||
import { BuildContext, CommandContext, createBuildContext } from '../context';
|
||||
import { findApplicationTarget, resolveTargetsAsync } from '../../project/ios/target';
|
||||
import { BuildRequestSender, JobData, prepareBuildRequestForPlatformAsync } from '../build';
|
||||
import { BuildContext, CommonContext, IosBuildContext } from '../context';
|
||||
import { transformMetadata } from '../graphql';
|
||||
import { Platform } from '../types';
|
||||
import { checkGoogleServicesFileAsync, checkNodeEnvVariable } from '../validate';
|
||||
import { validateAndSyncProjectConfigurationAsync } from './configure';
|
||||
import { ensureIosCredentialsAsync } from './credentials';
|
||||
import { transformGenericJob, transformManagedJob } from './graphql';
|
||||
import { transformJob } from './graphql';
|
||||
import { prepareJobAsync } from './prepareJob';
|
||||
|
||||
export async function prepareIosBuildAsync(
|
||||
commandCtx: CommandContext,
|
||||
easConfig: EasConfig
|
||||
): Promise<() => Promise<string | undefined>> {
|
||||
const buildCtx = createBuildContext<Platform.IOS>({
|
||||
commandCtx,
|
||||
platform: Platform.IOS,
|
||||
easConfig,
|
||||
});
|
||||
const { buildProfile } = buildCtx;
|
||||
export async function createIosContextAsync(
|
||||
ctx: CommonContext<Platform.IOS>
|
||||
): Promise<IosBuildContext> {
|
||||
const { buildProfile } = ctx;
|
||||
|
||||
if (
|
||||
buildProfile.workflow === Workflow.GENERIC &&
|
||||
!(await fs.pathExists(path.join(commandCtx.projectDir, 'ios')))
|
||||
) {
|
||||
throw new Error(
|
||||
`"ios" directory not found. If you're trying to build a managed project, set ${chalk.bold(
|
||||
`builds.ios.${commandCtx.profile}.workflow`
|
||||
)} in "eas.json" to "managed".`
|
||||
);
|
||||
if (ctx.workflow === Workflow.MANAGED) {
|
||||
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp);
|
||||
}
|
||||
|
||||
if (buildProfile.workflow === Workflow.MANAGED) {
|
||||
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(
|
||||
commandCtx.projectDir,
|
||||
commandCtx.exp
|
||||
);
|
||||
}
|
||||
checkNodeEnvVariable(ctx);
|
||||
await checkGoogleServicesFileAsync(ctx);
|
||||
|
||||
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
|
||||
{
|
||||
projectDir: commandCtx.projectDir,
|
||||
nonInteractive: commandCtx.nonInteractive,
|
||||
exp: commandCtx.exp,
|
||||
projectDir: ctx.projectDir,
|
||||
nonInteractive: ctx.nonInteractive,
|
||||
exp: ctx.exp,
|
||||
},
|
||||
buildProfile
|
||||
);
|
||||
const targets = await resolveTargetsAsync(
|
||||
{
|
||||
projectDir: commandCtx.projectDir,
|
||||
exp: commandCtx.exp,
|
||||
projectDir: ctx.projectDir,
|
||||
exp: ctx.exp,
|
||||
},
|
||||
xcodeBuildContext
|
||||
);
|
||||
const applicationTarget = findApplicationTarget(targets);
|
||||
const applicationTargetBuildSettings = resolveBuildSettings(ctx, applicationTarget);
|
||||
|
||||
return {
|
||||
bundleIdentifier: applicationTarget.bundleIdentifier,
|
||||
applicationTarget,
|
||||
applicationTargetBuildSettings,
|
||||
targets,
|
||||
xcodeBuildContext,
|
||||
};
|
||||
}
|
||||
|
||||
export async function prepareIosBuildAsync(
|
||||
ctx: BuildContext<Platform.IOS>
|
||||
): Promise<BuildRequestSender> {
|
||||
return await prepareBuildRequestForPlatformAsync({
|
||||
ctx: buildCtx,
|
||||
ctx,
|
||||
ensureCredentialsAsync: async (ctx: BuildContext<Platform.IOS>) => {
|
||||
return ensureIosCredentialsAsync(ctx, targets);
|
||||
return ensureIosCredentialsAsync(ctx, ctx.ios.targets);
|
||||
},
|
||||
ensureProjectConfiguredAsync: async () => {
|
||||
await validateAndSyncProjectConfigurationAsync({
|
||||
projectDir: commandCtx.projectDir,
|
||||
exp: commandCtx.exp,
|
||||
buildProfile,
|
||||
projectDir: ctx.projectDir,
|
||||
exp: ctx.exp,
|
||||
buildProfile: ctx.buildProfile,
|
||||
buildSettings: ctx.ios.applicationTargetBuildSettings,
|
||||
});
|
||||
},
|
||||
prepareJobAsync: async (
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
jobData: JobData<IosCredentials>
|
||||
): Promise<Job> => {
|
||||
return await prepareJobAsync(ctx, { ...jobData, buildScheme: xcodeBuildContext.buildScheme });
|
||||
return await prepareJobAsync(ctx, {
|
||||
...jobData,
|
||||
buildScheme: ctx.ios.xcodeBuildContext.buildScheme,
|
||||
});
|
||||
},
|
||||
sendBuildRequestAsync: async (
|
||||
appId: string,
|
||||
@@ -87,21 +86,27 @@ export async function prepareIosBuildAsync(
|
||||
metadata: Metadata
|
||||
): Promise<BuildResult> => {
|
||||
const graphqlMetadata = transformMetadata(metadata);
|
||||
if (job.type === Workflow.GENERIC) {
|
||||
const graphqlJob = transformGenericJob(job);
|
||||
return await BuildMutation.createIosGenericBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
});
|
||||
} else {
|
||||
const graphqlJob = transformManagedJob(job);
|
||||
return await BuildMutation.createIosManagedBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
});
|
||||
}
|
||||
const graphqlJob = transformJob(job);
|
||||
return await BuildMutation.createIosBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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 ?? {};
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Workflow } from '@expo/eas-build-job';
|
||||
import { IosBuildProfile, VersionAutoIncrement } from '@expo/eas-json';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { BuildProfile, IosVersionAutoIncrement } from '@expo/eas-json';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import Log from '../../log';
|
||||
import {
|
||||
ensureBundleIdentifierIsDefinedForManagedProjectAsync,
|
||||
warnIfBundleIdentifierDefinedInAppConfigForGenericProject,
|
||||
warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject,
|
||||
} from '../../project/ios/bundleIdentifier';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { ConfigureContext } from '../context';
|
||||
import { isExpoUpdatesInstalled } from '../utils/updates';
|
||||
import { configureUpdatesAsync, syncUpdatesConfigurationAsync } from './UpdatesModule';
|
||||
@@ -18,7 +20,7 @@ export async function configureIosAsync(ctx: ConfigureContext): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
warnIfBundleIdentifierDefinedInAppConfigForGenericProject(ctx.projectDir, ctx.exp);
|
||||
warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject(ctx.projectDir, ctx.exp);
|
||||
|
||||
if (isExpoUpdatesInstalled(ctx.projectDir)) {
|
||||
await configureUpdatesAsync(ctx.projectDir, ctx.exp);
|
||||
@@ -30,25 +32,28 @@ export async function validateAndSyncProjectConfigurationAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
buildProfile,
|
||||
buildSettings,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
buildProfile: IosBuildProfile;
|
||||
buildProfile: BuildProfile<Platform.IOS>;
|
||||
buildSettings: XCBuildConfiguration['buildSettings'];
|
||||
}): Promise<void> {
|
||||
const { workflow, autoIncrement } = buildProfile;
|
||||
const versionBumpStrategy = resolveVersionBumpStrategy(autoIncrement);
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
const { autoIncrement } = buildProfile;
|
||||
const versionBumpStrategy = resolveVersionBumpStrategy(autoIncrement ?? false);
|
||||
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
if (isExpoUpdatesInstalled(projectDir)) {
|
||||
await syncUpdatesConfigurationAsync(projectDir, exp);
|
||||
}
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, buildSettings });
|
||||
} else {
|
||||
await bumpVersionInAppJsonAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
|
||||
}
|
||||
}
|
||||
|
||||
function resolveVersionBumpStrategy(autoIncrement: VersionAutoIncrement): BumpStrategy {
|
||||
function resolveVersionBumpStrategy(autoIncrement: IosVersionAutoIncrement): BumpStrategy {
|
||||
if (autoIncrement === true) {
|
||||
return BumpStrategy.BUILD_NUMBER;
|
||||
} else if (autoIncrement === false) {
|
||||
@@ -56,6 +61,6 @@ function resolveVersionBumpStrategy(autoIncrement: VersionAutoIncrement): BumpSt
|
||||
} else if (autoIncrement === 'buildNumber') {
|
||||
return BumpStrategy.BUILD_NUMBER;
|
||||
} else {
|
||||
return BumpStrategy.SHORT_VERSION;
|
||||
return BumpStrategy.APP_VERSION;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createCredentialsContextAsync } from '../../credentials/context';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
|
||||
import IosCredentialsProvider from '../../credentials/ios/IosCredentialsProvider';
|
||||
import { getAppFromContext } from '../../credentials/ios/actions/BuildCredentialsUtils';
|
||||
import { resolveEntitlementsJsonAsync } from '../../credentials/ios/appstore/entitlements';
|
||||
import { IosCredentials, Target } from '../../credentials/ios/types';
|
||||
import { CredentialsResult } from '../build';
|
||||
import { BuildContext } from '../context';
|
||||
import { Platform } from '../types';
|
||||
import { logCredentialsSource } from '../utils/credentials';
|
||||
|
||||
export async function ensureIosCredentialsAsync(
|
||||
@@ -16,19 +16,14 @@ export async function ensureIosCredentialsAsync(
|
||||
return;
|
||||
}
|
||||
|
||||
const { projectDir } = buildCtx.commandCtx;
|
||||
|
||||
const ctx = await createCredentialsContextAsync(projectDir, {
|
||||
nonInteractive: buildCtx.commandCtx.nonInteractive,
|
||||
});
|
||||
|
||||
const provider = new IosCredentialsProvider(ctx, {
|
||||
app: getAppFromContext(ctx),
|
||||
const provider = new IosCredentialsProvider(buildCtx.credentialsCtx, {
|
||||
app: getAppFromContext(buildCtx.credentialsCtx),
|
||||
targets,
|
||||
iosCapabilitiesOptions: {
|
||||
entitlements: await resolveEntitlementsJsonAsync(
|
||||
buildCtx.commandCtx.projectDir,
|
||||
buildCtx.buildProfile.workflow
|
||||
buildCtx.projectDir,
|
||||
buildCtx.workflow,
|
||||
buildCtx.buildProfile.env ?? {}
|
||||
),
|
||||
},
|
||||
distribution: buildCtx.buildProfile.distribution ?? 'store',
|
||||
@@ -45,5 +40,5 @@ export async function ensureIosCredentialsAsync(
|
||||
}
|
||||
|
||||
function shouldProvideCredentials(buildCtx: BuildContext<Platform.IOS>): boolean {
|
||||
return buildCtx.buildProfile.distribution !== 'simulator';
|
||||
return !buildCtx.buildProfile.simulator;
|
||||
}
|
||||
@@ -1,56 +1,28 @@
|
||||
import { Env, Ios } from '@expo/eas-build-job';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import {
|
||||
DistributionType,
|
||||
IosGenericJobInput,
|
||||
IosJobSecretsInput,
|
||||
IosManagedBuildType,
|
||||
IosManagedJobInput,
|
||||
} from '../../graphql/generated';
|
||||
import { transformProjectArchive } from '../graphql';
|
||||
import { IosJobInput, IosJobSecretsInput } from '../../graphql/generated';
|
||||
import { transformProjectArchive, transformWorkflow } from '../graphql';
|
||||
|
||||
export function transformGenericJob(job: Ios.GenericJob): IosGenericJobInput {
|
||||
export function transformJob(job: Ios.Job): IosJobInput {
|
||||
return {
|
||||
type: transformWorkflow(job.type),
|
||||
projectArchive: transformProjectArchive(job.projectArchive),
|
||||
projectRootDirectory: job.projectRootDirectory,
|
||||
releaseChannel: job.releaseChannel,
|
||||
updates: job.updates,
|
||||
distribution: job.distribution && transformDistributionType(job.distribution),
|
||||
secrets: transformIosSecrets(job.secrets),
|
||||
builderEnvironment: job.builderEnvironment,
|
||||
cache: job.cache,
|
||||
scheme: job.scheme,
|
||||
buildConfiguration: job.buildConfiguration,
|
||||
artifactPath: job.artifactPath,
|
||||
};
|
||||
}
|
||||
|
||||
export function transformManagedJob(job: Ios.ManagedJob): IosManagedJobInput {
|
||||
return {
|
||||
projectArchive: transformProjectArchive(job.projectArchive),
|
||||
projectRootDirectory: job.projectRootDirectory,
|
||||
releaseChannel: job.releaseChannel,
|
||||
updates: job.updates,
|
||||
distribution: job.distribution && transformDistributionType(job.distribution),
|
||||
secrets: transformIosSecrets(job.secrets),
|
||||
builderEnvironment: job.builderEnvironment,
|
||||
cache: job.cache,
|
||||
buildType: job.buildType && transformBuildType(job.buildType),
|
||||
username: job.username,
|
||||
developmentClient: job.developmentClient,
|
||||
simulator: job.simulator,
|
||||
};
|
||||
}
|
||||
|
||||
function transformDistributionType(distributionType: Ios.DistributionType): DistributionType {
|
||||
if (distributionType === 'store') {
|
||||
return DistributionType.Store;
|
||||
} else if (distributionType === 'internal') {
|
||||
return DistributionType.Internal;
|
||||
} else {
|
||||
return DistributionType.Simulator;
|
||||
}
|
||||
}
|
||||
|
||||
function transformIosSecrets(secrets: {
|
||||
buildCredentials?: Ios.BuildCredentials;
|
||||
env?: Env;
|
||||
@@ -72,11 +44,3 @@ function transformIosSecrets(secrets: {
|
||||
environmentSecrets: secrets.env,
|
||||
};
|
||||
}
|
||||
|
||||
function transformBuildType(buildType: Ios.ManagedBuildType): IosManagedBuildType {
|
||||
if (buildType === Ios.ManagedBuildType.DEVELOPMENT_CLIENT) {
|
||||
return IosManagedBuildType.DevelopmentClient;
|
||||
} else {
|
||||
return IosManagedBuildType.Release;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import { ArchiveSource, Cache, Ios, Job, Workflow, sanitizeJob } from '@expo/eas-build-job';
|
||||
import { IosGenericBuildProfile, IosManagedBuildProfile } from '@expo/eas-json';
|
||||
import { ArchiveSource, Ios, Job, Platform, sanitizeJob } from '@expo/eas-build-job';
|
||||
import path from 'path';
|
||||
import slash from 'slash';
|
||||
|
||||
import { IosCredentials, TargetCredentials } from '../../credentials/ios/types';
|
||||
import { getUsername } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import vcs from '../../vcs';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { BuildContext } from '../context';
|
||||
import { Platform } from '../types';
|
||||
|
||||
interface JobData {
|
||||
projectArchive: ArchiveSource;
|
||||
@@ -15,49 +14,32 @@ interface JobData {
|
||||
buildScheme: string;
|
||||
}
|
||||
|
||||
const cacheDefaults = {
|
||||
disabled: false,
|
||||
customPaths: [],
|
||||
cacheDefaultPaths: true,
|
||||
};
|
||||
|
||||
export async function prepareJobAsync(
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
jobData: JobData
|
||||
): Promise<Job> {
|
||||
if (ctx.buildProfile.workflow === Workflow.GENERIC) {
|
||||
const partialJob = await prepareGenericJobAsync(ctx, jobData, ctx.buildProfile);
|
||||
return sanitizeJob(partialJob);
|
||||
} else if (ctx.buildProfile.workflow === Workflow.MANAGED) {
|
||||
const partialJob = await prepareManagedJobAsync(ctx, jobData, ctx.buildProfile);
|
||||
return sanitizeJob(partialJob);
|
||||
} else {
|
||||
throw new Error("Unknown workflow. Shouldn't happen");
|
||||
}
|
||||
}
|
||||
|
||||
interface CommonJobProperties {
|
||||
platform: Platform.IOS;
|
||||
projectArchive: ArchiveSource;
|
||||
builderEnvironment: Ios.BuilderEnvironment;
|
||||
distribution?: Ios.DistributionType;
|
||||
cache: Cache;
|
||||
secrets: {
|
||||
buildCredentials: Ios.BuildCredentials;
|
||||
environmentSecrets?: Record<string, string>;
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareJobCommonAsync(
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
{ credentials, projectArchive }: { credentials?: IosCredentials; projectArchive: ArchiveSource }
|
||||
): Promise<Partial<CommonJobProperties>> {
|
||||
const buildCredentials: CommonJobProperties['secrets']['buildCredentials'] = {};
|
||||
if (credentials) {
|
||||
const targetNames = Object.keys(credentials);
|
||||
const projectRootDirectory =
|
||||
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
const username = getUsername(ctx.exp, await ensureLoggedInAsync());
|
||||
const buildCredentials: Ios.Job['secrets']['buildCredentials'] = {};
|
||||
if (jobData.credentials) {
|
||||
const targetNames = Object.keys(jobData.credentials);
|
||||
for (const targetName of targetNames) {
|
||||
buildCredentials[targetName] = prepareTargetCredentials(credentials[targetName]);
|
||||
buildCredentials[targetName] = prepareTargetCredentials(jobData.credentials[targetName]);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
const job: Ios.Job = {
|
||||
type: ctx.workflow,
|
||||
platform: Platform.IOS,
|
||||
projectArchive,
|
||||
distribution: ctx.buildProfile.distribution,
|
||||
projectArchive: jobData.projectArchive,
|
||||
projectRootDirectory,
|
||||
builderEnvironment: {
|
||||
image: ctx.buildProfile.image,
|
||||
node: ctx.buildProfile.node,
|
||||
@@ -69,13 +51,23 @@ async function prepareJobCommonAsync(
|
||||
env: ctx.buildProfile.env,
|
||||
},
|
||||
cache: {
|
||||
...cacheDefaults,
|
||||
...ctx.buildProfile.cache,
|
||||
clear: ctx.commandCtx.clearCache,
|
||||
clear: ctx.clearCache,
|
||||
},
|
||||
secrets: {
|
||||
buildCredentials,
|
||||
},
|
||||
releaseChannel: ctx.buildProfile.releaseChannel,
|
||||
updates: { channel: ctx.buildProfile.channel },
|
||||
developmentClient: ctx.buildProfile.developmentClient,
|
||||
simulator: ctx.buildProfile.simulator,
|
||||
scheme: jobData.buildScheme,
|
||||
buildConfiguration: ctx.buildProfile.buildConfiguration,
|
||||
artifactPath: ctx.buildProfile.artifactPath,
|
||||
username,
|
||||
};
|
||||
return sanitizeJob(job);
|
||||
}
|
||||
|
||||
function prepareTargetCredentials(targetCredentials: TargetCredentials): Ios.TargetCredentials {
|
||||
@@ -87,47 +79,3 @@ function prepareTargetCredentials(targetCredentials: TargetCredentials): Ios.Tar
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareGenericJobAsync(
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
jobData: JobData,
|
||||
buildProfile: IosGenericBuildProfile
|
||||
): Promise<Partial<Ios.GenericJob>> {
|
||||
const projectRootDirectory =
|
||||
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
|
||||
return {
|
||||
...(await prepareJobCommonAsync(ctx, {
|
||||
credentials: jobData.credentials,
|
||||
projectArchive: jobData.projectArchive,
|
||||
})),
|
||||
type: Workflow.GENERIC,
|
||||
scheme: jobData.buildScheme,
|
||||
buildConfiguration: buildProfile.schemeBuildConfiguration,
|
||||
artifactPath: buildProfile.artifactPath,
|
||||
releaseChannel: buildProfile.releaseChannel,
|
||||
updates: { channel: buildProfile.channel },
|
||||
projectRootDirectory,
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareManagedJobAsync(
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
jobData: JobData,
|
||||
buildProfile: IosManagedBuildProfile
|
||||
): Promise<Partial<Ios.ManagedJob>> {
|
||||
const projectRootDirectory =
|
||||
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
|
||||
const username = getUsername(ctx.commandCtx.exp, await ensureLoggedInAsync());
|
||||
return {
|
||||
...(await prepareJobCommonAsync(ctx, {
|
||||
credentials: jobData.credentials,
|
||||
projectArchive: jobData.projectArchive,
|
||||
})),
|
||||
type: Workflow.MANAGED,
|
||||
buildType: buildProfile.buildType,
|
||||
username,
|
||||
releaseChannel: buildProfile.releaseChannel,
|
||||
updates: { channel: buildProfile.channel },
|
||||
projectRootDirectory,
|
||||
};
|
||||
}
|
||||
@@ -2,17 +2,18 @@ import { ExpoConfig, getConfigFilePaths } from '@expo/config';
|
||||
import { IOSConfig } from '@expo/config-plugins';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
import semver from 'semver';
|
||||
import path from 'path';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import Log from '../../log';
|
||||
import { resolveWorkflow } from '../../project/workflow';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
|
||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||
import { readPlistAsync, writePlistAsync } from './plist';
|
||||
import { bumpAppVersionAsync } from '../utils/version';
|
||||
|
||||
export enum BumpStrategy {
|
||||
SHORT_VERSION,
|
||||
APP_VERSION,
|
||||
BUILD_NUMBER,
|
||||
NOOP,
|
||||
}
|
||||
@@ -21,19 +22,21 @@ export async function bumpVersionAsync({
|
||||
bumpStrategy,
|
||||
projectDir,
|
||||
exp,
|
||||
buildSettings,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
bumpStrategy: BumpStrategy;
|
||||
buildSettings: XCBuildConfiguration['buildSettings'];
|
||||
}): Promise<void> {
|
||||
if (bumpStrategy === BumpStrategy.NOOP) {
|
||||
return;
|
||||
}
|
||||
ensureStaticConfigExists(projectDir);
|
||||
const infoPlist = await readInfoPlistAsync(projectDir);
|
||||
const infoPlist = await readInfoPlistAsync(projectDir, buildSettings);
|
||||
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
|
||||
Log.log('Updated versions in app.json');
|
||||
await writeVersionsToInfoPlistAsync({ projectDir, exp, infoPlist });
|
||||
await writeVersionsToInfoPlistAsync({ projectDir, exp, infoPlist, buildSettings });
|
||||
Log.log('Synchronized versions with Info.plist');
|
||||
}
|
||||
|
||||
@@ -51,29 +54,9 @@ export async function bumpVersionInAppJsonAsync({
|
||||
}
|
||||
ensureStaticConfigExists(projectDir);
|
||||
Log.addNewLineIfNone();
|
||||
if (bumpStrategy === BumpStrategy.SHORT_VERSION) {
|
||||
const shortVersion = IOSConfig.Version.getVersion(exp);
|
||||
if (semver.valid(shortVersion)) {
|
||||
const bumpedShortVersion = nullthrows(semver.inc(shortVersion, 'patch'));
|
||||
Log.log(
|
||||
`Bumping ${chalk.bold('expo.version')} from ${chalk.bold(shortVersion)} to ${chalk.bold(
|
||||
bumpedShortVersion
|
||||
)}`
|
||||
);
|
||||
await updateAppJsonConfigAsync({ projectDir, exp }, config => {
|
||||
config.version = bumpedShortVersion;
|
||||
});
|
||||
} else {
|
||||
Log.log(`${chalk.bold('expo.version')} = ${chalk.bold(shortVersion)} is not a valid semver`);
|
||||
const { bumpedShortVersion } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'bumpedShortVersion',
|
||||
message: 'What is the next version?',
|
||||
});
|
||||
await updateAppJsonConfigAsync({ projectDir, exp }, config => {
|
||||
config.version = bumpedShortVersion;
|
||||
});
|
||||
}
|
||||
if (bumpStrategy === BumpStrategy.APP_VERSION) {
|
||||
const appVersion = IOSConfig.Version.getVersion(exp);
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp });
|
||||
} else {
|
||||
const buildNumber = IOSConfig.Version.getBuildNumber(exp);
|
||||
if (buildNumber.match(/^\d+(\.\d+)*$/)) {
|
||||
@@ -104,12 +87,16 @@ export async function bumpVersionInAppJsonAsync({
|
||||
|
||||
export async function readShortVersionAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
exp: ExpoConfig,
|
||||
buildSettings: XCBuildConfiguration['buildSettings']
|
||||
): Promise<string | undefined> {
|
||||
const workflow = resolveWorkflow(projectDir, Platform.IOS);
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const infoPlist = await readInfoPlistAsync(projectDir);
|
||||
return infoPlist.CFBundleShortVersionString;
|
||||
const infoPlist = await readInfoPlistAsync(projectDir, buildSettings);
|
||||
return (
|
||||
infoPlist.CFBundleShortVersionString &&
|
||||
evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings)
|
||||
);
|
||||
} else {
|
||||
return exp.version;
|
||||
}
|
||||
@@ -117,45 +104,93 @@ export async function readShortVersionAsync(
|
||||
|
||||
export async function readBuildNumberAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
exp: ExpoConfig,
|
||||
buildSettings: XCBuildConfiguration['buildSettings']
|
||||
): Promise<string | undefined> {
|
||||
const workflow = resolveWorkflow(projectDir, Platform.IOS);
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const infoPlist = await readInfoPlistAsync(projectDir);
|
||||
return infoPlist.CFBundleVersion;
|
||||
const infoPlist = await readInfoPlistAsync(projectDir, buildSettings);
|
||||
return (
|
||||
infoPlist.CFBundleVersion && evaluateTemplateString(infoPlist.CFBundleVersion, buildSettings)
|
||||
);
|
||||
} else {
|
||||
return IOSConfig.Version.getBuildNumber(exp);
|
||||
}
|
||||
}
|
||||
|
||||
export async function maybeResolveVersionsAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig,
|
||||
buildSettings: XCBuildConfiguration['buildSettings']
|
||||
): Promise<{ appVersion?: string; appBuildVersion?: string }> {
|
||||
try {
|
||||
return {
|
||||
appBuildVersion: await readBuildNumberAsync(projectDir, exp, buildSettings),
|
||||
appVersion: await readShortVersionAsync(projectDir, exp, buildSettings),
|
||||
};
|
||||
} catch (err: any) {
|
||||
Log.warn('Failed to read app versions.');
|
||||
Log.debug(err);
|
||||
Log.warn(err.message);
|
||||
Log.warn('Proceeding anyway...');
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
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 });
|
||||
await writeInfoPlistAsync({ projectDir, infoPlist: updatedInfoPlist, buildSettings });
|
||||
return updatedInfoPlist;
|
||||
}
|
||||
|
||||
async function readInfoPlistAsync(projectDir: string): Promise<IOSConfig.InfoPlist> {
|
||||
const infoPlistPath = IOSConfig.Paths.getInfoPlistPath(projectDir);
|
||||
return (await readPlistAsync(infoPlistPath)) as IOSConfig.InfoPlist;
|
||||
export function getInfoPlistPath(
|
||||
projectDir: string,
|
||||
buildSettings: XCBuildConfiguration['buildSettings']
|
||||
): string {
|
||||
if (buildSettings.INFOPLIST_FILE) {
|
||||
const infoPlistFile = buildSettings.INFOPLIST_FILE.startsWith('"')
|
||||
? buildSettings.INFOPLIST_FILE.slice(1, -1)
|
||||
: buildSettings.INFOPLIST_FILE;
|
||||
const iosDir = path.join(projectDir, 'ios');
|
||||
const plistPath = evaluateTemplateString(infoPlistFile, {
|
||||
...buildSettings,
|
||||
SRCROOT: iosDir,
|
||||
});
|
||||
return path.isAbsolute(plistPath) ? plistPath : path.resolve(iosDir, plistPath);
|
||||
} else {
|
||||
return IOSConfig.Paths.getInfoPlistPath(projectDir);
|
||||
}
|
||||
}
|
||||
|
||||
async function readInfoPlistAsync(
|
||||
projectDir: string,
|
||||
buildSettings: XCBuildConfiguration['buildSettings']
|
||||
): Promise<IOSConfig.InfoPlist> {
|
||||
const infoPlistPath = getInfoPlistPath(projectDir, buildSettings);
|
||||
return ((await readPlistAsync(infoPlistPath)) ?? {}) as IOSConfig.InfoPlist;
|
||||
}
|
||||
|
||||
async function writeInfoPlistAsync({
|
||||
projectDir,
|
||||
infoPlist,
|
||||
buildSettings,
|
||||
}: {
|
||||
projectDir: string;
|
||||
infoPlist: IOSConfig.InfoPlist;
|
||||
buildSettings: XCBuildConfiguration['buildSettings'];
|
||||
}): Promise<void> {
|
||||
const infoPlistPath = IOSConfig.Paths.getInfoPlistPath(projectDir);
|
||||
const infoPlistPath = getInfoPlistPath(projectDir, buildSettings);
|
||||
await writePlistAsync(infoPlistPath, infoPlist);
|
||||
}
|
||||
|
||||
@@ -165,3 +200,19 @@ function ensureStaticConfigExists(projectDir: string): void {
|
||||
throw new Error('autoIncrement option is not supported when using app.config.js');
|
||||
}
|
||||
}
|
||||
|
||||
export function evaluateTemplateString(
|
||||
s: string,
|
||||
buildSettings: XCBuildConfiguration['buildSettings']
|
||||
): string {
|
||||
// necessary because XCBuildConfiguration['buildSettings'] is not a plain object
|
||||
const vars = { ...buildSettings };
|
||||
return s.replace(/\$\((\w+)\)/g, (match, key) => {
|
||||
if (vars.hasOwnProperty(key)) {
|
||||
const value = String(vars[key]);
|
||||
return value.startsWith('"') ? value.slice(1, -1) : value;
|
||||
} else {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,45 +1,78 @@
|
||||
import { Job } from '@expo/eas-build-job';
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import semver from 'semver';
|
||||
|
||||
import Log from '../log';
|
||||
import { ora } from '../ora';
|
||||
|
||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||
const PLUGIN_PACKAGE_VERSION = '0.0.56';
|
||||
|
||||
export async function runLocalBuildAsync(job: Job): Promise<void> {
|
||||
const execNameOrPath =
|
||||
process.env.EAS_LOCAL_BUILD_PLUGIN_PATH ??
|
||||
(await findWithNodeResolution()) ??
|
||||
PLUGIN_PACKAGE_NAME;
|
||||
try {
|
||||
const arg = Buffer.from(JSON.stringify({ job })).toString('base64');
|
||||
await spawnAsync(execNameOrPath, [arg], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
Log.warn(`Could not resolve executable ${execNameOrPath}.`);
|
||||
Log.warn(
|
||||
`Install ${PLUGIN_PACKAGE_NAME} package from npm e.g. ${chalk.bold(
|
||||
`npm install -g ${PLUGIN_PACKAGE_NAME}`
|
||||
)} and make sure it's in PATH.`
|
||||
);
|
||||
throw err;
|
||||
export interface LocalBuildOptions {
|
||||
enable: boolean;
|
||||
skipCleanup?: boolean;
|
||||
skipNativeBuild?: boolean;
|
||||
artifactsDir?: string;
|
||||
workingdir?: string;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
export async function runLocalBuildAsync(job: Job, options: LocalBuildOptions): Promise<void> {
|
||||
const { command, args } = await getCommandAndArgsAsync(job);
|
||||
let spinner;
|
||||
if (!options.verbose) {
|
||||
spinner = ora().start(options.skipNativeBuild ? 'Preparing project' : 'Building project');
|
||||
}
|
||||
let childProcess: ChildProcess | undefined;
|
||||
const interruptHandler = (): void => {
|
||||
if (childProcess) {
|
||||
childProcess.kill();
|
||||
}
|
||||
};
|
||||
process.on('SIGINT', interruptHandler);
|
||||
try {
|
||||
const spawnPromise = spawnAsync(command, args, {
|
||||
stdio: options.verbose ? 'inherit' : 'pipe',
|
||||
env: {
|
||||
...process.env,
|
||||
EAS_LOCAL_BUILD_WORKINGDIR: options.workingdir ?? process.env.EAS_LOCAL_BUILD_WORKINGDIR,
|
||||
...(options.skipCleanup || options.skipNativeBuild
|
||||
? { EAS_LOCAL_BUILD_SKIP_CLEANUP: '1' }
|
||||
: {}),
|
||||
...(options.skipNativeBuild ? { EAS_LOCAL_BUILD_SKIP_NATIVE_BUILD: '1' } : {}),
|
||||
...(options.artifactsDir ? { EAS_LOCAL_BUILD_ARTIFACTS_DIR: options.artifactsDir } : {}),
|
||||
},
|
||||
});
|
||||
childProcess = spawnPromise.child;
|
||||
await spawnPromise;
|
||||
} finally {
|
||||
process.removeListener('SIGINT', interruptHandler);
|
||||
spinner?.stop();
|
||||
}
|
||||
}
|
||||
|
||||
async function findWithNodeResolution(): Promise<string | undefined> {
|
||||
try {
|
||||
const resolvedPath = path.resolve(
|
||||
path.dirname(require.resolve(PLUGIN_PACKAGE_NAME)),
|
||||
'../bin/run'
|
||||
);
|
||||
if (await fs.pathExists(resolvedPath)) {
|
||||
return resolvedPath;
|
||||
async function getCommandAndArgsAsync(job: Job): Promise<{ command: string; args: string[] }> {
|
||||
const jobBase64 = Buffer.from(JSON.stringify({ job })).toString('base64');
|
||||
if (process.env.EAS_LOCAL_BUILD_PLUGIN_PATH) {
|
||||
return {
|
||||
command: process.env.EAS_LOCAL_BUILD_PLUGIN_PATH,
|
||||
args: [jobBase64],
|
||||
};
|
||||
} else {
|
||||
const args = [`${PLUGIN_PACKAGE_NAME}@${PLUGIN_PACKAGE_VERSION}`, jobBase64];
|
||||
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
|
||||
args.unshift('-y');
|
||||
}
|
||||
} catch {}
|
||||
return undefined;
|
||||
return {
|
||||
command: 'npx',
|
||||
args,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function isAtLeastNpm7Async(): Promise<boolean> {
|
||||
const version = (await spawnAsync('npm', ['--version'])).stdout.trim();
|
||||
return semver.gte(version, '7.0.0');
|
||||
}
|
||||
@@ -1,95 +1,103 @@
|
||||
import { Metadata } from '@expo/eas-build-job';
|
||||
import { CredentialsSource } from '@expo/eas-json';
|
||||
import { Updates } from '@expo/config-plugins';
|
||||
import { Metadata, Platform, sanitizeMetadata } from '@expo/eas-build-job';
|
||||
import { IosEnterpriseProvisioning } from '@expo/eas-json';
|
||||
import fs from 'fs-extra';
|
||||
import resolveFrom from 'resolve-from';
|
||||
|
||||
import { getApplicationId } from '../project/android/applicationId';
|
||||
import { getBundleIdentifier } from '../project/ios/bundleIdentifier';
|
||||
import Log from '../log';
|
||||
import { getUsername } from '../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import vcs from '../vcs';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import {
|
||||
readChannelSafelyAsync as readAndroidChannelSafelyAsync,
|
||||
readReleaseChannelSafelyAsync as readAndroidReleaseChannelSafelyAsync,
|
||||
} from './android/UpdatesModule';
|
||||
import { readVersionCode, readVersionName } from './android/version';
|
||||
import { maybeResolveVersionsAsync as maybeResolveAndroidVersionsAsync } from './android/version';
|
||||
import { BuildContext } from './context';
|
||||
import {
|
||||
readChannelSafelyAsync as readIosChannelSafelyAsync,
|
||||
readReleaseChannelSafelyAsync as readIosReleaseChannelSafelyAsync,
|
||||
} from './ios/UpdatesModule';
|
||||
import { readBuildNumberAsync, readShortVersionAsync } from './ios/version';
|
||||
import { Platform } from './types';
|
||||
import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios/version';
|
||||
import { isExpoUpdatesInstalled } from './utils/updates';
|
||||
|
||||
/**
|
||||
* We use require() to exclude package.json from TypeScript's analysis since it lives outside
|
||||
* the src directory and would change the directory structure of the emitted files
|
||||
* under the build directory
|
||||
*/
|
||||
const packageJSON = require('../../package.json');
|
||||
|
||||
export async function collectMetadata<T extends Platform>(
|
||||
ctx: BuildContext<T>,
|
||||
{
|
||||
credentialsSource,
|
||||
}: {
|
||||
credentialsSource?: CredentialsSource.LOCAL | CredentialsSource.REMOTE;
|
||||
// TODO(JJ): Replace this with the getRuntimeVersionNullable function in @expo/config-plugins
|
||||
function getRuntimeVersionNullable(
|
||||
...[config, platform]: Parameters<typeof Updates.getRuntimeVersion>
|
||||
): string | null {
|
||||
try {
|
||||
return Updates.getRuntimeVersion(config, platform);
|
||||
} catch (e) {
|
||||
Log.debug(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function collectMetadataAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<Metadata> {
|
||||
const channelOrReleaseChannel = await resolveChannelOrReleaseChannelAsync(ctx);
|
||||
return {
|
||||
// TODO: type error fixed in https://github.com/expo/eas-build/pull/34
|
||||
// remove @ts-expect-error after upgrading @expo/eas-build-job
|
||||
// @ts-expect-error error TS2322: not assignable to type 'Record<string, string | number>'
|
||||
const distribution =
|
||||
('simulator' in ctx.buildProfile && ctx.buildProfile.simulator
|
||||
? 'simulator'
|
||||
: ctx.buildProfile.distribution) ?? 'store';
|
||||
const metadata = {
|
||||
trackingContext: ctx.trackingCtx,
|
||||
appVersion: await resolveAppVersionAsync(ctx),
|
||||
appBuildVersion: await resolveAppBuildVersionAsync(ctx),
|
||||
cliVersion: packageJSON.version,
|
||||
workflow: ctx.buildProfile.workflow,
|
||||
credentialsSource,
|
||||
sdkVersion: ctx.commandCtx.exp.sdkVersion,
|
||||
...(await maybeResolveVersionsAsync(ctx)),
|
||||
cliVersion: easCliVersion,
|
||||
workflow: ctx.workflow,
|
||||
credentialsSource: ctx.buildProfile.credentialsSource,
|
||||
sdkVersion: ctx.exp.sdkVersion,
|
||||
runtimeVersion: getRuntimeVersionNullable(ctx.exp, ctx.platform) ?? undefined,
|
||||
reactNativeVersion: await getReactNativeVersionAsync(ctx),
|
||||
...channelOrReleaseChannel,
|
||||
distribution: ctx.buildProfile.distribution ?? 'store',
|
||||
appName: ctx.commandCtx.exp.name,
|
||||
distribution,
|
||||
appName: ctx.exp.name,
|
||||
appIdentifier: resolveAppIdentifier(ctx),
|
||||
buildProfile: ctx.commandCtx.profile,
|
||||
gitCommitHash: await vcs.getCommitHashAsync(),
|
||||
username: getUsername(ctx.commandCtx.exp, await ensureLoggedInAsync()),
|
||||
buildProfile: ctx.buildProfileName,
|
||||
gitCommitHash: await getVcsClient().getCommitHashAsync(),
|
||||
isGitWorkingTreeDirty: await getVcsClient().hasUncommittedChangesAsync(),
|
||||
username: getUsername(ctx.exp, await ensureLoggedInAsync()),
|
||||
...(ctx.platform === Platform.IOS && {
|
||||
iosEnterpriseProvisioning: resolveIosEnterpriseProvisioning(
|
||||
ctx as BuildContext<Platform.IOS>
|
||||
),
|
||||
}),
|
||||
};
|
||||
return sanitizeMetadata(metadata);
|
||||
}
|
||||
|
||||
async function resolveAppVersionAsync<T extends Platform>(
|
||||
async function maybeResolveVersionsAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<string | undefined> {
|
||||
): Promise<{ appBuildVersion?: string; appVersion?: string }> {
|
||||
if (ctx.platform === Platform.IOS) {
|
||||
return await readShortVersionAsync(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
const iosContext = ctx as BuildContext<Platform.IOS>;
|
||||
return await maybeResolveIosVersionsAsync(
|
||||
ctx.projectDir,
|
||||
ctx.exp,
|
||||
iosContext.ios.applicationTargetBuildSettings
|
||||
);
|
||||
} else if (ctx.platform === Platform.ANDROID) {
|
||||
const androidCtx = ctx as BuildContext<Platform.ANDROID>;
|
||||
return await maybeResolveAndroidVersionsAsync(ctx.projectDir, ctx.exp, androidCtx.buildProfile);
|
||||
} else {
|
||||
return readVersionName(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveAppBuildVersionAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<string | undefined> {
|
||||
if (ctx.platform === Platform.IOS) {
|
||||
return await readBuildNumberAsync(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
} else {
|
||||
const versionCode = readVersionCode(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
return versionCode !== undefined ? String(versionCode) : undefined;
|
||||
throw new Error(`Unsupported platform ${ctx.platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAppIdentifier<T extends Platform>(ctx: BuildContext<T>): string {
|
||||
if (ctx.platform === Platform.IOS) {
|
||||
return getBundleIdentifier(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
return (ctx as BuildContext<Platform.IOS>).ios.bundleIdentifier;
|
||||
} else {
|
||||
return getApplicationId(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
return (ctx as BuildContext<Platform.ANDROID>).android.applicationId;
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveChannelOrReleaseChannelAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<{ channel: string } | { releaseChannel: string } | null> {
|
||||
if (!isExpoUpdatesInstalled(ctx.commandCtx.projectDir)) {
|
||||
if (!isExpoUpdatesInstalled(ctx.projectDir)) {
|
||||
return null;
|
||||
}
|
||||
if (ctx.buildProfile.channel) {
|
||||
@@ -111,10 +119,10 @@ async function getNativeReleaseChannelAsync<T extends Platform>(
|
||||
): Promise<string> {
|
||||
switch (ctx.platform) {
|
||||
case Platform.ANDROID: {
|
||||
return (await readAndroidReleaseChannelSafelyAsync(ctx.commandCtx.projectDir)) ?? 'default';
|
||||
return (await readAndroidReleaseChannelSafelyAsync(ctx.projectDir)) ?? 'default';
|
||||
}
|
||||
case Platform.IOS: {
|
||||
return (await readIosReleaseChannelSafelyAsync(ctx.commandCtx.projectDir)) ?? 'default';
|
||||
return (await readIosReleaseChannelSafelyAsync(ctx.projectDir)) ?? 'default';
|
||||
}
|
||||
default:
|
||||
return 'default';
|
||||
@@ -126,12 +134,31 @@ async function getNativeChannelAsync<T extends Platform>(
|
||||
): Promise<string | undefined> {
|
||||
switch (ctx.platform) {
|
||||
case Platform.ANDROID: {
|
||||
return (await readAndroidChannelSafelyAsync(ctx.commandCtx.projectDir)) ?? undefined;
|
||||
return (await readAndroidChannelSafelyAsync(ctx.projectDir)) ?? undefined;
|
||||
}
|
||||
case Platform.IOS: {
|
||||
return (await readIosChannelSafelyAsync(ctx.commandCtx.projectDir)) ?? undefined;
|
||||
return (await readIosChannelSafelyAsync(ctx.projectDir)) ?? undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function getReactNativeVersionAsync(
|
||||
ctx: BuildContext<Platform>
|
||||
): Promise<string | undefined> {
|
||||
try {
|
||||
const reactNativePackageJsonPath = resolveFrom(ctx.projectDir, 'react-native/package.json');
|
||||
return (await fs.readJson(reactNativePackageJsonPath)).version;
|
||||
} catch (err) {
|
||||
Log.debug('Failed to resolve react-native version:');
|
||||
Log.debug(err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveIosEnterpriseProvisioning(
|
||||
ctx: BuildContext<Platform.IOS>
|
||||
): IosEnterpriseProvisioning | undefined {
|
||||
return ctx.buildProfile.enterpriseProvisioning;
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { BuildProfile, SubmitProfile } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { AppPlatform, BuildFragment, BuildStatus, SubmissionFragment } from '../graphql/generated';
|
||||
import { toAppPlatform, toPlatform } from '../graphql/types/AppPlatform';
|
||||
import Log from '../log';
|
||||
import {
|
||||
RequestedPlatform,
|
||||
appPlatformDisplayNames,
|
||||
appPlatformEmojis,
|
||||
toPlatforms,
|
||||
} from '../platform';
|
||||
import { checkExpoSdkIsSupportedAsync } from '../project/expoSdk';
|
||||
import { validateMetroConfigForManagedWorkflowAsync } from '../project/metroConfig';
|
||||
import { createSubmissionContextAsync } from '../submit/context';
|
||||
import {
|
||||
submitAsync,
|
||||
waitToCompleteAsync as waitForSubmissionsToCompleteAsync,
|
||||
} from '../submit/submit';
|
||||
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
|
||||
import { ProfileData, getProfilesAsync } from '../utils/profiles';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import { prepareAndroidBuildAsync } from './android/build';
|
||||
import { BuildRequestSender, waitForBuildEndAsync } from './build';
|
||||
import { ensureProjectConfiguredAsync } from './configure';
|
||||
import { BuildContext } from './context';
|
||||
import { createBuildContextAsync } from './createContext';
|
||||
import { prepareIosBuildAsync } from './ios/build';
|
||||
import { LocalBuildOptions } from './local';
|
||||
import { ensureExpoDevClientInstalledForDevClientBuildsAsync } from './utils/devClient';
|
||||
import { printBuildResults, printLogsUrls } from './utils/printBuildInfo';
|
||||
import { ensureRepoIsCleanAsync } from './utils/repository';
|
||||
|
||||
let metroConfigValidated = false;
|
||||
let sdkVersionChecked = false;
|
||||
|
||||
export interface BuildFlags {
|
||||
requestedPlatform: RequestedPlatform;
|
||||
skipProjectConfiguration: boolean;
|
||||
profile?: string;
|
||||
nonInteractive: boolean;
|
||||
wait: boolean;
|
||||
clearCache: boolean;
|
||||
json: boolean;
|
||||
autoSubmit: boolean;
|
||||
submitProfile?: string;
|
||||
localBuildOptions: LocalBuildOptions;
|
||||
}
|
||||
|
||||
export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFlags): Promise<void> {
|
||||
await getVcsClient().ensureRepoExistsAsync();
|
||||
await ensureRepoIsCleanAsync(flags.nonInteractive);
|
||||
|
||||
await ensureProjectConfiguredAsync(projectDir, flags.requestedPlatform);
|
||||
|
||||
const platforms = toPlatforms(flags.requestedPlatform);
|
||||
const buildProfiles = await getProfilesAsync({
|
||||
type: 'build',
|
||||
projectDir,
|
||||
platforms,
|
||||
profileName: flags.profile ?? undefined,
|
||||
});
|
||||
|
||||
await ensureExpoDevClientInstalledForDevClientBuildsAsync({
|
||||
projectDir,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
buildProfiles,
|
||||
});
|
||||
|
||||
const startedBuilds: {
|
||||
build: BuildFragment;
|
||||
buildProfile: ProfileData<'build'>;
|
||||
}[] = [];
|
||||
const buildCtxByPlatform: { [p in AppPlatform]?: BuildContext<Platform> } = {};
|
||||
|
||||
for (const buildProfile of buildProfiles) {
|
||||
const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({
|
||||
projectDir,
|
||||
flags,
|
||||
moreBuilds: platforms.length > 1,
|
||||
buildProfile,
|
||||
});
|
||||
if (maybeBuild) {
|
||||
startedBuilds.push({ build: maybeBuild, buildProfile });
|
||||
}
|
||||
buildCtxByPlatform[toAppPlatform(buildProfile.platform)] = buildCtx;
|
||||
}
|
||||
|
||||
if (flags.localBuildOptions.enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.newLine();
|
||||
printLogsUrls(startedBuilds.map(startedBuild => startedBuild.build));
|
||||
Log.newLine();
|
||||
|
||||
const submissions: SubmissionFragment[] = [];
|
||||
if (flags.autoSubmit) {
|
||||
const submitProfiles = await getProfilesAsync({
|
||||
projectDir,
|
||||
platforms,
|
||||
profileName: flags.submitProfile,
|
||||
type: 'submit',
|
||||
});
|
||||
for (const startedBuild of startedBuilds) {
|
||||
const submitProfile = nullthrows(
|
||||
submitProfiles.find(
|
||||
({ platform }) => toAppPlatform(platform) === startedBuild.build.platform
|
||||
)
|
||||
).profile;
|
||||
const submission = await prepareAndStartSubmissionAsync({
|
||||
build: startedBuild.build,
|
||||
buildCtx: nullthrows(buildCtxByPlatform[startedBuild.build.platform]),
|
||||
moreBuilds: startedBuilds.length > 1,
|
||||
projectDir,
|
||||
buildProfile: startedBuild.buildProfile.profile,
|
||||
submitProfile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
});
|
||||
submissions.push(submission);
|
||||
}
|
||||
|
||||
Log.newLine();
|
||||
printSubmissionDetailsUrls(submissions);
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
if (!flags.wait) {
|
||||
return;
|
||||
}
|
||||
|
||||
const builds = await waitForBuildEndAsync(startedBuilds.map(({ build }) => build.id));
|
||||
printBuildResults(builds, flags.json);
|
||||
|
||||
const haveAllBuildsFailedOrCanceled = builds.every(
|
||||
build => build?.status && [BuildStatus.Errored, BuildStatus.Canceled].includes(build?.status)
|
||||
);
|
||||
if (haveAllBuildsFailedOrCanceled || !flags.autoSubmit) {
|
||||
exitWithNonZeroCodeIfSomeBuildsFailed(builds);
|
||||
} else {
|
||||
// the following function also exits with non zero code if any of the submissions failed
|
||||
await waitForSubmissionsToCompleteAsync(submissions);
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareAndStartBuildAsync({
|
||||
projectDir,
|
||||
flags,
|
||||
moreBuilds,
|
||||
buildProfile,
|
||||
}: {
|
||||
projectDir: string;
|
||||
flags: BuildFlags;
|
||||
moreBuilds: boolean;
|
||||
buildProfile: ProfileData<'build'>;
|
||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
||||
const buildCtx = await createBuildContextAsync({
|
||||
buildProfileName: buildProfile.profileName,
|
||||
clearCache: flags.clearCache,
|
||||
buildProfile: buildProfile.profile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
platform: buildProfile.platform,
|
||||
projectDir,
|
||||
skipProjectConfiguration: flags.skipProjectConfiguration,
|
||||
localBuildOptions: flags.localBuildOptions,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
Log.newLine();
|
||||
const appPlatform = toAppPlatform(buildProfile.platform);
|
||||
Log.log(
|
||||
`${appPlatformEmojis[appPlatform]} ${chalk.bold(
|
||||
`${appPlatformDisplayNames[appPlatform]} build`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (buildCtx.workflow === Workflow.MANAGED) {
|
||||
if (!sdkVersionChecked) {
|
||||
await checkExpoSdkIsSupportedAsync(buildCtx);
|
||||
sdkVersionChecked = true;
|
||||
}
|
||||
if (!metroConfigValidated) {
|
||||
await validateMetroConfigForManagedWorkflowAsync(buildCtx);
|
||||
metroConfigValidated = true;
|
||||
}
|
||||
}
|
||||
|
||||
const build = await startBuildAsync(buildCtx);
|
||||
return {
|
||||
build,
|
||||
buildCtx,
|
||||
};
|
||||
}
|
||||
|
||||
async function startBuildAsync(ctx: BuildContext<Platform>): Promise<BuildFragment | undefined> {
|
||||
let sendBuildRequestAsync: BuildRequestSender;
|
||||
if (ctx.platform === Platform.ANDROID) {
|
||||
sendBuildRequestAsync = await prepareAndroidBuildAsync(ctx as BuildContext<Platform.ANDROID>);
|
||||
} else {
|
||||
sendBuildRequestAsync = await prepareIosBuildAsync(ctx as BuildContext<Platform.IOS>);
|
||||
}
|
||||
return await sendBuildRequestAsync();
|
||||
}
|
||||
|
||||
async function prepareAndStartSubmissionAsync({
|
||||
build,
|
||||
buildCtx,
|
||||
moreBuilds,
|
||||
projectDir,
|
||||
buildProfile,
|
||||
submitProfile,
|
||||
nonInteractive,
|
||||
}: {
|
||||
build: BuildFragment;
|
||||
buildCtx: BuildContext<Platform>;
|
||||
moreBuilds: boolean;
|
||||
projectDir: string;
|
||||
buildProfile: BuildProfile;
|
||||
submitProfile: SubmitProfile;
|
||||
nonInteractive: boolean;
|
||||
}): Promise<SubmissionFragment> {
|
||||
const platform = toPlatform(build.platform);
|
||||
const submissionCtx = await createSubmissionContextAsync({
|
||||
platform,
|
||||
projectDir,
|
||||
projectId: build.project.id,
|
||||
profile: submitProfile,
|
||||
archiveFlags: { id: build.id },
|
||||
nonInteractive,
|
||||
env: buildProfile.env,
|
||||
credentialsCtx: buildCtx.credentialsCtx,
|
||||
applicationIdentifier: buildCtx.android?.applicationId ?? buildCtx.ios?.bundleIdentifier,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
Log.newLine();
|
||||
Log.log(
|
||||
`${appPlatformEmojis[build.platform]} ${chalk.bold(
|
||||
`${appPlatformDisplayNames[build.platform]} submission`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
return await submitAsync(submissionCtx);
|
||||
}
|
||||
|
||||
function exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds: (BuildFragment | null)[]): void {
|
||||
const failedBuilds = (maybeBuilds.filter(i => i) as BuildFragment[]).filter(
|
||||
i => i.status === BuildStatus.Errored
|
||||
);
|
||||
if (failedBuilds.length > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,3 @@
|
||||
export { Platform } from '@expo/eas-build-job';
|
||||
|
||||
export enum RequestedPlatform {
|
||||
Android = 'android',
|
||||
Ios = 'ios',
|
||||
All = 'all',
|
||||
}
|
||||
|
||||
export enum BuildStatus {
|
||||
NEW = 'new',
|
||||
IN_QUEUE = 'in-queue',
|
||||
@@ -15,4 +7,8 @@ export enum BuildStatus {
|
||||
CANCELED = 'canceled',
|
||||
}
|
||||
|
||||
export type TrackingContext = Record<string, string | number | boolean>;
|
||||
export enum BuildDistributionType {
|
||||
STORE = 'store',
|
||||
INTERNAL = 'internal',
|
||||
SIMULATOR = 'simulator',
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { logEvent } from '../../analytics';
|
||||
|
||||
export enum Event {
|
||||
BUILD_COMMAND = 'build cli build command',
|
||||
PROJECT_UPLOAD_SUCCESS = 'build cli project upload success',
|
||||
PROJECT_UPLOAD_FAIL = 'build cli project upload fail',
|
||||
GATHER_CREDENTIALS_SUCCESS = 'build cli gather credentials success',
|
||||
GATHER_CREDENTIALS_FAIL = 'build cli gather credentials fail',
|
||||
CONFIGURE_PROJECT_SUCCESS = 'build cli configure project success',
|
||||
CONFIGURE_PROJECT_FAIL = 'build cli configure project fail',
|
||||
BUILD_REQUEST_SUCCESS = 'build cli build request success',
|
||||
BUILD_REQUEST_FAIL = 'build cli build request fail',
|
||||
|
||||
BUILD_STATUS_COMMAND = 'build cli build status',
|
||||
|
||||
CREDENTIALS_SYNC_COMMAND = 'build cli credentials sync command',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_SUCCESS = 'build cli credentials sync update local success',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_FAIL = 'build cli credentials sync update local fail',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_SUCCESS = 'build cli credentials sync update remote success',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_FAIL = 'build cli credentials sync update remote fail',
|
||||
}
|
||||
|
||||
export default {
|
||||
logEvent(name: Event, properties: Record<string, any>) {
|
||||
logEvent(name, properties);
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as ExpoConfig from '@expo/config';
|
||||
import JsonFile from '@expo/json-file';
|
||||
import assert from 'assert';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
export async function updateAppJsonConfigAsync(
|
||||
{
|
||||
@@ -15,10 +15,19 @@ export async function updateAppJsonConfigAsync(
|
||||
const paths = ExpoConfig.getConfigFilePaths(projectDir);
|
||||
assert(paths.staticConfigPath, "can't update dynamic config");
|
||||
|
||||
const rawStaticConfig = await fs.readJSON(paths.staticConfigPath);
|
||||
const rawStaticConfig = readAppJson(paths.staticConfigPath);
|
||||
rawStaticConfig.expo = rawStaticConfig.expo ?? {};
|
||||
modifyConfig(rawStaticConfig.expo);
|
||||
await fs.writeJson(paths.staticConfigPath, rawStaticConfig, { spaces: 2 });
|
||||
await JsonFile.writeAsync(paths.staticConfigPath, rawStaticConfig, { json5: false });
|
||||
|
||||
modifyConfig(exp);
|
||||
}
|
||||
|
||||
// TODO: remove this once @expo/config exports getStaticConfig
|
||||
export function readAppJson(appJsonPath: string): any {
|
||||
const config = JsonFile.read(appJsonPath, { json5: true });
|
||||
if (config) {
|
||||
return config as any;
|
||||
}
|
||||
throw new Error(`Failed to read app.json`);
|
||||
}
|
||||
@@ -3,13 +3,17 @@ import { CredentialsSource } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import Log from '../../log';
|
||||
import { requestedPlatformDisplayNames } from '../constants';
|
||||
import { requestedPlatformDisplayNames } from '../../platform';
|
||||
|
||||
export function logCredentialsSource(credentialsSource: CredentialsSource, platform: Platform) {
|
||||
export function logCredentialsSource(
|
||||
credentialsSource: CredentialsSource,
|
||||
platform: Platform
|
||||
): void {
|
||||
let message = `Using ${credentialsSource} ${requestedPlatformDisplayNames[platform]} credentials`;
|
||||
if (credentialsSource === CredentialsSource.LOCAL)
|
||||
if (credentialsSource === CredentialsSource.LOCAL) {
|
||||
message += ` ${chalk.dim('(credentials.json)')}`;
|
||||
else if (credentialsSource === CredentialsSource.REMOTE)
|
||||
} else if (credentialsSource === CredentialsSource.REMOTE) {
|
||||
message += ` ${chalk.dim('(Expo server)')}`;
|
||||
}
|
||||
Log.succeed(message);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import { Workflow } from '@expo/eas-build-job';
|
||||
import { Errors } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import resolveFrom from 'resolve-from';
|
||||
|
||||
import { toAppPlatform } from '../../graphql/types/AppPlatform';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { appPlatformDisplayNames } from '../../platform';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { expoCommandAsync } from '../../utils/expoCli';
|
||||
import { ProfileData } from '../../utils/profiles';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { reviewAndCommitChangesAsync } from './repository';
|
||||
|
||||
export async function ensureExpoDevClientInstalledForDevClientBuildsAsync({
|
||||
projectDir,
|
||||
nonInteractive = false,
|
||||
buildProfiles = [],
|
||||
}: {
|
||||
projectDir: string;
|
||||
nonInteractive?: boolean;
|
||||
buildProfiles?: ProfileData<'build'>[];
|
||||
}): Promise<void> {
|
||||
if (await isExpoDevClientInstalledAsync(projectDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buildProfilesWithDevelopmentClientRequired = buildProfiles.filter(
|
||||
buildProfile => buildProfile.profile.developmentClient
|
||||
);
|
||||
|
||||
const isDevelopmentClientRequired = buildProfilesWithDevelopmentClientRequired.some(Boolean);
|
||||
|
||||
if (!isDevelopmentClientRequired) {
|
||||
return;
|
||||
}
|
||||
|
||||
const platformsToCheck = buildProfilesWithDevelopmentClientRequired.map(
|
||||
({ platform }) => platform
|
||||
);
|
||||
|
||||
const workflowPerPlatformList = await Promise.all(
|
||||
platformsToCheck.map(platform => resolveWorkflowAsync(projectDir, platform))
|
||||
);
|
||||
|
||||
Log.newLine();
|
||||
Log.error(
|
||||
`You want to build a development client build for platforms: ${platformsToCheck
|
||||
.map(i => chalk.bold(appPlatformDisplayNames[toAppPlatform(i)]))
|
||||
.join(', ')}`
|
||||
);
|
||||
Log.error(
|
||||
`However, we detected that you don't have ${chalk.bold(
|
||||
'expo-dev-client'
|
||||
)} installed for your project.`
|
||||
);
|
||||
const areAllManaged = workflowPerPlatformList.every(i => i === Workflow.MANAGED);
|
||||
if (areAllManaged) {
|
||||
const install = await confirmAsync({
|
||||
message: 'Do you want EAS CLI to install expo-dev-client for you?',
|
||||
instructions: 'The command will abort unless you agree.',
|
||||
});
|
||||
if (install) {
|
||||
await installExpoDevClientAsync(projectDir, { nonInteractive });
|
||||
} else {
|
||||
Errors.error(`Install ${chalk.bold('expo-dev-client')} manually and come back later.`, {
|
||||
exit: 1,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Log.warn(`You'll need to install ${chalk.bold('expo-dev-client')} manually.`);
|
||||
Log.warn(
|
||||
learnMore('https://docs.expo.dev/clients/installation/', {
|
||||
learnMoreMessage: 'See installation instructions on how to do it.',
|
||||
dim: false,
|
||||
})
|
||||
);
|
||||
Log.warn('If you proceed anyway, you might not get the build you want.');
|
||||
Log.newLine();
|
||||
const shouldContinue = await confirmAsync({
|
||||
message: 'Do you want to proceed anyway?',
|
||||
initial: false,
|
||||
});
|
||||
if (!shouldContinue) {
|
||||
Errors.error('Come back later', { exit: 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function isExpoDevClientInstalledAsync(projectDir: string): Promise<boolean> {
|
||||
try {
|
||||
resolveFrom(projectDir, 'expo-dev-client/package.json');
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
Log.debug(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function installExpoDevClientAsync(
|
||||
projectDir: string,
|
||||
{ nonInteractive }: { nonInteractive: boolean }
|
||||
): Promise<void> {
|
||||
Log.newLine();
|
||||
Log.log(`Running ${chalk.bold('expo install expo-dev-client')}`);
|
||||
Log.newLine();
|
||||
await expoCommandAsync(projectDir, ['install', 'expo-dev-client']);
|
||||
Log.newLine();
|
||||
if (await getVcsClient().isCommitRequiredAsync()) {
|
||||
await reviewAndCommitChangesAsync('Install expo-dev-client', {
|
||||
nonInteractive,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { BuildFragment, BuildStatus as GraphQLBuildStatus } from '../../graphql/generated';
|
||||
import {
|
||||
AppPlatform,
|
||||
BuildFragment,
|
||||
BuildStatus as GraphQLBuildStatus,
|
||||
} from '../../graphql/generated';
|
||||
import { appPlatformDisplayNames } from '../../platform';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { appPlatformDisplayNames } from '../constants';
|
||||
import { getBuildLogsUrl } from './url';
|
||||
|
||||
export function formatGraphQLBuild(build: BuildFragment) {
|
||||
export function formatGraphQLBuild(build: BuildFragment): string {
|
||||
const actor = getActorName(build);
|
||||
const account = build.project.__typename === 'App' ? build.project.ownerAccount.name : 'unknown';
|
||||
const fields: { label: string; value: string }[] = [
|
||||
const fields: { label: string; value?: string | null }[] = [
|
||||
{ label: 'ID', value: build.id },
|
||||
{
|
||||
label: 'Platform',
|
||||
@@ -37,15 +40,43 @@ export function formatGraphQLBuild(build: BuildFragment) {
|
||||
},
|
||||
{
|
||||
label: 'Distribution',
|
||||
value: build.distribution?.toLowerCase() ?? chalk.gray('unknown'),
|
||||
value: build.distribution?.toLowerCase(),
|
||||
},
|
||||
{
|
||||
label: 'Enterprise Provisioning',
|
||||
value: build.iosEnterpriseProvisioning?.toLowerCase(),
|
||||
},
|
||||
{
|
||||
label: 'Release Channel',
|
||||
value: build.releaseChannel ?? chalk.gray('unknown'),
|
||||
value: build.releaseChannel,
|
||||
},
|
||||
{
|
||||
label: 'Channel',
|
||||
value: build.channel,
|
||||
},
|
||||
{
|
||||
label: 'SDK Version',
|
||||
value: build.sdkVersion,
|
||||
},
|
||||
{
|
||||
label: 'Runtime Version',
|
||||
value: build.runtimeVersion,
|
||||
},
|
||||
{
|
||||
label: 'Version',
|
||||
value: build.appVersion,
|
||||
},
|
||||
{
|
||||
label: build.platform === AppPlatform.Android ? 'Version code' : 'Build number',
|
||||
value: build.appBuildVersion,
|
||||
},
|
||||
{
|
||||
label: 'Commit',
|
||||
value: build.gitCommitHash,
|
||||
},
|
||||
{
|
||||
label: 'Logs',
|
||||
value: getBuildLogsUrl({ buildId: build.id, account }),
|
||||
value: getBuildLogsUrl(build),
|
||||
},
|
||||
{
|
||||
label: 'Artifact',
|
||||
@@ -57,13 +88,13 @@ export function formatGraphQLBuild(build: BuildFragment) {
|
||||
return '<in progress>';
|
||||
case GraphQLBuildStatus.Canceled:
|
||||
case GraphQLBuildStatus.Errored:
|
||||
return '---------';
|
||||
return null;
|
||||
case GraphQLBuildStatus.Finished: {
|
||||
const url = build.artifacts?.buildUrl;
|
||||
return url ? url : chalk.red('not found');
|
||||
}
|
||||
default:
|
||||
return 'unknown';
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -81,15 +112,13 @@ export function formatGraphQLBuild(build: BuildFragment) {
|
||||
{ label: 'Started by', value: actor ?? 'unknown' },
|
||||
];
|
||||
|
||||
return formatFields(fields);
|
||||
const filteredFields = fields.filter(({ value }) => value !== undefined && value !== null) as {
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
return formatFields(filteredFields);
|
||||
}
|
||||
|
||||
const getActorName = (build: BuildFragment): string => {
|
||||
if (!build.initiatingActor) {
|
||||
return 'unknown';
|
||||
} else if (build.initiatingActor.__typename === 'User') {
|
||||
return build.initiatingActor.username;
|
||||
} else {
|
||||
return build.initiatingActor.firstName ?? 'unknown';
|
||||
}
|
||||
return build.initiatingActor?.displayName || 'unknown';
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import indentString from 'indent-string';
|
||||
import qrcodeTerminal from 'qrcode-terminal';
|
||||
|
||||
import {
|
||||
AppPlatform,
|
||||
BuildError,
|
||||
BuildFragment,
|
||||
BuildStatus,
|
||||
@@ -12,51 +13,41 @@ import {
|
||||
EasBuildDeprecationInfoType,
|
||||
} from '../../graphql/generated';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import {
|
||||
appPlatformDisplayNames,
|
||||
appPlatformEmojis,
|
||||
requestedPlatformDisplayNames,
|
||||
} from '../constants';
|
||||
import { appPlatformDisplayNames, appPlatformEmojis } from '../../platform';
|
||||
import { printJsonOnlyOutput } from '../../utils/json';
|
||||
import { getBuildLogsUrl, getInternalDistributionInstallUrl } from './url';
|
||||
|
||||
export function printLogsUrls(
|
||||
accountName: string,
|
||||
builds: { platform: 'android' | 'ios'; buildId: string }[]
|
||||
): void {
|
||||
export function printLogsUrls(builds: BuildFragment[]): void {
|
||||
if (builds.length === 1) {
|
||||
const { buildId } = builds[0];
|
||||
const logsUrl = getBuildLogsUrl({
|
||||
buildId,
|
||||
account: accountName,
|
||||
});
|
||||
Log.log(`Build details: ${chalk.underline(logsUrl)}`);
|
||||
Log.log(`Build details: ${chalk.underline(getBuildLogsUrl(builds[0]))}`);
|
||||
} else {
|
||||
builds.forEach(({ buildId, platform }) => {
|
||||
const logsUrl = getBuildLogsUrl({
|
||||
buildId,
|
||||
account: accountName,
|
||||
});
|
||||
builds.forEach(build => {
|
||||
const logsUrl = getBuildLogsUrl(build);
|
||||
Log.log(
|
||||
`${requestedPlatformDisplayNames[platform]} build details: ${chalk.underline(logsUrl)}`
|
||||
`${appPlatformEmojis[build.platform]} ${
|
||||
appPlatformDisplayNames[build.platform]
|
||||
} build details: ${chalk.underline(logsUrl)}`
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function printBuildResults(accountName: string, builds: (BuildFragment | null)[]): void {
|
||||
Log.newLine();
|
||||
if (builds.length === 1) {
|
||||
const [build] = builds;
|
||||
assert(build, 'Build should be defined');
|
||||
printBuildResult(accountName, build);
|
||||
export function printBuildResults(builds: (BuildFragment | null)[], json: boolean): void {
|
||||
if (json) {
|
||||
printJsonOnlyOutput(builds);
|
||||
} else {
|
||||
(builds.filter(i => i) as BuildFragment[]).forEach(build =>
|
||||
printBuildResult(accountName, build)
|
||||
);
|
||||
Log.newLine();
|
||||
if (builds.length === 1) {
|
||||
const [build] = builds;
|
||||
assert(build, 'Build should be defined');
|
||||
printBuildResult(build);
|
||||
} else {
|
||||
(builds.filter(i => i) as BuildFragment[]).forEach(build => printBuildResult(build));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printBuildResult(accountName: string, build: BuildFragment): void {
|
||||
function printBuildResult(build: BuildFragment): void {
|
||||
Log.addNewLineIfNone();
|
||||
if (build.status === BuildStatus.Errored) {
|
||||
const userError = build.error;
|
||||
@@ -80,12 +71,14 @@ function printBuildResult(accountName: string, build: BuildFragment): void {
|
||||
}
|
||||
|
||||
if (build.distribution === DistributionType.Internal) {
|
||||
const logsUrl = getBuildLogsUrl({
|
||||
buildId: build.id,
|
||||
account: accountName,
|
||||
});
|
||||
const installUrl = getInternalDistributionInstallUrl(build);
|
||||
qrcodeTerminal.generate(installUrl, code => console.log(`${indentString(code, 2)}\n`));
|
||||
const logsUrl = getBuildLogsUrl(build);
|
||||
// It's tricky to install the .apk file directly on Android so let's fallback
|
||||
// to the build details page and let people press the button to download there
|
||||
const qrcodeUrl =
|
||||
build.platform === AppPlatform.Ios ? getInternalDistributionInstallUrl(build) : logsUrl;
|
||||
qrcodeTerminal.generate(qrcodeUrl, { small: true }, code =>
|
||||
Log.log(`${indentString(code, 2)}\n`)
|
||||
);
|
||||
Log.log(
|
||||
`${appPlatformEmojis[build.platform]} Open this link on your ${
|
||||
appPlatformDisplayNames[build.platform]
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import ora from 'ora';
|
||||
import path from 'path';
|
||||
import tar from 'tar';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { confirmAsync, promptAsync } from '../../prompts';
|
||||
import { formatBytes } from '../../utils/files';
|
||||
import { getTmpDirectory } from '../../utils/paths';
|
||||
import { endTimer, formatMilliseconds, startTimer } from '../../utils/timer';
|
||||
import vcs from '../../vcs';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
|
||||
export async function maybeBailOnRepoStatusAsync(): Promise<void> {
|
||||
if (await vcs.hasUncommittedChangesAsync()) {
|
||||
if (!(await getVcsClient().isCommitRequiredAsync())) {
|
||||
return;
|
||||
}
|
||||
Log.addNewLineIfNone();
|
||||
@@ -33,7 +33,7 @@ export async function maybeBailOnRepoStatusAsync(): Promise<void> {
|
||||
}
|
||||
|
||||
export async function ensureRepoIsCleanAsync(nonInteractive = false): Promise<void> {
|
||||
if (!(await vcs.hasUncommittedChangesAsync())) {
|
||||
if (!(await getVcsClient().isCommitRequiredAsync())) {
|
||||
return;
|
||||
}
|
||||
Log.addNewLineIfNone();
|
||||
@@ -70,7 +70,7 @@ export async function commitPromptAsync({
|
||||
initial: initialCommitMessage,
|
||||
validate: (input: string) => input !== '',
|
||||
});
|
||||
await vcs.commitAsync({ commitAllFiles, commitMessage: message });
|
||||
await getVcsClient().commitAsync({ commitAllFiles, commitMessage: message });
|
||||
}
|
||||
|
||||
export async function makeProjectTarballAsync(): Promise<{ path: string; size: number }> {
|
||||
@@ -95,7 +95,7 @@ export async function makeProjectTarballAsync(): Promise<{ path: string; size: n
|
||||
startTimer(compressTimerLabel);
|
||||
|
||||
try {
|
||||
await vcs.makeShallowCopyAsync(shallowClonePath);
|
||||
await getVcsClient().makeShallowCopyAsync(shallowClonePath);
|
||||
await tar.create({ cwd: shallowClonePath, file: tarPath, prefix: 'project', gzip: true }, [
|
||||
'.',
|
||||
]);
|
||||
@@ -121,3 +121,59 @@ export async function makeProjectTarballAsync(): Promise<{ path: string; size: n
|
||||
|
||||
return { size, path: tarPath };
|
||||
}
|
||||
|
||||
enum ShouldCommitChanges {
|
||||
Yes,
|
||||
ShowDiffFirst,
|
||||
Abort,
|
||||
}
|
||||
|
||||
export async function reviewAndCommitChangesAsync(
|
||||
initialCommitMessage: string,
|
||||
{ nonInteractive, askedFirstTime = true }: { nonInteractive: boolean; askedFirstTime?: boolean }
|
||||
): Promise<void> {
|
||||
if (process.env.EAS_BUILD_AUTOCOMMIT) {
|
||||
await getVcsClient().commitAsync({
|
||||
commitMessage: initialCommitMessage,
|
||||
commitAllFiles: false,
|
||||
nonInteractive,
|
||||
});
|
||||
Log.withTick('Committed changes.');
|
||||
return;
|
||||
}
|
||||
if (nonInteractive) {
|
||||
throw new Error(
|
||||
'Cannot commit changes when --non-interactive is specified. Run the command in interactive mode or set EAS_BUILD_AUTOCOMMIT=1 in your environment.'
|
||||
);
|
||||
}
|
||||
const { selected } = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'selected',
|
||||
message: 'Can we commit these changes to git for you?',
|
||||
choices: [
|
||||
{ title: 'Yes', value: ShouldCommitChanges.Yes },
|
||||
...(askedFirstTime
|
||||
? [{ title: 'Show the diff and ask me again', value: ShouldCommitChanges.ShowDiffFirst }]
|
||||
: []),
|
||||
{
|
||||
title: 'Abort build process',
|
||||
value: ShouldCommitChanges.Abort,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (selected === ShouldCommitChanges.Abort) {
|
||||
throw new Error(
|
||||
"Aborting, run the command again once you're ready. Make sure to commit any changes you've made."
|
||||
);
|
||||
} else if (selected === ShouldCommitChanges.Yes) {
|
||||
await commitPromptAsync({ initialCommitMessage });
|
||||
Log.withTick('Committed changes.');
|
||||
} else if (selected === ShouldCommitChanges.ShowDiffFirst) {
|
||||
await getVcsClient().showDiffAsync();
|
||||
await reviewAndCommitChangesAsync(initialCommitMessage, {
|
||||
nonInteractive,
|
||||
askedFirstTime: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ExpoConfig, getPackageJson } from '@expo/config';
|
||||
|
||||
export function isExpoUpdatesInstalled(projectDir: string) {
|
||||
export function isExpoUpdatesInstalled(projectDir: string): boolean {
|
||||
const packageJson = getPackageJson(projectDir);
|
||||
return packageJson.dependencies && 'expo-updates' in packageJson.dependencies;
|
||||
return !!(packageJson.dependencies && 'expo-updates' in packageJson.dependencies);
|
||||
}
|
||||
|
||||
export function ensureValidVersions(exp: ExpoConfig): void {
|
||||
|
||||
@@ -3,14 +3,17 @@ import assert from 'assert';
|
||||
import { getExpoApiBaseUrl, getExpoWebsiteBaseUrl } from '../../api';
|
||||
import { AppPlatform, BuildFragment } from '../../graphql/generated';
|
||||
|
||||
export function getBuildLogsUrl({
|
||||
buildId,
|
||||
account,
|
||||
}: {
|
||||
buildId: string;
|
||||
account: string;
|
||||
}): string {
|
||||
return `${getExpoWebsiteBaseUrl()}/accounts/${account}/builds/${buildId}`;
|
||||
export function getProjectDashboardUrl(accountName: string, projectName: string): string {
|
||||
return `${getExpoWebsiteBaseUrl()}/accounts/${accountName}/projects/${projectName}`;
|
||||
}
|
||||
|
||||
export function getBuildLogsUrl(build: BuildFragment): string {
|
||||
const { project } = build;
|
||||
if (project.__typename === 'App') {
|
||||
return `${getExpoWebsiteBaseUrl()}/accounts/${project.ownerAccount.name}/builds/${build.id}`;
|
||||
} else {
|
||||
return `${getExpoWebsiteBaseUrl()}/builds/${build.id}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getArtifactUrl(artifactId: string): string {
|
||||
@@ -19,7 +22,7 @@ export function getArtifactUrl(artifactId: string): string {
|
||||
|
||||
export function getInternalDistributionInstallUrl(build: BuildFragment): string {
|
||||
if (build.platform === AppPlatform.Ios) {
|
||||
return `itms-services://?action=download-manifest;url=${getExpoApiBaseUrl()}/--/api/v2/projects/${
|
||||
return `itms-services://?action=download-manifest;url=${getExpoApiBaseUrl()}/v2/projects/${
|
||||
build.project.id
|
||||
}/builds/${build.id}/manifest.plist`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ExpoConfig, getConfigFilePaths } from '@expo/config';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
import semver from 'semver';
|
||||
|
||||
import Log from '../../log';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { updateAppJsonConfigAsync } from './appJson';
|
||||
|
||||
export 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');
|
||||
}
|
||||
}
|
||||
|
||||
export async function bumpAppVersionAsync({
|
||||
appVersion,
|
||||
projectDir,
|
||||
exp,
|
||||
}: {
|
||||
appVersion: string;
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
}): Promise<void> {
|
||||
let bumpedAppVersion: string;
|
||||
if (semver.valid(appVersion)) {
|
||||
bumpedAppVersion = nullthrows(semver.inc(appVersion, 'patch'));
|
||||
Log.log(
|
||||
`Bumping ${chalk.bold('expo.version')} from ${chalk.bold(appVersion)} to ${chalk.bold(
|
||||
bumpedAppVersion
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
Log.log(`${chalk.bold('expo.version')} = ${chalk.bold(appVersion)} is not a valid semver`);
|
||||
bumpedAppVersion = (
|
||||
await promptAsync({
|
||||
type: 'text',
|
||||
name: 'bumpedAppVersion',
|
||||
message: 'What is the next version?',
|
||||
})
|
||||
).bumpedAppVersion;
|
||||
}
|
||||
await updateAppJsonConfigAsync({ projectDir, exp }, config => {
|
||||
config.version = bumpedAppVersion;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import Log, { learnMore } from '../log';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import { CommonContext } from './context';
|
||||
|
||||
export function checkNodeEnvVariable(ctx: CommonContext<Platform>): void {
|
||||
if (ctx.buildProfile.env?.NODE_ENV === 'production') {
|
||||
Log.warn(
|
||||
'You set NODE_ENV=production in the build profile. Remember that it will be available during the entire build process. In particular, it will make yarn/npm install only production packages.'
|
||||
);
|
||||
Log.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkGoogleServicesFileAsync<T extends Platform>(
|
||||
ctx: CommonContext<T>
|
||||
): Promise<void> {
|
||||
if (ctx.workflow === Workflow.GENERIC || ctx.buildProfile?.env?.GOOGLE_SERVICES_FILE) {
|
||||
return;
|
||||
}
|
||||
const googleServicesFilePath = ctx.exp[ctx.platform]?.googleServicesFile;
|
||||
if (!googleServicesFilePath) {
|
||||
return;
|
||||
}
|
||||
const rootDir = path.normalize(await getVcsClient().getRootPathAsync());
|
||||
const absGoogleServicesFilePath = path.resolve(ctx.projectDir, googleServicesFilePath);
|
||||
if (
|
||||
(await fs.pathExists(absGoogleServicesFilePath)) &&
|
||||
(!isInsideDirectory(absGoogleServicesFilePath, rootDir) ||
|
||||
(await getVcsClient().isFileIgnoredAsync(path.relative(rootDir, absGoogleServicesFilePath))))
|
||||
) {
|
||||
Log.warn(
|
||||
`File specified via "${ctx.platform}.googleServicesFile" field in your app.json is not checked in to your repository and won't be uploaded to the builder.`
|
||||
);
|
||||
Log.warn(
|
||||
`Use EAS Secret to pass all values that you don't want to include in your version control. ${learnMore(
|
||||
'https://docs.expo.dev/build-reference/variables/#using-secrets-in-environment-variables'
|
||||
)}`
|
||||
);
|
||||
Log.warn(
|
||||
`If you are using that file for compatibility with the classic build service (expo build) you can silence this warning by setting your build profile's env.GOOGLE_SERVICES_FILE in eas.json to any non-empty string.`
|
||||
);
|
||||
Log.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
function isInsideDirectory(file: string, directory: string): boolean {
|
||||
return file.startsWith(directory);
|
||||
}
|
||||
@@ -1,13 +1,19 @@
|
||||
import { Command } from '@oclif/command';
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import { Command } from '@oclif/core';
|
||||
import semver from 'semver';
|
||||
|
||||
import {
|
||||
AnalyticsEvent,
|
||||
flushAsync as flushAnalyticsAsync,
|
||||
initAsync as initAnalyticsAsync,
|
||||
logEvent,
|
||||
} from '../analytics';
|
||||
} from '../analytics/rudderstackClient';
|
||||
import { findProjectRootAsync } from '../project/projectUtils';
|
||||
import { getUserAsync } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { setVcsClient } from '../vcs';
|
||||
import GitClient from '../vcs/clients/git';
|
||||
|
||||
export default abstract class EasCommand extends Command {
|
||||
/**
|
||||
@@ -15,12 +21,21 @@ export default abstract class EasCommand extends Command {
|
||||
* force the user to log in
|
||||
*/
|
||||
protected requiresAuthentication = true;
|
||||
protected mustBeRunInsideProject = true;
|
||||
|
||||
async init() {
|
||||
protected abstract runAsync(): Promise<any>;
|
||||
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
async run(): Promise<any> {
|
||||
await initAnalyticsAsync();
|
||||
if (this.mustBeRunInsideProject) {
|
||||
await this.applyCliConfigAsync();
|
||||
}
|
||||
|
||||
if (this.requiresAuthentication) {
|
||||
await ensureLoggedInAsync();
|
||||
const { flags } = await this.parse();
|
||||
const nonInteractive = (flags as any)['non-interactive'] ?? false;
|
||||
await ensureLoggedInAsync({ nonInteractive });
|
||||
} else {
|
||||
await getUserAsync();
|
||||
}
|
||||
@@ -29,10 +44,26 @@ export default abstract class EasCommand extends Command {
|
||||
// commands/submit === submit, commands/build/list === build:list
|
||||
action: `eas ${this.id}`,
|
||||
});
|
||||
return this.runAsync();
|
||||
}
|
||||
|
||||
async finally(err: Error) {
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
async finally(err: Error): Promise<any> {
|
||||
await flushAnalyticsAsync();
|
||||
return super.finally(err);
|
||||
}
|
||||
|
||||
private async applyCliConfigAsync(): Promise<void> {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const config = await easJsonReader.getCliConfigAsync();
|
||||
if (config?.version && !semver.satisfies(easCliVersion, config.version)) {
|
||||
throw new Error(
|
||||
`You are on eas-cli@${easCliVersion} which does not satisfy the CLI version constraint in eas.json (${config.version})`
|
||||
);
|
||||
}
|
||||
if (config?.requireCommit) {
|
||||
setVcsClient(new GitClient());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,65 @@
|
||||
import { flushAsync, initAsync, logEvent } from '../../analytics';
|
||||
import { asMock } from '../../__tests__/utils';
|
||||
import { flushAsync, initAsync, logEvent } from '../../analytics/rudderstackClient';
|
||||
import { jester as mockJester } from '../../credentials/__tests__/fixtures-constants';
|
||||
import { getUserAsync } from '../../user/User';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import EasCommand from '../EasCommand';
|
||||
import TestEasCommand from './TestEasCommand';
|
||||
|
||||
TestEasCommand.prototype.authValue = jest.fn().mockImplementation(() => true);
|
||||
|
||||
jest.mock('../../user/User');
|
||||
jest.mock('../../user/actions', () => ({ ensureLoggedInAsync: jest.fn() }));
|
||||
jest.mock('../../analytics/rudderstackClient', () => {
|
||||
const { AnalyticsEvent } = jest.requireActual('../../analytics/rudderstackClient');
|
||||
return {
|
||||
AnalyticsEvent,
|
||||
logEvent: jest.fn(),
|
||||
initAsync: jest.fn(),
|
||||
flushAsync: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
let originalProcessArgv: string[];
|
||||
|
||||
beforeAll(() => {
|
||||
originalProcessArgv = process.argv;
|
||||
process.argv = [];
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.argv = originalProcessArgv;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
asMock(getUserAsync)
|
||||
.mockReset()
|
||||
.mockImplementation(() => mockJester);
|
||||
asMock(ensureLoggedInAsync)
|
||||
.mockReset()
|
||||
.mockImplementation(() => mockJester);
|
||||
asMock(initAsync).mockReset();
|
||||
asMock(flushAsync).mockReset();
|
||||
asMock(logEvent).mockReset();
|
||||
});
|
||||
|
||||
describe(EasCommand.name, () => {
|
||||
beforeAll(() => {
|
||||
TestEasCommand.prototype.authValue = jest.fn().mockImplementation(() => true);
|
||||
jest.mock('../../user/User', () => ({ getUserAsync: jest.fn(() => mockJester) }));
|
||||
jest.mock('../../user/actions', () => ({ ensureLoggedInAsync: jest.fn(() => mockJester) }));
|
||||
jest.mock('../../analytics', () => {
|
||||
const { AnalyticsEvent } = jest.requireActual('../../analytics');
|
||||
return {
|
||||
AnalyticsEvent,
|
||||
logEvent: jest.fn(),
|
||||
initAsync: jest.fn(),
|
||||
flushAsync: jest.fn(),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(ensureLoggedInAsync as jest.Mock).mockClear();
|
||||
(initAsync as jest.Mock).mockClear();
|
||||
(flushAsync as jest.Mock).mockClear();
|
||||
(logEvent as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
describe('without exceptions', () => {
|
||||
// The first test in this suite should have an increased timeout
|
||||
// because of the implementation of Command from @oclif/command.
|
||||
// It seems that loading config takes significant amount of time
|
||||
// and I'm not sure how to mock it.
|
||||
//
|
||||
// See https://github.com/oclif/command/blob/master/src/command.ts#L80
|
||||
// and look for "Config.load"
|
||||
it('ensures the user is logged in', async () => {
|
||||
await TestEasCommand.run();
|
||||
|
||||
expect(ensureLoggedInAsync).toHaveReturnedWith(mockJester);
|
||||
});
|
||||
expect(ensureLoggedInAsync).toHaveBeenCalled();
|
||||
}, 15_000);
|
||||
|
||||
it('ensures the user data is read from cache', async () => {
|
||||
(TestEasCommand.prototype.authValue as jest.Mock).mockImplementationOnce(() => false);
|
||||
asMock(TestEasCommand.prototype.authValue).mockImplementationOnce(() => false);
|
||||
|
||||
await TestEasCommand.run();
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ import EasCommand from '../EasCommand';
|
||||
export default class TestEasCommand extends EasCommand {
|
||||
requiresAuthentication = this.authValue();
|
||||
|
||||
authValue() {
|
||||
authValue(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
async run() {}
|
||||
async runAsync(): Promise<void> {}
|
||||
}
|
||||
|
||||
TestEasCommand.id = 'TestEasCommand'; // normally oclif will assign ids, but b/c this is located outside the commands folder it will not
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Command } from '@oclif/command';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log from '../../log';
|
||||
import { showLoginPromptAsync } from '../../user/actions';
|
||||
|
||||
export default class AccountLogin extends Command {
|
||||
export default class AccountLogin extends EasCommand {
|
||||
static description = 'log in with your Expo account';
|
||||
|
||||
static aliases = ['login'];
|
||||
|
||||
async run() {
|
||||
Log.log('Log in to EAS');
|
||||
protected mustBeRunInsideProject = false;
|
||||
protected requiresAuthentication = false;
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
await showLoginPromptAsync();
|
||||
Log.log('Logged in');
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { Command } from '@oclif/command';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log from '../../log';
|
||||
import { logoutAsync } from '../../user/User';
|
||||
|
||||
export default class AccountLogout extends Command {
|
||||
export default class AccountLogout extends EasCommand {
|
||||
static description = 'log out';
|
||||
|
||||
static aliases = ['logout'];
|
||||
|
||||
async run() {
|
||||
protected mustBeRunInsideProject = false;
|
||||
protected requiresAuthentication = false;
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
await logoutAsync();
|
||||
Log.log('Logged out');
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { Command } from '@oclif/command';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log from '../../log';
|
||||
import { getActorDisplayName, getUserAsync } from '../../user/User';
|
||||
|
||||
export default class AccountView extends Command {
|
||||
export default class AccountView extends EasCommand {
|
||||
static description = 'show the username you are logged in as';
|
||||
|
||||
static aliases = ['whoami'];
|
||||
|
||||
async run() {
|
||||
protected mustBeRunInsideProject = false;
|
||||
protected requiresAuthentication = false;
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const user = await getUserAsync();
|
||||
if (user) {
|
||||
Log.log(chalk.green(getActorDisplayName(user)));
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { Command } from '@oclif/command';
|
||||
|
||||
import EasCommand from '../commandUtils/EasCommand';
|
||||
import Log from '../log';
|
||||
import UserSettings from '../user/UserSettings';
|
||||
|
||||
export default class AnalyticsView extends Command {
|
||||
static description = 'View or change analytics settings';
|
||||
export default class AnalyticsView extends EasCommand {
|
||||
static description = 'view or change analytics settings';
|
||||
|
||||
static args = [{ name: 'STATUS', options: ['on', 'off'] }];
|
||||
|
||||
async run() {
|
||||
const { STATUS: status } = this.parse(AnalyticsView).args;
|
||||
protected requiresAuthentication = false;
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { STATUS: status } = (await this.parse(AnalyticsView)).args;
|
||||
if (status) {
|
||||
await UserSettings.setAsync('amplitudeEnabled', status === 'on');
|
||||
await UserSettings.setAsync('analyticsEnabled', status === 'on');
|
||||
Log.withTick(`${status === 'on' ? 'Enabling' : 'Disabling'} analytics.`);
|
||||
} else {
|
||||
const amplitudeEnabled = await UserSettings.getAsync('amplitudeEnabled', null);
|
||||
const analyticsEnabled = await UserSettings.getAsync('analyticsEnabled', null);
|
||||
Log.log(
|
||||
`Analytics are ${
|
||||
amplitudeEnabled === false ? 'disabled' : 'enabled'
|
||||
analyticsEnabled === false ? 'disabled' : 'enabled'
|
||||
} on this eas-cli installation.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
CreateUpdateBranchForAppMutation,
|
||||
@@ -16,7 +17,7 @@ import {
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import vcs from '../../vcs';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
|
||||
export async function createUpdateBranchOnAppAsync({
|
||||
appId,
|
||||
@@ -49,8 +50,7 @@ export async function createUpdateBranchOnAppAsync({
|
||||
return newBranch;
|
||||
}
|
||||
|
||||
export default class BranchCreate extends Command {
|
||||
static hidden = true;
|
||||
export default class BranchCreate extends EasCommand {
|
||||
static description = 'Create a branch on the current project.';
|
||||
|
||||
static args = [
|
||||
@@ -62,23 +62,19 @@ export default class BranchCreate extends Command {
|
||||
];
|
||||
|
||||
static flags = {
|
||||
json: flags.boolean({
|
||||
json: Flags.boolean({
|
||||
description: 'return a json with the new branch ID and name.',
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
args: { name },
|
||||
flags,
|
||||
} = this.parse(BranchCreate);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
} = await this.parse(BranchCreate);
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const fullName = await getProjectFullNameAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
@@ -93,7 +89,8 @@ export default class BranchCreate extends Command {
|
||||
name: 'name',
|
||||
message: 'Please name the branch:',
|
||||
initial:
|
||||
(await vcs.getBranchNameAsync()) || `branch-${Math.random().toString(36).substr(2, 4)}`,
|
||||
(await getVcsClient().getBranchNameAsync()) ||
|
||||
`branch-${Math.random().toString(36).substr(2, 4)}`,
|
||||
validate: value => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
DeleteUpdateBranchMutation,
|
||||
@@ -22,7 +23,7 @@ import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
async function getBranchInfoAsync({
|
||||
appId,
|
||||
name,
|
||||
}: GetBranchInfoQueryVariables): Promise<{ branchId: string }> {
|
||||
}: GetBranchInfoQueryVariables): Promise<GetBranchInfoQuery> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<GetBranchInfoQuery, GetBranchInfoQueryVariables>(
|
||||
@@ -42,15 +43,12 @@ async function getBranchInfoAsync({
|
||||
{
|
||||
appId,
|
||||
name,
|
||||
}
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
const branchId = data.app?.byId.updateBranchByName?.id;
|
||||
if (!branchId) {
|
||||
throw new Error(`Could not find branch ${name} on ${appId}`);
|
||||
}
|
||||
return { branchId };
|
||||
return data;
|
||||
}
|
||||
|
||||
async function deleteBranchOnAppAsync({
|
||||
@@ -77,9 +75,8 @@ async function deleteBranchOnAppAsync({
|
||||
return data.updateBranch.deleteUpdateBranch;
|
||||
}
|
||||
|
||||
export default class BranchDelete extends Command {
|
||||
static hidden = true;
|
||||
static description = 'Republish an update group';
|
||||
export default class BranchDelete extends EasCommand {
|
||||
static description = 'Delete a branch on the current project';
|
||||
|
||||
static args = [
|
||||
{
|
||||
@@ -89,22 +86,19 @@ export default class BranchDelete extends Command {
|
||||
},
|
||||
];
|
||||
static flags = {
|
||||
json: flags.boolean({
|
||||
json: Flags.boolean({
|
||||
description: `return JSON with the edited branch's ID and name.`,
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
args: { name },
|
||||
flags: { json: jsonFlag },
|
||||
} = this.parse(BranchDelete);
|
||||
} = await this.parse(BranchDelete);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const fullName = await getProjectFullNameAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
@@ -122,7 +116,11 @@ export default class BranchDelete extends Command {
|
||||
}));
|
||||
}
|
||||
|
||||
const { branchId } = await getBranchInfoAsync({ appId: projectId, name });
|
||||
const data = await getBranchInfoAsync({ appId: projectId, name });
|
||||
const branchId = data.app?.byId.updateBranchByName?.id;
|
||||
if (!branchId) {
|
||||
throw new Error(`Could not find branch ${name} on ${fullName}`);
|
||||
}
|
||||
|
||||
if (!jsonFlag) {
|
||||
Log.addNewLineIfNone();
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import CliTable from 'cli-table3';
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import { BranchesByAppQuery, BranchesByAppQueryVariables } from '../../graphql/generated';
|
||||
import {
|
||||
BranchesByAppQuery,
|
||||
BranchesByAppQueryVariables,
|
||||
UpdateBranchFragment,
|
||||
} from '../../graphql/generated';
|
||||
import { UpdateBranchFragmentNode } from '../../graphql/types/UpdateBranch';
|
||||
import Log from '../../log';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { UPDATE_COLUMNS, formatUpdate, getPlatformsForGroup } from '../../update/utils';
|
||||
|
||||
const BRANCHES_LIMIT = 10_000;
|
||||
|
||||
export async function listBranchesAsync({ projectId }: { projectId: string }) {
|
||||
export async function listBranchesAsync({
|
||||
projectId,
|
||||
}: {
|
||||
projectId: string;
|
||||
}): Promise<UpdateBranchFragment[]> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<BranchesByAppQuery, BranchesByAppQueryVariables>(
|
||||
@@ -23,34 +34,18 @@ export async function listBranchesAsync({ projectId }: { projectId: string }) {
|
||||
id
|
||||
updateBranches(offset: 0, limit: $limit) {
|
||||
id
|
||||
name
|
||||
updates(offset: 0, limit: 10) {
|
||||
id
|
||||
actor {
|
||||
__typename
|
||||
id
|
||||
... on User {
|
||||
username
|
||||
}
|
||||
... on Robot {
|
||||
firstName
|
||||
}
|
||||
}
|
||||
createdAt
|
||||
message
|
||||
runtimeVersion
|
||||
group
|
||||
platform
|
||||
}
|
||||
...UpdateBranchFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(UpdateBranchFragmentNode)}
|
||||
`,
|
||||
{
|
||||
appId: projectId,
|
||||
limit: BRANCHES_LIMIT,
|
||||
}
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
@@ -58,25 +53,20 @@ export async function listBranchesAsync({ projectId }: { projectId: string }) {
|
||||
return data?.app?.byId.updateBranches ?? [];
|
||||
}
|
||||
|
||||
export default class BranchList extends Command {
|
||||
static hidden = true;
|
||||
|
||||
export default class BranchList extends EasCommand {
|
||||
static description = 'List all branches on this project.';
|
||||
|
||||
static flags = {
|
||||
json: flags.boolean({
|
||||
json: Flags.boolean({
|
||||
description: 'return output as JSON',
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
const { flags } = this.parse(BranchList);
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(BranchList);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const branches = await listBranchesAsync({ projectId });
|
||||
@@ -84,7 +74,7 @@ export default class BranchList extends Command {
|
||||
Log.log(JSON.stringify(branches, null, 2));
|
||||
} else {
|
||||
const table = new CliTable({
|
||||
head: ['branch', ...UPDATE_COLUMNS],
|
||||
head: ['Branch', ...UPDATE_COLUMNS],
|
||||
wordWrap: true,
|
||||
});
|
||||
|
||||
@@ -100,6 +90,8 @@ export default class BranchList extends Command {
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.bold('Branches with their most recent update group:'));
|
||||
Log.log(table.toString());
|
||||
if (branches.length >= BRANCHES_LIMIT) {
|
||||
|
||||
@@ -1,352 +1,10 @@
|
||||
import { getConfig, getDefaultTarget } from '@expo/config';
|
||||
import { getRuntimeVersionForSDKVersion } from '@expo/sdk-runtime-versions';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import Table from 'cli-table3';
|
||||
import dateFormat from 'dateformat';
|
||||
import gql from 'graphql-tag';
|
||||
import { uniqBy } from 'lodash';
|
||||
import ora from 'ora';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
Actor,
|
||||
GetUpdateGroupAsyncQuery,
|
||||
RootQueryUpdatesByGroupArgs,
|
||||
Update,
|
||||
UpdateInfoGroup,
|
||||
} from '../../graphql/generated';
|
||||
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectAccountNameAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import {
|
||||
PublishPlatform,
|
||||
buildBundlesAsync,
|
||||
buildUpdateInfoGroupAsync,
|
||||
collectAssets,
|
||||
uploadAssetsAsync,
|
||||
} from '../../project/publish';
|
||||
import { promptAsync, selectAsync } from '../../prompts';
|
||||
import { formatUpdate } from '../../update/utils';
|
||||
import vcs from '../../vcs';
|
||||
import { listBranchesAsync } from './list';
|
||||
import { viewUpdateBranchAsync } from './view';
|
||||
|
||||
export const defaultPublishPlatforms: PublishPlatform[] = ['android', 'ios'];
|
||||
type PlatformFlag = PublishPlatform | 'all';
|
||||
|
||||
async function getUpdateGroupAsync({
|
||||
group,
|
||||
}: RootQueryUpdatesByGroupArgs): Promise<GetUpdateGroupAsyncQuery['updatesByGroup']> {
|
||||
const { updatesByGroup } = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<GetUpdateGroupAsyncQuery, RootQueryUpdatesByGroupArgs>(
|
||||
gql`
|
||||
query getUpdateGroupAsync($group: ID!) {
|
||||
updatesByGroup(group: $group) {
|
||||
id
|
||||
group
|
||||
runtimeVersion
|
||||
manifestFragment
|
||||
platform
|
||||
message
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
group,
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return updatesByGroup;
|
||||
}
|
||||
|
||||
export default class BranchPublish extends Command {
|
||||
export default class BranchPublish extends EasCommand {
|
||||
static description = 'deprecated, use "eas update"';
|
||||
static hidden = true;
|
||||
static description = 'Publish an update group to a branch.';
|
||||
|
||||
static args = [
|
||||
{
|
||||
name: 'name',
|
||||
description: 'Name of the branch to publish on',
|
||||
},
|
||||
];
|
||||
|
||||
static flags = {
|
||||
message: flags.string({
|
||||
description: 'Short message describing the updates.',
|
||||
required: false,
|
||||
}),
|
||||
republish: flags.boolean({
|
||||
description: 'republish an update group',
|
||||
exclusive: ['input-dir', 'skip-bundler'],
|
||||
}),
|
||||
group: flags.string({
|
||||
description: 'update group to republish',
|
||||
exclusive: ['input-dir', 'skip-bundler'],
|
||||
}),
|
||||
'input-dir': flags.string({
|
||||
description: 'location of the bundle',
|
||||
default: 'dist',
|
||||
required: false,
|
||||
}),
|
||||
'skip-bundler': flags.boolean({
|
||||
description: `skip running Expo CLI to bundle the app before publishing`,
|
||||
default: false,
|
||||
}),
|
||||
platform: flags.enum({
|
||||
char: 'p',
|
||||
description: `Only publish to a single platform`,
|
||||
options: [...defaultPublishPlatforms, 'all'],
|
||||
default: 'all',
|
||||
required: false,
|
||||
}),
|
||||
json: flags.boolean({
|
||||
description: `return a json with the new update group.`,
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
let {
|
||||
args: { name },
|
||||
flags: {
|
||||
json: jsonFlag,
|
||||
message,
|
||||
republish,
|
||||
group,
|
||||
'input-dir': inputDir,
|
||||
'skip-bundler': skipBundler,
|
||||
},
|
||||
} = this.parse(BranchPublish);
|
||||
const platformFlag = this.parse(BranchPublish).flags.platform as PlatformFlag;
|
||||
// If a group was specified, that means we are republishing it.
|
||||
republish = group ? true : republish;
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const accountName = await getProjectAccountNameAsync(exp);
|
||||
let { slug, runtimeVersion, sdkVersion } = exp;
|
||||
|
||||
// When a SDK version is supplied instead of a runtime version and we're in the managed workflow
|
||||
// construct the runtimeVersion with special meaning indicating that the runtime is an
|
||||
// Expo SDK preset runtime that can be launched in Expo Go.
|
||||
const isManagedProject = getDefaultTarget(projectDir) === 'managed';
|
||||
if (!runtimeVersion && sdkVersion && isManagedProject) {
|
||||
Log.withTick('Generating runtime version from sdk version');
|
||||
runtimeVersion = getRuntimeVersionForSDKVersion(sdkVersion);
|
||||
}
|
||||
|
||||
if (!runtimeVersion) {
|
||||
throw new Error(
|
||||
"Couldn't find 'runtimeVersion'. Please specify it under the 'expo' key in 'app.json'"
|
||||
);
|
||||
}
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
if (!name) {
|
||||
const validationMessage = 'branch name may not be empty.';
|
||||
if (jsonFlag) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
|
||||
const branches = await listBranchesAsync({ projectId });
|
||||
name = await selectAsync<string>(
|
||||
'which branch would you like to publish on?',
|
||||
branches.map(branch => {
|
||||
return {
|
||||
title: `${branch.name} ${chalk.grey(
|
||||
`- current update: ${formatUpdate(branch.updates[0])}`
|
||||
)}`,
|
||||
value: branch.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
assert(name, 'branch name must be specified.');
|
||||
|
||||
const { id: branchId, updates } = await viewUpdateBranchAsync({
|
||||
appId: projectId,
|
||||
name,
|
||||
});
|
||||
|
||||
let updateInfoGroup: UpdateInfoGroup = {};
|
||||
let oldMessage: string, oldRuntimeVersion: string;
|
||||
if (republish) {
|
||||
// If we are republishing, we don't need to worry about building the bundle or uploading the assets.
|
||||
// Instead we get the `updateInfoGroup` from the update we wish to republish.
|
||||
let updatesToRepublish: Pick<
|
||||
Update,
|
||||
'group' | 'message' | 'runtimeVersion' | 'manifestFragment' | 'platform'
|
||||
>[];
|
||||
if (group) {
|
||||
updatesToRepublish = await getUpdateGroupAsync({ group });
|
||||
} else {
|
||||
// Drop into interactive mode if the user has not specified an update group to republish.
|
||||
if (jsonFlag) {
|
||||
throw new Error('You must specify the update group to republish.');
|
||||
}
|
||||
|
||||
const updateGroups = uniqBy(updates, u => u.group)
|
||||
.filter(update => {
|
||||
// Only show groups that have updates on the specified platform(s).
|
||||
return platformFlag === 'all' || update.platform === platformFlag;
|
||||
})
|
||||
.map(update => ({
|
||||
title: formatUpdateTitle(update),
|
||||
value: update.group,
|
||||
}));
|
||||
if (updateGroups.length === 0) {
|
||||
throw new Error(
|
||||
`There are no updates on branch "${name}" published on the platform(s) ${platformFlag}. Did you mean to publish a new update instead?`
|
||||
);
|
||||
}
|
||||
|
||||
const selectedUpdateGroup = await selectAsync<string>(
|
||||
'which update would you like to republish?',
|
||||
updateGroups
|
||||
);
|
||||
updatesToRepublish = updates.filter(update => update.group === selectedUpdateGroup);
|
||||
}
|
||||
const updatesToRepublishFilteredByPlatform = updatesToRepublish.filter(
|
||||
// Only republish to the specified platforms
|
||||
update => platformFlag === 'all' || update.platform === platformFlag
|
||||
);
|
||||
if (updatesToRepublishFilteredByPlatform.length === 0) {
|
||||
throw new Error(
|
||||
`There are no updates on branch "${name}" published on the platform(s) "${platformFlag}" with group ID "${
|
||||
group ? group : updatesToRepublish[0].group
|
||||
}". Did you mean to publish a new update instead?`
|
||||
);
|
||||
}
|
||||
|
||||
let publicationPlatformMessage: string;
|
||||
if (platformFlag === 'all') {
|
||||
if (updatesToRepublishFilteredByPlatform.length !== defaultPublishPlatforms.length) {
|
||||
Log.warn(`You are republishing an update that wasn't published for all platforms.`);
|
||||
}
|
||||
publicationPlatformMessage = `The republished update will appear on the same plaforms it was originally published on: ${updatesToRepublishFilteredByPlatform
|
||||
.map(update => update.platform)
|
||||
.join(',')}`;
|
||||
} else {
|
||||
publicationPlatformMessage = `The republished update will appear only on: ${platformFlag}`;
|
||||
}
|
||||
Log.withTick(publicationPlatformMessage);
|
||||
|
||||
for (const update of updatesToRepublishFilteredByPlatform) {
|
||||
const { manifestFragment } = update;
|
||||
const platform = update.platform as PublishPlatform;
|
||||
|
||||
updateInfoGroup[platform] = JSON.parse(manifestFragment);
|
||||
}
|
||||
|
||||
// These are the same for each member of an update group
|
||||
group = updatesToRepublishFilteredByPlatform[0].group;
|
||||
oldMessage = updatesToRepublishFilteredByPlatform[0].message ?? '';
|
||||
oldRuntimeVersion = updatesToRepublishFilteredByPlatform[0].runtimeVersion;
|
||||
} else {
|
||||
// build bundle and upload assets for a new publish
|
||||
if (!skipBundler) {
|
||||
await buildBundlesAsync({ projectDir, inputDir });
|
||||
}
|
||||
|
||||
const assetSpinner = ora('Uploading assets...').start();
|
||||
try {
|
||||
const platforms = platformFlag === 'all' ? defaultPublishPlatforms : [platformFlag];
|
||||
const assets = collectAssets({ inputDir: inputDir!, platforms });
|
||||
await uploadAssetsAsync(assets);
|
||||
updateInfoGroup = await buildUpdateInfoGroupAsync(assets);
|
||||
assetSpinner.succeed('Uploaded assets!');
|
||||
} catch (e) {
|
||||
assetSpinner.fail('Failed to upload assets');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
const validationMessage = 'publish message may not be empty.';
|
||||
if (jsonFlag) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
({ publishMessage: message } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'publishMessage',
|
||||
message: `Please enter a publication message.`,
|
||||
initial: republish
|
||||
? `Republish "${oldMessage!}" - group: ${group}`
|
||||
: (await vcs.getLastCommitMessageAsync())?.trim(),
|
||||
validate: value => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
|
||||
let newUpdateGroup;
|
||||
const publishSpinner = ora('Publishing...').start();
|
||||
try {
|
||||
newUpdateGroup = await PublishMutation.publishUpdateGroupAsync({
|
||||
branchId,
|
||||
updateInfoGroup,
|
||||
runtimeVersion: republish ? oldRuntimeVersion! : runtimeVersion,
|
||||
message,
|
||||
});
|
||||
publishSpinner.succeed('Published!');
|
||||
} catch (e) {
|
||||
publishSpinner.fail('Failed to published updates');
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (jsonFlag) {
|
||||
Log.log(JSON.stringify(newUpdateGroup));
|
||||
} else {
|
||||
const outputMessage = new Table({
|
||||
wordWrap: true,
|
||||
chars: {
|
||||
top: '',
|
||||
'top-mid': '',
|
||||
'top-left': '',
|
||||
'top-right': '',
|
||||
bottom: '',
|
||||
'bottom-mid': '',
|
||||
'bottom-left': '',
|
||||
'bottom-right': '',
|
||||
left: '',
|
||||
'left-mid': '',
|
||||
mid: '',
|
||||
'mid-mid': '',
|
||||
right: '',
|
||||
'right-mid': '',
|
||||
middle: ' ',
|
||||
},
|
||||
style: { 'padding-left': 0, 'padding-right': 0 },
|
||||
});
|
||||
outputMessage.push(
|
||||
[chalk.dim('project'), `@${accountName}/${slug}`],
|
||||
[chalk.dim('branch'), name],
|
||||
[chalk.dim('runtimeVersion'), runtimeVersion],
|
||||
[chalk.dim('groupID'), newUpdateGroup.group],
|
||||
[chalk.dim('message'), message]
|
||||
);
|
||||
Log.log(outputMessage.toString());
|
||||
}
|
||||
async runAsync(): Promise<void> {
|
||||
throw new Error(BranchPublish.description);
|
||||
}
|
||||
}
|
||||
|
||||
function formatUpdateTitle(
|
||||
update: Pick<Update, 'message' | 'createdAt' | 'runtimeVersion'> & {
|
||||
actor?: Pick<Actor, 'firstName'> | null;
|
||||
}
|
||||
): string {
|
||||
const { message, createdAt, actor, runtimeVersion } = update;
|
||||
return `[${dateFormat(createdAt, 'mmm dd HH:MM')} by ${
|
||||
actor?.firstName ?? 'unknown'
|
||||
}, runtimeVersion: ${runtimeVersion}] ${message}`;
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
EditUpdateBranchInput,
|
||||
@@ -49,34 +50,30 @@ async function renameUpdateBranchOnAppAsync({
|
||||
return data.updateBranch.editUpdateBranch;
|
||||
}
|
||||
|
||||
export default class BranchRename extends Command {
|
||||
static hidden = true;
|
||||
export default class BranchRename extends EasCommand {
|
||||
static description = 'Rename a branch.';
|
||||
|
||||
static flags = {
|
||||
from: flags.string({
|
||||
from: Flags.string({
|
||||
description: 'current name of the branch.',
|
||||
required: false,
|
||||
}),
|
||||
to: flags.string({
|
||||
to: Flags.string({
|
||||
description: 'new name of the branch.',
|
||||
required: false,
|
||||
}),
|
||||
json: flags.boolean({
|
||||
json: Flags.boolean({
|
||||
description: `return a json with the edited branch's ID and name.`,
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
flags: { json: jsonFlag, from: currentName, to: newName },
|
||||
} = this.parse(BranchRename);
|
||||
} = await this.parse(BranchRename);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const fullName = await getProjectFullNameAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
@@ -1,46 +1,25 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import Table from 'cli-table3';
|
||||
import gql from 'graphql-tag';
|
||||
import { groupBy } from 'lodash';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
Maybe,
|
||||
Robot,
|
||||
Update,
|
||||
UpdateBranch,
|
||||
User,
|
||||
ViewBranchQuery,
|
||||
ViewBranchQueryVariables,
|
||||
} from '../../graphql/generated';
|
||||
import { ViewBranchQuery, ViewBranchQueryVariables } from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectFullNameAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
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';
|
||||
|
||||
const PAGE_LIMIT = 10_000;
|
||||
|
||||
type TruncatedUpdate = Pick<Update, 'group' | 'message' | 'createdAt' | 'actor'>;
|
||||
|
||||
export async function viewUpdateBranchAsync({
|
||||
appId,
|
||||
name,
|
||||
}: Pick<ViewBranchQueryVariables, 'appId' | 'name'>): Promise<
|
||||
Pick<UpdateBranch, 'id' | 'name'> & {
|
||||
updates: (Pick<
|
||||
Update,
|
||||
'id' | 'group' | 'message' | 'createdAt' | 'runtimeVersion' | 'platform' | 'manifestFragment'
|
||||
> & {
|
||||
actor?: Maybe<Pick<User, 'firstName' | 'id'> | Pick<Robot, 'firstName' | 'id'>>;
|
||||
})[];
|
||||
}
|
||||
> {
|
||||
}: Pick<ViewBranchQueryVariables, 'appId' | 'name'>): Promise<ViewBranchQuery> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<ViewBranchQuery, ViewBranchQueryVariables>(
|
||||
@@ -79,19 +58,15 @@ export async function viewUpdateBranchAsync({
|
||||
appId,
|
||||
name,
|
||||
limit: PAGE_LIMIT,
|
||||
}
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch', 'Update'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
const updateBranch = data.app?.byId.updateBranchByName;
|
||||
if (!updateBranch) {
|
||||
throw new Error(`Could not find branch "${name}"`);
|
||||
}
|
||||
return updateBranch;
|
||||
return data;
|
||||
}
|
||||
|
||||
export default class BranchView extends Command {
|
||||
static hidden = true;
|
||||
export default class BranchView extends EasCommand {
|
||||
static description = 'View a branch.';
|
||||
|
||||
static args = [
|
||||
@@ -103,25 +78,20 @@ export default class BranchView extends Command {
|
||||
];
|
||||
|
||||
static flags = {
|
||||
json: flags.boolean({
|
||||
json: Flags.boolean({
|
||||
description: `return a json with the branch's ID name and recent update groups.`,
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
args: { name },
|
||||
flags: { json: jsonFlag },
|
||||
} = this.parse(BranchView);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
} = await this.parse(BranchView);
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const fullName = await getProjectFullNameAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
if (!name) {
|
||||
@@ -137,10 +107,14 @@ export default class BranchView extends Command {
|
||||
}));
|
||||
}
|
||||
|
||||
const UpdateBranch = await viewUpdateBranchAsync({
|
||||
const { app } = await viewUpdateBranchAsync({
|
||||
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]
|
||||
@@ -168,12 +142,16 @@ export default class BranchView extends Command {
|
||||
]);
|
||||
}
|
||||
|
||||
Log.withTick(
|
||||
`️Branch: ${chalk.bold(UpdateBranch.name)} on project ${chalk.bold(
|
||||
fullName
|
||||
)}. Branch ID: ${chalk.bold(UpdateBranch.id)}`
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.bold('Branch:'));
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'Name', value: UpdateBranch.name },
|
||||
{ label: 'ID', value: UpdateBranch.id },
|
||||
])
|
||||
);
|
||||
Log.log(chalk.bold('Recent update groups published on this branch:'));
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.bold('Recently published update groups:'));
|
||||
Log.log(groupTable.toString());
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Command } from '@oclif/command';
|
||||
import chalk from 'chalk';
|
||||
import gql from 'graphql-tag';
|
||||
import ora from 'ora';
|
||||
|
||||
import { appPlatformEmojis } from '../../build/constants';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
Build,
|
||||
@@ -14,6 +12,8 @@ import {
|
||||
} from '../../graphql/generated';
|
||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { appPlatformEmojis } from '../../platform';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectFullNameAsync,
|
||||
@@ -67,9 +67,9 @@ async function selectBuildToCancelAsync(
|
||||
let builds;
|
||||
try {
|
||||
const [newBuilds, inQueueBuilds, inProgressBuilds] = await Promise.all([
|
||||
BuildQuery.allForAppAsync(projectId, { status: BuildStatus.New }),
|
||||
BuildQuery.allForAppAsync(projectId, { status: BuildStatus.InQueue }),
|
||||
BuildQuery.allForAppAsync(projectId, { status: BuildStatus.InProgress }),
|
||||
BuildQuery.allForAppAsync(projectId, { filter: { status: BuildStatus.New } }),
|
||||
BuildQuery.allForAppAsync(projectId, { filter: { status: BuildStatus.InQueue } }),
|
||||
BuildQuery.allForAppAsync(projectId, { filter: { status: BuildStatus.InProgress } }),
|
||||
]);
|
||||
spinner.stop();
|
||||
builds = [...newBuilds, ...inQueueBuilds, ...inProgressBuilds];
|
||||
@@ -109,15 +109,15 @@ async function ensureBuildExistsAsync(buildId: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export default class BuildCancel extends Command {
|
||||
export default class BuildCancel extends EasCommand {
|
||||
static description = 'Cancel a build.';
|
||||
|
||||
static args = [{ name: 'BUILD_ID' }];
|
||||
|
||||
async run() {
|
||||
const { BUILD_ID: buildIdFromArg } = this.parse(BuildCancel).args;
|
||||
async runAsync(): Promise<void> {
|
||||
const { BUILD_ID: buildIdFromArg } = (await this.parse(BuildCancel)).args;
|
||||
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectFullName = await getProjectFullNameAsync(exp);
|
||||
|
||||
Loaded 100 of 521 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user