mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
460
Commits
v9.0.6
...
@quin/test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8d7ac68da | ||
|
|
1b713af42f | ||
|
|
3ac6472682 | ||
|
|
4b8a9d195f | ||
|
|
30c5e9f4c1 | ||
|
|
d0c8f6bab7 | ||
|
|
faf6bffc62 | ||
|
|
ad55fdae37 | ||
|
|
b5f77404cb | ||
|
|
a1d4f26236 | ||
|
|
6c7bae2349 | ||
|
|
d7e48c2894 | ||
|
|
918a7f9501 | ||
|
|
852dac030f | ||
|
|
911cc90b93 | ||
|
|
b9234abff1 | ||
|
|
574bde9831 | ||
|
|
78837a413a | ||
|
|
6f63315f8e | ||
|
|
e8ac97c8cc | ||
|
|
a6fc31787d | ||
|
|
3056d8e23a | ||
|
|
1bf6757a72 | ||
|
|
b948dd5674 | ||
|
|
0e7a8bfe5e | ||
|
|
b86696fcdf | ||
|
|
f334bdbcc7 | ||
|
|
f16433048a | ||
|
|
e83061de36 | ||
|
|
73e0170836 | ||
|
|
078c7287d3 | ||
|
|
a65e691325 | ||
|
|
b306109676 | ||
|
|
035ca03b96 | ||
|
|
b3d092423b | ||
|
|
b768966ec4 | ||
|
|
1b40c1c20a | ||
|
|
98344b8fc2 | ||
|
|
f5480b4770 | ||
|
|
a2929ad364 | ||
|
|
6deee68a00 | ||
|
|
16d190ab84 | ||
|
|
6f2fc7c37b | ||
|
|
1b78407f15 | ||
|
|
05c8fde824 | ||
|
|
d9af6cb301 | ||
|
|
365e71f042 | ||
|
|
e74c606efa | ||
|
|
28f3a92c38 | ||
|
|
be9fb59e6d | ||
|
|
365a7465f4 | ||
|
|
85a66629c8 | ||
|
|
8a2d68fcb5 | ||
|
|
62819e0956 | ||
|
|
5c6880f4ce | ||
|
|
d5f562edf8 | ||
|
|
077d8b3d86 | ||
|
|
21c8131bcf | ||
|
|
18bf41782f | ||
|
|
0513fc51a6 | ||
|
|
528988dc0f | ||
|
|
7f46baeb21 | ||
|
|
608d61ed69 | ||
|
|
2b2b20a0f9 | ||
|
|
77423d1179 | ||
|
|
ebc277dfe4 | ||
|
|
427c8f152d | ||
|
|
1c245d23b4 | ||
|
|
29c4695351 | ||
|
|
7b968f6e0d | ||
|
|
8a38cc24e1 | ||
|
|
a85b9e2d80 | ||
|
|
e0f69327da | ||
|
|
76d4e65d92 | ||
|
|
58917ecbb4 | ||
|
|
e75a102361 | ||
|
|
061f626b2c | ||
|
|
3159550468 | ||
|
|
a20bd0051e | ||
|
|
0b64d1c8df | ||
|
|
fe37600323 | ||
|
|
ab507d0bdd | ||
|
|
7505b8d40a | ||
|
|
b31ddee6e7 | ||
|
|
01ce802b73 | ||
|
|
f75ef22154 | ||
|
|
7d3f4e8fd4 | ||
|
|
004caf4bb7 | ||
|
|
5a16f3d6d8 | ||
|
|
d2e4cb5fc6 | ||
|
|
2c1c9b91cb | ||
|
|
b447194c85 | ||
|
|
9ab095597a | ||
|
|
b1edff7242 | ||
|
|
4d9e37269c | ||
|
|
e7b48949db | ||
|
|
1c3edbe492 | ||
|
|
deaa3a9105 | ||
|
|
cbfcdaec5d | ||
|
|
37a32c93ee | ||
|
|
6461a78fa2 | ||
|
|
86c8ac093f | ||
|
|
608cdff981 | ||
|
|
4dc4ef85fa | ||
|
|
eaf15acb76 | ||
|
|
ae4ca91658 | ||
|
|
23ebb77952 | ||
|
|
ed2352f289 | ||
|
|
38ae8b68f1 | ||
|
|
5e02807ac9 | ||
|
|
f05d6ca3a2 | ||
|
|
b57082647d | ||
|
|
9d4fdc3c75 | ||
|
|
d1af9e385f | ||
|
|
59bbe19dde | ||
|
|
5e5b55f9e4 | ||
|
|
a1d086ffc6 | ||
|
|
2a96d04be3 | ||
|
|
d126ee988a | ||
|
|
4bb550e3bb | ||
|
|
6a55c65908 | ||
|
|
ab3495da5e | ||
|
|
63b67635e0 | ||
|
|
4b5cda3bbc | ||
|
|
63e3c1f2ab | ||
|
|
3f1543989f | ||
|
|
1c964d2ab1 | ||
|
|
bc282b6262 | ||
|
|
17661d1cf0 | ||
|
|
8d35745124 | ||
|
|
3740ee4ef9 | ||
|
|
751d3d1287 | ||
|
|
1fd14c07e1 | ||
|
|
39ea1705d8 | ||
|
|
2c96b9929d | ||
|
|
0dae24ae47 | ||
|
|
a63efe01a1 | ||
|
|
803457b37a | ||
|
|
dcb1305501 | ||
|
|
97fe2d62d2 | ||
|
|
ca0850b82b | ||
|
|
039a7b408b | ||
|
|
68660ae845 | ||
|
|
34bb2c8a75 | ||
|
|
ac5cf3d7dd | ||
|
|
8d686b855b | ||
|
|
19973bb86f | ||
|
|
d132ac09de | ||
|
|
4c5e5d4ded | ||
|
|
44af16efe1 | ||
|
|
f34487cc8e | ||
|
|
7ea1c5de17 | ||
|
|
261f611770 | ||
|
|
f9337f419a | ||
|
|
4280e10836 | ||
|
|
a2fe99c97e | ||
|
|
868fd399a4 | ||
|
|
7d60ba2cea | ||
|
|
db7461a569 | ||
|
|
facffbdcd7 | ||
|
|
dbdefa0e3d | ||
|
|
66daeade84 | ||
|
|
48ebd9bb78 | ||
|
|
3d3574d351 | ||
|
|
8da6adf4da | ||
|
|
bd2d39f26b | ||
|
|
e16e1f3871 | ||
|
|
d8bd6a1548 | ||
|
|
0f2fad3a65 | ||
|
|
78e05c48c8 | ||
|
|
fa10404843 | ||
|
|
5bf2c3a65b | ||
|
|
ec71e1168f | ||
|
|
8bf28029dc | ||
|
|
fc3c1e73a8 | ||
|
|
bb3915dc7e | ||
|
|
753df2c428 | ||
|
|
3111492f83 | ||
|
|
9c95755a46 | ||
|
|
da727ba170 | ||
|
|
4571910c4d | ||
|
|
c7cfcafff8 | ||
|
|
6f913f7320 | ||
|
|
0a49296231 | ||
|
|
7fc09b2376 | ||
|
|
fed28a87be | ||
|
|
073bdcd575 | ||
|
|
6979c8d67b | ||
|
|
0869bc511f | ||
|
|
693562d742 | ||
|
|
734a071345 | ||
|
|
26fd7a7daa | ||
|
|
df13e0433e | ||
|
|
fa72248d45 | ||
|
|
2b51db72db | ||
|
|
79696394be | ||
|
|
86c99efdcb | ||
|
|
ee500109f6 | ||
|
|
7eff738efe | ||
|
|
fdb167df34 | ||
|
|
027d3629a6 | ||
|
|
c30c1cd5cf | ||
|
|
a5d3ea8aba | ||
|
|
37f53e0976 | ||
|
|
97a26dce40 | ||
|
|
b0b14788dd | ||
|
|
b81c60e8a4 | ||
|
|
d47b80a125 | ||
|
|
3f2dcc7e09 | ||
|
|
b039da04e2 | ||
|
|
9e55fcbe7c | ||
|
|
281ae7bad6 | ||
|
|
433c47b221 | ||
|
|
5be97c50ae | ||
|
|
2bc0e968cf | ||
|
|
a426164e85 | ||
|
|
ac799bbf76 | ||
|
|
cf8e715cd5 | ||
|
|
71ca181895 | ||
|
|
7c9970d279 | ||
|
|
f40484ec4d | ||
|
|
746d64ac30 | ||
|
|
3782767f9a | ||
|
|
efb1c87fe3 | ||
|
|
c4075b9fd0 | ||
|
|
5bd3760b57 | ||
|
|
dd190869aa | ||
|
|
c7b2b8388c | ||
|
|
3e6cf78760 | ||
|
|
5478814cf6 | ||
|
|
6449ea7c16 | ||
|
|
88740682cd | ||
|
|
8c05a711fc | ||
|
|
d0f01cb58f | ||
|
|
a212662003 | ||
|
|
3139124daf | ||
|
|
8c4be1c556 | ||
|
|
3a7dfb0001 | ||
|
|
214cff043c | ||
|
|
0515c9a6fa | ||
|
|
367b763adf | ||
|
|
49dba20200 | ||
|
|
f0b14bf6a1 | ||
|
|
161acbb160 | ||
|
|
5aad520c23 | ||
|
|
d6ec3ae106 | ||
|
|
feb8d2d8f4 | ||
|
|
c07fd222d9 | ||
|
|
1873804c8b | ||
|
|
1cc4fd8880 | ||
|
|
f83d7e0be7 | ||
|
|
b225ba4bf3 | ||
|
|
4fb5972f18 | ||
|
|
463f73eec2 | ||
|
|
2dbaed5b2a | ||
|
|
20b76f72b0 | ||
|
|
56444432b9 | ||
|
|
02022d8d17 | ||
|
|
a7c77e8ab3 | ||
|
|
6489ddc6ad | ||
|
|
496e68935a | ||
|
|
a346af2869 | ||
|
|
7699573100 | ||
|
|
3f832765c2 | ||
|
|
af16cbaaea | ||
|
|
0d384d1722 | ||
|
|
58d2088bf7 | ||
|
|
c622014b64 | ||
|
|
81feffa0c5 | ||
|
|
9521cc45da | ||
|
|
1dfe24d48a | ||
|
|
99765c1802 | ||
|
|
de388cfd27 | ||
|
|
37a68c5237 | ||
|
|
6823e4f66c | ||
|
|
ffa2f034f9 | ||
|
|
8243f5f0cb | ||
|
|
01676a4f88 | ||
|
|
7edc10feaf | ||
|
|
fba26e6b50 | ||
|
|
69014fe21d | ||
|
|
217a1f4fdb | ||
|
|
0863b9a477 | ||
|
|
081a254d9b | ||
|
|
ba7eb7ea07 | ||
|
|
f5df6e6e77 | ||
|
|
c0c27559eb | ||
|
|
2e002ad709 | ||
|
|
2e211f4904 | ||
|
|
981f7c9727 | ||
|
|
24245d76cf | ||
|
|
d42cbe0146 | ||
|
|
2cf6dd49db | ||
|
|
09c5038c58 | ||
|
|
d33ebe1ae4 | ||
|
|
41fd76c154 | ||
|
|
dadf58fa70 | ||
|
|
a7f939fb5f | ||
|
|
e54b5a243c | ||
|
|
d2a89c1d77 | ||
|
|
9c4ff98d68 | ||
|
|
d7dfef0c36 | ||
|
|
fdb30eeacc | ||
|
|
d4c6440753 | ||
|
|
87b0cf41a8 | ||
|
|
7847b5297c | ||
|
|
539ef8f3ad | ||
|
|
819259ab1a | ||
|
|
47e1989c9c | ||
|
|
fa3262951b | ||
|
|
66ce64e0a3 | ||
|
|
5c553cee7b | ||
|
|
2fb079d4bb | ||
|
|
d15cd90c81 | ||
|
|
0fbd3e47f8 | ||
|
|
12efaf12cc | ||
|
|
a404bb383d | ||
|
|
feaa8cbf22 | ||
|
|
542ea7a791 | ||
|
|
7d80b0540f | ||
|
|
cc4e0ebda1 | ||
|
|
1621fb3a9b | ||
|
|
51b5946854 | ||
|
|
b8ed63cadc | ||
|
|
8fc0648419 | ||
|
|
4fbc6aa3ff | ||
|
|
83bf6de727 | ||
|
|
a2219beaba | ||
|
|
90a2612792 | ||
|
|
d94b57c1b7 | ||
|
|
d63fc1f01e | ||
|
|
57e7481a68 | ||
|
|
7bb18e2489 | ||
|
|
359464d55c | ||
|
|
e993e9a370 | ||
|
|
75046b0b35 | ||
|
|
3d55a4df40 | ||
|
|
b32ff2e733 | ||
|
|
6245fb418d | ||
|
|
50943f333a | ||
|
|
e930383a10 | ||
|
|
8c31825908 | ||
|
|
fbc39240a5 | ||
|
|
f31054c5a5 | ||
|
|
7af8b62a7b | ||
|
|
56d492cbac | ||
|
|
4f53ce353c | ||
|
|
736552b358 | ||
|
|
b4a3f5b526 | ||
|
|
28592f27fa | ||
|
|
63502586f8 | ||
|
|
714532a532 | ||
|
|
8cbed45be9 | ||
|
|
3466a0c3dc | ||
|
|
b28943d8f1 | ||
|
|
e01a70943b | ||
|
|
56510f0925 | ||
|
|
1540b1354e | ||
|
|
61455420b1 | ||
|
|
d3fdc1a95b | ||
|
|
a28439a485 | ||
|
|
8af8569f16 | ||
|
|
9b49fe3a9f | ||
|
|
2c8f9a67c8 | ||
|
|
9597ea2832 | ||
|
|
5976dc7346 | ||
|
|
75b310e7e0 | ||
|
|
63009b0437 | ||
|
|
7994676f3e | ||
|
|
987c23fc2d | ||
|
|
2bf5819075 | ||
|
|
fdbeb862ec | ||
|
|
9a05359dcb | ||
|
|
ce76d4d7d0 | ||
|
|
3c58bd4515 | ||
|
|
f4f4756e07 | ||
|
|
31b3d81b1a | ||
|
|
5cf526592c | ||
|
|
8646dc81d9 | ||
|
|
54ae76e85e | ||
|
|
63381b8ee4 | ||
|
|
52ab2d5792 | ||
|
|
ade09fe6dc | ||
|
|
71261b5b26 | ||
|
|
37cec329d4 | ||
|
|
fc88d65691 | ||
|
|
4699ae51f0 | ||
|
|
aabe1ff806 | ||
|
|
c8bd95535e | ||
|
|
71184cbcd5 | ||
|
|
1629e2b585 | ||
|
|
4a82ac90d3 | ||
|
|
5745639bb2 | ||
|
|
17ecdb2a95 | ||
|
|
42b105c869 | ||
|
|
ac04891b28 | ||
|
|
14a454edfa | ||
|
|
c0651a880c | ||
|
|
c39f76392d | ||
|
|
f2ed434828 | ||
|
|
9344fba85f | ||
|
|
e5bfcf4e90 | ||
|
|
40a7b144ea | ||
|
|
97d1e25908 | ||
|
|
2b23c11d6e | ||
|
|
1c61465c5f | ||
|
|
0516b507bc | ||
|
|
2e14870b60 | ||
|
|
559baf9150 | ||
|
|
bd0a696b4e | ||
|
|
230e364379 | ||
|
|
eaebb72813 | ||
|
|
cf39201de3 | ||
|
|
bb3e815716 | ||
|
|
beec6ad305 | ||
|
|
84b47924b7 | ||
|
|
3ff62a9c79 | ||
|
|
2751a7e0de | ||
|
|
7139e4f576 | ||
|
|
ecbe49abaa | ||
|
|
6af86b545f | ||
|
|
a9a77b6fcb | ||
|
|
82fccc8193 | ||
|
|
5f800b5be6 | ||
|
|
3817e2d387 | ||
|
|
eb4c5e073d | ||
|
|
eefc133eda | ||
|
|
7f9969429d | ||
|
|
26ff083b7c | ||
|
|
6414361d4c | ||
|
|
28ce6b7dc7 | ||
|
|
4772f1aea5 | ||
|
|
2a1fd290b9 | ||
|
|
e83565c518 | ||
|
|
f38a739858 | ||
|
|
3fcbcf5f6b | ||
|
|
30936911ad | ||
|
|
a0606adde9 | ||
|
|
f0e3b648a1 | ||
|
|
3ed5e7821e | ||
|
|
466339b90e | ||
|
|
1d07e08a70 | ||
|
|
c21270e062 | ||
|
|
afb548599e | ||
|
|
0b54169631 | ||
|
|
af13f3987e | ||
|
|
3ea71e57cb | ||
|
|
261dbb4be4 | ||
|
|
5dd02e2b40 | ||
|
|
9447cbc478 | ||
|
|
562bd3fe9c | ||
|
|
d6840de401 | ||
|
|
d9bd20a47b | ||
|
|
13da8d4bfc | ||
|
|
a99fe9aef5 | ||
|
|
61109ea2c1 | ||
|
|
f889929f5f | ||
|
|
4bb314192e | ||
|
|
1a170283db | ||
|
|
ca803eed3f |
No files matched your search
@@ -5,3 +5,4 @@
|
||||
packages/eas-cli/src/graphql/generated.ts
|
||||
|
||||
node_modules
|
||||
ts-declarations
|
||||
@@ -30,12 +30,6 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'no-underscore-dangle': ['error', { allow: ['__typename'] }],
|
||||
'@typescript-eslint/explicit-function-return-type': [
|
||||
'warn',
|
||||
{
|
||||
allowExpressions: true,
|
||||
},
|
||||
],
|
||||
'async-protect/async-suffix': 'error',
|
||||
'node/no-sync': 'error',
|
||||
},
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
commentConfiguration:
|
||||
preamble: 'Subscribed to pull request'
|
||||
epilogue: 'Generated by [CodeMention](https://github.com/expo/expo/actions/workflows/codemention.yaml)'
|
||||
rules:
|
||||
- patterns: ['**/*']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela', 'radoslawkrzemien']
|
||||
- patterns: ['packages/eas-cli/src/update/**']
|
||||
mentions: ['wschurman', 'quinlanj']
|
||||
- patterns: ['packages/eas-cli/src/commands/update/**']
|
||||
mentions: ['wschurman', 'quinlanj']
|
||||
- patterns: ['packages/eas-cli/src/channel/**']
|
||||
mentions: ['wschurman', 'quinlanj']
|
||||
- patterns: ['packages/eas-cli/src/commands/channel/**']
|
||||
mentions: ['wschurman', 'quinlanj']
|
||||
- patterns: ['packages/eas-cli/schema/**']
|
||||
mentions: ['bycedric']
|
||||
- patterns: ['packages/eas-cli/src/metadata/**']
|
||||
mentions: ['bycedric']
|
||||
- patterns: ['packages/eas-cli/src/commands/metadata/**']
|
||||
mentions: ['bycedric']
|
||||
@@ -1,48 +0,0 @@
|
||||
name: Changelog bot
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
pr-commented:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.issue.pull_request && github.event.issue.user.login == github.event.comment.user.login && startsWith(github.event.comment.body, '/changelog-entry ') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.EXPO_BOT_PAT }}
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Checkout Pull Request
|
||||
run: gh 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
|
||||
@@ -36,6 +36,5 @@ jobs:
|
||||
refresh-message-position: true
|
||||
message: ${{ contains(github.event.pull_request.labels.*.name, 'no changelog') && '⏩ The changelog entry check has been skipped since the "no changelog" label is present.' || '✅ Thank you for adding the changelog entry!' }}
|
||||
message-failure: |
|
||||
❌ It looks like a changelog entry is missing for this PR. You have two options: you can add it manually, or you can use the changelog bot to do it for you.
|
||||
🤖 To use the bot, simply comment on this PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`.
|
||||
❌ It looks like a changelog entry is missing for this PR. Add it manually to CHANGELOG.md.
|
||||
⏩ If this PR doesn't require a changelog entry, such as if it's an internal change that doesn't affect the user experience, you can add the "no changelog" label to the PR.
|
||||
@@ -0,0 +1,14 @@
|
||||
name: codemention
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, ready_for_review]
|
||||
jobs:
|
||||
codemention:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: tobyhs/[email protected]
|
||||
with:
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -226,16 +226,3 @@ jobs:
|
||||
SLACK_USERNAME: 'EAS CLI CI'
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
SLACK_FOOTER: ''
|
||||
open-pr-in-universe:
|
||||
name: Open a PR with `@expo/eas-json` bump in universe
|
||||
needs: publish-to-npm
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Open a PR with `@expo/eas-json` version bump in universe
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
repo: expo/universe
|
||||
ref: main
|
||||
workflow: update-eas-json.yml
|
||||
token: ${{ secrets.EXPO_BOT_PAT }}
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: ['16', '18', '20']
|
||||
node: ['18', '20', '22']
|
||||
include:
|
||||
- node: '20'
|
||||
coverage: true
|
||||
@@ -28,6 +28,9 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: yarn typecheck
|
||||
- run: |
|
||||
git config --global user.email "[email protected]"
|
||||
git config --global user.name "Test"
|
||||
- run: yarn test
|
||||
if: ${{ !matrix.coverage }}
|
||||
env:
|
||||
@@ -49,7 +52,7 @@ jobs:
|
||||
|
||||
notify-slack:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
needs: [test]
|
||||
name: Notify Slack
|
||||
if: ${{ github.ref == 'refs/heads/main' && always() }}
|
||||
steps:
|
||||
|
||||
+600
@@ -12,6 +12,606 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Fix `eas fingerprint:compare` URL generation and pretty prints. ([#2909](https://github.com/expo/eas-cli/pull/2909) by [@quinlanj](https://github.com/quinlanj))
|
||||
- fix formatFields to handle empty array. ([#2914](https://github.com/expo/eas-cli/pull/2914) by [@quinlanj](https://github.com/quinlanj))
|
||||
- fix git diff header in `fingerprint:compare`. ([#2915](https://github.com/expo/eas-cli/pull/2915) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [15.0.12](https://github.com/expo/eas-cli/releases/tag/v15.0.12) - 2025-02-22
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix APNS key creation. ([#2916](https://github.com/expo/eas-cli/pull/2916) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
|
||||
## [15.0.11](https://github.com/expo/eas-cli/releases/tag/v15.0.11) - 2025-02-21
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix internal TestFlight group creation. ([#2906](https://github.com/expo/eas-cli/pull/2906) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Narrow amount of data queried for basic update channel operations. ([#2901](https://github.com/expo/eas-cli/pull/2901) by [@wschurman](https://github.com/wschurman))
|
||||
- Fix `eas fingerprint:compare` description. ([#2908](https://github.com/expo/eas-cli/pull/2908) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Skip auto-creation of TestFlight group when there are already exisitng TestFlight groups and allow to opt out of the behavior by setting `EAS_NO_AUTO_TESTFLIGHT_SETUP` env var. ([#2856](https://github.com/expo/eas-cli/pull/2856) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [15.0.10](https://github.com/expo/eas-cli/releases/tag/v15.0.10) - 2025-02-11
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix files deleted in working directory not being removed from the project archive when `requireCommit` is false. ([#2900](https://github.com/expo/eas-cli/pull/2900) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
## [15.0.9](https://github.com/expo/eas-cli/releases/tag/v15.0.9) - 2025-02-09
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix files not being ignored when creating a tarball on Windows in Git repository in no `requireCommit` mode. ([#2894](https://github.com/expo/eas-cli/pull/2894) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
## [15.0.8](https://github.com/expo/eas-cli/releases/tag/v15.0.8) - 2025-02-09
|
||||
|
||||
## [15.0.7](https://github.com/expo/eas-cli/releases/tag/v15.0.7) - 2025-02-09
|
||||
|
||||
## [15.0.6](https://github.com/expo/eas-cli/releases/tag/v15.0.6) - 2025-02-07
|
||||
|
||||
## [15.0.5](https://github.com/expo/eas-cli/releases/tag/v15.0.5) - 2025-02-06
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Do not copy files over onto a cloned Git repository when packing the project archive if `requireCommit` is true. ([#2885](https://github.com/expo/eas-cli/pull/2885) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
- Fix `EISDIR` error when archiving project with submodules ignored. ([#2884](https://github.com/expo/eas-cli/pull/2884) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
## [15.0.4](https://github.com/expo/eas-cli/releases/tag/v15.0.4) - 2025-02-05
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fixed `GitClient` not respecting `.easignore` file. ([#2873](https://github.com/expo/eas-cli/pull/2873) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
- Fix symlink support in `makeShallowCopyAsync`. ([#2874](https://github.com/expo/eas-cli/pull/2874) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
- Allow excluding `.git` directory from project archive by adding it to `.easignore`. ([#2879](https://github.com/expo/eas-cli/pull/2879) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Popup website in fingerprint:compare. ([#2859](https://github.com/expo/eas-cli/pull/2859) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Fix fingerprint:compare URL. ([#2861](https://github.com/expo/eas-cli/pull/2861) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Make less gql calls in fingerprint:compare. ([#2860](https://github.com/expo/eas-cli/pull/2860) by [@quinlanj](https://github.com/quinlanj))
|
||||
- No longer require owner field for SDK >= 53 or canary. ([#2835](https://github.com/expo/eas-cli/pull/2835) by [@wschurman](https://github.com/wschurman))
|
||||
- Add --open flag to fingerprint:compare. ([#2872](https://github.com/expo/eas-cli/pull/2872) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [15.0.3](https://github.com/expo/eas-cli/releases/tag/v15.0.3) - 2025-02-04
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fixed EAS server environment variables does not pass to `npx expo-updates runtimeversion:resolve` call. ([#2867](https://github.com/expo/eas-cli/pull/2867) by [@kudo](https://github.com/kudo))
|
||||
|
||||
## [15.0.2](https://github.com/expo/eas-cli/releases/tag/v15.0.2) - 2025-02-04
|
||||
|
||||
## [15.0.1](https://github.com/expo/eas-cli/releases/tag/v15.0.1) - 2025-02-04
|
||||
|
||||
## [15.0.0](https://github.com/expo/eas-cli/releases/tag/v15.0.0) - 2025-02-04
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Use Git to archive projects containing a Git repository. (Previously, Git would only be used if `requireCommit` flag in `eas.json` was set to `true`.) ([#2841](https://github.com/expo/eas-cli/pull/2841) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Print warning for `NoVcsClient` only once. ([#2863](https://github.com/expo/eas-cli/pull/2863) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add update support for fingerprint:compare. ([#2850](https://github.com/expo/eas-cli/pull/2850) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add update group id support for fingerprint:compare. ([#2851](https://github.com/expo/eas-cli/pull/2851) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add better interactive support for fingerprint:compare. ([#2854](https://github.com/expo/eas-cli/pull/2854) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [14.7.1](https://github.com/expo/eas-cli/releases/tag/v14.7.1) - 2025-01-31
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Account for `ios.config.usesNonExemptEncryption` in non-exempt encryption status prompt. ([#2852](https://github.com/expo/eas-cli/pull/2852) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
|
||||
## [14.7.0](https://github.com/expo/eas-cli/releases/tag/v14.7.0) - 2025-01-30
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `--submit` and `-s` as aliases for `--auto-submit` flag. ([#2846](https://github.com/expo/eas-cli/pull/2846) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Skip non-exempt check in non-interactive mode. ([#2849](https://github.com/expo/eas-cli/pull/2849) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
|
||||
## [14.6.0](https://github.com/expo/eas-cli/releases/tag/v14.6.0) - 2025-01-30
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Prompt to set non-exempt encryption status for the iOS app to support faster store submissions. ([#2843](https://github.com/expo/eas-cli/pull/2843) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Automatically create internal TestFlight group in EAS Submit command. ([#2839](https://github.com/expo/eas-cli/pull/2839) by [@evanbacon](https://github.com/evanbacon))
|
||||
- Sanitize and generate names for EAS Submit to prevent failures due to invalid characters or taken names. ([#2842](https://github.com/expo/eas-cli/pull/2842) by [@evanbacon](https://github.com/evanbacon))
|
||||
- Release `eas fingerprint:compare`. ([#2821](https://github.com/expo/eas-cli/pull/2821) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Make new autoIncremented builds start at nr 1 by default ([#2828](https://github.com/expo/eas-cli/pull/2828) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
|
||||
## [14.5.0](https://github.com/expo/eas-cli/releases/tag/v14.5.0) - 2025-01-22
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Allow filtering by `--fingerprint_hash` in `eas build:list` command. ([#2818](https://github.com/expo/eas-cli/pull/2818) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Ensure that the AASA file is served with content type application/json ([#2829](https://github.com/expo/eas-cli/pull/2829) by [@kadikraman](https://github.com/kadikraman))
|
||||
- Ensure that the AppleID provided in prompt or saved to cache does not contain invalid unprintable characters ([#2830](https://github.com/expo/eas-cli/pull/2830) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Fix logs typos in the `eas deploy` command. ([#2822](https://github.com/expo/eas-cli/pull/2822) by [@kadikraman](https://github.com/kadikraman))
|
||||
- Make `deploy` the top level command for hosting. ([#2824](https://github.com/expo/eas-cli/pull/2824) by [@kadikraman](https://github.com/kadikraman))
|
||||
- Allow longer submit profile extension chain (up to 5, same as build profile) ([#2831](https://github.com/expo/eas-cli/pull/2831) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
- Make variable naming more explicit, remove deprecated runtimeFingerprintSource uses. ([#2816](https://github.com/expo/eas-cli/pull/2816) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
## [14.4.1](https://github.com/expo/eas-cli/releases/tag/v14.4.1) - 2025-01-15
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Enable shell execution for env:exec commands. ([#2788](https://github.com/expo/eas-cli/pull/2788) by [@tharakadesilva](https://github.com/tharakadesilva))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Make automatic env resolution message shorter. ([#2806](https://github.com/expo/eas-cli/pull/2806) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Make "No remote versions are configured" message green instead of yellow. ([#2805](https://github.com/expo/eas-cli/pull/2805) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Upload local fingerprint on `eas fingerprint:compare`. ([#2808](https://github.com/expo/eas-cli/pull/2808) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Upgrade `eas-cli-local-build-plugin` to `1.0.163` to support Bun's new text-based lock file in local builds. ([#2817](https://github.com/expo/eas-cli/pull/2817) by [@shiroyasha9](https://github.com/shiroyasha9))
|
||||
|
||||
## [14.4.0](https://github.com/expo/eas-cli/releases/tag/v14.4.0) - 2025-01-09
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Load `.env` variables even when `--environment` is specified for `deploy` command. Conflicts will be highlighted by a warning message. ([#2783](https://github.com/expo/eas-cli/pull/2783) by [@kitten](https://github.com/kitten))
|
||||
- Silence all non-command output in non-interactive mode of eas env:exec. ([#2800](https://github.com/expo/eas-cli/pull/2800) by [@wschurman](https://github.com/wschurman))
|
||||
- Unhide `deploy` and `deploy:alias` commands ([#2807](https://github.com/expo/eas-cli/pull/2807) by [@kitten](https://github.com/kitten))
|
||||
|
||||
## [14.3.1](https://github.com/expo/eas-cli/releases/tag/v14.3.1) - 2025-01-08
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Bump `@expo/[email protected]` to support Bun text-based lock files. ([#2801](https://github.com/expo/eas-cli/pull/2801) by [@kudo](https://github.com/kudo))
|
||||
|
||||
## [14.3.0](https://github.com/expo/eas-cli/releases/tag/v14.3.0) - 2025-01-07
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Upload `package.json` when uploading workflow sources. ([#2786](https://github.com/expo/eas-cli/pull/2786) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Show `eas deploy` upload error messages. ([#2771](https://github.com/expo/eas-cli/pull/2771) by [@kadikraman](https://github.com/kadikraman))
|
||||
- Prevent EAS CLI dependencies check from running repeatedly. ([#2781](https://github.com/expo/eas-cli/pull/2781) by [@kitten](https://github.com/kitten))
|
||||
- Prevent optimistic request body parsing for `eas deploy`. ([#2784](https://github.com/expo/eas-cli/pull/2784) by [@kadikraman](https://github.com/kadikraman))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Update log output for `worker` deploy and alias commands. ([#2780](https://github.com/expo/eas-cli/pull/2780) by [@kitten](https://github.com/kitten))
|
||||
- Update various messages wording. ([#2790](https://github.com/expo/eas-cli/pull/2790) by [@simek](https://github.com/simek))
|
||||
- Use node18 as tsconfig base. ([#2739](https://github.com/expo/eas-cli/pull/2739) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [14.2.0](https://github.com/expo/eas-cli/releases/tag/v14.2.0) - 2024-12-13
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `eas deploy --dry-run` flag to output tarball. ([#2761](https://github.com/expo/eas-cli/pull/2761) by [@kitten](https://github.com/kitten))
|
||||
- Allow specifying credentials for android builds. ([#2775](https://github.com/expo/eas-cli/pull/2775) by [@khamilowicz](https://github.com/khamilowicz))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Remove random branch name generation for --auto branch name non-vcs fallback. ([#2747](https://github.com/expo/eas-cli/pull/2747) by [@wschurman](https://github.com/wschurman))
|
||||
- Upgrade @expo/multipart-body-parser. ([#2751](https://github.com/expo/eas-cli/pull/2751) by [@wschurman](https://github.com/wschurman))
|
||||
- Pass env var flag to worker deployments. ([#2763](https://github.com/expo/eas-cli/pull/2763) by [@kadikraman](https://github.com/kadikraman))
|
||||
- Fix request for switching providers when doing Apple auth. ([#2769](https://github.com/expo/eas-cli/pull/2769) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Improve logging and validation in `eas env:exec` command. ([#2762](https://github.com/expo/eas-cli/pull/2762) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [14.1.0](https://github.com/expo/eas-cli/releases/tag/v14.1.0) - 2024-12-10
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add support for enabeling broadcast Push Notifications capability option, by setting `ios.usesBroadcastPushNotifications` to `true` in app config. ([#2748](https://github.com/expo/eas-cli/pull/2748) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add link to FYI page mentioning the workaround for Apple SMS 2FA issue. ([#2752](https://github.com/expo/eas-cli/pull/2752) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [14.0.3](https://github.com/expo/eas-cli/releases/tag/v14.0.3) - 2024-12-09
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Bump `@expo/apple-utils` to fix sending two-factor authentication codes via SMS. ([#2750](https://github.com/expo/eas-cli/pull/2750) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Change update message to allow faster copy and paste. ([#2661](https://github.com/expo/eas-cli/pull/2661) by [@jonluca](https://github.com/jonluca))
|
||||
- Allow using `$schema` field in `eas.json`. ([#2624](https://github.com/expo/eas-cli/pull/2624) by [@saiichihashimoto](https://github.com/saiichihashimoto))
|
||||
|
||||
## [14.0.2](https://github.com/expo/eas-cli/releases/tag/v14.0.2) - 2024-12-06
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Print warning instead of throwing an error when validating `CFBundleShortVersionString` against App Store requirements. ([#2741](https://github.com/expo/eas-cli/pull/2741) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [14.0.1](https://github.com/expo/eas-cli/releases/tag/v14.0.1) - 2024-12-06
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Bump `@expo/apple-utils` to improve error handling and input validation and change auth headers. ([#2745](https://github.com/expo/eas-cli/pull/2745) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [14.0.0](https://github.com/expo/eas-cli/releases/tag/v14.0.0) - 2024-12-06
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Change behavior of roll-back-to-embedded to not use current project state. ([#2722](https://github.com/expo/eas-cli/pull/2722) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add hidden fingerprint:compare feature. ([#2736](https://github.com/expo/eas-cli/pull/2736) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Update `@expo/config` and `@expo/config-plugins` to fix `eas build` command for bare iOS projects on Windows. ([#2744](https://github.com/expo/eas-cli/pull/2744) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Fix outdated gitignored Google services file warning. ([#2730](https://github.com/expo/eas-cli/pull/2730) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [13.4.2](https://github.com/expo/eas-cli/releases/tag/v13.4.2) - 2024-11-25
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade `@expo` packages to SDK 52 versions. ([#2706](https://github.com/expo/eas-cli/pull/2706) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [13.4.1](https://github.com/expo/eas-cli/releases/tag/v13.4.1) - 2024-11-22
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Improve logging in `eas env:pull`. ([#2720](https://github.com/expo/eas-cli/pull/2720) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [13.4.0](https://github.com/expo/eas-cli/releases/tag/v13.4.0) - 2024-11-22
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Calculate fingerprint on each update. ([#2687](https://github.com/expo/eas-cli/pull/2687) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Calculate fingerprint on each update republish. ([#2708](https://github.com/expo/eas-cli/pull/2708) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add `eas env` commands. ([#2711](https://github.com/expo/eas-cli/pull/2711) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Add `--environment` flag to `eas update` command. ([#2711](https://github.com/expo/eas-cli/pull/2711) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Load readable environment variables from EAS servers on every build. ([#2644](https://github.com/expo/eas-cli/pull/2644) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Add environment field to `eas.schema.json`. ([#2719](https://github.com/expo/eas-cli/pull/2719) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Deprecate `eas secret` commands. ([#2705](https://github.com/expo/eas-cli/pull/2705) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [13.3.0](https://github.com/expo/eas-cli/releases/tag/v13.3.0) - 2024-11-18
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Added `eas workflow` commands. [#2650](https://github.com/expo/eas-cli/pull/2650), [#2669](https://github.com/expo/eas-cli/pull/2669), [#2678](https://github.com/expo/eas-cli/pull/2678) by [@jonsamp](https://github.com/jonsamp) and [@sjchmiela](https://github.com/sjchmiela) ([#2683](https://github.com/expo/eas-cli/pull/2683) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
## [13.2.3](https://github.com/expo/eas-cli/releases/tag/v13.2.3) - 2024-11-15
|
||||
|
||||
## [13.2.2](https://github.com/expo/eas-cli/releases/tag/v13.2.2) - 2024-11-15
|
||||
|
||||
## [13.2.1](https://github.com/expo/eas-cli/releases/tag/v13.2.1) - 2024-11-14
|
||||
|
||||
## [13.2.0](https://github.com/expo/eas-cli/releases/tag/v13.2.0) - 2024-11-13
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add EAS_SKIP_AUTO_FINGERPRINT to skip fingerprint computation on build ([#2675](https://github.com/expo/eas-cli/pull/2675) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Default build-logger-level to debug when EXPO_DEBUG is set. ([#2676](https://github.com/expo/eas-cli/pull/2676) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
## [13.1.1](https://github.com/expo/eas-cli/releases/tag/v13.1.1) - 2024-11-08
|
||||
|
||||
## [13.1.0](https://github.com/expo/eas-cli/releases/tag/v13.1.0) - 2024-11-07
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Compute fingerprint for builds with SDK 52 and higher ([#2663](https://github.com/expo/eas-cli/pull/2663) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [13.0.1](https://github.com/expo/eas-cli/releases/tag/v13.0.1) - 2024-11-05
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade EAS Metadata with new properties from App Store. ([#2671](https://github.com/expo/eas-cli/pull/2671) by [@byCedric](https://github.com/byCedric))
|
||||
|
||||
## [13.0.0](https://github.com/expo/eas-cli/releases/tag/v13.0.0) - 2024-11-05
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Resolve versioned expo config using `npx expo config` command instead of using fixed `@expo/config` version shipped with EAS CLI, if available. ([#2529](https://github.com/expo/eas-cli/pull/2529) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add `macos-sonoma-14.6-xcode-16.1` image and `sdk-52` image tag to `eas.schema.json`. ([#2666](https://github.com/expo/eas-cli/pull/2666) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Update `@expo` packages versions. ([#2530](https://github.com/expo/eas-cli/pull/2530) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [12.6.2](https://github.com/expo/eas-cli/releases/tag/v12.6.2) - 2024-10-29
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Make email case insensitive during Apple authentication. ([#2659](https://github.com/expo/eas-cli/pull/2659) by [@byCedric](https://github.com/byCedric))
|
||||
|
||||
## [12.6.1](https://github.com/expo/eas-cli/releases/tag/v12.6.1) - 2024-10-25
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Create dynamic logged in context field and clean up erroneous SessionManager context field uses. ([#2648](https://github.com/expo/eas-cli/pull/2648) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
## [12.6.0](https://github.com/expo/eas-cli/releases/tag/v12.6.0) - 2024-10-21
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `eas channel:pause` and `eas channel:resume` commands to pause/resume update delivery to builds using specific update channels and display paused status in channel details output. ([#2614](https://github.com/expo/eas-cli/pull/2614) by [@fiberjw](https://github.com/fiberjw))
|
||||
- Add interactivity to eas update:edit command. ([#2638](https://github.com/expo/eas-cli/pull/2638) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
## [12.5.4](https://github.com/expo/eas-cli/releases/tag/v12.5.4) - 2024-10-19
|
||||
|
||||
## [12.5.3](https://github.com/expo/eas-cli/releases/tag/v12.5.3) - 2024-10-16
|
||||
|
||||
## [12.5.2](https://github.com/expo/eas-cli/releases/tag/v12.5.2) - 2024-10-11
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Disallow republishing an update that is being rolled-out. ([#2602](https://github.com/expo/eas-cli/pull/2602) by [@wschurman](https://github.com/wschurman))
|
||||
- Bump `@expo/apple-utils` to `2.0.2` resolving the Apple authentication error. ([#2641](https://github.com/expo/eas-cli/pull/2641) by [@byCedric](https://github.com/byCedric))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Implement new `worker` deploy API flow. ([#2601](https://github.com/expo/eas-cli/pull/2601) by [@kitten](https://github.com/kitten)))
|
||||
- Unhide rollout-percentage flag on update publish command. ([#2608](https://github.com/expo/eas-cli/pull/2608) by [@wschurman](https://github.com/wschurman))
|
||||
- Let folks know about the new concurrency add-on ([#2611](https://github.com/expo/eas-cli/pull/2611) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [12.5.1](https://github.com/expo/eas-cli/releases/tag/v12.5.1) - 2024-09-27
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Support assigning dev domain names in non interactive mode. ([#2595](https://github.com/expo/eas-cli/pull/2595) by [@byCedric](https://github.com/byCedric))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Simplify the output of `eas deploy --json`. ([#2596](https://github.com/expo/eas-cli/pull/2596) by [@byCedric](https://github.com/byCedric))
|
||||
- Support deploying Expo Router server exports without client directory. ([#2597](https://github.com/expo/eas-cli/pull/2597) by [@byCedric](https://github.com/byCedric))
|
||||
- Add exit option to `eas credentials` interactive menu. ([#2570](https://github.com/expo/eas-cli/pull/2570) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [12.5.0](https://github.com/expo/eas-cli/releases/tag/v12.5.0) - 2024-09-23
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Log command execution to assist in debugging local builds. ([#2526](https://github.com/expo/eas-cli/pull/2526) by [@trajano](https://github.com/trajano))
|
||||
- Allow submitting builds in progress ([#2543](https://github.com/expo/eas-cli/pull/2543) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
- Use `EAS_DANGEROUS_OVERRIDE_ANDROID_APPLICATION_ID` and `EAS_DANGEROUS_OVERRIDE_IOS_BUNDLE_IDENTIFIER` environment variables as overrides of the Android application ID and iOS bundle identifier in managed workflow too. ([#2576](https://github.com/expo/eas-cli/pull/2576) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
- Add destination branch arguments to update:republish command. ([#2575](https://github.com/expo/eas-cli/pull/2575) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Avoid malforming `app.json` with empty `.expo` object. ([#2573](https://github.com/expo/eas-cli/pull/2573) by [@byCedric](https://github.com/byCedric))
|
||||
- Fix typo causing `worker:deploy` asset upload errors not to be shown properly. ([#2579](https://github.com/expo/eas-cli/pull/2579) by [@kitten](https://github.com/kitten))
|
||||
|
||||
## [12.4.1](https://github.com/expo/eas-cli/releases/tag/v12.4.1) - 2024-09-14
|
||||
|
||||
## [12.4.0](https://github.com/expo/eas-cli/releases/tag/v12.4.0) - 2024-09-14
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `worker:alias` command to assign aliases from the CLI. ([#2548](https://github.com/expo/eas-cli/pull/2548) by [@byCedric](https://github.com/byCedric))
|
||||
- Add `worker --prod` flag to deploy to production from the CLI. ([#2550](https://github.com/expo/eas-cli/pull/2550) by [@byCedric](https://github.com/byCedric))
|
||||
- Add `worker --alias` flag to assign custom aliases when deploying. ([#2551](https://github.com/expo/eas-cli/pull/2551) by [@byCedric](https://github.com/byCedric)))
|
||||
- Add `worker --id` flag to use a custom deployment identifier. ([#2552](https://github.com/expo/eas-cli/pull/2552) by [@byCedric](https://github.com/byCedric)))
|
||||
- Add `worker --environment` flag to deploy with EAS environment variables. ([#2557](https://github.com/expo/eas-cli/pull/2557) by [@kitten](https://github.com/kitten)))
|
||||
- Add `worker --export-dir` flag to select exported directory. ([#2560](https://github.com/expo/eas-cli/pull/2560) by [@byCedric](https://github.com/byCedric)))
|
||||
- Add `worker --json` flag to allow integrating with 3rd parties and custom tooling. ([#2561](https://github.com/expo/eas-cli/pull/2561) by [@byCedric](https://github.com/byCedric)))
|
||||
- Add `worker:alias --json` flag to allow integrating with 3rd parties and custom tooling. ([#2562](https://github.com/expo/eas-cli/pull/2562) by [@byCedric](https://github.com/byCedric)))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Avoid merging `expo.extra` plugin-generated data with `expo.extra.eas.projectId` in `eas init`. ([#2554](https://github.com/expo/eas-cli/pull/2554) by [@byCedric](https://github.com/byCedric)))
|
||||
- Restore "export not found" error and hide recent export timestamps. ([#2566](https://github.com/expo/eas-cli/pull/2566) by [@byCedric](https://github.com/byCedric)))
|
||||
- Check if export is available before validating project ID in `eas worker`. ([#2569](https://github.com/expo/eas-cli/pull/2569) by [@byCedric](https://github.com/byCedric)))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Make error message for invalid CFBundleShortVersionString more descriptive and actionable. Improve CFBundleShortVersionString validation regex. ([#2542](https://github.com/expo/eas-cli/pull/2542) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Add missing `--non-interactive` argument to `worker:deploy` command. ([#2544](https://github.com/expo/eas-cli/pull/2544) by [@kitten](https://github.com/kitten))
|
||||
- Source `@expo/env` dotenv files for worker deployments. ([#2545](https://github.com/expo/eas-cli/pull/2545) by [@kitten](https://github.com/kitten))
|
||||
- Support `worker --production` and clean up command output. ([#2555](https://github.com/expo/eas-cli/pull/2555) by [@byCedric](https://github.com/byCedric)))
|
||||
- Unify both `worker` and `worker:alias` command output. ([#2558](https://github.com/expo/eas-cli/pull/2558) by [@byCedric](https://github.com/byCedric)))
|
||||
- Share similar table/json output in both `worker` and `worker:alias` command outputs. ([#2563](https://github.com/expo/eas-cli/pull/2563) by [@byCedric](https://github.com/byCedric)))
|
||||
- Polish the project URL prompt when setting up new projects. ([#2564](https://github.com/expo/eas-cli/pull/2564) by [@byCedric](https://github.com/byCedric)))
|
||||
- Always assume `static` exports in `eas deploy` and add modified time. ([#2565](https://github.com/expo/eas-cli/pull/2565) by [@byCedric](https://github.com/byCedric)))
|
||||
- Update the `eas worker --help` `--environment` description. ([#2567](https://github.com/expo/eas-cli/pull/2567) by [@byCedric](https://github.com/byCedric)))
|
||||
- Remove the cursor space after selecting project dev domain. ([#2568](https://github.com/expo/eas-cli/pull/2568) by [@byCedric](https://github.com/byCedric)))
|
||||
- Reword the dev domain prompt to mention "preview URL". ([#2572](https://github.com/expo/eas-cli/pull/2572) by [@byCedric](https://github.com/byCedric)))
|
||||
|
||||
## [12.3.0](https://github.com/expo/eas-cli/releases/tag/v12.3.0) - 2024-09-09
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `--non-interactive` and `--force` support when `--id` is not passed to the `eas init` command. ([#1983](https://github.com/expo/eas-cli/pull/1983) by [@mymattcarroll](https://github.com/mymattcarroll))
|
||||
|
||||
## [12.1.1](https://github.com/expo/eas-cli/releases/tag/v12.1.1) - 2024-09-09
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fixed an issue where extensions retrieved the main app's entitlements instead of their own. ([#2532](https://github.com/expo/eas-cli/pull/2532) by [@HarrisHan](https://github.com/HarrisHan))
|
||||
|
||||
## [12.1.0](https://github.com/expo/eas-cli/releases/tag/v12.1.0) - 2024-09-06
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- **Internal/Experimental:** Add EAS Worker command ([#2447](https://github.com/expo/eas-cli/pull/2447) by [@kitten](https://github.com/kitten))
|
||||
- Upload fingeprint source as part of eas update command. ([#2533](https://github.com/expo/eas-cli/pull/2533) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
## [12.0.0](https://github.com/expo/eas-cli/releases/tag/v12.0.0) - 2024-09-04
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Prompt the users to set `appVersionSource`, while mentioning that `remote` is the default. ([#2411](https://github.com/expo/eas-cli/pull/2411) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add support for syncing Journaling Suggestions, Managed App Installation UI, and 5G Network Slicing capabilities. ([#2525](https://github.com/expo/eas-cli/pull/2525) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [11.0.3](https://github.com/expo/eas-cli/releases/tag/v11.0.3) - 2024-08-31
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Revert config-related packages to SDK 50 version from SDK 51 version to fix broken entitlements behavior for older SDKs. ([#2524](https://github.com/expo/eas-cli/pull/2524) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [11.0.2](https://github.com/expo/eas-cli/releases/tag/v11.0.2) - 2024-08-27
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Remove unncessary static project config context definition from `eas build` command context, to fix resolving dynamic projest ID and slug for dynamic app configs. ([#2521](https://github.com/expo/eas-cli/pull/2521) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [11.0.1](https://github.com/expo/eas-cli/releases/tag/v11.0.1) - 2024-08-26
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Make `eas config` command not require authentication when running in `--eas-json-only` mode. ([#2517](https://github.com/expo/eas-cli/pull/2517) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [11.0.0](https://github.com/expo/eas-cli/releases/tag/v11.0.0) - 2024-08-26
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Remove long-deprecated `eas update` flags. ([#2501](https://github.com/expo/eas-cli/pull/2501) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add new rollout update type for `eas update` and `eas update:edit`. ([#2502](https://github.com/expo/eas-cli/pull/2502), [#2503](https://github.com/expo/eas-cli/pull/2503) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade packages to SDK 51 release. ([#2498](https://github.com/expo/eas-cli/pull/2498) by [@wschurman](https://github.com/wschurman))
|
||||
- Enable typescript linting and various lint rules. ([#2505](https://github.com/expo/eas-cli/pull/2505), [#2507](https://github.com/expo/eas-cli/pull/2507), [#2508](https://github.com/expo/eas-cli/pull/2508), [#2509](https://github.com/expo/eas-cli/pull/2509), [#2510](https://github.com/expo/eas-cli/pull/2510) by [@wschurman](https://github.com/wschurman))
|
||||
- Include debug info in fingerprint metadata during build. ([#2513](https://github.com/expo/eas-cli/pull/2513) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
## [10.2.4](https://github.com/expo/eas-cli/releases/tag/v10.2.4) - 2024-08-19
|
||||
|
||||
## [10.2.3](https://github.com/expo/eas-cli/releases/tag/v10.2.3) - 2024-08-13
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add support for `EAS_DANGEROUS_OVERRIDE_IOS_BUNDLE_IDENTIFIER` for bare workflow iOS builds. ([#2469](https://github.com/expo/eas-cli/pull/2469) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Update images list in `eas.schema.json` and warn users when using the deprecated Android images. ([#2450](https://github.com/expo/eas-cli/pull/2450) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [10.2.2](https://github.com/expo/eas-cli/releases/tag/v10.2.2) - 2024-07-31
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Pass correct path to `vcsClient.isFileIgnoredAsync` check for monorepos to validate that custom build config file is not ignored. ([#2470](https://github.com/expo/eas-cli/pull/2470) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Bump asset upload timeout from 90 to 180 seconds. ([#2466](https://github.com/expo/eas-cli/pull/2466) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [10.2.1](https://github.com/expo/eas-cli/releases/tag/v10.2.1) - 2024-07-18
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Indicate if a user is logged in using `EXPO_TOKEN` when running `eas whoami` command. ([#2461](https://github.com/expo/eas-cli/pull/2461) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Throw error when `eas login` command is run with `EXPO_TOKEN` environment variable set. ([#2461](https://github.com/expo/eas-cli/pull/2461) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Check if user is already logged in when running `eas login` command. ([#2461](https://github.com/expo/eas-cli/pull/2461) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [10.2.0](https://github.com/expo/eas-cli/releases/tag/v10.2.0) - 2024-07-15
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Added flag `--emit-metadata` to emit `eas-update-metadata.json` in the bundle folder with detailed information about the generated updates ([#2451](https://github.com/expo/eas-cli/pull/2451) by [@rainst](https://github.com/rainst))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Bump `@expo/apple-utils` version to use the fallback Apple Developer Portal domain on every internal server error. ([#2459](https://github.com/expo/eas-cli/pull/2459) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [10.1.1](https://github.com/expo/eas-cli/releases/tag/v10.1.1) - 2024-07-04
|
||||
|
||||
## [10.1.0](https://github.com/expo/eas-cli/releases/tag/v10.1.0) - 2024-07-02
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Save local fingeprint sources during build. ([#2422](https://github.com/expo/eas-cli/pull/2422) by [@kadikraman](https://github.com/kadikraman))
|
||||
|
||||
## [10.0.3](https://github.com/expo/eas-cli/releases/tag/v10.0.3) - 2024-06-26
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Track usage of `--local` build mode in analytics. ([#2445](https://github.com/expo/eas-cli/pull/2445) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Remove any mentions of deleted Xcode < 15 images. ([#2438](https://github.com/expo/eas-cli/pull/2438) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [10.0.2](https://github.com/expo/eas-cli/releases/tag/v10.0.2) - 2024-06-17
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix parsing of `build.gradle` file by `gradle-to-js` parser by filtering out empty single line comments. ([#2435](https://github.com/expo/eas-cli/pull/2435) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [10.0.1](https://github.com/expo/eas-cli/releases/tag/v10.0.1) - 2024-06-17
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add clarification to private-key flag in command line help. ([#2432](https://github.com/expo/eas-cli/pull/2432) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [10.0.0](https://github.com/expo/eas-cli/releases/tag/v10.0.0) - 2024-06-13
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Drop support for Node 16. ([#2413](https://github.com/expo/eas-cli/pull/2413) by [@byCedric](https://github.com/byCedric))
|
||||
- Update [`eas-build`](https://github.com/expo/eas-build) dependencies to the version requiring Node 18 as minimal Node version. ([#2416](https://github.com/expo/eas-cli/pull/2416) by [@expo-bot](https://github.com/expo-bot))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Resolve correct submit profile configuration for `eas build` command with `--auto-submit-with-profile` flag. ([#2425](https://github.com/expo/eas-cli/pull/2425) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Correctly parse the EXPO_APPLE_PROVIER_ID environment variable. ([#2349](https://github.com/expo/eas-cli/pull/2349) by [@louix](https://github.com/louix))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Update lockfile to only include `@types/[email protected]`. ([#2412](https://github.com/expo/eas-cli/pull/2412) by [@byCedric](https://github.com/byCedric))
|
||||
- Update test workflow Node versions to 18, 20, and 22. ([#2413](https://github.com/expo/eas-cli/pull/2413) by [@byCedric](https://github.com/byCedric))
|
||||
|
||||
## [9.2.0](https://github.com/expo/eas-cli/releases/tag/v9.2.0) - 2024-06-06
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `target-profile` and `source-profile` flags to the `eas build:resign` command. ([#2410](https://github.com/expo/eas-cli/pull/2410) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Display build profile in the output of `eas build:list`. ([#2408](https://github.com/expo/eas-cli/pull/2408) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Use the correct app config for no GitHub flow in `init:onboarding`. ([#2397](https://github.com/expo/eas-cli/pull/2397) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Disallow picking expired builds as submit archive source. ([#2406](https://github.com/expo/eas-cli/pull/2406) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Print network error message if present. ([#2407](https://github.com/expo/eas-cli/pull/2407) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Make flags for `eas build:list` command more aligned with flags for rest of the commands. ([#2409](https://github.com/expo/eas-cli/pull/2409) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [9.1.0](https://github.com/expo/eas-cli/releases/tag/v9.1.0) - 2024-05-23
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Make `eas init:onboarding` command public. ([#2399](https://github.com/expo/eas-cli/pull/2399) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [9.0.10](https://github.com/expo/eas-cli/releases/tag/v9.0.10) - 2024-05-22
|
||||
|
||||
## [9.0.9](https://github.com/expo/eas-cli/releases/tag/v9.0.9) - 2024-05-22
|
||||
|
||||
## [9.0.8](https://github.com/expo/eas-cli/releases/tag/v9.0.8) - 2024-05-21
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies. ([#2387](https://github.com/expo/eas-cli/pull/2387) by [@expo-bot](https://github.com/expo-bot))
|
||||
- Improve displaying of device registration QR code. ([#2391](https://github.com/expo/eas-cli/pull/2391) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [9.0.7](https://github.com/expo/eas-cli/releases/tag/v9.0.7) - 2024-05-17
|
||||
|
||||
## [9.0.6](https://github.com/expo/eas-cli/releases/tag/v9.0.6) - 2024-05-15
|
||||
|
||||
## [9.0.5](https://github.com/expo/eas-cli/releases/tag/v9.0.5) - 2024-05-13
|
||||
|
||||
## [9.0.4](https://github.com/expo/eas-cli/releases/tag/v9.0.4) - 2024-05-13
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
* @szdziedzic @khamilowicz @sjchmiela
|
||||
|
||||
packages/eas-cli/src/build @szdziedzic @khamilowicz @sjchmiela
|
||||
packages/eas-cli/src/commands/build @szdziedzic @khamilowicz @sjchmiela
|
||||
|
||||
packages/eas-cli/src/submit @szdziedzic @khamilowicz @sjchmiela
|
||||
packages/eas-cli/src/commands/submit.ts @szdziedzic @khamilowicz @sjchmiela
|
||||
|
||||
packages/eas-cli/src/update @EvanBacon @byCedric @wschurman
|
||||
packages/eas-cli/src/commands/update @EvanBacon @byCedric @wschurman
|
||||
|
||||
packages/eas-cli/schema @bycedric
|
||||
packages/eas-cli/src/metadata @bycedric
|
||||
packages/eas-cli/src/commands/metadata @bycedric
|
||||
|
||||
packages/eas-json @szdziedzic @khamilowicz @sjchmiela
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"version": "9.0.6",
|
||||
"version": "15.0.12",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
"@types/jest": "29.5.11",
|
||||
"@types/node": "20.11.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-universe": "12.0.0",
|
||||
"eslint-config-universe": "13.0.0",
|
||||
"eslint-plugin-async-protect": "3.1.0",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"jest": "29.7.0",
|
||||
|
||||
+731
-188
File diff suppressed because it is too large.
Load diff
@@ -12,3 +12,6 @@ if (process.env.TMPDIR) {
|
||||
(fs.realpath as any).native = jest.fn();
|
||||
|
||||
module.exports = fs;
|
||||
|
||||
// Ensure requiring node:fs returns the mock too. This is needed for expo/config and expo/json-file.
|
||||
jest.mock('node:fs', () => fs);
|
||||
@@ -7,6 +7,7 @@ documents:
|
||||
- 'src/commands/**/*.ts'
|
||||
- 'src/branch/**/*.ts'
|
||||
- 'src/channel/**/*.ts'
|
||||
- 'src/worker/**/*.ts'
|
||||
generates:
|
||||
src/graphql/generated.ts:
|
||||
plugins:
|
||||
|
||||
Generated
+16939
-637
File diff suppressed because it is too large.
Load diff
@@ -1,34 +1,34 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "9.0.6",
|
||||
"version": "15.0.12",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
},
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/apple-utils": "1.7.0",
|
||||
"@expo/apple-utils": "2.1.11",
|
||||
"@expo/code-signing-certificates": "0.0.5",
|
||||
"@expo/config": "8.5.4",
|
||||
"@expo/config-plugins": "7.8.4",
|
||||
"@expo/config-types": "50.0.0",
|
||||
"@expo/eas-build-job": "1.0.108",
|
||||
"@expo/eas-json": "9.0.4",
|
||||
"@expo/json-file": "8.2.37",
|
||||
"@expo/logger": "1.0.57",
|
||||
"@expo/multipart-body-parser": "1.1.0",
|
||||
"@expo/osascript": "2.0.33",
|
||||
"@expo/package-manager": "1.1.2",
|
||||
"@expo/pkcs12": "0.0.8",
|
||||
"@expo/plist": "0.0.20",
|
||||
"@expo/config": "10.0.6",
|
||||
"@expo/config-plugins": "9.0.12",
|
||||
"@expo/eas-build-job": "1.0.165",
|
||||
"@expo/eas-json": "15.0.0",
|
||||
"@expo/env": "^1.0.0",
|
||||
"@expo/json-file": "8.3.3",
|
||||
"@expo/logger": "1.0.117",
|
||||
"@expo/multipart-body-parser": "2.0.0",
|
||||
"@expo/osascript": "2.1.4",
|
||||
"@expo/package-manager": "1.7.0",
|
||||
"@expo/pkcs12": "0.1.3",
|
||||
"@expo/plist": "0.2.0",
|
||||
"@expo/plugin-help": "5.1.23",
|
||||
"@expo/plugin-warn-if-update-available": "2.5.1",
|
||||
"@expo/prebuild-config": "6.7.3",
|
||||
"@expo/prebuild-config": "8.0.17",
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.7.0",
|
||||
"@expo/steps": "1.0.108",
|
||||
"@expo/spawn-async": "1.7.2",
|
||||
"@expo/steps": "1.0.166",
|
||||
"@expo/timeago.js": "1.0.0",
|
||||
"@oclif/core": "^1.26.2",
|
||||
"@oclif/plugin-autocomplete": "^2.3.10",
|
||||
@@ -41,6 +41,7 @@
|
||||
"chalk": "4.1.2",
|
||||
"cli-progress": "3.12.0",
|
||||
"dateformat": "4.6.3",
|
||||
"diff": "7.0.0",
|
||||
"dotenv": "16.3.1",
|
||||
"env-paths": "2.2.0",
|
||||
"envinfo": "7.11.0",
|
||||
@@ -63,7 +64,8 @@
|
||||
"log-symbols": "4.1.0",
|
||||
"mime": "3.0.0",
|
||||
"minimatch": "5.1.2",
|
||||
"nanoid": "3.3.4",
|
||||
"minizlib": "3.0.1",
|
||||
"nanoid": "3.3.8",
|
||||
"node-fetch": "2.6.7",
|
||||
"node-forge": "1.3.1",
|
||||
"nullthrows": "1.1.1",
|
||||
@@ -78,20 +80,25 @@
|
||||
"semver": "7.5.4",
|
||||
"slash": "3.0.0",
|
||||
"tar": "6.2.1",
|
||||
"tar-stream": "3.1.7",
|
||||
"terminal-link": "2.1.1",
|
||||
"tslib": "2.6.2",
|
||||
"turndown": "7.1.2",
|
||||
"untildify": "4.0.0",
|
||||
"uuid": "9.0.1",
|
||||
"wrap-ansi": "7.0.0"
|
||||
"wrap-ansi": "7.0.0",
|
||||
"yaml": "2.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||
"@tsconfig/node18": "18.2.4",
|
||||
"@types/cli-progress": "3.11.5",
|
||||
"@types/dateformat": "3.0.1",
|
||||
"@types/diff": "6.0.0",
|
||||
"@types/envinfo": "7.8.3",
|
||||
"@types/express": "4.17.15",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
@@ -105,12 +112,13 @@
|
||||
"@types/prompts": "2.4.2",
|
||||
"@types/semver": "7.5.6",
|
||||
"@types/tar": "6.1.10",
|
||||
"@types/tar-stream": "3.1.3",
|
||||
"@types/tough-cookie": "4.0.2",
|
||||
"@types/uuid": "9.0.7",
|
||||
"@types/wrap-ansi": "3.0.0",
|
||||
"axios": "0.27.2",
|
||||
"axios": "0.28.1",
|
||||
"eslint-plugin-graphql": "4.0.0",
|
||||
"express": "4.19.2",
|
||||
"express": "4.20.0",
|
||||
"memfs": "3.4.13",
|
||||
"mockdate": "3.0.5",
|
||||
"nock": "13.4.0",
|
||||
@@ -121,7 +129,7 @@
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"files": [
|
||||
"/bin",
|
||||
@@ -172,16 +180,22 @@
|
||||
"secret": {
|
||||
"description": "manage project and account secrets"
|
||||
},
|
||||
"env": {
|
||||
"description": "manage project and account environment variables"
|
||||
},
|
||||
"update": {
|
||||
"description": "manage individual updates"
|
||||
},
|
||||
"webhook": {
|
||||
"description": "manage webhooks"
|
||||
},
|
||||
"workflow": {
|
||||
"description": "manage workflows"
|
||||
}
|
||||
},
|
||||
"warn-if-update-available": {
|
||||
"timeoutInDays": 0.5,
|
||||
"message": "<%= chalk('★') %> <%= chalk.bold(config.name + '@' + latest) %> <%= chalk.bold('is now available.') %>\n<%= chalk.dim('To upgrade, run ') %><%= chalk.dim(chalk.bold('npm install -g eas-cli')) %><%= chalk.dim('.') %>\n<%= chalk.dim('Proceeding with outdated version.') %>\n"
|
||||
"message": "<%= chalk('★') %> <%= chalk.bold(config.name + '@' + latest) %> <%= chalk.bold('is now available.') %>\n<%= chalk.dim('To upgrade, run:') %>\n<%= chalk.dim(chalk.bold('npm install -g eas-cli')) %>\n<%= chalk.dim('Proceeding with outdated version.') %>\n"
|
||||
},
|
||||
"update": {
|
||||
"node": {
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-06/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"configVersion": {
|
||||
"enum": [0],
|
||||
"description": "The EAS metadata store configuration version"
|
||||
},
|
||||
"apple": {
|
||||
"$ref": "#/definitions/AppleConfig"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"configVersion",
|
||||
"apple"
|
||||
],
|
||||
"definitions": {
|
||||
"apple": {
|
||||
"AppleKidsAge": {
|
||||
@@ -323,7 +337,8 @@
|
||||
"gambling",
|
||||
"unrestrictedWebAccess",
|
||||
"kidsAgeBand",
|
||||
"seventeenPlus"
|
||||
"ageRatingOverride",
|
||||
"koreaAgeRatingOverride"
|
||||
],
|
||||
"defaultSnippets": [
|
||||
{
|
||||
@@ -345,7 +360,9 @@
|
||||
"gambling": false,
|
||||
"unrestrictedWebAccess": false,
|
||||
"kidsAgeBand": null,
|
||||
"seventeenPlus": false
|
||||
"ageRatingOverride": "NONE",
|
||||
"koreaAgeRatingOverride": "NONE",
|
||||
"lootBox": false
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -367,7 +384,9 @@
|
||||
"unrestrictedWebAccess": false,
|
||||
"gambling": false,
|
||||
"kidsAgeBand": null,
|
||||
"seventeenPlus": false
|
||||
"ageRatingOverride": "${13|NONE,SEVENTEEN_PLUS,UNRATED|}",
|
||||
"koreaAgeRatingOverride": "${14|NONE,FIFTEEN_PLUS,NINETEEN_PLUS|}",
|
||||
"lootBox": false
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -442,8 +461,24 @@
|
||||
},
|
||||
"seventeenPlus": {
|
||||
"type": "boolean",
|
||||
"deprecated": true,
|
||||
"deprecationMessage": "This property is deprecated, use `ageRatingOverride` instead.",
|
||||
"description": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 17, you can manually set the age rating to 17+.\n@see https://help.apple.com/app-store-connect/#/dev599d50efb",
|
||||
"markdownDescription": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 17, you can manually set the age rating to 17+.\n\n[Learn more](https://help.apple.com/app-store-connect/#/dev599d50efb)"
|
||||
},
|
||||
"ageRatingOverride": {
|
||||
"$ref": "#/definitions/apple/AppleAgeRatingOverride",
|
||||
"description": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 17, you can manually set the age rating to 17+.\n\nIf your app will be distributed through alternative app marketplaces or websites in the European Union and you believe your app is [Unrated](https://developer.apple.com/help/app-store-connect/reference/age-ratings), set this to `UNRATED`.\n\n@see https://help.apple.com/app-store-connect/#/dev599d50efb",
|
||||
"markdownDescription": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 17, you can manually set the age rating to 17+.\n\nIf your app will be distributed through alternative app marketplaces or websites in the European Union and you believe your app is [Unrated](https://developer.apple.com/help/app-store-connect/reference/age-ratings), set this to `UNRATED`.\n\n[Learn more](https://help.apple.com/app-store-connect/#/dev599d50efb)"
|
||||
},
|
||||
"koreaAgeRatingOverride": {
|
||||
"$ref": "#/definitions/apple/AppleKoreaAgeRatingOverride",
|
||||
"description": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 15 or 19, you can manually set the age rating to 15+ or 19+.\n\nIf your app will be distributed through alternative app marketplaces or websites in the European Union and you believe your app is [Unrated](https://developer.apple.com/help/app-store-connect/reference/age-ratings), set this to `UNRATED`.\n\n@see https://help.apple.com/app-store-connect/#/dev599d50efb",
|
||||
"markdownDescription": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 15 or 19, you can manually set the age rating to 15+ or 19+.\n\nIf your app will be distributed through alternative app marketplaces or websites in the European Union and you believe your app is [Unrated](https://developer.apple.com/help/app-store-connect/reference/age-ratings), set this to `UNRATED`.\n\n[Learn more](https://help.apple.com/app-store-connect/#/dev599d50efb)"
|
||||
},
|
||||
"lootBox": {
|
||||
"type": "boolean",
|
||||
"description": "Does your app contain purchasable loot boxes? These virtual containers provide players with in-game items, including functional cards or cosmetic modifications, based on random chance."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -565,6 +600,20 @@
|
||||
"maxLength": 4000
|
||||
}
|
||||
}
|
||||
},
|
||||
"AppleAgeRatingOverride": {
|
||||
"enum": [
|
||||
"NONE",
|
||||
"SEVENTEEN_PLUS",
|
||||
"UNRATED"
|
||||
]
|
||||
},
|
||||
"AppleKoreaAgeRatingOverride": {
|
||||
"enum": [
|
||||
"NONE",
|
||||
"FIFTEEN_PLUS",
|
||||
"NINETEEN_PLUS"
|
||||
]
|
||||
}
|
||||
},
|
||||
"AppleConfig": {
|
||||
@@ -775,19 +824,5 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"configVersion": {
|
||||
"enum": [0],
|
||||
"description": "The EAS metadata store configuration version"
|
||||
},
|
||||
"apple": {
|
||||
"$ref": "#/definitions/AppleConfig"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"configVersion",
|
||||
"apple"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
module.exports = {
|
||||
extends: ['universe/shared/typescript-analysis'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.d.ts'],
|
||||
parserOptions: {
|
||||
project: './packages/eas-cli/tsconfig.json',
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/explicit-function-return-type': [
|
||||
'warn',
|
||||
{
|
||||
allowExpressions: true,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/prefer-nullish-coalescing': ['warn', { ignorePrimitives: true }],
|
||||
'@typescript-eslint/no-confusing-void-expression': 'warn',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/no-misused-promises': [
|
||||
'error',
|
||||
{
|
||||
checksVoidReturn: false,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'no-void': ['warn', { allowAsStatement: true }],
|
||||
'no-return-await': 'off',
|
||||
'@typescript-eslint/return-await': ['error', 'always'],
|
||||
'@typescript-eslint/no-confusing-non-null-assertion': 'warn',
|
||||
'@typescript-eslint/no-extra-non-null-assertion': 'warn',
|
||||
'@typescript-eslint/prefer-as-const': 'warn',
|
||||
'@typescript-eslint/prefer-includes': 'warn',
|
||||
'@typescript-eslint/prefer-readonly': 'warn',
|
||||
'@typescript-eslint/prefer-string-starts-ends-with': 'warn',
|
||||
'@typescript-eslint/prefer-ts-expect-error': 'warn',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'warn',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`BuildVersionGetView reading version aborts when the appVersionSource is not specified and the user chooses to set it to LOCAL 1`] = `"Aborting..."`;
|
||||
|
||||
exports[`BuildVersionGetView reading version when appVersionSource is set to local 1`] = `"This project is not configured for using remote version source. Add [1m{"cli": { "appVersionSource": "remote" }}[22m in eas.json or re-run this command without "--non-interactive" flag."`;
|
||||
@@ -1,10 +1,18 @@
|
||||
import { AppVersionSource, EasJson } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { getMockEasJson, mockCommandContext, mockProjectId, mockTestCommand } from './utils';
|
||||
import {
|
||||
getMockEasJson,
|
||||
mockCommandContext,
|
||||
mockProjectId,
|
||||
mockTestCommand,
|
||||
withLocalVersionSource,
|
||||
withRemoteVersionSource,
|
||||
} from './utils';
|
||||
import BuildVersionGetView from '../../commands/build/version/get';
|
||||
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
||||
import Log from '../../log';
|
||||
import { AppVersionSourceUpdateOption } from '../../project/remoteVersionSource';
|
||||
import * as prompts from '../../prompts';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
|
||||
jest.mock('../../project/applicationIdentifier');
|
||||
@@ -12,16 +20,7 @@ jest.mock('fs');
|
||||
jest.mock('../../log');
|
||||
jest.mock('../../utils/json');
|
||||
jest.mock('../../graphql/queries/AppVersionQuery');
|
||||
|
||||
function withRemoteVersionSource(easJson: EasJson): EasJson {
|
||||
return {
|
||||
...easJson,
|
||||
cli: {
|
||||
...easJson.cli,
|
||||
appVersionSource: AppVersionSource.REMOTE,
|
||||
},
|
||||
};
|
||||
}
|
||||
jest.mock('../../prompts');
|
||||
|
||||
describe(BuildVersionGetView, () => {
|
||||
afterEach(() => {
|
||||
@@ -49,6 +48,29 @@ describe(BuildVersionGetView, () => {
|
||||
expect(printJsonOnlyOutput).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('reading version for platform android when the appVersionSource is not specified and the user chooses to set it to REMOTE', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||
buildVersion: '100',
|
||||
storeVersion: '1.0.0',
|
||||
}));
|
||||
const cmd = mockTestCommand(BuildVersionGetView, ['--platform=android'], ctx);
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_REMOTE);
|
||||
|
||||
await cmd.run();
|
||||
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
|
||||
ctx.loggedIn.graphqlClient,
|
||||
mockProjectId,
|
||||
'ANDROID',
|
||||
'eas.test.com'
|
||||
);
|
||||
expect(Log.log).toHaveBeenCalledWith(`Android versionCode - ${chalk.bold('100')}`);
|
||||
expect(enableJsonOutput).not.toHaveBeenCalled();
|
||||
expect(printJsonOnlyOutput).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('reading version for platform android when no remote version is set', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {
|
||||
easJson: withRemoteVersionSource(getMockEasJson()),
|
||||
@@ -145,7 +167,9 @@ describe(BuildVersionGetView, () => {
|
||||
});
|
||||
|
||||
test('reading version when appVersionSource is set to local ', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {});
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {
|
||||
easJson: withLocalVersionSource(getMockEasJson()),
|
||||
});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||
buildVersion: '100',
|
||||
storeVersion: '1.0.0',
|
||||
@@ -158,4 +182,51 @@ describe(BuildVersionGetView, () => {
|
||||
);
|
||||
await expect(cmd.run()).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('reading version aborts when the appVersionSource is not specified and the user chooses to set it to LOCAL', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||
buildVersion: '100',
|
||||
storeVersion: '1.0.0',
|
||||
}));
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_LOCAL);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionGetView, ['--platform=android'], ctx);
|
||||
await expect(cmd.run()).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('reading version aborts when the appVersionSource is not specified and the user chooses to configure it manually', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||
buildVersion: '100',
|
||||
storeVersion: '1.0.0',
|
||||
}));
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.ABORT);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionGetView, ['--platform=android'], ctx);
|
||||
await expect(cmd.run()).rejects.toThrowError('Aborted.');
|
||||
});
|
||||
|
||||
test('reading version sets appVersionSource to LOCAL and aborts when the appVersionSource is not specified and is run in non-interactive mode', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||
buildVersion: '100',
|
||||
storeVersion: '1.0.0',
|
||||
}));
|
||||
|
||||
const cmd = mockTestCommand(
|
||||
BuildVersionGetView,
|
||||
['--non-interactive', '--json', '--platform=android'],
|
||||
ctx
|
||||
);
|
||||
await expect(cmd.run()).rejects.toThrowError(
|
||||
`This project is not configured for using remote version source. Add ${chalk.bold(
|
||||
'{"cli": { "appVersionSource": "remote" }}'
|
||||
)} in eas.json or re-run this command without "--non-interactive" flag.`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AppVersionSource, EasJson } from '@expo/eas-json';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
@@ -8,12 +7,15 @@ import {
|
||||
mockCommandContext,
|
||||
mockProjectId,
|
||||
mockTestCommand,
|
||||
withLocalVersionSource,
|
||||
withRemoteVersionSource,
|
||||
} from './utils';
|
||||
import BuildVersionSetView from '../../commands/build/version/set';
|
||||
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
|
||||
import { AppQuery } from '../../graphql/queries/AppQuery';
|
||||
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
||||
import Log from '../../log';
|
||||
import { AppVersionSourceUpdateOption } from '../../project/remoteVersionSource';
|
||||
import * as prompts from '../../prompts';
|
||||
|
||||
jest.mock('../../project/applicationIdentifier');
|
||||
@@ -25,16 +27,6 @@ jest.mock('../../log');
|
||||
jest.mock('../../prompts');
|
||||
jest.mock('../../utils/json');
|
||||
|
||||
function withRemoteVersionSource(easJson: EasJson): EasJson {
|
||||
return {
|
||||
...easJson,
|
||||
cli: {
|
||||
...easJson.cli,
|
||||
appVersionSource: AppVersionSource.REMOTE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe(BuildVersionSetView, () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -67,6 +59,34 @@ describe(BuildVersionSetView, () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('setting version for platform android when the appVersionSource is not specified and the user chooses to set it to REMOTE', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {});
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(prompts.promptAsync).mockImplementationOnce(async () => ({
|
||||
version: '1000',
|
||||
}));
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_REMOTE);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
|
||||
await cmd.run();
|
||||
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
|
||||
ctx.loggedIn.graphqlClient,
|
||||
mockProjectId,
|
||||
'ANDROID',
|
||||
'eas.test.com'
|
||||
);
|
||||
expect(AppVersionMutation.createAppVersionAsync).toHaveBeenCalledWith(
|
||||
ctx.loggedIn.graphqlClient,
|
||||
expect.objectContaining({
|
||||
buildVersion: '1000',
|
||||
storeVersion: '1.0.0',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('printing current remote version before prompting for a new one', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {
|
||||
easJson: withRemoteVersionSource(getMockEasJson()),
|
||||
@@ -115,7 +135,9 @@ describe(BuildVersionSetView, () => {
|
||||
});
|
||||
|
||||
test('setting version aborts when appVersionSource is set to local and users refuse auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {});
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {
|
||||
easJson: withLocalVersionSource(getMockEasJson()),
|
||||
});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => false);
|
||||
@@ -125,10 +147,26 @@ describe(BuildVersionSetView, () => {
|
||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('setting version when appVersionSource is set to local and user allows auto configuration', async () => {
|
||||
test('setting version aborts when the appVersionSource is not specified and the user chooses to set it to LOCAL, and they refuse auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_LOCAL);
|
||||
jest.mocked(prompts.confirmAsync).mockImplementationOnce(async () => false);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
|
||||
await expect(cmd.run()).rejects.toThrowError('Aborting...');
|
||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('setting version when appVersionSource is set to local and user allows auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {
|
||||
easJson: withLocalVersionSource(getMockEasJson()),
|
||||
});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => true);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
|
||||
@@ -137,4 +175,33 @@ describe(BuildVersionSetView, () => {
|
||||
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
|
||||
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
|
||||
});
|
||||
|
||||
test('setting version when the appVersionSource is not specified and the user chooses to set it to LOCAL, and they allow auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_LOCAL);
|
||||
jest.mocked(prompts.confirmAsync).mockImplementationOnce(async () => true);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
|
||||
await cmd.run();
|
||||
|
||||
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
|
||||
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
|
||||
});
|
||||
|
||||
test('setting version aborts when the appVersionSource is not specified and the user chooses to configure manually', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.ABORT);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
|
||||
await expect(cmd.run()).rejects.toThrowError('Aborted.');
|
||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Workflow } from '@expo/eas-build-job';
|
||||
import { AppVersionSource, EasJson } from '@expo/eas-json';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
@@ -9,6 +8,8 @@ import {
|
||||
mockCommandContext,
|
||||
mockProjectId,
|
||||
mockTestCommand,
|
||||
withLocalVersionSource,
|
||||
withRemoteVersionSource,
|
||||
} from './utils';
|
||||
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../build/android/version';
|
||||
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../build/ios/version';
|
||||
@@ -19,6 +20,7 @@ import { AppQuery } from '../../graphql/queries/AppQuery';
|
||||
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
||||
import { getAppBuildGradleAsync } from '../../project/android/gradleUtils';
|
||||
import { resolveTargetsAsync } from '../../project/ios/target';
|
||||
import { AppVersionSourceUpdateOption } from '../../project/remoteVersionSource';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import * as prompts from '../../prompts';
|
||||
|
||||
@@ -37,16 +39,6 @@ jest.mock('../../log');
|
||||
jest.mock('../../prompts');
|
||||
jest.mock('../../utils/json');
|
||||
|
||||
function withRemoteVersionSource(easJson: EasJson): EasJson {
|
||||
return {
|
||||
...easJson,
|
||||
cli: {
|
||||
...easJson.cli,
|
||||
appVersionSource: AppVersionSource.REMOTE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe(BuildVersionSyncView, () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -79,6 +71,34 @@ describe(BuildVersionSyncView, () => {
|
||||
expect(syncAndroidAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('syncing version for managed project on platform android when appVersionSource is not set and the user chooses to set it to REMOTE', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {});
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||
buildVersion: '1000',
|
||||
storeVersion: '0.0.1',
|
||||
}));
|
||||
jest.mocked(prompts.promptAsync).mockImplementationOnce(async () => ({
|
||||
version: '1000',
|
||||
}));
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_REMOTE);
|
||||
jest.mocked(resolveWorkflowAsync).mockImplementation(async () => Workflow.MANAGED);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
|
||||
const syncAndroidAsync = jest.spyOn(cmd, 'syncAndroidAsync' as any);
|
||||
|
||||
await cmd.run();
|
||||
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
|
||||
ctx.loggedIn.graphqlClient,
|
||||
mockProjectId,
|
||||
'ANDROID',
|
||||
'eas.test.com'
|
||||
);
|
||||
expect(syncAndroidAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('syncing version for bare project on platform android', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {
|
||||
easJson: withRemoteVersionSource(getMockEasJson()),
|
||||
@@ -179,7 +199,9 @@ describe(BuildVersionSyncView, () => {
|
||||
});
|
||||
|
||||
test('syncing version aborts when appVersionSource is set to local and users refuse auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {});
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {
|
||||
easJson: withLocalVersionSource(getMockEasJson()),
|
||||
});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => false);
|
||||
@@ -189,10 +211,26 @@ describe(BuildVersionSyncView, () => {
|
||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('syncing version when appVersionSource is set to local and user allows auto configuration', async () => {
|
||||
test('syncing version aborts when appVersionSource is not set and the user chooses to set it to LOCAL, and they refuse auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_LOCAL);
|
||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => false);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
|
||||
await expect(cmd.run()).rejects.toThrowError('Aborting...');
|
||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('syncing version when appVersionSource is set to local and user allows auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {
|
||||
easJson: withLocalVersionSource(getMockEasJson()),
|
||||
});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => true);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
|
||||
@@ -201,4 +239,33 @@ describe(BuildVersionSyncView, () => {
|
||||
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
|
||||
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
|
||||
});
|
||||
|
||||
test('syncing version when appVersionSource is not set and the user chooses to set it to LOCAL and they allow auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_LOCAL);
|
||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => true);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
|
||||
await cmd.run();
|
||||
|
||||
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
|
||||
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
|
||||
});
|
||||
|
||||
test('syncing version aborts when appVersionSource is not set and the user chooses to configure manually', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.ABORT);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
|
||||
await expect(cmd.run()).rejects.toThrowError('Aborted.');
|
||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { EasJson } from '@expo/eas-json';
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { AppVersionSource, EasJson } from '@expo/eas-json';
|
||||
import { Command, Config } from '@oclif/core';
|
||||
import { vol } from 'memfs';
|
||||
import path from 'path';
|
||||
@@ -148,3 +148,23 @@ export const getError = <TError = any>(call: () => unknown): TError | NoErrorThr
|
||||
return error as TError;
|
||||
}
|
||||
};
|
||||
|
||||
export function withRemoteVersionSource(easJson: EasJson): EasJson {
|
||||
return {
|
||||
...easJson,
|
||||
cli: {
|
||||
...easJson.cli,
|
||||
appVersionSource: AppVersionSource.REMOTE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function withLocalVersionSource(easJson: EasJson): EasJson {
|
||||
return {
|
||||
...easJson,
|
||||
cli: {
|
||||
...easJson.cli,
|
||||
appVersionSource: AppVersionSource.LOCAL,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
*/
|
||||
export class SelectBranch implements EASUpdateAction<UpdateBranchBasicInfoFragment | null> {
|
||||
constructor(
|
||||
private options: {
|
||||
private readonly options: {
|
||||
filterPredicate?: (branchInfo: UpdateBranchBasicInfoFragment) => boolean;
|
||||
printedType?: string;
|
||||
pageSize?: number;
|
||||
@@ -91,8 +91,8 @@ export class SelectBranch implements EASUpdateAction<UpdateBranchBasicInfoFragme
|
||||
queryParams: QueryParams
|
||||
): Promise<Connection<UpdateBranchBasicInfoFragment>> =>
|
||||
filterPredicate
|
||||
? this.filterQueryAsync(ctx, queryParams, filterPredicate)
|
||||
: this.queryAsync(ctx, queryParams);
|
||||
? await this.filterQueryAsync(ctx, queryParams, filterPredicate)
|
||||
: await this.queryAsync(ctx, queryParams);
|
||||
|
||||
const getTitleAsync = async (branchInfo: UpdateBranchBasicInfoFragment): Promise<string> =>
|
||||
branchInfo.name;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import chalk from 'chalk';
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { BranchNotFoundError } from './utils';
|
||||
@@ -8,10 +9,11 @@ import { withErrorHandlingAsync } from '../graphql/client';
|
||||
import {
|
||||
CreateUpdateBranchForAppMutation,
|
||||
CreateUpdateBranchForAppMutationVariables,
|
||||
UpdateBranch,
|
||||
UpdateBranchBasicInfoFragment,
|
||||
UpdateBranchFragment,
|
||||
} from '../graphql/generated';
|
||||
import { BranchQuery } from '../graphql/queries/BranchQuery';
|
||||
import { UpdateBranchBasicInfoFragmentNode } from '../graphql/types/UpdateBranchBasicInfo';
|
||||
import Log from '../log';
|
||||
import { formatBranch, getBranchDescription } from '../update/utils';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
@@ -84,7 +86,9 @@ export async function listAndRenderBranchesOnAppAsync(
|
||||
queryBranchesOnProjectAsync(graphqlClient, limit, offset, projectId),
|
||||
promptOptions: {
|
||||
title: 'Load more branches?',
|
||||
renderListItems: branches => renderPageOfBranches(branches, paginatedQueryOptions),
|
||||
renderListItems: branches => {
|
||||
renderPageOfBranches(branches, paginatedQueryOptions);
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -124,7 +128,7 @@ function renderPageOfBranches(
|
||||
export async function createUpdateBranchOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, name }: CreateUpdateBranchForAppMutationVariables
|
||||
): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
|
||||
): Promise<UpdateBranchBasicInfoFragment> {
|
||||
const result = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateUpdateBranchForAppMutation, CreateUpdateBranchForAppMutationVariables>(
|
||||
@@ -133,15 +137,17 @@ export async function createUpdateBranchOnAppAsync(
|
||||
updateBranch {
|
||||
createUpdateBranchForApp(appId: $appId, name: $name) {
|
||||
id
|
||||
name
|
||||
...UpdateBranchBasicInfoFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(UpdateBranchBasicInfoFragmentNode)}
|
||||
`,
|
||||
{
|
||||
appId,
|
||||
name,
|
||||
}
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
@@ -161,22 +167,21 @@ export async function ensureBranchExistsAsync(
|
||||
appId: string;
|
||||
branchName: string;
|
||||
}
|
||||
): Promise<{ branchId: string; createdBranch: boolean }> {
|
||||
): Promise<{ branch: UpdateBranchBasicInfoFragment; createdBranch: boolean }> {
|
||||
try {
|
||||
const updateBranch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||
appId,
|
||||
name: branchName,
|
||||
});
|
||||
|
||||
const { id } = updateBranch;
|
||||
return { branchId: id, createdBranch: false };
|
||||
return { branch: updateBranch, createdBranch: false };
|
||||
} catch (error) {
|
||||
if (error instanceof BranchNotFoundError) {
|
||||
const newUpdateBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
|
||||
appId,
|
||||
name: branchName,
|
||||
});
|
||||
return { branchId: newUpdateBranch.id, createdBranch: true };
|
||||
return { branch: newUpdateBranch, createdBranch: true };
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Client } from '../vcs/vcs';
|
||||
|
||||
export async function getDefaultBranchNameAsync(vcsClient: Client): Promise<string> {
|
||||
return (
|
||||
(await vcsClient.getBranchNameAsync()) || `branch-${Math.random().toString(36).substring(2, 4)}`
|
||||
);
|
||||
export async function getDefaultBranchNameAsync(vcsClient: Client): Promise<string | null> {
|
||||
return await vcsClient.getBranchNameAsync();
|
||||
}
|
||||
|
||||
export class BranchNotFoundError extends Error {
|
||||
|
||||
@@ -47,14 +47,14 @@ describe(selectBuildToCancelAsync.name, () => {
|
||||
it('does not return build id when confirmation is rejected', async () => {
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||
jest.mocked(confirmAsync).mockResolvedValueOnce(false);
|
||||
expect(selectBuildToCancelAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(null);
|
||||
await expect(selectBuildToCancelAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(null);
|
||||
});
|
||||
|
||||
it('returns build id when confirmation is confirmed', async () => {
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||
jest.mocked(selectAsync).mockResolvedValueOnce(selectedBuildId);
|
||||
jest.mocked(confirmAsync).mockResolvedValueOnce(true);
|
||||
expect(selectBuildToCancelAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
||||
await expect(selectBuildToCancelAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
||||
selectedBuildId
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { AppVersionSource, EasJsonAccessor } from '@expo/eas-json';
|
||||
import fs from 'fs-extra';
|
||||
import { vol } from 'memfs';
|
||||
|
||||
import { easCliVersion } from '../../utils/easCli';
|
||||
import GitClient from '../../vcs/clients/git';
|
||||
import { Client } from '../../vcs/vcs';
|
||||
import { ensureProjectConfiguredAsync } from '../configure';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('../../vcs/vcs');
|
||||
jest.mock('../../vcs/clients/git');
|
||||
|
||||
beforeEach(async () => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
describe(ensureProjectConfiguredAsync, () => {
|
||||
it('returns false and does not configure if eas.json exists', async () => {
|
||||
vol.fromJSON({
|
||||
'./eas.json': JSON.stringify({
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
},
|
||||
build: {
|
||||
development: {
|
||||
developmentClient: true,
|
||||
distribution: 'internal',
|
||||
},
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
},
|
||||
production: {},
|
||||
},
|
||||
submit: {
|
||||
production: {},
|
||||
},
|
||||
}),
|
||||
});
|
||||
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeTruthy();
|
||||
const vcsClientMock = jest.mocked(new GitClient({ requireCommit: false }));
|
||||
vcsClientMock.showChangedFilesAsync.mockImplementation(async () => {});
|
||||
vcsClientMock.isCommitRequiredAsync.mockImplementation(async () => false);
|
||||
vcsClientMock.trackFileAsync.mockImplementation(async () => {});
|
||||
const result = await ensureProjectConfiguredAsync({
|
||||
projectDir: '.',
|
||||
nonInteractive: false,
|
||||
vcsClient: vcsClientMock as unknown as Client,
|
||||
});
|
||||
expect(result).toBeFalsy();
|
||||
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeTruthy();
|
||||
});
|
||||
it('returns true and configures if eas.json does not exist', async () => {
|
||||
const writeFileMock = jest.spyOn(fs, 'writeFile');
|
||||
writeFileMock.mockImplementation(async (...args) => {
|
||||
const easJsonPath = args[0] as string;
|
||||
const easJsonData = args[1] as string;
|
||||
vol.fromJSON({
|
||||
[easJsonPath]: easJsonData,
|
||||
});
|
||||
});
|
||||
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeFalsy();
|
||||
const vcsClientMock = jest.mocked(new GitClient({ requireCommit: false }));
|
||||
vcsClientMock.showChangedFilesAsync.mockImplementation(async () => {});
|
||||
vcsClientMock.isCommitRequiredAsync.mockImplementation(async () => false);
|
||||
vcsClientMock.trackFileAsync.mockImplementation(async () => {});
|
||||
const result = await ensureProjectConfiguredAsync({
|
||||
projectDir: '.',
|
||||
nonInteractive: false,
|
||||
vcsClient: vcsClientMock as unknown as Client,
|
||||
});
|
||||
expect(result).toBeTruthy();
|
||||
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeTruthy();
|
||||
const easJson = await EasJsonAccessor.fromProjectPath('.').readAsync();
|
||||
expect(easJson.cli?.appVersionSource).toEqual(AppVersionSource.REMOTE);
|
||||
});
|
||||
});
|
||||
@@ -47,14 +47,14 @@ describe(selectBuildToDeleteAsync.name, () => {
|
||||
it('does not return build id when confirmation is rejected', async () => {
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||
jest.mocked(confirmAsync).mockResolvedValueOnce(false);
|
||||
expect(selectBuildToDeleteAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(null);
|
||||
await expect(selectBuildToDeleteAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(null);
|
||||
});
|
||||
|
||||
it('returns build id when confirmation is confirmed', async () => {
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||
jest.mocked(selectAsync).mockResolvedValueOnce(selectedBuildId);
|
||||
jest.mocked(confirmAsync).mockResolvedValueOnce(true);
|
||||
expect(selectBuildToDeleteAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
||||
await expect(selectBuildToDeleteAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
||||
selectedBuildId
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,23 +1,43 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
import { AndroidBuildProfile } from '@expo/eas-json/build/build/types';
|
||||
import assert from 'assert';
|
||||
import fs from 'fs-extra';
|
||||
import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { instance, mock } from 'ts-mockito';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
import { getAppBuildGradleAsync, resolveConfigValue } from '../../../project/android/gradleUtils';
|
||||
import { resolveVcsClient } from '../../../vcs';
|
||||
import { Client } from '../../../vcs/vcs';
|
||||
import {
|
||||
BumpStrategy,
|
||||
bumpVersionAsync,
|
||||
bumpVersionInAppJsonAsync,
|
||||
maybeResolveVersionsAsync,
|
||||
resolveRemoteVersionCodeAsync,
|
||||
} from '../version';
|
||||
|
||||
const fsReal = jest.requireActual('fs').promises as typeof fs;
|
||||
jest.mock('fs');
|
||||
jest.mock('../../../commandUtils/context/contextUtils/createGraphqlClient');
|
||||
jest.mock('../../../vcs/vcs');
|
||||
jest.mock('../../../graphql/queries/AppVersionQuery');
|
||||
jest.mock('../../../graphql/mutations/AppVersionMutation');
|
||||
jest.mock('../../../ora', () => ({
|
||||
ora: jest.fn().mockImplementation(() => ({
|
||||
start: jest.fn().mockImplementation(() => ({
|
||||
succeed: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
fail: jest.fn(),
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
|
||||
const vcsClient = resolveVcsClient();
|
||||
|
||||
@@ -216,12 +236,160 @@ describe(maybeResolveVersionsAsync, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe(resolveRemoteVersionCodeAsync, () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('uses current remote versionCode when remote version set, autoIncrement=false', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue({
|
||||
buildVersion: '11',
|
||||
storeVersion: '1.2.3',
|
||||
});
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject();
|
||||
|
||||
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationId: 'fakeApplicationId',
|
||||
buildProfile: {} as AndroidBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('11');
|
||||
expect(createAppVersionAsyncSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('initializes versionCode from local files when remote version not set, autoIncrement=false', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ versionCode: 22, versionName: '2.3.4' });
|
||||
|
||||
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationId: 'fakeApplicationId',
|
||||
buildProfile: {} as AndroidBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('22');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('2.3.4');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('22');
|
||||
});
|
||||
|
||||
it('initializes versionCode starting with 1 when remote version not set and not set in local files, autoIncrement=false', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ versionCode: null, versionName: null });
|
||||
|
||||
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationId: 'fakeApplicationId',
|
||||
buildProfile: {} as AndroidBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('1');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('1');
|
||||
});
|
||||
|
||||
it('increments current remote versionCode when remote version set, autoIncrement=true', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue({
|
||||
buildVersion: '11',
|
||||
storeVersion: '1.2.3',
|
||||
});
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject();
|
||||
|
||||
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationId: 'fakeApplicationId',
|
||||
buildProfile: { autoIncrement: true } as AndroidBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('12');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('3.0.0');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('12');
|
||||
});
|
||||
|
||||
it('increments versionCode from local files when remote version not set, autoIncrement=true', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ versionCode: 22, versionName: '2.3.4' });
|
||||
|
||||
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationId: 'fakeApplicationId',
|
||||
buildProfile: { autoIncrement: true } as AndroidBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('23');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('2.3.4');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('23');
|
||||
});
|
||||
|
||||
it('initializes versionCode starting with 1 when remote version not set and not set in local files, autoIncrement=true', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ versionCode: null, versionName: null });
|
||||
|
||||
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationId: 'fakeApplicationId',
|
||||
buildProfile: { autoIncrement: true } as AndroidBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('1');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('1');
|
||||
});
|
||||
});
|
||||
|
||||
function initBareWorkflowProject({
|
||||
versionCode = 123,
|
||||
versionName = '3.0.0',
|
||||
}: {
|
||||
versionCode?: number;
|
||||
versionName?: string;
|
||||
versionCode?: number | null;
|
||||
versionName?: string | null;
|
||||
} = {}): ExpoConfig {
|
||||
const fakeExp: ExpoConfig = {
|
||||
name: 'myproject',
|
||||
@@ -239,8 +407,8 @@ function initBareWorkflowProject({
|
||||
'./android/app/build.gradle': `android {
|
||||
defaultConfig {
|
||||
applicationId "com.expo.testapp"
|
||||
versionCode ${versionCode}
|
||||
versionName "${versionName}"
|
||||
${versionCode ? `versionCode "${versionCode}"` : ''}
|
||||
${versionName ? `versionName "${versionName}"` : ''}
|
||||
}
|
||||
}`,
|
||||
'./android/app/src/main/AndroidManifest.xml': 'fake',
|
||||
|
||||
@@ -107,7 +107,7 @@ export async function prepareAndroidBuildAsync(
|
||||
? false
|
||||
: ctx.buildProfile.autoIncrement,
|
||||
vcsClient: ctx.vcsClient,
|
||||
env: ctx.buildProfile.env,
|
||||
env: ctx.env,
|
||||
});
|
||||
},
|
||||
prepareJobAsync: async (
|
||||
@@ -151,6 +151,7 @@ async function ensureAndroidCredentialsAsync(
|
||||
ctx.android.gradleContext
|
||||
);
|
||||
const provider = new AndroidCredentialsProvider(ctx.credentialsCtx, {
|
||||
name: ctx.buildProfile.keystoreName,
|
||||
app: {
|
||||
account: nullthrows(
|
||||
ctx.user.accounts.find(a => a.name === ctx.accountName),
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
transformProjectArchive,
|
||||
transformWorkflow,
|
||||
} from '../graphql';
|
||||
import { buildProfileEnvironmentToEnvironment } from '../utils/environment';
|
||||
|
||||
export function transformJob(job: Android.Job): AndroidJobInput {
|
||||
return {
|
||||
@@ -29,6 +30,7 @@ export function transformJob(job: Android.Job): AndroidJobInput {
|
||||
experimental: job.experimental,
|
||||
mode: transformBuildMode(job.mode),
|
||||
customBuildConfig: job.customBuildConfig,
|
||||
environment: buildProfileEnvironmentToEnvironment(job.environment),
|
||||
loggerLevel: job.loggerLevel
|
||||
? loggerLevelToGraphQLWorkerLoggerLevel[job.loggerLevel]
|
||||
: undefined,
|
||||
|
||||
@@ -56,6 +56,15 @@ export async function prepareJobAsync(
|
||||
? getCustomBuildConfigPathForJob(buildProfile.config)
|
||||
: undefined;
|
||||
|
||||
let buildMode;
|
||||
if (ctx.repack) {
|
||||
buildMode = BuildMode.REPACK;
|
||||
} else if (buildProfile.config) {
|
||||
buildMode = BuildMode.CUSTOM;
|
||||
} else {
|
||||
buildMode = BuildMode.BUILD;
|
||||
}
|
||||
|
||||
const job: Android.Job = {
|
||||
type: ctx.workflow,
|
||||
platform: Platform.ANDROID,
|
||||
@@ -83,6 +92,7 @@ export async function prepareJobAsync(
|
||||
gradleCommand: buildProfile.gradleCommand,
|
||||
applicationArchivePath: buildProfile.applicationArchivePath ?? buildProfile.artifactPath,
|
||||
buildArtifactPaths: buildProfile.buildArtifactPaths,
|
||||
environment: ctx.buildProfile.environment,
|
||||
buildType,
|
||||
username,
|
||||
...(ctx.android.versionCodeOverride && {
|
||||
@@ -93,14 +103,23 @@ export async function prepareJobAsync(
|
||||
experimental: {
|
||||
prebuildCommand: buildProfile.prebuildCommand,
|
||||
},
|
||||
mode: buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
|
||||
mode: buildMode,
|
||||
triggeredBy: BuildTrigger.EAS_CLI,
|
||||
...(maybeCustomBuildConfigPath && {
|
||||
customBuildConfig: {
|
||||
path: maybeCustomBuildConfigPath,
|
||||
},
|
||||
}),
|
||||
...(ctx.repack && {
|
||||
customBuildConfig: {
|
||||
path: '__eas/repack.yml',
|
||||
},
|
||||
}),
|
||||
loggerLevel: ctx.loggerLevel,
|
||||
// Technically, these are unused, but let's include them here for type consistency.
|
||||
// See: https://github.com/expo/eas-build/pull/454
|
||||
appId: ctx.projectId,
|
||||
initiatingUserId: ctx.user.id,
|
||||
};
|
||||
|
||||
return sanitizeBuildJob(job) as Android.Job;
|
||||
|
||||
@@ -44,7 +44,7 @@ export async function bumpVersionAsync({
|
||||
|
||||
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
||||
const isMultiFlavor =
|
||||
buildGradle.android?.productFlavors || buildGradle.android?.flavorDimensions;
|
||||
buildGradle.android?.productFlavors ?? buildGradle.android?.flavorDimensions;
|
||||
if (isMultiFlavor) {
|
||||
throw new Error(
|
||||
'Automatic version bumping is not supported for multi-flavor Android projects.'
|
||||
@@ -99,7 +99,7 @@ export async function maybeResolveVersionsAsync(
|
||||
exp: ExpoConfig,
|
||||
buildProfile: BuildProfile<Platform.ANDROID>,
|
||||
vcsClient: Client
|
||||
): Promise<{ appVersion?: string; appBuildVersion?: string }> {
|
||||
): Promise<{ appVersion?: string; appBuildVersion?: string; isVersionInitialized?: boolean }> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
||||
@@ -113,6 +113,11 @@ export async function maybeResolveVersionsAsync(
|
||||
resolveConfigValue(buildGradle, 'versionName', parsedGradleCommand?.flavor) ?? '1.0.0',
|
||||
appBuildVersion:
|
||||
resolveConfigValue(buildGradle, 'versionCode', parsedGradleCommand?.flavor) ?? '1',
|
||||
isVersionInitialized: !resolveConfigValue(
|
||||
buildGradle,
|
||||
'versionCode',
|
||||
parsedGradleCommand?.flavor
|
||||
),
|
||||
};
|
||||
} catch {
|
||||
return {};
|
||||
@@ -204,14 +209,18 @@ export async function resolveRemoteVersionCodeAsync(
|
||||
|
||||
const localVersions = await maybeResolveVersionsAsync(projectDir, exp, buildProfile, vcsClient);
|
||||
let currentBuildVersion: string;
|
||||
let shouldInitializeVersionCode = false;
|
||||
if (remoteVersions?.buildVersion) {
|
||||
currentBuildVersion = remoteVersions.buildVersion;
|
||||
} else {
|
||||
if (localVersions.appBuildVersion) {
|
||||
Log.warn(
|
||||
'No remote versions are configured for this project, versionCode will be initialized based on the value from the local project.'
|
||||
Log.log(
|
||||
chalk.green(
|
||||
'No remote versions are configured for this project, versionCode will be initialized based on the value from the local project.'
|
||||
)
|
||||
);
|
||||
currentBuildVersion = localVersions.appBuildVersion;
|
||||
shouldInitializeVersionCode = localVersions.isVersionInitialized ?? false;
|
||||
} else {
|
||||
Log.error(
|
||||
`Remote versions are not configured and EAS CLI was not able to read the current version from your project. Use "eas build:version:set" to initialize remote versions.`
|
||||
@@ -221,7 +230,10 @@ export async function resolveRemoteVersionCodeAsync(
|
||||
}
|
||||
if (!buildProfile.autoIncrement && remoteVersions?.buildVersion) {
|
||||
return currentBuildVersion;
|
||||
} else if (!buildProfile.autoIncrement && !remoteVersions?.buildVersion) {
|
||||
} else if (
|
||||
(!buildProfile.autoIncrement && !remoteVersions?.buildVersion) ||
|
||||
shouldInitializeVersionCode
|
||||
) {
|
||||
const spinner = ora(
|
||||
`Initializing versionCode with ${chalk.bold(currentBuildVersion)}.`
|
||||
).start();
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
ArchiveSource,
|
||||
ArchiveSourceType,
|
||||
BuildJob,
|
||||
FingerprintSource,
|
||||
Metadata,
|
||||
Platform,
|
||||
} from '@expo/eas-build-job';
|
||||
@@ -34,8 +35,10 @@ import { collectMetadataAsync } from './metadata';
|
||||
import { printDeprecationWarnings } from './utils/printBuildInfo';
|
||||
import {
|
||||
LocalFile,
|
||||
assertProjectTarballSizeDoesNotExceedLimit,
|
||||
makeProjectMetadataFileAsync,
|
||||
makeProjectTarballAsync,
|
||||
maybeWarnAboutProjectTarballSize,
|
||||
reviewAndCommitChangesAsync,
|
||||
} from './utils/repository';
|
||||
import { BuildEvent } from '../analytics/AnalyticsManager';
|
||||
@@ -56,12 +59,16 @@ import { BuildQuery } from '../graphql/queries/BuildQuery';
|
||||
import Log, { learnMore, link } from '../log';
|
||||
import { Ora, ora } from '../ora';
|
||||
import {
|
||||
RequestedPlatform,
|
||||
appPlatformDisplayNames,
|
||||
appPlatformEmojis,
|
||||
requestedPlatformDisplayNames,
|
||||
} from '../platform';
|
||||
import { maybeUploadFingerprintAsync } from '../project/maybeUploadFingerprintAsync';
|
||||
import { resolveRuntimeVersionAsync } from '../project/resolveRuntimeVersionAsync';
|
||||
import { uploadFileAtPathToGCSAsync } from '../uploads';
|
||||
import { formatBytes } from '../utils/files';
|
||||
import { createFingerprintAsync } from '../utils/fingerprintCli';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
import { createProgressTracker } from '../utils/progress';
|
||||
import { sleepAsync } from '../utils/promise';
|
||||
@@ -124,7 +131,9 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
|
||||
await withAnalyticsAsync(
|
||||
ctx.analytics,
|
||||
async () => await builder.syncProjectConfigurationAsync(ctx),
|
||||
async () => {
|
||||
await builder.syncProjectConfigurationAsync(ctx);
|
||||
},
|
||||
{
|
||||
attemptEvent: BuildEvent.CONFIGURE_PROJECT_ATTEMPT,
|
||||
successEvent: BuildEvent.CONFIGURE_PROJECT_SUCCESS,
|
||||
@@ -135,9 +144,15 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
|
||||
if (await ctx.vcsClient.isCommitRequiredAsync()) {
|
||||
Log.addNewLineIfNone();
|
||||
const platformToRequestedPlatform: Record<Platform, RequestedPlatform> = {
|
||||
[Platform.ANDROID]: RequestedPlatform.Android,
|
||||
[Platform.IOS]: RequestedPlatform.Ios,
|
||||
};
|
||||
await reviewAndCommitChangesAsync(
|
||||
ctx.vcsClient,
|
||||
`[EAS Build] Run EAS Build for ${requestedPlatformDisplayNames[ctx.platform as Platform]}`,
|
||||
`[EAS Build] Run EAS Build for ${
|
||||
requestedPlatformDisplayNames[platformToRequestedPlatform[ctx.platform]]
|
||||
}`,
|
||||
{ nonInteractive: ctx.nonInteractive }
|
||||
);
|
||||
}
|
||||
@@ -162,7 +177,9 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
}
|
||||
assert(projectArchive);
|
||||
|
||||
const metadata = await collectMetadataAsync(ctx);
|
||||
const runtimeAndFingerprintMetadata =
|
||||
await computeAndMaybeUploadRuntimeAndFingerprintMetadataAsync(ctx);
|
||||
const metadata = await collectMetadataAsync(ctx, runtimeAndFingerprintMetadata);
|
||||
const buildParams = resolveBuildParamsInput(ctx, metadata);
|
||||
const job = await builder.prepareJobAsync(ctx, {
|
||||
projectArchive,
|
||||
@@ -171,7 +188,7 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
|
||||
return async () => {
|
||||
if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN) {
|
||||
await runLocalBuildAsync(job, metadata, ctx.localBuildOptions);
|
||||
await runLocalBuildAsync(job, metadata, ctx.localBuildOptions, ctx.env);
|
||||
return undefined;
|
||||
} else if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL) {
|
||||
await BuildMutation.updateBuildMetadataAsync(ctx.graphqlClient, {
|
||||
@@ -260,35 +277,27 @@ async function uploadProjectAsync<TPlatform extends Platform>(
|
||||
);
|
||||
const projectTarball = await makeProjectTarballAsync(ctx.vcsClient);
|
||||
|
||||
if (projectTarball.size > 1024 * 1024 * 100) {
|
||||
Log.warn(
|
||||
`Your project archive is ${formatBytes(
|
||||
projectTarball.size
|
||||
)}. You can reduce its size and the time it takes to upload by excluding files that are unnecessary for the build process in ${chalk.bold(
|
||||
'.easignore'
|
||||
)} file. ${learnMore('https://expo.fyi/eas-build-archive')}`
|
||||
);
|
||||
}
|
||||
|
||||
if (projectTarball.size > 2 * 1024 * 1024 * 1024) {
|
||||
throw new Error('Project archive is too big. Maximum allowed size is 2GB.');
|
||||
}
|
||||
maybeWarnAboutProjectTarballSize(projectTarball.size);
|
||||
assertProjectTarballSizeDoesNotExceedLimit(projectTarball.size);
|
||||
|
||||
projectTarballPath = projectTarball.path;
|
||||
const bucketKey = await uploadFileAtPathToGCSAsync(
|
||||
ctx.graphqlClient,
|
||||
UploadSessionType.EasBuildGcsProjectSources,
|
||||
projectTarball.path,
|
||||
createProgressTracker({
|
||||
total: projectTarball.size,
|
||||
message: ratio =>
|
||||
`Uploading to EAS Build (${formatBytes(projectTarball.size * ratio)} / ${formatBytes(
|
||||
projectTarball.size
|
||||
)})`,
|
||||
completedMessage: (duration: string) => `Uploaded to EAS ${chalk.dim(duration)}`,
|
||||
})
|
||||
);
|
||||
const { metadataLocation } = await uploadMetadataFileAsync<TPlatform>(projectTarball, ctx);
|
||||
const [bucketKey, { metadataLocation }] = await Promise.all([
|
||||
uploadFileAtPathToGCSAsync(
|
||||
ctx.graphqlClient,
|
||||
UploadSessionType.EasBuildGcsProjectSources,
|
||||
projectTarball.path,
|
||||
createProgressTracker({
|
||||
total: projectTarball.size,
|
||||
message: ratio =>
|
||||
`Uploading to EAS Build (${formatBytes(
|
||||
projectTarball.size * ratio
|
||||
)} / ${formatBytes(projectTarball.size)})`,
|
||||
completedMessage: (duration: string) => `Uploaded to EAS ${chalk.dim(duration)}`,
|
||||
})
|
||||
),
|
||||
uploadMetadataFileAsync<TPlatform>(projectTarball, ctx),
|
||||
]);
|
||||
|
||||
if (metadataLocation) {
|
||||
return { bucketKey, metadataLocation };
|
||||
}
|
||||
@@ -327,15 +336,7 @@ async function uploadMetadataFileAsync<TPlatform extends Platform>(
|
||||
const metadataLocation = await uploadFileAtPathToGCSAsync(
|
||||
ctx.graphqlClient,
|
||||
UploadSessionType.EasBuildGcsProjectMetadata,
|
||||
projectMetadataFile.path,
|
||||
createProgressTracker({
|
||||
total: projectMetadataFile.size,
|
||||
message: ratio =>
|
||||
`Uploading metadata to EAS Build (${formatBytes(
|
||||
projectMetadataFile!.size * ratio
|
||||
)} / ${formatBytes(projectMetadataFile!.size)})`,
|
||||
completedMessage: (duration: string) => `Uploaded to EAS ${chalk.dim(duration)}`,
|
||||
})
|
||||
projectMetadataFile.path
|
||||
);
|
||||
return { metadataLocation };
|
||||
} catch (err: any) {
|
||||
@@ -478,8 +479,8 @@ async function handleSingleBuildProgressAsync(
|
||||
spinner.succeed('Build finished');
|
||||
return { refetch: false };
|
||||
case BuildStatus.New:
|
||||
spinner.text = `Build is waiting to enter the queue. Check your concurrency limit at ${link(
|
||||
formatAccountSubscriptionsUrl(accountName)
|
||||
spinner.text = `Build concurrency limit reached for your account. Build will enter queue once a concurrency becomes available. Add additional concurrencies at ${link(
|
||||
formatAccountBillingUrl(accountName)
|
||||
)}.`;
|
||||
break;
|
||||
case BuildStatus.InQueue: {
|
||||
@@ -501,7 +502,7 @@ async function handleSingleBuildProgressAsync(
|
||||
Log.log('Start builds sooner in the priority queue.');
|
||||
Log.log(
|
||||
`Sign up for EAS Production or Enterprise at ${link(
|
||||
formatAccountSubscriptionsUrl(accountName)
|
||||
formatAccountBillingUrl(accountName)
|
||||
)}`
|
||||
);
|
||||
}
|
||||
@@ -648,9 +649,105 @@ function formatEstimatedWaitTime(estimatedWaitTimeLeftSeconds: number): string {
|
||||
}
|
||||
}
|
||||
|
||||
function formatAccountSubscriptionsUrl(accountName: string): string {
|
||||
return new URL(
|
||||
`/accounts/${accountName}/settings/subscriptions`,
|
||||
getExpoWebsiteBaseUrl()
|
||||
).toString();
|
||||
function formatAccountBillingUrl(accountName: string): string {
|
||||
return new URL(`/accounts/${accountName}/settings/billing`, getExpoWebsiteBaseUrl()).toString();
|
||||
}
|
||||
|
||||
async function computeAndMaybeUploadRuntimeAndFingerprintMetadataAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<{
|
||||
runtimeVersion?: string | undefined;
|
||||
fingerprintHash?: string | undefined;
|
||||
fingerprintSource?: FingerprintSource | undefined;
|
||||
}> {
|
||||
const runtimeAndFingerprintMetadata =
|
||||
await computeAndMaybeUploadFingerprintFromExpoUpdatesAsync(ctx);
|
||||
if (!runtimeAndFingerprintMetadata?.fingerprintHash) {
|
||||
const fingerprint = await computeAndMaybeUploadFingerprintWithoutExpoUpdatesAsync(ctx);
|
||||
return {
|
||||
...runtimeAndFingerprintMetadata,
|
||||
...fingerprint,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...runtimeAndFingerprintMetadata,
|
||||
fingerprintHash: runtimeAndFingerprintMetadata.runtimeVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function computeAndMaybeUploadFingerprintFromExpoUpdatesAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<{
|
||||
runtimeVersion?: string;
|
||||
fingerprintSource?: FingerprintSource;
|
||||
fingerprintHash?: string;
|
||||
}> {
|
||||
const resolvedRuntimeVersion = await resolveRuntimeVersionAsync({
|
||||
exp: ctx.exp,
|
||||
platform: ctx.platform,
|
||||
workflow: ctx.workflow,
|
||||
projectDir: ctx.projectDir,
|
||||
env: ctx.env,
|
||||
cwd: ctx.projectDir,
|
||||
});
|
||||
|
||||
if (!resolvedRuntimeVersion) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* It's ok for fingerprintSources or runtimeVersion to be empty
|
||||
* fingerprintSources only exist if the project is using runtimeVersion.policy: fingerprint
|
||||
*/
|
||||
if (
|
||||
!resolvedRuntimeVersion.expoUpdatesRuntimeFingerprint ||
|
||||
!resolvedRuntimeVersion.runtimeVersion
|
||||
) {
|
||||
return {
|
||||
runtimeVersion: resolvedRuntimeVersion.runtimeVersion ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const uploadedFingerprint = await maybeUploadFingerprintAsync({
|
||||
hash: resolvedRuntimeVersion.runtimeVersion,
|
||||
fingerprint: resolvedRuntimeVersion.expoUpdatesRuntimeFingerprint,
|
||||
graphqlClient: ctx.graphqlClient,
|
||||
localBuildMode: ctx.localBuildOptions.localBuildMode,
|
||||
});
|
||||
return {
|
||||
runtimeVersion: uploadedFingerprint.hash,
|
||||
fingerprintSource: uploadedFingerprint.fingerprintSource,
|
||||
fingerprintHash: resolvedRuntimeVersion.expoUpdatesRuntimeFingerprintHash ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async function computeAndMaybeUploadFingerprintWithoutExpoUpdatesAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>,
|
||||
{ debug }: { debug?: boolean } = {}
|
||||
): Promise<{
|
||||
fingerprintHash?: string;
|
||||
fingerprintSource?: FingerprintSource;
|
||||
}> {
|
||||
const fingerprint = await createFingerprintAsync(ctx.projectDir, {
|
||||
workflow: ctx.workflow,
|
||||
platforms: [ctx.platform],
|
||||
env: ctx.env,
|
||||
});
|
||||
if (!fingerprint) {
|
||||
return {};
|
||||
}
|
||||
const uploadedFingerprint = await maybeUploadFingerprintAsync({
|
||||
hash: fingerprint.hash,
|
||||
fingerprint: {
|
||||
fingerprintSources: fingerprint.sources,
|
||||
isDebugFingerprintSource: debug ?? false,
|
||||
},
|
||||
graphqlClient: ctx.graphqlClient,
|
||||
localBuildMode: ctx.localBuildOptions.localBuildMode,
|
||||
});
|
||||
return {
|
||||
fingerprintHash: uploadedFingerprint.hash,
|
||||
fingerprintSource: uploadedFingerprint.fingerprintSource,
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { EasJson, EasJsonAccessor } from '@expo/eas-json';
|
||||
import { AppVersionSource, EasJson, EasJsonAccessor } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { maybeBailOnRepoStatusAsync, reviewAndCommitChangesAsync } from './utils/repository';
|
||||
import Log, { learnMore } from '../log';
|
||||
import { ora } from '../ora';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { Client } from '../vcs/vcs';
|
||||
|
||||
@@ -52,9 +53,82 @@ async function configureAsync({
|
||||
}
|
||||
}
|
||||
|
||||
export async function doesBuildProfileExistAsync({
|
||||
projectDir,
|
||||
profileName,
|
||||
}: {
|
||||
projectDir: string;
|
||||
profileName: string;
|
||||
}): Promise<boolean> {
|
||||
try {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
const easJson = await easJsonAccessor.readRawJsonAsync();
|
||||
if (!easJson.build?.[profileName]) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
Log.error(`We were unable to read ${chalk.bold('eas.json')} contents. Error: ${error}.`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createBuildProfileAsync({
|
||||
projectDir,
|
||||
profileName,
|
||||
profileContents,
|
||||
vcsClient,
|
||||
nonInteractive,
|
||||
}: {
|
||||
projectDir: string;
|
||||
profileName: string;
|
||||
profileContents: Record<string, any>;
|
||||
vcsClient: Client;
|
||||
nonInteractive: boolean;
|
||||
}): Promise<void> {
|
||||
const spinner = ora(`Adding "${profileName}" build profile to ${chalk.bold('eas.json')}`).start();
|
||||
try {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
await easJsonAccessor.readRawJsonAsync();
|
||||
|
||||
easJsonAccessor.patch(easJsonRawObject => {
|
||||
return {
|
||||
...easJsonRawObject,
|
||||
build: {
|
||||
...easJsonRawObject.build,
|
||||
[profileName]: profileContents,
|
||||
},
|
||||
};
|
||||
});
|
||||
await easJsonAccessor.writeAsync();
|
||||
spinner.succeed(
|
||||
`Successfully added "${profileName}" build profile to ${chalk.bold('eas.json')}.`
|
||||
);
|
||||
|
||||
if (await vcsClient.isCommitRequiredAsync()) {
|
||||
Log.newLine();
|
||||
await reviewAndCommitChangesAsync(
|
||||
vcsClient,
|
||||
`Add "${profileName}" build profile to eas.json`,
|
||||
{
|
||||
nonInteractive,
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail(
|
||||
`We were not able to configure "${profileName}" build profile inside of ${chalk.bold(
|
||||
'eas.json'
|
||||
)}. Error: ${error}.`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const EAS_JSON_DEFAULT: EasJson = {
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
appVersionSource: AppVersionSource.REMOTE,
|
||||
},
|
||||
build: {
|
||||
development: {
|
||||
@@ -64,7 +138,9 @@ const EAS_JSON_DEFAULT: EasJson = {
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
},
|
||||
production: {},
|
||||
production: {
|
||||
autoIncrement: true,
|
||||
},
|
||||
},
|
||||
submit: {
|
||||
production: {},
|
||||
|
||||
@@ -62,4 +62,6 @@ export interface BuildContext<T extends Platform> {
|
||||
requiredPackageManager: NodePackageManager['name'] | null;
|
||||
vcsClient: Client;
|
||||
loggerLevel?: LoggerLevel;
|
||||
repack: boolean;
|
||||
env: Record<string, string>;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import { createAndroidContextAsync } from './android/build';
|
||||
import { BuildContext, CommonContext } from './context';
|
||||
import { createIosContextAsync } from './ios/build';
|
||||
import { LocalBuildOptions } from './local';
|
||||
import { LocalBuildMode, LocalBuildOptions } from './local';
|
||||
import { resolveBuildResourceClassAsync } from './utils/resourceClass';
|
||||
import { Analytics, AnalyticsEventProperties, BuildEvent } from '../analytics/AnalyticsManager';
|
||||
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
|
||||
@@ -42,6 +42,8 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
customBuildConfigMetadata,
|
||||
buildLoggerLevel,
|
||||
freezeCredentials,
|
||||
repack,
|
||||
env,
|
||||
}: {
|
||||
buildProfileName: string;
|
||||
buildProfile: BuildProfile<T>;
|
||||
@@ -62,8 +64,12 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
customBuildConfigMetadata?: CustomBuildConfigMetadata;
|
||||
buildLoggerLevel?: LoggerLevel;
|
||||
freezeCredentials: boolean;
|
||||
repack: boolean;
|
||||
env: Record<string, string>;
|
||||
}): Promise<BuildContext<T>> {
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env });
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
|
||||
env,
|
||||
});
|
||||
const projectName = exp.slug;
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
const workflow = await resolveWorkflowAsync(projectDir, platform, vcsClient);
|
||||
@@ -85,7 +91,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
user: actor,
|
||||
graphqlClient,
|
||||
analytics,
|
||||
env: buildProfile.env,
|
||||
env,
|
||||
easJsonCliConfig,
|
||||
vcsClient,
|
||||
freezeCredentials,
|
||||
@@ -106,6 +112,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
...devClientProperties,
|
||||
no_wait: noWait,
|
||||
run_from_ci: runFromCI,
|
||||
local: localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN,
|
||||
};
|
||||
analytics.logEvent(BuildEvent.BUILD_COMMAND, analyticsEventProperties);
|
||||
|
||||
@@ -143,6 +150,8 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
developmentClient,
|
||||
requiredPackageManager,
|
||||
loggerLevel: buildLoggerLevel,
|
||||
repack,
|
||||
env,
|
||||
};
|
||||
if (platform === Platform.ANDROID) {
|
||||
const common = commonContext as CommonContext<Platform.ANDROID>;
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import { Env } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
|
||||
import { isEnvironment } from './utils/environment';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EnvironmentVariableEnvironment } from '../graphql/generated';
|
||||
import { EnvironmentVariablesQuery } from '../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log, { learnMore } from '../log';
|
||||
|
||||
export async function evaluateConfigWithEnvVarsAsync<Config extends { projectId: string }, Opts>({
|
||||
buildProfile,
|
||||
buildProfileName,
|
||||
graphqlClient,
|
||||
getProjectConfig,
|
||||
opts,
|
||||
}: {
|
||||
buildProfile: BuildProfile;
|
||||
buildProfileName: string;
|
||||
graphqlClient: ExpoGraphqlClient | null;
|
||||
opts: Opts;
|
||||
getProjectConfig(opts: Opts): Promise<Config>;
|
||||
}): Promise<Config & { env: Env }> {
|
||||
if (!graphqlClient) {
|
||||
Log.warn('An Expo user account is required to fetch environment variables.');
|
||||
const config = await getProjectConfig(opts);
|
||||
return { env: buildProfile.env ?? {}, ...config };
|
||||
}
|
||||
const { projectId } = await getProjectConfig({ env: buildProfile.env, ...opts });
|
||||
const env = await resolveEnvVarsAsync({
|
||||
buildProfile,
|
||||
buildProfileName,
|
||||
graphqlClient,
|
||||
projectId,
|
||||
});
|
||||
const config = await getProjectConfig({ ...opts, env });
|
||||
|
||||
return { env, ...config };
|
||||
}
|
||||
|
||||
async function resolveEnvVarsAsync({
|
||||
buildProfile,
|
||||
buildProfileName,
|
||||
graphqlClient,
|
||||
projectId,
|
||||
}: {
|
||||
buildProfile: BuildProfile;
|
||||
buildProfileName: string;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
projectId: string;
|
||||
}): Promise<Env> {
|
||||
const environment =
|
||||
buildProfile.environment?.toUpperCase() ??
|
||||
resolveSuggestedEnvironmentForBuildProfileConfiguration(buildProfile);
|
||||
|
||||
if (!isEnvironment(environment)) {
|
||||
Log.log(
|
||||
`Loaded "env" configuration for the "${buildProfileName}" profile: ${
|
||||
buildProfile.env && Object.keys(buildProfile.env).length > 0
|
||||
? Object.keys(buildProfile.env).join(', ')
|
||||
: 'no environment variables specified'
|
||||
}. ${learnMore('https://docs.expo.dev/build-reference/variables/')}`
|
||||
);
|
||||
return { ...buildProfile.env };
|
||||
}
|
||||
|
||||
try {
|
||||
const environmentVariables = await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
appId: projectId,
|
||||
environment,
|
||||
}
|
||||
);
|
||||
const serverEnvVars = Object.fromEntries(
|
||||
environmentVariables
|
||||
.filter(({ name, value }) => name && value)
|
||||
.map(({ name, value }) => [name, value])
|
||||
) as Record<string, string>;
|
||||
|
||||
if (Object.keys(serverEnvVars).length > 0) {
|
||||
Log.log(
|
||||
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS: ${Object.keys(
|
||||
serverEnvVars
|
||||
).join(', ')}.`
|
||||
);
|
||||
} else {
|
||||
Log.log(
|
||||
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS.`
|
||||
);
|
||||
}
|
||||
|
||||
if (buildProfile.env && Object.keys(buildProfile.env).length > 0) {
|
||||
Log.log(
|
||||
`Environment variables loaded from the "${buildProfileName}" build profile "env" configuration: ${
|
||||
buildProfile.env && Object.keys(buildProfile.env).join(', ')
|
||||
}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
buildProfile.env &&
|
||||
Object.keys(buildProfile.env).length > 0 &&
|
||||
Object.keys(serverEnvVars).length > 0
|
||||
) {
|
||||
const overlappingKeys = Object.keys(serverEnvVars).filter(
|
||||
key => buildProfile.env && Object.keys(buildProfile.env).includes(key)
|
||||
);
|
||||
if (overlappingKeys.length > 0) {
|
||||
Log.warn(
|
||||
`The following environment variables are defined in both the "${buildProfileName}" build profile "env" configuration and the "${environment.toLowerCase()}" environment on EAS: ${overlappingKeys.join(
|
||||
', '
|
||||
)}. The values from the build profile configuration will be used.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Log.newLine();
|
||||
|
||||
return { ...serverEnvVars, ...buildProfile.env };
|
||||
} catch (e) {
|
||||
Log.error(`Failed to pull env variables for environment ${environment} from EAS servers`);
|
||||
Log.error(e);
|
||||
Log.error(
|
||||
'This can possibly be a bug in EAS/EAS CLI. Report it here: https://github.com/expo/eas-cli/issues'
|
||||
);
|
||||
return { ...buildProfile.env };
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSuggestedEnvironmentForBuildProfileConfiguration(
|
||||
buildProfile: BuildProfile
|
||||
): EnvironmentVariableEnvironment {
|
||||
const environment =
|
||||
buildProfile.distribution === 'store'
|
||||
? EnvironmentVariableEnvironment.Production
|
||||
: buildProfile.developmentClient
|
||||
? EnvironmentVariableEnvironment.Development
|
||||
: EnvironmentVariableEnvironment.Preview;
|
||||
|
||||
Log.log(
|
||||
`Resolved "${environment.toLowerCase()}" environment for the build. ${learnMore(
|
||||
'https://docs.expo.dev/eas/environment-variables/#setting-the-environment-for-your-builds'
|
||||
)}`
|
||||
);
|
||||
|
||||
return environment;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ArchiveSourceType,
|
||||
BuildMode,
|
||||
BuildTrigger,
|
||||
FingerprintSourceType,
|
||||
Metadata,
|
||||
Workflow,
|
||||
} from '@expo/eas-build-job';
|
||||
@@ -15,8 +16,10 @@ import {
|
||||
BuildMetadataInput,
|
||||
BuildWorkflow,
|
||||
DistributionType,
|
||||
FingerprintSourceInput,
|
||||
BuildMode as GraphQLBuildMode,
|
||||
BuildTrigger as GraphQLBuildTrigger,
|
||||
FingerprintSourceType as GraphQLFingeprintSourceType,
|
||||
ProjectArchiveSourceInput,
|
||||
ProjectArchiveSourceType,
|
||||
WorkerLoggerLevel,
|
||||
@@ -42,6 +45,8 @@ export function transformProjectArchive(archiveSource: ArchiveSource): ProjectAr
|
||||
export function transformMetadata(metadata: Metadata): BuildMetadataInput {
|
||||
return {
|
||||
...metadata,
|
||||
fingerprintSource:
|
||||
metadata.fingerprintSource && transformFingerprintSource(metadata.fingerprintSource),
|
||||
credentialsSource:
|
||||
metadata.credentialsSource && transformCredentialsSource(metadata.credentialsSource),
|
||||
distribution: metadata.distribution && transformDistribution(metadata.distribution),
|
||||
@@ -52,6 +57,20 @@ export function transformMetadata(metadata: Metadata): BuildMetadataInput {
|
||||
};
|
||||
}
|
||||
|
||||
export function transformFingerprintSource(
|
||||
fingerprintSource: NonNullable<Metadata['fingerprintSource']>
|
||||
): FingerprintSourceInput | null {
|
||||
if (fingerprintSource.type !== FingerprintSourceType.GCS) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: GraphQLFingeprintSourceType.Gcs,
|
||||
bucketKey: fingerprintSource.bucketKey,
|
||||
isDebugFingerprint: fingerprintSource.isDebugFingerprint,
|
||||
};
|
||||
}
|
||||
|
||||
function transformCredentialsSource(
|
||||
credentialsSource: Metadata['credentialsSource']
|
||||
): BuildCredentialsSource {
|
||||
@@ -94,6 +113,7 @@ const buildModeToGraphQLBuildMode: Record<BuildMode, GraphQLBuildMode> = {
|
||||
[BuildMode.BUILD]: GraphQLBuildMode.Build,
|
||||
[BuildMode.CUSTOM]: GraphQLBuildMode.Custom,
|
||||
[BuildMode.RESIGN]: GraphQLBuildMode.Resign,
|
||||
[BuildMode.REPACK]: GraphQLBuildMode.Repack,
|
||||
};
|
||||
|
||||
export function transformBuildMode(buildMode: BuildMode): GraphQLBuildMode {
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { IOSConfig } from '@expo/config-plugins';
|
||||
import { IosBuildProfile } from '@expo/eas-json/build/build/types';
|
||||
import fs from 'fs-extra';
|
||||
import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
import { instance, mock } from 'ts-mockito';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { Target } from '../../../credentials/ios/types';
|
||||
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
import { readPlistAsync } from '../../../utils/plist';
|
||||
import { resolveVcsClient } from '../../../vcs';
|
||||
import { Client } from '../../../vcs/vcs';
|
||||
import {
|
||||
BumpStrategy,
|
||||
bumpVersionAsync,
|
||||
@@ -15,9 +22,23 @@ import {
|
||||
getInfoPlistPath,
|
||||
readBuildNumberAsync,
|
||||
readShortVersionAsync,
|
||||
resolveRemoteBuildNumberAsync,
|
||||
} from '../version';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('../../../commandUtils/context/contextUtils/createGraphqlClient');
|
||||
jest.mock('../../../vcs/vcs');
|
||||
jest.mock('../../../graphql/queries/AppVersionQuery');
|
||||
jest.mock('../../../graphql/mutations/AppVersionMutation');
|
||||
jest.mock('../../../ora', () => ({
|
||||
ora: jest.fn().mockImplementation(() => ({
|
||||
start: jest.fn().mockImplementation(() => ({
|
||||
succeed: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
fail: jest.fn(),
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
|
||||
const vcsClient = resolveVcsClient();
|
||||
|
||||
@@ -300,25 +321,6 @@ describe(readShortVersionAsync, () => {
|
||||
);
|
||||
expect(buildNumber).toBe('1.0.0');
|
||||
});
|
||||
|
||||
it('fails when build number is invalid', async () => {
|
||||
const exp = initBareWorkflowProject({
|
||||
appVersion: '$(CURRENT_PROJECT_VERSION)',
|
||||
});
|
||||
|
||||
await expect(
|
||||
readShortVersionAsync(
|
||||
'/app',
|
||||
exp,
|
||||
{
|
||||
CURRENT_PROJECT_VERSION: '0.0.7.1.028',
|
||||
},
|
||||
vcsClient
|
||||
)
|
||||
).rejects.toThrowError(
|
||||
'CFBundleShortVersionString (version field in app.json/app.config.js) must be a period-separated list of three non-negative integers. Current value: 0.0.7.1.028'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('managed project', () => {
|
||||
@@ -378,6 +380,154 @@ describe(getInfoPlistPath, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe(resolveRemoteBuildNumberAsync, () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('uses current remote buildNumber when remote version set, autoIncrement=false', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue({
|
||||
buildVersion: '11',
|
||||
storeVersion: '1.2.3',
|
||||
});
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject();
|
||||
|
||||
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationTarget: {} as Target,
|
||||
buildProfile: {} as IosBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('11');
|
||||
expect(createAppVersionAsyncSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('initializes buildNumber from local files when remote version not set, autoIncrement=false', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ buildNumber: '22', appVersion: '2.3.4' });
|
||||
|
||||
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationTarget: {} as Target,
|
||||
buildProfile: {} as IosBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('22');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('2.3.4');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('22');
|
||||
});
|
||||
|
||||
it('initializes buildNumber starting with 1 when remote version not set and local version is 1, autoIncrement=false', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ buildNumber: '1', appVersion: '1.0.0' });
|
||||
|
||||
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationTarget: {} as Target,
|
||||
buildProfile: {} as IosBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('1');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('1');
|
||||
});
|
||||
|
||||
it('increments current remote buildNumber when remote version set, autoIncrement=true', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue({
|
||||
buildVersion: '11',
|
||||
storeVersion: '1.2.3',
|
||||
});
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject();
|
||||
|
||||
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationTarget: {} as Target,
|
||||
buildProfile: { autoIncrement: true } as IosBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('12');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('12');
|
||||
});
|
||||
|
||||
it('increments buildNumber from local files when remote version not set, autoIncrement=true', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ buildNumber: '22', appVersion: '2.3.4' });
|
||||
|
||||
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationTarget: {} as Target,
|
||||
buildProfile: { autoIncrement: true } as IosBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('23');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('2.3.4');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('23');
|
||||
});
|
||||
|
||||
it('initializes buildNumber starting with 1 when remote version not set and local version is 1, autoIncrement=true', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ buildNumber: '1', appVersion: '1.0.0' });
|
||||
|
||||
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationTarget: {} as Target,
|
||||
buildProfile: { autoIncrement: true } as IosBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('1');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('1');
|
||||
});
|
||||
});
|
||||
|
||||
function initBareWorkflowProject({
|
||||
appVersion = '1.0.0',
|
||||
buildNumber = '1',
|
||||
|
||||
@@ -10,6 +10,7 @@ import { IosCredentials } from '../../credentials/ios/types';
|
||||
import { BuildParamsInput } from '../../graphql/generated';
|
||||
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
|
||||
import { ensureBundleIdentifierIsDefinedForManagedProjectAsync } from '../../project/ios/bundleIdentifier';
|
||||
import { ensureNonExemptEncryptionIsDefinedForManagedProjectAsync } from '../../project/ios/exemptEncryption';
|
||||
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
|
||||
import { findApplicationTarget, resolveTargetsAsync } from '../../project/ios/target';
|
||||
import { BuildRequestSender, JobData, prepareBuildRequestForPlatformAsync } from '../build';
|
||||
@@ -24,10 +25,11 @@ import {
|
||||
export async function createIosContextAsync(
|
||||
ctx: CommonContext<Platform.IOS>
|
||||
): Promise<IosBuildContext> {
|
||||
const { buildProfile } = ctx;
|
||||
const { buildProfile, env } = ctx;
|
||||
|
||||
if (ctx.workflow === Workflow.MANAGED) {
|
||||
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx);
|
||||
await ensureNonExemptEncryptionIsDefinedForManagedProjectAsync(ctx);
|
||||
}
|
||||
|
||||
checkNodeEnvVariable(ctx);
|
||||
@@ -47,7 +49,7 @@ export async function createIosContextAsync(
|
||||
projectDir: ctx.projectDir,
|
||||
exp: ctx.exp,
|
||||
xcodeBuildContext,
|
||||
env: buildProfile.env,
|
||||
env,
|
||||
vcsClient: ctx.vcsClient,
|
||||
});
|
||||
const applicationTarget = findApplicationTarget(targets);
|
||||
@@ -77,7 +79,7 @@ export async function prepareIosBuildAsync(
|
||||
return await prepareBuildRequestForPlatformAsync({
|
||||
ctx,
|
||||
ensureCredentialsAsync: async (ctx: BuildContext<Platform.IOS>) => {
|
||||
return ensureIosCredentialsAsync(ctx, ctx.ios.targets);
|
||||
return await ensureIosCredentialsAsync(ctx, ctx.ios.targets);
|
||||
},
|
||||
syncProjectConfigurationAsync: async () => {
|
||||
await syncProjectConfigurationAsync({
|
||||
@@ -89,7 +91,7 @@ export async function prepareIosBuildAsync(
|
||||
? false
|
||||
: ctx.buildProfile.autoIncrement,
|
||||
vcsClient: ctx.vcsClient,
|
||||
env: ctx.buildProfile.env,
|
||||
env: ctx.env,
|
||||
});
|
||||
},
|
||||
prepareJobAsync: async (
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
transformProjectArchive,
|
||||
transformWorkflow,
|
||||
} from '../graphql';
|
||||
import { buildProfileEnvironmentToEnvironment } from '../utils/environment';
|
||||
|
||||
export function transformJob(job: Ios.Job): IosJobInput {
|
||||
return {
|
||||
@@ -31,6 +32,7 @@ export function transformJob(job: Ios.Job): IosJobInput {
|
||||
experimental: job.experimental,
|
||||
mode: transformBuildMode(job.mode),
|
||||
customBuildConfig: job.customBuildConfig,
|
||||
environment: buildProfileEnvironmentToEnvironment(job.environment),
|
||||
loggerLevel: job.loggerLevel
|
||||
? loggerLevelToGraphQLWorkerLoggerLevel[job.loggerLevel]
|
||||
: undefined,
|
||||
|
||||
@@ -48,6 +48,15 @@ export async function prepareJobAsync(
|
||||
? getCustomBuildConfigPathForJob(buildProfile.config)
|
||||
: undefined;
|
||||
|
||||
let buildMode;
|
||||
if (ctx.repack) {
|
||||
buildMode = BuildMode.REPACK;
|
||||
} else if (buildProfile.config) {
|
||||
buildMode = BuildMode.CUSTOM;
|
||||
} else {
|
||||
buildMode = BuildMode.BUILD;
|
||||
}
|
||||
|
||||
const job: Ios.Job = {
|
||||
type: ctx.workflow,
|
||||
platform: Platform.IOS,
|
||||
@@ -79,6 +88,7 @@ export async function prepareJobAsync(
|
||||
buildConfiguration: buildProfile.buildConfiguration,
|
||||
applicationArchivePath: buildProfile.applicationArchivePath ?? buildProfile.artifactPath,
|
||||
buildArtifactPaths: buildProfile.buildArtifactPaths,
|
||||
environment: ctx.buildProfile.environment,
|
||||
username,
|
||||
...(ctx.ios.buildNumberOverride && {
|
||||
version: {
|
||||
@@ -88,14 +98,23 @@ export async function prepareJobAsync(
|
||||
experimental: {
|
||||
prebuildCommand: buildProfile.prebuildCommand,
|
||||
},
|
||||
mode: buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
|
||||
mode: buildMode,
|
||||
triggeredBy: BuildTrigger.EAS_CLI,
|
||||
...(maybeCustomBuildConfigPath && {
|
||||
customBuildConfig: {
|
||||
path: maybeCustomBuildConfigPath,
|
||||
},
|
||||
}),
|
||||
...(ctx.repack && {
|
||||
customBuildConfig: {
|
||||
path: '__eas/repack.yml',
|
||||
},
|
||||
}),
|
||||
loggerLevel: ctx.loggerLevel,
|
||||
// Technically, these are unused, but let's include them here for type consistency.
|
||||
// See: https://github.com/expo/eas-build/pull/454
|
||||
appId: ctx.projectId,
|
||||
initiatingUserId: ctx.user.id,
|
||||
};
|
||||
return sanitizeBuildJob(job) as Ios.Job;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Target } from '../../credentials/ios/types';
|
||||
import { AppPlatform } from '../../graphql/generated';
|
||||
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
|
||||
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
||||
import Log from '../../log';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { findApplicationTarget } from '../../project/ios/target';
|
||||
import { getNextBuildNumber, isValidBuildNumber } from '../../project/ios/versions';
|
||||
@@ -23,7 +23,7 @@ import { Client } from '../../vcs/vcs';
|
||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
|
||||
|
||||
const SHORT_VERSION_REGEX = /^\d+.\d+.\d+$/;
|
||||
const SHORT_VERSION_REGEX = /^\d+(\.\d+){0,2}$/;
|
||||
|
||||
export enum BumpStrategy {
|
||||
APP_VERSION,
|
||||
@@ -100,11 +100,27 @@ export async function bumpVersionInAppJsonAsync({
|
||||
}
|
||||
}
|
||||
|
||||
function validateShortVersion(shortVersion: string | undefined): void {
|
||||
function validateShortVersion({
|
||||
shortVersion,
|
||||
workflow,
|
||||
}: {
|
||||
shortVersion: string | undefined;
|
||||
workflow: Workflow;
|
||||
}): void {
|
||||
if (shortVersion && !SHORT_VERSION_REGEX.test(shortVersion)) {
|
||||
throw new Error(
|
||||
`CFBundleShortVersionString (version field in app.json/app.config.js) must be a period-separated list of three non-negative integers. Current value: ${shortVersion}`
|
||||
);
|
||||
if (workflow === Workflow.MANAGED) {
|
||||
Log.warn(
|
||||
`The required format for "version" field from app.json/app.config.ts for App Store iOS builds is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: ${shortVersion}. Edit the "version" field in your app.json/app.config.ts to match the format required by Apple. ${learnMore(
|
||||
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
Log.warn(
|
||||
`The required format for "CFBundleShortVersionString" in Info.plist for App Store iOS builds is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: ${shortVersion}. Edit the "CFBundleShortVersionString" in your Info.plist to match the format required by Apple. ${learnMore(
|
||||
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,10 +138,10 @@ export async function readShortVersionAsync(
|
||||
infoPlist.CFBundleShortVersionString &&
|
||||
evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings);
|
||||
|
||||
validateShortVersion(shortVersion);
|
||||
validateShortVersion({ shortVersion, workflow });
|
||||
return shortVersion;
|
||||
} else {
|
||||
validateShortVersion(exp.version);
|
||||
validateShortVersion({ shortVersion: exp.version, workflow });
|
||||
return exp.version;
|
||||
}
|
||||
}
|
||||
@@ -314,14 +330,18 @@ export async function resolveRemoteBuildNumberAsync(
|
||||
vcsClient
|
||||
);
|
||||
let currentBuildVersion: string;
|
||||
let shouldInitializeBuildNumber = false;
|
||||
if (remoteVersions?.buildVersion) {
|
||||
currentBuildVersion = remoteVersions.buildVersion;
|
||||
} else {
|
||||
if (localBuildNumber) {
|
||||
Log.warn(
|
||||
'No remote versions are configured for this project, buildNumber will be initialized based on the value from the local project.'
|
||||
Log.log(
|
||||
chalk.green(
|
||||
'No remote versions are configured for this project, buildNumber will be initialized based on the value from the local project.'
|
||||
)
|
||||
);
|
||||
currentBuildVersion = localBuildNumber;
|
||||
shouldInitializeBuildNumber = localBuildNumber === '1';
|
||||
} else {
|
||||
Log.error(
|
||||
`Remote versions are not configured and EAS CLI was not able to read the current version from your project. Use "eas build:version:set" to initialize remote versions.`
|
||||
@@ -331,7 +351,10 @@ export async function resolveRemoteBuildNumberAsync(
|
||||
}
|
||||
if (!buildProfile.autoIncrement && remoteVersions?.buildVersion) {
|
||||
return currentBuildVersion;
|
||||
} else if (!buildProfile.autoIncrement && !remoteVersions?.buildVersion) {
|
||||
} else if (
|
||||
(!buildProfile.autoIncrement && !remoteVersions?.buildVersion) ||
|
||||
shouldInitializeBuildNumber
|
||||
) {
|
||||
const spinner = ora(
|
||||
`Initializing buildNumber with ${chalk.bold(currentBuildVersion)}.`
|
||||
).start();
|
||||
|
||||
@@ -3,10 +3,11 @@ import spawnAsync from '@expo/spawn-async';
|
||||
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 = '1.0.107';
|
||||
const PLUGIN_PACKAGE_VERSION = '1.0.168';
|
||||
|
||||
export enum LocalBuildMode {
|
||||
/**
|
||||
@@ -39,7 +40,8 @@ export interface LocalBuildOptions {
|
||||
export async function runLocalBuildAsync(
|
||||
job: Job,
|
||||
metadata: Metadata,
|
||||
options: LocalBuildOptions
|
||||
options: LocalBuildOptions,
|
||||
env: Record<string, string>
|
||||
): Promise<void> {
|
||||
const { command, args } = await getCommandAndArgsAsync(job, metadata);
|
||||
let spinner;
|
||||
@@ -54,18 +56,26 @@ export async function runLocalBuildAsync(
|
||||
};
|
||||
process.on('SIGINT', interruptHandler);
|
||||
try {
|
||||
const mergedEnv = {
|
||||
...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 } : {}),
|
||||
...(options.artifactPath ? { EAS_LOCAL_BUILD_ARTIFACT_PATH: options.artifactPath } : {}),
|
||||
};
|
||||
// log command execution to assist in debugging local builds
|
||||
Log.debug('Running local build, using local-build-plugin', {
|
||||
command,
|
||||
args,
|
||||
env: mergedEnv,
|
||||
});
|
||||
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 } : {}),
|
||||
...(options.artifactPath ? { EAS_LOCAL_BUILD_ARTIFACT_PATH: options.artifactPath } : {}),
|
||||
},
|
||||
env: mergedEnv,
|
||||
});
|
||||
childProcess = spawnPromise.child;
|
||||
await spawnPromise;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Metadata, Platform, sanitizeMetadata } from '@expo/eas-build-job';
|
||||
import { FingerprintSource, Metadata, Platform, sanitizeMetadata } from '@expo/eas-build-job';
|
||||
import { IosEnterpriseProvisioning } from '@expo/eas-json';
|
||||
import fs from 'fs-extra';
|
||||
import resolveFrom from 'resolve-from';
|
||||
@@ -10,13 +10,17 @@ import { LocalBuildMode } from './local';
|
||||
import { BuildDistributionType } from './types';
|
||||
import Log from '../log';
|
||||
import { getUsername, isExpoUpdatesInstalled } from '../project/projectUtils';
|
||||
import { resolveRuntimeVersionAsync } from '../project/resolveRuntimeVersionAsync';
|
||||
import { readChannelSafelyAsync as readAndroidChannelSafelyAsync } from '../update/android/UpdatesModule';
|
||||
import { readChannelSafelyAsync as readIosChannelSafelyAsync } from '../update/ios/UpdatesModule';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
|
||||
export async function collectMetadataAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
ctx: BuildContext<T>,
|
||||
runtimeAndFingerprintMetadata: {
|
||||
runtimeVersion?: string | undefined;
|
||||
fingerprintHash?: string | undefined;
|
||||
fingerprintSource?: FingerprintSource | undefined;
|
||||
}
|
||||
): Promise<Metadata> {
|
||||
const channelObject = await resolveChannelAsync(ctx);
|
||||
const distribution = ctx.buildProfile.distribution ?? BuildDistributionType.STORE;
|
||||
@@ -27,15 +31,9 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
workflow: ctx.workflow,
|
||||
credentialsSource: ctx.buildProfile.credentialsSource,
|
||||
sdkVersion: ctx.exp.sdkVersion,
|
||||
runtimeVersion:
|
||||
(await resolveRuntimeVersionAsync({
|
||||
exp: ctx.exp,
|
||||
platform: ctx.platform,
|
||||
workflow: ctx.workflow,
|
||||
projectDir: ctx.projectDir,
|
||||
env: ctx.buildProfile.env,
|
||||
cwd: ctx.projectDir,
|
||||
})) ?? undefined,
|
||||
runtimeVersion: runtimeAndFingerprintMetadata?.runtimeVersion,
|
||||
fingerprintHash: runtimeAndFingerprintMetadata?.fingerprintHash,
|
||||
fingerprintSource: runtimeAndFingerprintMetadata?.fingerprintSource,
|
||||
reactNativeVersion: await getReactNativeVersionAsync(ctx.projectDir),
|
||||
...channelObject,
|
||||
distribution,
|
||||
|
||||
@@ -50,8 +50,9 @@ export async function listAndRenderBuildsOnAppAsync(
|
||||
}),
|
||||
promptOptions: {
|
||||
title: 'Load more builds?',
|
||||
renderListItems: builds =>
|
||||
renderPageOfBuilds({ builds, projectDisplayName, paginatedQueryOptions }),
|
||||
renderListItems: builds => {
|
||||
renderPageOfBuilds({ builds, projectDisplayName, paginatedQueryOptions });
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Env, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import {
|
||||
AppVersionSource,
|
||||
BuildProfile,
|
||||
EasJson,
|
||||
EasJsonAccessor,
|
||||
EasJsonUtils,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
import { LoggerLevel } from '@expo/logger';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import { pathExists } from 'fs-extra';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { prepareAndroidBuildAsync } from './android/build';
|
||||
@@ -19,6 +19,7 @@ import { BuildRequestSender, MaybeBuildFragment, waitForBuildEndAsync } from './
|
||||
import { ensureProjectConfiguredAsync } from './configure';
|
||||
import { BuildContext } from './context';
|
||||
import { createBuildContextAsync } from './createContext';
|
||||
import { evaluateConfigWithEnvVarsAsync } from './evaluateConfigWithEnvVarsAsync';
|
||||
import { prepareIosBuildAsync } from './ios/build';
|
||||
import { LocalBuildMode, LocalBuildOptions } from './local';
|
||||
import { ensureExpoDevClientInstalledForDevClientBuildsAsync } from './utils/devClient';
|
||||
@@ -57,11 +58,12 @@ import {
|
||||
validateAppVersionRuntimePolicySupportAsync,
|
||||
} from '../project/projectUtils';
|
||||
import {
|
||||
ensureAppVersionSourceIsSetAsync,
|
||||
validateAppConfigForRemoteVersionSource,
|
||||
validateBuildProfileVersionSettings,
|
||||
validateBuildProfileVersionSettingsAsync,
|
||||
} from '../project/remoteVersionSource';
|
||||
import { confirmAsync } from '../prompts';
|
||||
import { runAsync } from '../run/run';
|
||||
import { getEasBuildRunCachedAppPath, runAsync } from '../run/run';
|
||||
import { isRunnableOnSimulatorOrEmulator } from '../run/utils';
|
||||
import { createSubmissionContextAsync } from '../submit/context';
|
||||
import {
|
||||
@@ -95,17 +97,30 @@ export interface BuildFlags {
|
||||
message?: string;
|
||||
buildLoggerLevel?: LoggerLevel;
|
||||
freezeCredentials: boolean;
|
||||
repack: boolean;
|
||||
}
|
||||
|
||||
export async function runBuildAndSubmitAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
analytics: Analytics,
|
||||
vcsClient: Client,
|
||||
projectDir: string,
|
||||
flags: BuildFlags,
|
||||
actor: Actor,
|
||||
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn
|
||||
): Promise<{
|
||||
export async function runBuildAndSubmitAsync({
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
projectDir,
|
||||
flags,
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
downloadSimBuildAutoConfirm,
|
||||
envOverride,
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
analytics: Analytics;
|
||||
vcsClient: Client;
|
||||
projectDir: string;
|
||||
flags: BuildFlags;
|
||||
actor: Actor;
|
||||
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
|
||||
downloadSimBuildAutoConfirm?: boolean;
|
||||
envOverride?: Env;
|
||||
}): Promise<{
|
||||
buildIds: string[];
|
||||
}> {
|
||||
await vcsClient.ensureRepoExistsAsync();
|
||||
@@ -128,39 +143,28 @@ export async function runBuildAndSubmitAsync(
|
||||
profileName: flags.profile ?? undefined,
|
||||
projectDir,
|
||||
});
|
||||
Log.log(
|
||||
`Loaded "env" configuration for the "${buildProfiles[0].profileName}" profile: ${
|
||||
buildProfiles[0].profile.env
|
||||
? Object.keys(buildProfiles[0].profile.env).join(', ')
|
||||
: 'no environment variables specified'
|
||||
}. ${learnMore('https://docs.expo.dev/build-reference/variables/')}`
|
||||
);
|
||||
|
||||
for (const buildProfile of buildProfiles) {
|
||||
if (buildProfile.profile.image && ['default', 'stable'].includes(buildProfile.profile.image)) {
|
||||
Log.warn(
|
||||
`The "image" field in the build profile "${buildProfile.profileName}" is set to "${buildProfile.profile.image}". This tag is deprecated and will be removed in the future. Use other images or tags listed here: https://docs.expo.dev/build-reference/infrastructure/`
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
} else if (
|
||||
buildProfile.profile.image &&
|
||||
[
|
||||
'macos-monterey-12.4-xcode-13.4',
|
||||
'macos-monterey-12.3-xcode-13.3',
|
||||
'macos-monterey-12.1-xcode-13.2',
|
||||
'macos-ventura-13.4-xcode-14.3.1',
|
||||
'macos-ventura-13.3-xcode-14.3',
|
||||
'macos-monterey-12.6-xcode-14.2',
|
||||
'macos-monterey-12.6-xcode-14.1',
|
||||
'macos-monterey-12.6-xcode-14.0',
|
||||
'ubuntu-20.04-jdk-11-ndk-r19c',
|
||||
'ubuntu-20.04-jdk-8-ndk-r19c',
|
||||
'ubuntu-20.04-jdk-11-ndk-r21e',
|
||||
'ubuntu-20.04-jdk-8-ndk-r21e',
|
||||
'ubuntu-22.04-jdk-8-ndk-r21e',
|
||||
'ubuntu-20.04-jdk-11-ndk-r23b',
|
||||
].includes(buildProfile.profile.image)
|
||||
) {
|
||||
Log.warn(
|
||||
`The "image" field in the build profile "${buildProfile.profileName}" is set to "${
|
||||
buildProfile.profile.image
|
||||
}". This image is using Xcode in version lower then 15. Apple's new requirement states that starting on April 29th, 2024, apps uploaded to App Store Connect must be built with Xcode 15 for iOS 17, iPadOS 17, tvOS 17, or watchOS 10. Start using Xcode 15 image today by setting "macos-sonoma-14.4-xcode-15.3" or "latest" as your image value. This image will be removed from EAS Build on May 29th, 2024. ${learnMore(
|
||||
'https://expo.dev/changelog/2024/03-29-eas-build-upcoming-ios-images-updates'
|
||||
}". This image is deprecated and will be removed on September 1st, 2024. ${learnMore(
|
||||
'https://expo.dev/changelog/2024/07-12-eas-build-upcoming-android-images-updates'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
@@ -176,7 +180,12 @@ export async function runBuildAndSubmitAsync(
|
||||
const customBuildConfigMetadataByPlatform: { [p in AppPlatform]?: CustomBuildConfigMetadata } =
|
||||
{};
|
||||
for (const buildProfile of buildProfiles) {
|
||||
validateBuildProfileVersionSettings(buildProfile, easJsonCliConfig);
|
||||
await validateBuildProfileVersionSettingsAsync(
|
||||
buildProfile,
|
||||
easJsonCliConfig,
|
||||
projectDir,
|
||||
flags
|
||||
);
|
||||
const maybeMetadata = await validateCustomBuildConfigAsync({
|
||||
projectDir,
|
||||
profile: buildProfile.profile,
|
||||
@@ -195,6 +204,17 @@ export async function runBuildAndSubmitAsync(
|
||||
|
||||
for (const buildProfile of buildProfiles) {
|
||||
const platform = toAppPlatform(buildProfile.platform);
|
||||
|
||||
const { env } = !envOverride
|
||||
? await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: buildProfile.profile,
|
||||
buildProfileName: buildProfile.profileName,
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: buildProfile.profile.env },
|
||||
})
|
||||
: { env: envOverride };
|
||||
|
||||
const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({
|
||||
projectDir,
|
||||
flags,
|
||||
@@ -207,6 +227,7 @@ export async function runBuildAndSubmitAsync(
|
||||
vcsClient,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
customBuildConfigMetadata: customBuildConfigMetadataByPlatform[platform],
|
||||
env,
|
||||
});
|
||||
if (maybeBuild) {
|
||||
startedBuilds.push({ build: maybeBuild, buildProfile });
|
||||
@@ -254,9 +275,9 @@ export async function runBuildAndSubmitAsync(
|
||||
buildCtx: nullthrows(buildCtxByPlatform[startedBuild.build.platform]),
|
||||
moreBuilds: startedBuilds.length > 1,
|
||||
projectDir,
|
||||
buildProfile: startedBuild.buildProfile.profile,
|
||||
submitProfile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
selectedSubmitProfileName: flags.submitProfile,
|
||||
});
|
||||
startedBuild.build = await BuildQuery.withSubmissionsByIdAsync(
|
||||
graphqlClient,
|
||||
@@ -302,7 +323,7 @@ export async function runBuildAndSubmitAsync(
|
||||
[BuildStatus.Errored, BuildStatus.Canceled, BuildStatus.PendingCancel].includes(build?.status)
|
||||
);
|
||||
|
||||
await maybeDownloadAndRunSimulatorBuildsAsync(builds, flags);
|
||||
await maybeDownloadAndRunSimulatorBuildsAsync(builds, flags, downloadSimBuildAutoConfirm);
|
||||
|
||||
if (haveAllBuildsFailedOrCanceled || !flags.autoSubmit) {
|
||||
if (flags.json) {
|
||||
@@ -342,6 +363,7 @@ async function prepareAndStartBuildAsync({
|
||||
vcsClient,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
customBuildConfigMetadata,
|
||||
env,
|
||||
}: {
|
||||
projectDir: string;
|
||||
flags: BuildFlags;
|
||||
@@ -354,6 +376,7 @@ async function prepareAndStartBuildAsync({
|
||||
vcsClient: Client;
|
||||
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
|
||||
customBuildConfigMetadata?: CustomBuildConfigMetadata;
|
||||
env: Env;
|
||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
||||
const buildCtx = await createBuildContextAsync({
|
||||
buildProfileName: buildProfile.profileName,
|
||||
@@ -373,8 +396,10 @@ async function prepareAndStartBuildAsync({
|
||||
vcsClient,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
customBuildConfigMetadata,
|
||||
buildLoggerLevel: flags.buildLoggerLevel,
|
||||
buildLoggerLevel: flags.buildLoggerLevel ?? (Log.isDebug ? LoggerLevel.DEBUG : undefined),
|
||||
freezeCredentials: flags.freezeCredentials,
|
||||
repack: flags.repack,
|
||||
env,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
@@ -413,6 +438,25 @@ async function prepareAndStartBuildAsync({
|
||||
}
|
||||
|
||||
await validateAppVersionRuntimePolicySupportAsync(buildCtx.projectDir, buildCtx.exp);
|
||||
if (
|
||||
easJsonCliConfig?.appVersionSource === undefined &&
|
||||
buildProfile.profile.autoIncrement !== 'version'
|
||||
) {
|
||||
if (buildProfile.profile.autoIncrement !== true) {
|
||||
Log.warn(
|
||||
`The field "cli.appVersionSource" is not set, but it will be required in the future. ${learnMore(
|
||||
'https://docs.expo.dev/build-reference/app-versions/'
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
easJsonCliConfig = await ensureAppVersionSourceIsSetAsync(
|
||||
easJsonAccessor,
|
||||
easJsonCliConfig,
|
||||
flags.nonInteractive
|
||||
);
|
||||
}
|
||||
}
|
||||
if (easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE) {
|
||||
validateAppConfigForRemoteVersionSource(buildCtx.exp, buildProfile.platform);
|
||||
}
|
||||
@@ -449,16 +493,16 @@ async function prepareAndStartSubmissionAsync({
|
||||
buildCtx,
|
||||
moreBuilds,
|
||||
projectDir,
|
||||
buildProfile,
|
||||
submitProfile,
|
||||
selectedSubmitProfileName,
|
||||
nonInteractive,
|
||||
}: {
|
||||
build: BuildFragment;
|
||||
buildCtx: BuildContext<Platform>;
|
||||
moreBuilds: boolean;
|
||||
projectDir: string;
|
||||
buildProfile: BuildProfile;
|
||||
submitProfile: SubmitProfile;
|
||||
selectedSubmitProfileName?: string;
|
||||
nonInteractive: boolean;
|
||||
}): Promise<SubmissionFragment> {
|
||||
const platform = toPlatform(build.platform);
|
||||
@@ -468,7 +512,7 @@ async function prepareAndStartSubmissionAsync({
|
||||
profile: submitProfile,
|
||||
archiveFlags: { id: build.id },
|
||||
nonInteractive,
|
||||
env: buildProfile.env,
|
||||
env: buildCtx.env,
|
||||
credentialsCtx: buildCtx.credentialsCtx,
|
||||
applicationIdentifier: buildCtx.android?.applicationId ?? buildCtx.ios?.bundleIdentifier,
|
||||
actor: buildCtx.user,
|
||||
@@ -478,6 +522,7 @@ async function prepareAndStartSubmissionAsync({
|
||||
exp: buildCtx.exp,
|
||||
vcsClient: buildCtx.vcsClient,
|
||||
isVerboseFastlaneEnabled: false,
|
||||
specifiedProfile: selectedSubmitProfileName,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
@@ -501,18 +546,29 @@ function exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds: (BuildFragment | nul
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadAndRunAsync(build: BuildFragment): Promise<void> {
|
||||
export async function downloadAndRunAsync(build: BuildFragment): Promise<void> {
|
||||
assert(build.artifacts?.applicationArchiveUrl);
|
||||
const cachedAppPath = getEasBuildRunCachedAppPath(build.project.id, build.id, build.platform);
|
||||
|
||||
if (await pathExists(cachedAppPath)) {
|
||||
Log.newLine();
|
||||
Log.log(`Using cached app...`);
|
||||
await runAsync(cachedAppPath, build.platform);
|
||||
return;
|
||||
}
|
||||
|
||||
const buildPath = await downloadAndMaybeExtractAppAsync(
|
||||
build.artifacts.applicationArchiveUrl,
|
||||
build.platform
|
||||
build.platform,
|
||||
cachedAppPath
|
||||
);
|
||||
await runAsync(buildPath, build.platform);
|
||||
}
|
||||
|
||||
async function maybeDownloadAndRunSimulatorBuildsAsync(
|
||||
builds: MaybeBuildFragment[],
|
||||
flags: BuildFlags
|
||||
flags: BuildFlags,
|
||||
autoConfirm?: boolean
|
||||
): Promise<void> {
|
||||
const simBuilds = builds.filter(truthy).filter(isRunnableOnSimulatorOrEmulator);
|
||||
|
||||
@@ -520,11 +576,15 @@ async function maybeDownloadAndRunSimulatorBuildsAsync(
|
||||
for (const simBuild of simBuilds) {
|
||||
if (simBuild.platform === AppPlatform.Android || process.platform === 'darwin') {
|
||||
Log.newLine();
|
||||
const confirm = await confirmAsync({
|
||||
message: `Install and run the ${
|
||||
simBuild.platform === AppPlatform.Android ? 'Android' : 'iOS'
|
||||
} build on ${simBuild.platform === AppPlatform.Android ? 'an emulator' : 'a simulator'}?`,
|
||||
});
|
||||
const confirm =
|
||||
autoConfirm ??
|
||||
(await confirmAsync({
|
||||
message: `Install and run the ${
|
||||
simBuild.platform === AppPlatform.Android ? 'Android' : 'iOS'
|
||||
} build on ${
|
||||
simBuild.platform === AppPlatform.Android ? 'an emulator' : 'a simulator'
|
||||
}?`,
|
||||
}));
|
||||
if (confirm) {
|
||||
await downloadAndRunAsync(simBuild);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { ResourceClass } from '@expo/eas-json';
|
||||
import { LoggerLevel } from '@expo/logger';
|
||||
|
||||
import { LocalBuildOptions } from './local';
|
||||
import { RequestedPlatform } from '../platform';
|
||||
|
||||
export enum BuildStatus {
|
||||
NEW = 'new',
|
||||
IN_QUEUE = 'in-queue',
|
||||
@@ -14,3 +20,20 @@ export enum BuildDistributionType {
|
||||
/** @deprecated Use simulator flag instead */
|
||||
SIMULATOR = 'simulator',
|
||||
}
|
||||
|
||||
export interface BuildFlags {
|
||||
requestedPlatform: RequestedPlatform;
|
||||
profile?: string;
|
||||
nonInteractive: boolean;
|
||||
wait: boolean;
|
||||
clearCache: boolean;
|
||||
json: boolean;
|
||||
autoSubmit: boolean;
|
||||
submitProfile?: string;
|
||||
localBuildOptions: LocalBuildOptions;
|
||||
resourceClass?: ResourceClass;
|
||||
message?: string;
|
||||
buildLoggerLevel?: LoggerLevel;
|
||||
freezeCredentials: boolean;
|
||||
repack: boolean;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
|
||||
import { EnvironmentVariableEnvironment } from '../../graphql/generated';
|
||||
|
||||
type Environment = NonNullable<BuildProfile['environment']>;
|
||||
|
||||
const BuildProfileEnvironmentToEnvironment: Record<Environment, EnvironmentVariableEnvironment> = {
|
||||
production: EnvironmentVariableEnvironment.Production,
|
||||
preview: EnvironmentVariableEnvironment.Preview,
|
||||
development: EnvironmentVariableEnvironment.Development,
|
||||
};
|
||||
|
||||
export function isEnvironment(env: string): env is EnvironmentVariableEnvironment {
|
||||
return Object.values(EnvironmentVariableEnvironment).includes(
|
||||
env as EnvironmentVariableEnvironment
|
||||
);
|
||||
}
|
||||
|
||||
export function buildProfileEnvironmentToEnvironment(
|
||||
environment: BuildProfile['environment']
|
||||
): EnvironmentVariableEnvironment | null {
|
||||
if (!environment) {
|
||||
return null;
|
||||
}
|
||||
return BuildProfileEnvironmentToEnvironment[environment];
|
||||
}
|
||||
@@ -39,6 +39,10 @@ export function formatGraphQLBuild(build: BuildFragment): string {
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Profile',
|
||||
value: build.buildProfile,
|
||||
},
|
||||
{
|
||||
label: 'Message',
|
||||
value: build.message,
|
||||
|
||||
@@ -62,7 +62,9 @@ export function printBuildResults(builds: (BuildFragment | null)[]): void {
|
||||
assert(build, 'Build should be defined');
|
||||
printBuildResult(build);
|
||||
} else {
|
||||
(builds.filter(i => i) as BuildFragment[]).forEach(build => printBuildResult(build));
|
||||
(builds.filter(i => i) as BuildFragment[]).forEach(build => {
|
||||
printBuildResult(build);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,9 +99,9 @@ function printBuildResult(build: BuildFragment): void {
|
||||
// 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`)
|
||||
);
|
||||
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]
|
||||
|
||||
@@ -4,7 +4,7 @@ import path from 'path';
|
||||
import tar from 'tar';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import Log from '../../log';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { confirmAsync, promptAsync } from '../../prompts';
|
||||
import { formatBytes } from '../../utils/files';
|
||||
@@ -100,44 +100,22 @@ export type LocalFile = {
|
||||
};
|
||||
|
||||
export async function makeProjectMetadataFileAsync(archivePath: string): Promise<LocalFile> {
|
||||
const spinner = ora('Creating project metadata file');
|
||||
const timerLabel = 'makeProjectMetadataFileAsync';
|
||||
const timer = setTimeout(
|
||||
() => {
|
||||
spinner.start();
|
||||
},
|
||||
Log.isDebug ? 1 : 1000
|
||||
);
|
||||
startTimer(timerLabel);
|
||||
|
||||
await fs.mkdirp(getTmpDirectory());
|
||||
const metadataLocation = path.join(getTmpDirectory(), `${uuidv4()}-eas-build-metadata.json`);
|
||||
const archiveContent: string[] = [];
|
||||
|
||||
try {
|
||||
await tar.list({
|
||||
file: archivePath,
|
||||
onentry: (entry: tar.ReadEntry) => {
|
||||
if (entry.type === 'File' && !entry.path.includes('.git/')) {
|
||||
archiveContent.push(entry.path);
|
||||
}
|
||||
},
|
||||
});
|
||||
await tar.list({
|
||||
file: archivePath,
|
||||
onentry: (entry: tar.ReadEntry) => {
|
||||
if (entry.type === 'File' && !entry.path.includes('.git/')) {
|
||||
archiveContent.push(entry.path);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await fs.writeJSON(metadataLocation, {
|
||||
archiveContent,
|
||||
});
|
||||
} catch (e) {
|
||||
clearTimeout(timer);
|
||||
if (spinner.isSpinning) {
|
||||
spinner.fail();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
clearTimeout(timer);
|
||||
|
||||
const duration = endTimer(timerLabel);
|
||||
const prettyTime = formatMilliseconds(duration);
|
||||
spinner.succeed(`Created project metadata file ${chalk.dim(prettyTime)}`);
|
||||
await fs.writeJSON(metadataLocation, {
|
||||
archiveContent,
|
||||
});
|
||||
|
||||
return { path: metadataLocation, size: await fs.stat(metadataLocation).then(stat => stat.size) };
|
||||
}
|
||||
@@ -191,6 +169,34 @@ export async function makeProjectTarballAsync(vcsClient: Client): Promise<LocalF
|
||||
return { size, path: tarPath };
|
||||
}
|
||||
|
||||
export function maybeWarnAboutProjectTarballSize(size: number): void {
|
||||
if (size <= 150 /* MiB */ * 1024 /* KiB */ * 1024 /* B */) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.warn(
|
||||
`Your project archive is ${formatBytes(
|
||||
size
|
||||
)}. You can reduce its size and the time it takes to upload by excluding files that are unnecessary for the build process in ${chalk.bold(
|
||||
'.easignore'
|
||||
)} file. ${learnMore('https://expo.fyi/eas-build-archive')}`
|
||||
);
|
||||
}
|
||||
|
||||
const MAX_ALLOWED_PROJECT_TARBALL_SIZE =
|
||||
2 /* GiB */ * 1024 /* MiB */ * 1024 /* KiB */ * 1024; /* B */
|
||||
export function assertProjectTarballSizeDoesNotExceedLimit(size: number): void {
|
||||
if (size <= MAX_ALLOWED_PROJECT_TARBALL_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Project archive is too big. Maximum allowed size is ${formatBytes(
|
||||
MAX_ALLOWED_PROJECT_TARBALL_SIZE
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
|
||||
enum ShouldCommitChanges {
|
||||
Yes,
|
||||
ShowDiffFirst,
|
||||
|
||||
@@ -50,3 +50,25 @@ export function getUpdateGroupUrl(
|
||||
getExpoWebsiteBaseUrl()
|
||||
).toString();
|
||||
}
|
||||
|
||||
export function getWorkflowRunUrl(
|
||||
accountName: string,
|
||||
projectName: string,
|
||||
workflowRunId: string
|
||||
): string {
|
||||
return new URL(
|
||||
`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(
|
||||
projectName
|
||||
)}/workflows/${workflowRunId}`,
|
||||
getExpoWebsiteBaseUrl()
|
||||
).toString();
|
||||
}
|
||||
|
||||
export function getProjectGitHubSettingsUrl(accountName: string, projectName: string): string {
|
||||
return new URL(
|
||||
`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(
|
||||
projectName
|
||||
)}/github`,
|
||||
getExpoWebsiteBaseUrl()
|
||||
).toString();
|
||||
}
|
||||
@@ -9,9 +9,9 @@ import Log, { learnMore } from '../log';
|
||||
import { isPNGAsync } from '../utils/image';
|
||||
|
||||
export function checkNodeEnvVariable(ctx: CommonContext<Platform>): void {
|
||||
if (ctx.buildProfile.env?.NODE_ENV === 'production') {
|
||||
if (ctx.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.'
|
||||
'You set NODE_ENV=production in the build profile or environment variables. Remember that it will be available during the entire build process. In particular, it will make yarn/npm install only production packages.'
|
||||
);
|
||||
Log.newLine();
|
||||
}
|
||||
@@ -27,24 +27,28 @@ export async function checkGoogleServicesFileAsync<T extends Platform>(
|
||||
if (!googleServicesFilePath) {
|
||||
return;
|
||||
}
|
||||
const googleServicesEnvVar =
|
||||
ctx.platform === Platform.ANDROID
|
||||
? ctx.env.GOOGLE_SERVICES_JSON
|
||||
: ctx.env.GOOGLE_SERVICES_INFO_PLIST;
|
||||
const rootDir = path.normalize(await ctx.vcsClient.getRootPathAsync());
|
||||
const absGoogleServicesFilePath = path.resolve(ctx.projectDir, googleServicesFilePath);
|
||||
if (
|
||||
(await fs.pathExists(absGoogleServicesFilePath)) &&
|
||||
(!isInsideDirectory(absGoogleServicesFilePath, rootDir) ||
|
||||
(await ctx.vcsClient.isFileIgnoredAsync(path.relative(rootDir, absGoogleServicesFilePath))))
|
||||
(await ctx.vcsClient.isFileIgnoredAsync(
|
||||
path.relative(rootDir, absGoogleServicesFilePath)
|
||||
))) &&
|
||||
!googleServicesEnvVar
|
||||
) {
|
||||
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'
|
||||
`Use EAS file environment variables with secret or sensitive visibility to pass all values that you don't want to include in your version control to build process. ${learnMore(
|
||||
'https://docs.expo.dev/eas/environment-variables/#file-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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ import {
|
||||
|
||||
describe(assertVersion, () => {
|
||||
it('throws if the branch mapping is not the correct version', () => {
|
||||
expect(() => assertVersion(testChannelBasicInfo, 5)).toThrowError(BranchMappingValidationError);
|
||||
expect(() => {
|
||||
assertVersion(testChannelBasicInfo, 5);
|
||||
}).toThrowError(BranchMappingValidationError);
|
||||
});
|
||||
it('asserts the correct version', () => {
|
||||
assertVersion(testChannelBasicInfo, 0);
|
||||
|
||||
@@ -128,6 +128,7 @@ export const testChannelObject: UpdateChannelObject = {
|
||||
branchMapping:
|
||||
'{"data":[{"branchId":"754bf17f-efc0-46ab-8a59-a03f20e53e9b","branchMappingLogic":{"operand":0.15,"clientKey":"rolloutToken","branchMappingOperator":"hash_lt"}},{"branchId":"6941a8dd-5c0a-48bc-8876-f49c88ed419f","branchMappingLogic":"true"}],"version":0}',
|
||||
updateBranches: [testUpdateBranch1, testUpdateBranch2],
|
||||
isPaused: false,
|
||||
__typename: 'UpdateChannel',
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
*/
|
||||
export class SelectChannel implements EASUpdateAction<UpdateChannelBasicInfoFragment | null> {
|
||||
constructor(
|
||||
private options: {
|
||||
private readonly options: {
|
||||
filterPredicate?: (channelInfo: UpdateChannelBasicInfoFragment) => boolean;
|
||||
printedType?: string;
|
||||
pageSize?: number;
|
||||
@@ -91,8 +91,8 @@ export class SelectChannel implements EASUpdateAction<UpdateChannelBasicInfoFrag
|
||||
queryParams: QueryParams
|
||||
): Promise<Connection<UpdateChannelBasicInfoFragment>> =>
|
||||
filterPredicate
|
||||
? this.filterQueryAsync(ctx, queryParams, filterPredicate)
|
||||
: this.queryAsync(ctx, queryParams);
|
||||
? await this.filterQueryAsync(ctx, queryParams, filterPredicate)
|
||||
: await this.queryAsync(ctx, queryParams);
|
||||
|
||||
const getTitleAsync = async (channelInfo: UpdateChannelBasicInfoFragment): Promise<string> =>
|
||||
channelInfo.name;
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { BranchQuery, UpdateBranchOnChannelObject } from '../graphql/queries/BranchQuery';
|
||||
import { ChannelQuery, UpdateChannelObject } from '../graphql/queries/ChannelQuery';
|
||||
import { UpdateChannelBasicInfoFragmentNode } from '../graphql/types/UpdateChannelBasicInfo';
|
||||
import Log from '../log';
|
||||
import Log, { learnMore } from '../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../project/projectUtils';
|
||||
import formatFields from '../utils/formatFields';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
@@ -89,7 +89,9 @@ export async function listAndRenderChannelsOnAppAsync(
|
||||
queryChannelsOnAppAsync(graphqlClient, { limit, offset, appId: projectId }),
|
||||
promptOptions: {
|
||||
title: 'Load more channels?',
|
||||
renderListItems: channels => renderPageOfChannels(channels, paginatedQueryOptions),
|
||||
renderListItems: channels => {
|
||||
renderPageOfChannels(channels, paginatedQueryOptions);
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -108,7 +110,11 @@ export async function listAndRenderBranchesAndUpdatesOnChannelAsync(
|
||||
}
|
||||
): Promise<void> {
|
||||
const channel = await ChannelQuery.viewUpdateChannelAsync(graphqlClient, { appId, channelName });
|
||||
renderChannelHeaderContent({ channelName: channel.name, channelId: channel.id });
|
||||
renderChannelHeaderContent({
|
||||
channelName: channel.name,
|
||||
channelId: channel.id,
|
||||
isPaused: channel.isPaused,
|
||||
});
|
||||
|
||||
if (paginatedQueryOptions.nonInteractive) {
|
||||
const branches = await queryBranchesAndUpdateGroupsOnChannelAsync(graphqlClient, {
|
||||
@@ -131,8 +137,9 @@ export async function listAndRenderBranchesAndUpdatesOnChannelAsync(
|
||||
}),
|
||||
promptOptions: {
|
||||
title: 'Load more channels?',
|
||||
renderListItems: branches =>
|
||||
renderPageOfBranchesOnChannel(channel, branches, paginatedQueryOptions),
|
||||
renderListItems: branches => {
|
||||
renderPageOfBranchesOnChannel(channel, branches, paginatedQueryOptions);
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -164,7 +171,11 @@ function renderPageOfChannels(
|
||||
printJsonOnlyOutput({ currentPage });
|
||||
} else {
|
||||
for (const channel of currentPage) {
|
||||
renderChannelHeaderContent({ channelName: channel.name, channelId: channel.id });
|
||||
renderChannelHeaderContent({
|
||||
channelName: channel.name,
|
||||
channelId: channel.id,
|
||||
isPaused: channel.isPaused,
|
||||
});
|
||||
Log.addNewLineIfNone();
|
||||
logChannelDetails(channel);
|
||||
|
||||
@@ -193,9 +204,11 @@ function renderPageOfBranchesOnChannel(
|
||||
function renderChannelHeaderContent({
|
||||
channelName,
|
||||
channelId,
|
||||
isPaused,
|
||||
}: {
|
||||
channelName: string;
|
||||
channelId: string;
|
||||
isPaused: boolean;
|
||||
}): void {
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.bold('Channel:'));
|
||||
@@ -203,6 +216,7 @@ function renderChannelHeaderContent({
|
||||
formatFields([
|
||||
{ label: 'Name', value: channelName },
|
||||
{ label: 'ID', value: channelId },
|
||||
{ label: 'Status', value: isPaused ? 'Paused' : 'Active' },
|
||||
])
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
@@ -275,7 +289,7 @@ export async function doesChannelExistAsync(
|
||||
{ appId, channelName }: { appId: string; channelName: string }
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||
await ChannelQuery.viewUpdateChannelBasicInfoAsync(graphqlClient, {
|
||||
appId,
|
||||
channelName,
|
||||
});
|
||||
@@ -289,7 +303,6 @@ export async function doesChannelExistAsync(
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a channel and links it to a branch with the same name.
|
||||
*
|
||||
* @param appId the app ID, also known as the project ID
|
||||
@@ -305,7 +318,7 @@ export async function createAndLinkChannelAsync(
|
||||
}: { appId: string; channelName: string; shouldPrintJson?: boolean }
|
||||
): Promise<ChannelBasicInfo> {
|
||||
let branchId: string;
|
||||
let branchMessage: string;
|
||||
let hasCreatedBranch = false;
|
||||
|
||||
try {
|
||||
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||
@@ -313,7 +326,6 @@ export async function createAndLinkChannelAsync(
|
||||
name: channelName,
|
||||
});
|
||||
branchId = branch.id;
|
||||
branchMessage = `We found a branch with the same name`;
|
||||
} catch (error) {
|
||||
if (error instanceof BranchNotFoundError) {
|
||||
const newBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
|
||||
@@ -321,7 +333,7 @@ export async function createAndLinkChannelAsync(
|
||||
name: channelName,
|
||||
});
|
||||
branchId = newBranch.id;
|
||||
branchMessage = `We also went ahead and made a branch with the same name`;
|
||||
hasCreatedBranch = true;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
@@ -344,26 +356,25 @@ export async function createAndLinkChannelAsync(
|
||||
if (shouldPrintJson) {
|
||||
printJsonOnlyOutput(newChannel);
|
||||
} else {
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, appId);
|
||||
const learnMoreLink = learnMore('https://docs.expo.dev/eas-update/eas-cli/');
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.withTick(
|
||||
`Created a new channel on project ${chalk.bold(
|
||||
await getDisplayNameForProjectIdAsync(graphqlClient, appId)
|
||||
)}`
|
||||
);
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'Name', value: newChannel.name },
|
||||
{ label: 'ID', value: newChannel.id },
|
||||
])
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
Log.withTick(`${branchMessage} and have pointed the channel at it.`);
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'Name', value: newChannel.name },
|
||||
{ label: 'ID', value: branchId },
|
||||
])
|
||||
);
|
||||
if (hasCreatedBranch) {
|
||||
Log.withTick(
|
||||
`Created update channel ${chalk.bold(`"${newChannel.name}"`)} and branch ${chalk.bold(
|
||||
`"${newChannel.name}"`
|
||||
)} on ${chalk.bold(projectDisplayName)} project. ${learnMoreLink}`
|
||||
);
|
||||
} else {
|
||||
Log.withTick(
|
||||
`Created update channel ${chalk.bold(`"${newChannel.name}"`)} on ${chalk.bold(
|
||||
projectDisplayName
|
||||
)} project and connected it with existing ${chalk.bold(
|
||||
`"${newChannel.name}"`
|
||||
)} branch. ${learnMoreLink}`
|
||||
);
|
||||
}
|
||||
}
|
||||
return newChannel;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import nullthrows from 'nullthrows';
|
||||
|
||||
import AnalyticsContextField from './context/AnalyticsContextField';
|
||||
import ContextField from './context/ContextField';
|
||||
import DynamicLoggedInContextField from './context/DynamicLoggedInContextField';
|
||||
import {
|
||||
DynamicPrivateProjectConfigContextField,
|
||||
DynamicPublicProjectConfigContextField,
|
||||
@@ -15,6 +16,8 @@ import MaybeLoggedInContextField from './context/MaybeLoggedInContextField';
|
||||
import { OptionalPrivateProjectConfigContextField } from './context/OptionalPrivateProjectConfigContextField';
|
||||
import { PrivateProjectConfigContextField } from './context/PrivateProjectConfigContextField';
|
||||
import ProjectDirContextField from './context/ProjectDirContextField';
|
||||
import { ProjectIdContextField } from './context/ProjectIdContextField';
|
||||
import { ServerSideEnvironmentVariablesContextField } from './context/ServerSideEnvironmentVariablesContextField';
|
||||
import SessionManagementContextField from './context/SessionManagementContextField';
|
||||
import VcsClientContextField from './context/VcsClientContextField';
|
||||
import { EasCommandError } from './errors';
|
||||
@@ -23,6 +26,7 @@ import {
|
||||
CommandEvent,
|
||||
createAnalyticsAsync,
|
||||
} from '../analytics/AnalyticsManager';
|
||||
import { EnvironmentVariableEnvironment } from '../graphql/generated';
|
||||
import Log from '../log';
|
||||
import SessionManager from '../user/SessionManager';
|
||||
import { Client } from '../vcs/vcs';
|
||||
@@ -43,8 +47,26 @@ export type ContextOutput<
|
||||
[P in keyof T]: T[P];
|
||||
};
|
||||
|
||||
type GetContextType<Type> = {
|
||||
[Property in keyof Type]: any;
|
||||
};
|
||||
|
||||
const BASE_GRAPHQL_ERROR_MESSAGE: string = 'GraphQL request failed.';
|
||||
|
||||
interface BaseGetContextAsyncArgs {
|
||||
nonInteractive: boolean;
|
||||
vcsClientOverride?: Client;
|
||||
}
|
||||
|
||||
interface GetContextAsyncArgsWithRequiredServerSideEnvironmentArgument
|
||||
extends BaseGetContextAsyncArgs {
|
||||
withServerSideEnvironment: EnvironmentVariableEnvironment | null;
|
||||
}
|
||||
|
||||
interface GetContextAsyncArgsWithoutServerSideEnvironmentArgument extends BaseGetContextAsyncArgs {
|
||||
withServerSideEnvironment?: never;
|
||||
}
|
||||
|
||||
export default abstract class EasCommand extends Command {
|
||||
protected static readonly ContextOptions = {
|
||||
/**
|
||||
@@ -61,8 +83,16 @@ export default abstract class EasCommand extends Command {
|
||||
MaybeLoggedIn: {
|
||||
maybeLoggedIn: new MaybeLoggedInContextField(),
|
||||
},
|
||||
/**
|
||||
* Specify this context if the logged-in requirement is only necessary in a particular execution of the command.
|
||||
*/
|
||||
DynamicLoggedIn: {
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
getDynamicLoggedInAsync: new DynamicLoggedInContextField(),
|
||||
},
|
||||
/**
|
||||
* Specify this context requirement if the command needs to mutate the user session.
|
||||
* @deprecated Should not be used outside of session management commands, which currently only includes `login` and `logout`.
|
||||
*/
|
||||
SessionManagment: {
|
||||
sessionManager: new SessionManagementContextField(),
|
||||
@@ -72,7 +102,7 @@ export default abstract class EasCommand extends Command {
|
||||
* run within a project directory, null otherwise.
|
||||
*/
|
||||
OptionalProjectConfig: {
|
||||
privateProjectConfig: new OptionalPrivateProjectConfigContextField(),
|
||||
optionalPrivateProjectConfig: new OptionalPrivateProjectConfigContextField(),
|
||||
},
|
||||
/**
|
||||
* Require this command to be run in a project directory. Return the project directory in the context.
|
||||
@@ -107,6 +137,16 @@ export default abstract class EasCommand extends Command {
|
||||
Vcs: {
|
||||
vcsClient: new VcsClientContextField(),
|
||||
},
|
||||
ServerSideEnvironmentVariables: {
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
getServerSideEnvironmentVariablesAsync: new ServerSideEnvironmentVariablesContextField(),
|
||||
},
|
||||
/**
|
||||
* Require the project to be identified and registered on server. Returns the project ID evaluated from the app config.
|
||||
*/
|
||||
ProjectId: {
|
||||
projectId: new ProjectIdContextField(),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -141,7 +181,19 @@ export default abstract class EasCommand extends Command {
|
||||
} = object,
|
||||
>(
|
||||
commandClass: { contextDefinition: ContextInput<C> },
|
||||
{ nonInteractive, vcsClientOverride }: { nonInteractive: boolean; vcsClientOverride?: Client }
|
||||
{
|
||||
nonInteractive,
|
||||
vcsClientOverride,
|
||||
// if specified and not null, the env vars from the selected environment will be fetched from the server
|
||||
// to resolve dynamic config (if dynamic config context is used) and enable getServerSideEnvironmentVariablesAsync function (if server side environment variables context is used)
|
||||
withServerSideEnvironment,
|
||||
}: C extends
|
||||
| GetContextType<typeof EasCommand.ContextOptions.ProjectConfig>
|
||||
| GetContextType<typeof EasCommand.ContextOptions.DynamicProjectConfig>
|
||||
| GetContextType<typeof EasCommand.ContextOptions.OptionalProjectConfig>
|
||||
| GetContextType<typeof EasCommand.ContextOptions.ServerSideEnvironmentVariables>
|
||||
? GetContextAsyncArgsWithRequiredServerSideEnvironmentArgument
|
||||
: GetContextAsyncArgsWithoutServerSideEnvironmentArgument
|
||||
): Promise<ContextOutput<C>> {
|
||||
const contextDefinition = commandClass.contextDefinition;
|
||||
|
||||
@@ -155,6 +207,7 @@ export default abstract class EasCommand extends Command {
|
||||
sessionManager: this.sessionManager,
|
||||
analytics: this.analytics,
|
||||
vcsClientOverride,
|
||||
withServerSideEnvironment,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
@@ -187,13 +240,13 @@ export default abstract class EasCommand extends Command {
|
||||
action: `eas ${this.id}`,
|
||||
});
|
||||
|
||||
return this.runAsync();
|
||||
return await this.runAsync();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
override async finally(err: Error): Promise<any> {
|
||||
await this.analytics.flushAsync();
|
||||
return super.finally(err);
|
||||
return await super.finally(err);
|
||||
}
|
||||
|
||||
protected override catch(err: Error): Promise<any> {
|
||||
@@ -201,7 +254,7 @@ export default abstract class EasCommand extends Command {
|
||||
if (err instanceof EasCommandError) {
|
||||
Log.error(err.message);
|
||||
} else if (err instanceof CombinedError && err?.graphQLErrors) {
|
||||
const cleanMessage = err?.graphQLErrors
|
||||
const cleanGQLErrorsMessage = err?.graphQLErrors
|
||||
.map((graphQLError: GraphQLError) => {
|
||||
const messageLine = graphQLError.message.replace('[GraphQL] ', '');
|
||||
const requestIdLine = graphQLError.extensions?.requestId
|
||||
@@ -223,6 +276,9 @@ export default abstract class EasCommand extends Command {
|
||||
return defaultMsg;
|
||||
})
|
||||
.join('\n');
|
||||
const cleanMessage = err.networkError
|
||||
? `${cleanGQLErrorsMessage}\n${err.networkError.message}`
|
||||
: cleanGQLErrorsMessage;
|
||||
Log.error(cleanMessage);
|
||||
baseMessage = BASE_GRAPHQL_ERROR_MESSAGE;
|
||||
} else {
|
||||
|
||||
@@ -38,7 +38,12 @@ export async function fetchBuildsAsync({
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
projectId: string;
|
||||
filters?: { statuses?: BuildStatus[]; platform?: RequestedPlatform; profile?: string };
|
||||
filters?: {
|
||||
statuses?: BuildStatus[];
|
||||
platform?: RequestedPlatform;
|
||||
profile?: string;
|
||||
hasFingerprint?: boolean;
|
||||
};
|
||||
}): Promise<BuildFragment[]> {
|
||||
let builds: BuildFragment[];
|
||||
const queryFilters: InputMaybe<BuildFilter> = {};
|
||||
@@ -48,6 +53,9 @@ export async function fetchBuildsAsync({
|
||||
if (filters?.profile) {
|
||||
queryFilters['buildProfile'] = filters.profile;
|
||||
}
|
||||
if (filters?.hasFingerprint) {
|
||||
queryFilters['hasFingerprint'] = filters.hasFingerprint;
|
||||
}
|
||||
if (!filters?.statuses) {
|
||||
builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Analytics } from '../../analytics/AnalyticsManager';
|
||||
import { EnvironmentVariableEnvironment } from '../../graphql/generated';
|
||||
import SessionManager from '../../user/SessionManager';
|
||||
import { Client } from '../../vcs/vcs';
|
||||
|
||||
@@ -7,6 +8,10 @@ export interface ContextOptions {
|
||||
analytics: Analytics;
|
||||
nonInteractive: boolean;
|
||||
vcsClientOverride?: Client;
|
||||
/**
|
||||
* If specified, env variables from the selected environment will be fetched from the server and used to evaluate the dynamic config.
|
||||
*/
|
||||
withServerSideEnvironment?: EnvironmentVariableEnvironment | null;
|
||||
}
|
||||
|
||||
export default abstract class ContextField<T> {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import ContextField, { ContextOptions } from './ContextField';
|
||||
import { ExpoGraphqlClient, createGraphqlClient } from './contextUtils/createGraphqlClient';
|
||||
import { LoggedInAuthenticationInfo } from '../../user/SessionManager';
|
||||
import { Actor } from '../../user/User';
|
||||
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
|
||||
import FeatureGating from '../gating/FeatureGating';
|
||||
|
||||
export type DynamicLoggedInContextFn = () => Promise<{
|
||||
actor: Actor;
|
||||
featureGating: FeatureGating;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
authenticationInfo: LoggedInAuthenticationInfo;
|
||||
}>;
|
||||
|
||||
export default class DynamicLoggedInContextField extends ContextField<DynamicLoggedInContextFn> {
|
||||
async getValueAsync({
|
||||
nonInteractive,
|
||||
sessionManager,
|
||||
}: ContextOptions): Promise<DynamicLoggedInContextFn> {
|
||||
return async () => {
|
||||
const { actor, authenticationInfo } = await sessionManager.ensureLoggedInAsync({
|
||||
nonInteractive,
|
||||
});
|
||||
const featureGateServerValues: { [key: string]: boolean } = actor?.featureGates ?? {};
|
||||
|
||||
const graphqlClient = createGraphqlClient(authenticationInfo);
|
||||
|
||||
return {
|
||||
actor,
|
||||
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
|
||||
graphqlClient,
|
||||
authenticationInfo,
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
|
||||
import ContextField, { ContextOptions } from './ContextField';
|
||||
import { createGraphqlClient } from './contextUtils/createGraphqlClient';
|
||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
||||
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
|
||||
import {
|
||||
ExpoConfigOptions,
|
||||
getPrivateExpoConfig,
|
||||
getPublicExpoConfig,
|
||||
getPrivateExpoConfigAsync,
|
||||
getPublicExpoConfigAsync,
|
||||
} from '../../project/expoConfig';
|
||||
|
||||
export type DynamicConfigContextFn = (options?: ExpoConfigOptions) => Promise<{
|
||||
@@ -19,15 +21,34 @@ export class DynamicPublicProjectConfigContextField extends ContextField<Dynamic
|
||||
async getValueAsync({
|
||||
nonInteractive,
|
||||
sessionManager,
|
||||
withServerSideEnvironment,
|
||||
}: ContextOptions): Promise<DynamicConfigContextFn> {
|
||||
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||
return async (options?: ExpoConfigOptions) => {
|
||||
const expBefore = getPublicExpoConfig(projectDir, options);
|
||||
const expBefore = await getPublicExpoConfigAsync(projectDir, options);
|
||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||
nonInteractive,
|
||||
env: options?.env,
|
||||
});
|
||||
const exp = getPublicExpoConfig(projectDir, options);
|
||||
if (withServerSideEnvironment) {
|
||||
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
|
||||
nonInteractive,
|
||||
});
|
||||
const graphqlClient = createGraphqlClient(authenticationInfo);
|
||||
const serverSideEnvironmentVariables = await loadServerSideEnvironmentVariablesAsync({
|
||||
environment: withServerSideEnvironment,
|
||||
projectId,
|
||||
graphqlClient,
|
||||
});
|
||||
options = {
|
||||
...options,
|
||||
env: {
|
||||
...options?.env,
|
||||
...serverSideEnvironmentVariables,
|
||||
},
|
||||
};
|
||||
}
|
||||
const exp = await getPublicExpoConfigAsync(projectDir, options);
|
||||
return {
|
||||
exp,
|
||||
projectDir,
|
||||
@@ -41,15 +62,34 @@ export class DynamicPrivateProjectConfigContextField extends ContextField<Dynami
|
||||
async getValueAsync({
|
||||
nonInteractive,
|
||||
sessionManager,
|
||||
withServerSideEnvironment,
|
||||
}: ContextOptions): Promise<DynamicConfigContextFn> {
|
||||
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||
return async (options?: ExpoConfigOptions) => {
|
||||
const expBefore = getPrivateExpoConfig(projectDir, options);
|
||||
const expBefore = await getPrivateExpoConfigAsync(projectDir, options);
|
||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||
nonInteractive,
|
||||
env: options?.env,
|
||||
});
|
||||
const exp = getPrivateExpoConfig(projectDir, options);
|
||||
if (withServerSideEnvironment) {
|
||||
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
|
||||
nonInteractive,
|
||||
});
|
||||
const graphqlClient = createGraphqlClient(authenticationInfo);
|
||||
const serverSideEnvironmentVariables = await loadServerSideEnvironmentVariablesAsync({
|
||||
environment: withServerSideEnvironment,
|
||||
projectId,
|
||||
graphqlClient,
|
||||
});
|
||||
options = {
|
||||
...options,
|
||||
env: {
|
||||
...options?.env,
|
||||
...serverSideEnvironmentVariables,
|
||||
},
|
||||
};
|
||||
}
|
||||
const exp = await getPrivateExpoConfigAsync(projectDir, options);
|
||||
return {
|
||||
exp,
|
||||
projectDir,
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import ContextField, { ContextOptions } from './ContextField';
|
||||
import { ExpoGraphqlClient, createGraphqlClient } from './contextUtils/createGraphqlClient';
|
||||
import { LoggedInAuthenticationInfo } from '../../user/SessionManager';
|
||||
import { Actor } from '../../user/User';
|
||||
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
|
||||
import FeatureGating from '../gating/FeatureGating';
|
||||
|
||||
export default class LoggedInContextField extends ContextField<{
|
||||
type LoggedInContextType = {
|
||||
actor: Actor;
|
||||
featureGating: FeatureGating;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
}> {
|
||||
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<{
|
||||
actor: Actor;
|
||||
featureGating: FeatureGating;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
}> {
|
||||
authenticationInfo: LoggedInAuthenticationInfo;
|
||||
};
|
||||
|
||||
export default class LoggedInContextField extends ContextField<LoggedInContextType> {
|
||||
async getValueAsync({
|
||||
nonInteractive,
|
||||
sessionManager,
|
||||
}: ContextOptions): Promise<LoggedInContextType> {
|
||||
const { actor, authenticationInfo } = await sessionManager.ensureLoggedInAsync({
|
||||
nonInteractive,
|
||||
});
|
||||
@@ -25,6 +28,7 @@ export default class LoggedInContextField extends ContextField<{
|
||||
actor,
|
||||
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
|
||||
graphqlClient,
|
||||
authenticationInfo,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,18 @@ import { Actor } from '../../user/User';
|
||||
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
|
||||
import FeatureGating from '../gating/FeatureGating';
|
||||
|
||||
export default class MaybeLoggedInContextField extends ContextField<{
|
||||
type MaybeLoggedInContextType = {
|
||||
actor: Actor | null;
|
||||
featureGating: FeatureGating;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
}> {
|
||||
async getValueAsync({ sessionManager }: ContextOptions): Promise<{
|
||||
actor: Actor | null;
|
||||
featureGating: FeatureGating;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
}> {
|
||||
authenticationInfo: {
|
||||
accessToken: string | null;
|
||||
sessionSecret: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
export default class MaybeLoggedInContextField extends ContextField<MaybeLoggedInContextType> {
|
||||
async getValueAsync({ sessionManager }: ContextOptions): Promise<MaybeLoggedInContextType> {
|
||||
const authenticationInfo = {
|
||||
accessToken: sessionManager.getAccessToken(),
|
||||
sessionSecret: sessionManager.getSessionSecret(),
|
||||
@@ -27,6 +29,7 @@ export default class MaybeLoggedInContextField extends ContextField<{
|
||||
actor: actor ?? null,
|
||||
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
|
||||
graphqlClient,
|
||||
authenticationInfo,
|
||||
};
|
||||
}
|
||||
}
|
||||
+24
-5
@@ -1,10 +1,12 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { InvalidEasJsonError } from '@expo/eas-json/build/errors';
|
||||
|
||||
import ContextField, { ContextOptions } from './ContextField';
|
||||
import { createGraphqlClient } from './contextUtils/createGraphqlClient';
|
||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
||||
import { getPrivateExpoConfig } from '../../project/expoConfig';
|
||||
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
|
||||
import { getPrivateExpoConfigAsync } from '../../project/expoConfig';
|
||||
|
||||
export class OptionalPrivateProjectConfigContextField extends ContextField<
|
||||
| {
|
||||
@@ -14,7 +16,11 @@ export class OptionalPrivateProjectConfigContextField extends ContextField<
|
||||
}
|
||||
| undefined
|
||||
> {
|
||||
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<
|
||||
async getValueAsync({
|
||||
nonInteractive,
|
||||
sessionManager,
|
||||
withServerSideEnvironment,
|
||||
}: ContextOptions): Promise<
|
||||
| {
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
@@ -35,11 +41,24 @@ export class OptionalPrivateProjectConfigContextField extends ContextField<
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const expBefore = getPrivateExpoConfig(projectDir);
|
||||
const expBefore = await getPrivateExpoConfigAsync(projectDir);
|
||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||
nonInteractive,
|
||||
});
|
||||
const exp = getPrivateExpoConfig(projectDir);
|
||||
let serverSideEnvVars: Record<string, string> | undefined;
|
||||
if (withServerSideEnvironment) {
|
||||
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
|
||||
nonInteractive,
|
||||
});
|
||||
const graphqlClient = createGraphqlClient(authenticationInfo);
|
||||
const serverSideEnvironmentVariables = await loadServerSideEnvironmentVariablesAsync({
|
||||
environment: withServerSideEnvironment,
|
||||
projectId,
|
||||
graphqlClient,
|
||||
});
|
||||
serverSideEnvVars = serverSideEnvironmentVariables;
|
||||
}
|
||||
const exp = await getPrivateExpoConfigAsync(projectDir, { env: serverSideEnvVars });
|
||||
return {
|
||||
exp,
|
||||
projectDir,
|
||||
|
||||
@@ -1,26 +1,45 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
|
||||
import ContextField, { ContextOptions } from './ContextField';
|
||||
import { createGraphqlClient } from './contextUtils/createGraphqlClient';
|
||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
||||
import { getPrivateExpoConfig } from '../../project/expoConfig';
|
||||
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
|
||||
import { getPrivateExpoConfigAsync } from '../../project/expoConfig';
|
||||
|
||||
export class PrivateProjectConfigContextField extends ContextField<{
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
projectDir: string;
|
||||
}> {
|
||||
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<{
|
||||
async getValueAsync({
|
||||
nonInteractive,
|
||||
sessionManager,
|
||||
withServerSideEnvironment,
|
||||
}: ContextOptions): Promise<{
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
projectDir: string;
|
||||
}> {
|
||||
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||
const expBefore = getPrivateExpoConfig(projectDir);
|
||||
const expBefore = await getPrivateExpoConfigAsync(projectDir);
|
||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||
nonInteractive,
|
||||
});
|
||||
const exp = getPrivateExpoConfig(projectDir);
|
||||
let serverSideEnvVars: Record<string, string> | undefined;
|
||||
if (withServerSideEnvironment) {
|
||||
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
|
||||
nonInteractive,
|
||||
});
|
||||
const graphqlClient = createGraphqlClient(authenticationInfo);
|
||||
const serverSideEnvironmentVariables = await loadServerSideEnvironmentVariablesAsync({
|
||||
environment: withServerSideEnvironment,
|
||||
projectId,
|
||||
graphqlClient,
|
||||
});
|
||||
serverSideEnvVars = serverSideEnvironmentVariables;
|
||||
}
|
||||
const exp = await getPrivateExpoConfigAsync(projectDir, { env: serverSideEnvVars });
|
||||
|
||||
return {
|
||||
projectId,
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import ContextField, { ContextOptions } from './ContextField';
|
||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
||||
import { getPrivateExpoConfigAsync } from '../../project/expoConfig';
|
||||
|
||||
export class ProjectIdContextField extends ContextField<string> {
|
||||
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<string> {
|
||||
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||
const expBefore = await getPrivateExpoConfigAsync(projectDir);
|
||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||
nonInteractive,
|
||||
});
|
||||
return projectId;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import ContextField, { ContextOptions } from './ContextField';
|
||||
import { createGraphqlClient } from './contextUtils/createGraphqlClient';
|
||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
||||
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
|
||||
import { getPublicExpoConfigAsync } from '../../project/expoConfig';
|
||||
|
||||
type GetServerSideEnvironmentVariablesFn = (
|
||||
maybeEnv?: Record<string, string>
|
||||
) => Promise<Record<string, string>>;
|
||||
|
||||
export class ServerSideEnvironmentVariablesContextField extends ContextField<GetServerSideEnvironmentVariablesFn> {
|
||||
async getValueAsync({
|
||||
nonInteractive,
|
||||
sessionManager,
|
||||
withServerSideEnvironment,
|
||||
}: ContextOptions): Promise<GetServerSideEnvironmentVariablesFn> {
|
||||
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||
return async (maybeEnv?: Record<string, string>) => {
|
||||
if (!withServerSideEnvironment) {
|
||||
throw new Error(
|
||||
'withServerSideEnvironment parameter is required to evaluate ServerSideEnvironmentVariablesContextField'
|
||||
);
|
||||
}
|
||||
const exp = await getPublicExpoConfigAsync(projectDir, { env: maybeEnv });
|
||||
const projectId = await getProjectIdAsync(sessionManager, exp, {
|
||||
nonInteractive,
|
||||
env: maybeEnv,
|
||||
});
|
||||
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
|
||||
nonInteractive,
|
||||
});
|
||||
const graphqlClient = createGraphqlClient(authenticationInfo);
|
||||
const serverSideEnvironmentVariables = await loadServerSideEnvironmentVariablesAsync({
|
||||
environment: withServerSideEnvironment,
|
||||
projectId,
|
||||
graphqlClient,
|
||||
});
|
||||
return serverSideEnvironmentVariables;
|
||||
};
|
||||
}
|
||||
}
|
||||
+177
-79
@@ -6,6 +6,7 @@ import { Role } from '../../../../graphql/generated';
|
||||
import { AppQuery } from '../../../../graphql/queries/AppQuery';
|
||||
import { learnMore } from '../../../../log';
|
||||
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||
import { isExpoInstalled } from '../../../../project/projectUtils';
|
||||
import SessionManager from '../../../../user/SessionManager';
|
||||
import { findProjectRootAsync } from '../findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { getProjectIdAsync } from '../getProjectIdAsync';
|
||||
@@ -21,6 +22,7 @@ jest.mock('../../../../ora', () => ({
|
||||
}),
|
||||
}));
|
||||
jest.mock('../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync');
|
||||
jest.mock('../../../../project/projectUtils');
|
||||
|
||||
describe(getProjectIdAsync, () => {
|
||||
let sessionManager: SessionManager;
|
||||
@@ -71,11 +73,17 @@ describe(getProjectIdAsync, () => {
|
||||
);
|
||||
|
||||
jest.mocked(findProjectRootAsync).mockResolvedValue('/app');
|
||||
jest.mocked(isExpoInstalled).mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('gets the project ID from app config if exists', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
exp: {
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
@@ -88,7 +96,7 @@ describe(getProjectIdAsync, () => {
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).resolves.toEqual('1234');
|
||||
@@ -96,7 +104,13 @@ describe(getProjectIdAsync, () => {
|
||||
|
||||
it('throws when the owner is out of sync', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'test', owner: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
exp: {
|
||||
sdkVersion: '54.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
owner: 'wat',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
@@ -109,7 +123,13 @@ describe(getProjectIdAsync, () => {
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'test', owner: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
{
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
owner: 'wat',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
@@ -119,83 +139,143 @@ describe(getProjectIdAsync, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when the owner is not specified and is different than logged in user', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
describe('when sdkVersion is less than 53', () => {
|
||||
it('throws when the owner is not specified and is different than logged in user', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: {
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (totallybrent) does not match the logged in user (notnotbrent) and the "owner" field is not specified. To ensure all libraries work correctly, "owner": "totallybrent" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (totallybrent) does not match the logged in user (notnotbrent) and the "owner" field is not specified. To ensure all libraries work correctly, "owner": "totallybrent" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
it('throws when the owner is not specified and is different than logged in user which is a robot', async () => {
|
||||
const sessionManagerMock = mock<SessionManager>();
|
||||
when(sessionManagerMock.ensureLoggedInAsync(anything())).thenResolve({
|
||||
actor: {
|
||||
__typename: 'Robot',
|
||||
id: 'robot_id',
|
||||
accounts: [
|
||||
{
|
||||
id: 'account_id_1',
|
||||
name: 'notnotbrent',
|
||||
users: [{ role: Role.Admin, actor: { id: 'robot_id' } }],
|
||||
},
|
||||
{
|
||||
id: 'account_id_2',
|
||||
name: 'dominik',
|
||||
users: [{ role: Role.ViewOnly, actor: { id: 'robot_id' } }],
|
||||
},
|
||||
],
|
||||
isExpoAdmin: false,
|
||||
featureGates: {},
|
||||
},
|
||||
authenticationInfo: { accessToken: 'fake', sessionSecret: null },
|
||||
});
|
||||
const sessionManagerRobot = instance(sessionManagerMock);
|
||||
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: {
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManagerRobot,
|
||||
{
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (totallybrent) must be specified in "owner" field when using a robot access token. To ensure all libraries work correctly, "owner": "totallybrent" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when the owner is not specified and is different than logged in user which is a robot', async () => {
|
||||
const sessionManagerMock = mock<SessionManager>();
|
||||
when(sessionManagerMock.ensureLoggedInAsync(anything())).thenResolve({
|
||||
actor: {
|
||||
__typename: 'Robot',
|
||||
id: 'robot_id',
|
||||
accounts: [
|
||||
{
|
||||
id: 'account_id_1',
|
||||
name: 'notnotbrent',
|
||||
users: [{ role: Role.Admin, actor: { id: 'robot_id' } }],
|
||||
},
|
||||
{
|
||||
id: 'account_id_2',
|
||||
name: 'dominik',
|
||||
users: [{ role: Role.ViewOnly, actor: { id: 'robot_id' } }],
|
||||
},
|
||||
],
|
||||
isExpoAdmin: false,
|
||||
featureGates: {},
|
||||
},
|
||||
authenticationInfo: { accessToken: 'fake', sessionSecret: null },
|
||||
});
|
||||
const sessionManagerRobot = instance(sessionManagerMock);
|
||||
describe('when sdkVersion is 53 or higher', () => {
|
||||
it('does not throw when the owner is not specified', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: {
|
||||
sdkVersion: '53.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
});
|
||||
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{
|
||||
sdkVersion: '53.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManagerRobot,
|
||||
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (totallybrent) must be specified in "owner" field when using a robot access token. To ensure all libraries work correctly, "owner": "totallybrent" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when the slug is out of sync', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
exp: {
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'wat',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
@@ -208,7 +288,7 @@ describe(getProjectIdAsync, () => {
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
@@ -219,10 +299,17 @@ describe(getProjectIdAsync, () => {
|
||||
});
|
||||
|
||||
it('fetches the project ID when not in app config, and sets it in the config', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||
jest
|
||||
.mocked(getConfig)
|
||||
.mockReturnValue({ exp: { sdkVersion: '52.0.0', name: 'test', slug: 'test' } } as any);
|
||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||
type: 'success',
|
||||
config: { expo: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } } },
|
||||
config: {
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '2345' } },
|
||||
},
|
||||
});
|
||||
jest
|
||||
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
||||
@@ -230,7 +317,7 @@ describe(getProjectIdAsync, () => {
|
||||
|
||||
const projectId = await getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'test' },
|
||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'test' },
|
||||
{
|
||||
nonInteractive: false,
|
||||
}
|
||||
@@ -249,7 +336,9 @@ describe(getProjectIdAsync, () => {
|
||||
});
|
||||
|
||||
it('throws if writing the ID back to the config fails', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||
jest
|
||||
.mocked(getConfig)
|
||||
.mockReturnValue({ exp: { sdkVersion: '52.0.0', name: 'test', slug: 'test' } } as any);
|
||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||
type: 'fail',
|
||||
config: null,
|
||||
@@ -259,13 +348,22 @@ describe(getProjectIdAsync, () => {
|
||||
.mockImplementation(async () => '4567');
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(sessionManager, { name: 'test', slug: 'test' }, { nonInteractive: false })
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'test' },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('throws if extra.eas.projectId is not a string', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: 1234 } } },
|
||||
exp: {
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: 1234 } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
@@ -278,7 +376,7 @@ describe(getProjectIdAsync, () => {
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'wat', extra: { eas: { projectId: 1234 } } },
|
||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'wat', extra: { eas: { projectId: 1234 } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
|
||||
@@ -38,7 +38,7 @@ export function createGraphqlClient(authInfo: {
|
||||
return !!(
|
||||
err &&
|
||||
!operation.context.noRetry &&
|
||||
(err.networkError || err.graphQLErrors.some(e => e?.extensions?.isTransient))
|
||||
(err.networkError ?? err.graphQLErrors.some(e => e?.extensions?.isTransient))
|
||||
);
|
||||
},
|
||||
}),
|
||||
|
||||
+7
-2
@@ -42,7 +42,7 @@ async function ensureEasCliIsNotInDependenciesAsync(projectDir: string): Promise
|
||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your project dependencies.`);
|
||||
}
|
||||
|
||||
const maybeRepoRoot = PackageManagerUtils.findWorkspaceRoot(projectDir) ?? projectDir;
|
||||
const maybeRepoRoot = PackageManagerUtils.resolveWorkspaceRoot(projectDir) ?? projectDir;
|
||||
if (maybeRepoRoot !== projectDir && (await isEasCliInDependenciesAsync(maybeRepoRoot))) {
|
||||
printCliVersionWarning = true;
|
||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your monorepo dependencies.`);
|
||||
@@ -103,6 +103,8 @@ export async function findProjectRootAsync({
|
||||
}
|
||||
}
|
||||
|
||||
let ranEnsureEasCliIsNotInDependencies = false;
|
||||
|
||||
/**
|
||||
* Determine the project root directory and ensure some constraints about the project setup
|
||||
* like CLI version and dependencies.
|
||||
@@ -113,6 +115,9 @@ export async function findProjectRootAsync({
|
||||
export async function findProjectDirAndVerifyProjectSetupAsync(): Promise<string> {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
await applyCliConfigAsync(projectDir);
|
||||
await ensureEasCliIsNotInDependenciesAsync(projectDir);
|
||||
if (!ranEnsureEasCliIsNotInDependencies) {
|
||||
ranEnsureEasCliIsNotInDependencies = true;
|
||||
await ensureEasCliIsNotInDependenciesAsync(projectDir);
|
||||
}
|
||||
return projectDir;
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
import { getProjectConfigDescription } from '@expo/config';
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { ExpoConfig, getProjectConfigDescription } from '@expo/config';
|
||||
import { Env } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
import semver from 'semver';
|
||||
|
||||
import { ExpoGraphqlClient, createGraphqlClient } from './createGraphqlClient';
|
||||
import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import Log, { learnMore } from '../../../log';
|
||||
import { ora } from '../../../ora';
|
||||
import { createOrModifyExpoConfigAsync, getPrivateExpoConfig } from '../../../project/expoConfig';
|
||||
import {
|
||||
createOrModifyExpoConfigAsync,
|
||||
getPrivateExpoConfigAsync,
|
||||
} from '../../../project/expoConfig';
|
||||
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||
import { toAppPrivacy } from '../../../project/projectUtils';
|
||||
import SessionManager from '../../../user/SessionManager';
|
||||
import { Actor, getActorUsername } from '../../../user/User';
|
||||
|
||||
@@ -18,13 +20,15 @@ import { Actor, getActorUsername } from '../../../user/User';
|
||||
* Save an EAS project ID to the appropriate field in the app config.
|
||||
*
|
||||
* @deprecated Should not be used outside of context functions except in the init command.
|
||||
* @deprecated Starting from `@expo/config` from SDK 52, the `modifyConfigAsync` function is merging existing data. Once this is released, we can use that instead of manually merging.
|
||||
*/
|
||||
export async function saveProjectIdToAppConfigAsync(
|
||||
projectDir: string,
|
||||
projectId: string,
|
||||
options: { env?: Env } = {}
|
||||
): Promise<void> {
|
||||
const exp = getPrivateExpoConfig(projectDir, options);
|
||||
// NOTE(cedric): we disable plugins to avoid writing plugin-generated content to `expo.extra`
|
||||
const exp = await getPrivateExpoConfigAsync(projectDir, { skipPlugins: true, ...options });
|
||||
const result = await createOrModifyExpoConfigAsync(
|
||||
projectDir,
|
||||
{
|
||||
@@ -96,11 +100,13 @@ export async function validateOrSetProjectIdAsync({
|
||||
graphqlClient,
|
||||
actor,
|
||||
options,
|
||||
cwd,
|
||||
}: {
|
||||
exp: ExpoConfig;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
actor: Actor;
|
||||
options: { env?: Env; nonInteractive: boolean };
|
||||
cwd?: string;
|
||||
}): Promise<string> {
|
||||
const localProjectId = exp.extra?.eas?.projectId;
|
||||
if (localProjectId) {
|
||||
@@ -122,29 +128,33 @@ export async function validateOrSetProjectIdAsync({
|
||||
);
|
||||
}
|
||||
|
||||
const actorUsername = getActorUsername(actor);
|
||||
if (!exp.owner && appForProjectId.ownerAccount.name !== actorUsername) {
|
||||
if (actorUsername) {
|
||||
throw new Error(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (${
|
||||
appForProjectId.ownerAccount.name
|
||||
}) does not match the logged in user (${actorUsername}) and the "owner" field is not specified. To ensure all libraries work correctly, "owner": "${
|
||||
appForProjectId.ownerAccount.name
|
||||
}" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
// robot caller
|
||||
throw new Error(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (${
|
||||
appForProjectId.ownerAccount.name
|
||||
}) must be specified in "owner" field when using a robot access token. To ensure all libraries work correctly, "owner": "${
|
||||
appForProjectId.ownerAccount.name
|
||||
}" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
const sdkVersion = exp.sdkVersion;
|
||||
// SDK 53 and above no longer require owner field in app config
|
||||
if (sdkVersion && semver.satisfies(sdkVersion, '< 53.0.0')) {
|
||||
const actorUsername = getActorUsername(actor);
|
||||
if (!exp.owner && appForProjectId.ownerAccount.name !== actorUsername) {
|
||||
if (actorUsername) {
|
||||
throw new Error(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (${
|
||||
appForProjectId.ownerAccount.name
|
||||
}) does not match the logged in user (${actorUsername}) and the "owner" field is not specified. To ensure all libraries work correctly, "owner": "${
|
||||
appForProjectId.ownerAccount.name
|
||||
}" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
// robot caller
|
||||
throw new Error(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (${
|
||||
appForProjectId.ownerAccount.name
|
||||
}) must be specified in "owner" field when using a robot access token. To ensure all libraries work correctly, "owner": "${
|
||||
appForProjectId.ownerAccount.name
|
||||
}" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +171,9 @@ export async function validateOrSetProjectIdAsync({
|
||||
return localProjectId;
|
||||
}
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const projectDir = await findProjectRootAsync({
|
||||
cwd,
|
||||
});
|
||||
if (!projectDir) {
|
||||
throw new Error('This command must be run inside a project directory.');
|
||||
}
|
||||
@@ -189,7 +201,6 @@ export async function validateOrSetProjectIdAsync({
|
||||
{
|
||||
accountName: getAccountNameForEASProjectSync(exp, actor),
|
||||
projectName: exp.slug,
|
||||
privacy: toAppPrivacy(exp.privacy),
|
||||
},
|
||||
{
|
||||
nonInteractive: options.nonInteractive,
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import { ExpoGraphqlClient } from './createGraphqlClient';
|
||||
import { EnvironmentVariableEnvironment } from '../../../graphql/generated';
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
|
||||
const cachedServerSideEnvironmentVariables: Record<
|
||||
EnvironmentVariableEnvironment,
|
||||
Record<string, string> | null
|
||||
> = {
|
||||
[EnvironmentVariableEnvironment.Development]: null,
|
||||
[EnvironmentVariableEnvironment.Preview]: null,
|
||||
[EnvironmentVariableEnvironment.Production]: null,
|
||||
};
|
||||
|
||||
export async function loadServerSideEnvironmentVariablesAsync({
|
||||
environment,
|
||||
projectId,
|
||||
graphqlClient,
|
||||
}: {
|
||||
environment: EnvironmentVariableEnvironment;
|
||||
projectId: string;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
}): Promise<Record<string, string>> {
|
||||
// don't load environment variables if they were already loaded while executing a command
|
||||
const cachedEnvVarsForEnvironment = cachedServerSideEnvironmentVariables[environment];
|
||||
if (cachedEnvVarsForEnvironment) {
|
||||
return cachedEnvVarsForEnvironment;
|
||||
}
|
||||
|
||||
const environmentVariables = await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
appId: projectId,
|
||||
environment,
|
||||
}
|
||||
);
|
||||
const serverEnvVars = Object.fromEntries(
|
||||
environmentVariables
|
||||
.filter(({ name, value }) => name && value)
|
||||
.map(({ name, value }) => [name, value])
|
||||
) as Record<string, string>;
|
||||
|
||||
if (Object.keys(serverEnvVars).length > 0) {
|
||||
Log.log(
|
||||
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS: ${Object.keys(
|
||||
serverEnvVars
|
||||
).join(', ')}.`
|
||||
);
|
||||
} else {
|
||||
Log.log(
|
||||
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS.`
|
||||
);
|
||||
}
|
||||
Log.newLine();
|
||||
|
||||
cachedServerSideEnvironmentVariables[environment] = serverEnvVars;
|
||||
|
||||
return serverEnvVars;
|
||||
}
|
||||
@@ -1,5 +1,17 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
|
||||
import { EnvironmentVariableEnvironment } from '../graphql/generated';
|
||||
|
||||
// NOTE: not exactly true, but, provided mapToLowercase and upperCaseAsync
|
||||
// are used in tandem, it saves on unnecessary typying in commands
|
||||
async function upperCaseAsync<T>(input: string): Promise<T> {
|
||||
return input.toUpperCase() as T;
|
||||
}
|
||||
|
||||
function mapToLowercase<T extends string>(options: T[]): T[] {
|
||||
return options.map(option => option.toLowerCase()) as T[];
|
||||
}
|
||||
|
||||
export const EasNonInteractiveAndJsonFlags = {
|
||||
json: Flags.boolean({
|
||||
description: 'Enable JSON output, non-JSON messages will be printed to stderr.',
|
||||
@@ -10,6 +22,52 @@ export const EasNonInteractiveAndJsonFlags = {
|
||||
}),
|
||||
};
|
||||
|
||||
export const EasEnvironmentFlagParameters = {
|
||||
description: "Environment variable's environment",
|
||||
parse: upperCaseAsync,
|
||||
options: mapToLowercase([
|
||||
EnvironmentVariableEnvironment.Development,
|
||||
EnvironmentVariableEnvironment.Preview,
|
||||
EnvironmentVariableEnvironment.Production,
|
||||
]),
|
||||
};
|
||||
|
||||
export const EASEnvironmentFlag = {
|
||||
environment: Flags.enum<EnvironmentVariableEnvironment>(EasEnvironmentFlagParameters),
|
||||
};
|
||||
|
||||
export const EASMultiEnvironmentFlag = {
|
||||
environment: Flags.enum<EnvironmentVariableEnvironment>({
|
||||
...EasEnvironmentFlagParameters,
|
||||
multiple: true,
|
||||
}),
|
||||
};
|
||||
|
||||
export const EASVariableFormatFlag = {
|
||||
format: Flags.enum({
|
||||
description: 'Output format',
|
||||
options: ['long', 'short'],
|
||||
default: 'short',
|
||||
}),
|
||||
};
|
||||
|
||||
export const EASVariableVisibilityFlag = {
|
||||
visibility: Flags.enum<'plaintext' | 'sensitive' | 'secret'>({
|
||||
description: 'Visibility of the variable',
|
||||
options: ['plaintext', 'sensitive', 'secret'],
|
||||
}),
|
||||
};
|
||||
|
||||
export type EASEnvironmentVariableScopeFlagValue = 'project' | 'account';
|
||||
|
||||
export const EASEnvironmentVariableScopeFlag = {
|
||||
scope: Flags.enum<EASEnvironmentVariableScopeFlagValue>({
|
||||
description: 'Scope for the variable',
|
||||
options: ['project', 'account'],
|
||||
default: 'project',
|
||||
}),
|
||||
};
|
||||
|
||||
export const EASNonInteractiveFlag = {
|
||||
'non-interactive': Flags.boolean({
|
||||
description: 'Run the command in non-interactive mode.',
|
||||
@@ -21,3 +79,14 @@ export const EasJsonOnlyFlag = {
|
||||
description: 'Enable JSON output, non-JSON messages will be printed to stderr.',
|
||||
}),
|
||||
};
|
||||
|
||||
export const EasUpdateEnvironmentFlag = {
|
||||
environment: Flags.enum<EnvironmentVariableEnvironment | null>({
|
||||
description:
|
||||
'Environment to use for the server-side defined EAS environment variables during command execution.',
|
||||
options: mapToLowercase(Object.values(EnvironmentVariableEnvironment)),
|
||||
parse: upperCaseAsync,
|
||||
required: false,
|
||||
default: null,
|
||||
}),
|
||||
};
|
||||
@@ -63,7 +63,11 @@ export default class FeatureGating {
|
||||
}
|
||||
|
||||
public static overrideKeyForEachInTest(key: FeatureGateKey, enabled: boolean): void {
|
||||
beforeEach(() => FeatureGateTestOverrides.setOverride(key, enabled));
|
||||
afterEach(() => FeatureGateTestOverrides.removeOverride(key));
|
||||
beforeEach(() => {
|
||||
FeatureGateTestOverrides.setOverride(key, enabled);
|
||||
});
|
||||
afterEach(() => {
|
||||
FeatureGateTestOverrides.removeOverride(key);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log from '../../log';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { getActorDisplayName } from '../../user/User';
|
||||
|
||||
export default class AccountLogin extends EasCommand {
|
||||
static override description = 'log in with your Expo account';
|
||||
@@ -17,6 +20,7 @@ export default class AccountLogin extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.MaybeLoggedIn,
|
||||
...this.ContextOptions.SessionManagment,
|
||||
};
|
||||
|
||||
@@ -25,7 +29,28 @@ export default class AccountLogin extends EasCommand {
|
||||
flags: { sso },
|
||||
} = await this.parse(AccountLogin);
|
||||
|
||||
const { sessionManager } = await this.getContextAsync(AccountLogin, { nonInteractive: false });
|
||||
const {
|
||||
sessionManager,
|
||||
maybeLoggedIn: { actor },
|
||||
} = await this.getContextAsync(AccountLogin, { nonInteractive: false });
|
||||
|
||||
if (sessionManager.getAccessToken()) {
|
||||
throw new Error(
|
||||
'EXPO_TOKEN is set in your environment, and is being used for all EAS authentication. To use username/password authentication, unset EXPO_TOKEN in your environment and re-run the command.'
|
||||
);
|
||||
}
|
||||
|
||||
if (actor) {
|
||||
Log.warn(`You are already logged in as ${chalk.bold(getActorDisplayName(actor))}.`);
|
||||
|
||||
const shouldContinue = await confirmAsync({
|
||||
message: 'Do you want to continue?',
|
||||
});
|
||||
if (!shouldContinue) {
|
||||
Errors.error('Aborted', { exit: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
await sessionManager.showLoginPromptAsync({ sso });
|
||||
Log.log('Logged in');
|
||||
}
|
||||
|
||||
@@ -16,10 +16,13 @@ export default class AccountView extends EasCommand {
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
maybeLoggedIn: { actor },
|
||||
maybeLoggedIn: { actor, authenticationInfo },
|
||||
} = await this.getContextAsync(AccountView, { nonInteractive: true });
|
||||
if (actor) {
|
||||
Log.log(chalk.green(getActorDisplayName(actor)));
|
||||
const loggedInAs = authenticationInfo.accessToken
|
||||
? `${getActorDisplayName(actor)} (authenticated using EXPO_TOKEN)`
|
||||
: getActorDisplayName(actor);
|
||||
Log.log(chalk.green(loggedInAs));
|
||||
|
||||
// personal account is included, only show if more accounts that personal account
|
||||
// but do show personal account in list if there are more
|
||||
|
||||
@@ -10,7 +10,7 @@ export default class AnalyticsView extends EasCommand {
|
||||
async runAsync(): Promise<void> {
|
||||
const { STATUS: status } = (await this.parse(AnalyticsView)).args;
|
||||
if (status) {
|
||||
setAnalyticsEnabledAsync(status === 'on');
|
||||
await setAnalyticsEnabledAsync(status === 'on');
|
||||
Log.withTick(`${status === 'on' ? 'Enabling' : 'Disabling'} analytics.`);
|
||||
} else {
|
||||
const analyticsEnabled = await getAnalyticsEnabledAsync();
|
||||
|
||||
@@ -25,7 +25,7 @@ export default class BranchCreate extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
@@ -36,7 +36,7 @@ export default class BranchCreate extends EasCommand {
|
||||
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(BranchCreate);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BranchCreate, {
|
||||
@@ -58,7 +58,7 @@ export default class BranchCreate extends EasCommand {
|
||||
type: 'text',
|
||||
name: 'name',
|
||||
message: 'Provide a branch name:',
|
||||
initial: await getDefaultBranchNameAsync(vcsClient),
|
||||
initial: (await getDefaultBranchNameAsync(vcsClient)) ?? undefined,
|
||||
validate: value => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export default class BranchDelete extends EasCommand {
|
||||
static override description = 'delete a branch';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
@@ -108,7 +108,7 @@ export default class BranchDelete extends EasCommand {
|
||||
}
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BranchDelete, { nonInteractive });
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
@@ -13,14 +13,14 @@ export default class BranchList extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(BranchList);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BranchList, {
|
||||
nonInteractive: flags['non-interactive'],
|
||||
|
||||
@@ -63,7 +63,7 @@ export default class BranchRename extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ export default class BranchRename extends EasCommand {
|
||||
flags: { json: jsonFlag, from: currentName, to: newName, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(BranchRename);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BranchRename, {
|
||||
nonInteractive,
|
||||
|
||||
@@ -27,7 +27,7 @@ export default class BranchView extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ export default class BranchView extends EasCommand {
|
||||
} = await this.parse(BranchView);
|
||||
const { 'non-interactive': nonInteractive } = flags;
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BranchView, {
|
||||
nonInteractive,
|
||||
|
||||
@@ -39,7 +39,7 @@ async function cancelBuildAsync(
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.build!.cancel;
|
||||
return data.build.cancel;
|
||||
}
|
||||
|
||||
export async function selectBuildToCancelAsync(
|
||||
@@ -110,7 +110,7 @@ export default class BuildCancel extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
@@ -128,7 +128,7 @@ export default class BuildCancel extends EasCommand {
|
||||
}
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BuildCancel, {
|
||||
nonInteractive,
|
||||
|
||||
@@ -38,6 +38,7 @@ export default class BuildConfigure extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildConfigure, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
Log.log(
|
||||
|
||||
@@ -33,7 +33,7 @@ async function deleteBuildAsync(
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.build!.deleteBuild;
|
||||
return data.build.deleteBuild;
|
||||
}
|
||||
|
||||
export async function selectBuildToDeleteAsync(
|
||||
@@ -94,7 +94,7 @@ export default class BuildDelete extends EasCommand {
|
||||
}),
|
||||
};
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
@@ -112,7 +112,7 @@ export default class BuildDelete extends EasCommand {
|
||||
}
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BuildDelete, { nonInteractive });
|
||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { BuildProfile, EasJsonAccessor } from '@expo/eas-json';
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import {
|
||||
createBuildProfileAsync,
|
||||
doesBuildProfileExistAsync,
|
||||
ensureProjectConfiguredAsync,
|
||||
} from '../../build/configure';
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../../build/evaluateConfigWithEnvVarsAsync';
|
||||
import { downloadAndRunAsync, runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
||||
import { ensureRepoIsCleanAsync } from '../../build/utils/repository';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { BuildFragment, BuildStatus, DistributionType } from '../../graphql/generated';
|
||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
import { toAppPlatform } from '../../graphql/types/AppPlatform';
|
||||
import Log from '../../log';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { confirmAsync, promptAsync } from '../../prompts';
|
||||
import { expoCommandAsync } from '../../utils/expoCli';
|
||||
import { createFingerprintAsync } from '../../utils/fingerprintCli';
|
||||
import { ProfileData, getProfilesAsync } from '../../utils/profiles';
|
||||
import { Client } from '../../vcs/vcs';
|
||||
|
||||
const DEFAULT_EAS_BUILD_RUN_PROFILE_NAME = 'development-simulator';
|
||||
|
||||
export default class BuildDev extends EasCommand {
|
||||
static override hidden: true;
|
||||
|
||||
static override description =
|
||||
'run dev client simulator/emulator build with matching fingerprint or create a new one';
|
||||
|
||||
static override flags = {
|
||||
platform: Flags.enum<Platform.IOS | Platform.ANDROID>({
|
||||
char: 'p',
|
||||
options: [Platform.IOS, Platform.ANDROID],
|
||||
}),
|
||||
profile: Flags.string({
|
||||
char: 'e',
|
||||
description: `Name of the build profile from eas.json. It must be a profile allowing to create emulator/simulator internal distribution dev client builds. The "${DEFAULT_EAS_BUILD_RUN_PROFILE_NAME}" build profile will be selected by default.`,
|
||||
helpValue: 'PROFILE_NAME',
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
...this.ContextOptions.Vcs,
|
||||
...this.ContextOptions.Analytics,
|
||||
...this.ContextOptions.ProjectId,
|
||||
};
|
||||
|
||||
protected override async runAsync(): Promise<any> {
|
||||
const { flags } = await this.parse(BuildDev);
|
||||
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
projectDir,
|
||||
analytics,
|
||||
vcsClient,
|
||||
projectId,
|
||||
} = await this.getContextAsync(BuildDev, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const platform = await this.selectPlatformAsync(flags.platform);
|
||||
if (process.platform !== 'darwin' && platform === Platform.IOS) {
|
||||
Errors.error('Running iOS builds in simulator is only supported on macOS.', { exit: 1 });
|
||||
}
|
||||
|
||||
await vcsClient.ensureRepoExistsAsync();
|
||||
await ensureRepoIsCleanAsync(vcsClient, flags.nonInteractive);
|
||||
await ensureProjectConfiguredAsync({
|
||||
projectDir,
|
||||
nonInteractive: false,
|
||||
vcsClient,
|
||||
});
|
||||
|
||||
const buildProfile = await this.ensureValidBuildRunProfileExistsAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
selectedBuildProfileName: flags.profile,
|
||||
vcsClient,
|
||||
});
|
||||
|
||||
const workflow = await resolveWorkflowAsync(projectDir, platform, vcsClient);
|
||||
|
||||
const { env } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: buildProfile.profile,
|
||||
buildProfileName: buildProfile.profileName,
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: buildProfile.profile.env },
|
||||
});
|
||||
|
||||
const fingerprint = await createFingerprintAsync(projectDir, {
|
||||
env,
|
||||
workflow,
|
||||
platforms: [platform],
|
||||
});
|
||||
if (!fingerprint) {
|
||||
Errors.error('Failed to calculate fingerprint', { exit: 1 });
|
||||
}
|
||||
Log.log(`✨ Calculated fingerprint hash: ${fingerprint.hash}`);
|
||||
Log.newLine();
|
||||
|
||||
const builds = await this.getBuildsAsync({
|
||||
graphqlClient,
|
||||
projectId,
|
||||
platform,
|
||||
fingerprint,
|
||||
});
|
||||
if (builds.length !== 0) {
|
||||
const build = builds[0];
|
||||
Log.succeed(
|
||||
`🎯 Found successful build with matching fingerprint on EAS servers. Running it...`
|
||||
);
|
||||
|
||||
if (build.artifacts?.applicationArchiveUrl) {
|
||||
await downloadAndRunAsync(build);
|
||||
await this.startDevServerAsync({ projectDir, platform });
|
||||
return;
|
||||
} else {
|
||||
Log.warn('Artifacts for this build expired. New build will be started.');
|
||||
}
|
||||
}
|
||||
|
||||
Log.log('🚀 No successful build with matching fingerprint found. Starting a new build...');
|
||||
|
||||
const previousBuildsForSelectedProfile = await this.getBuildsAsync({
|
||||
graphqlClient,
|
||||
projectId,
|
||||
platform,
|
||||
});
|
||||
if (
|
||||
previousBuildsForSelectedProfile.length > 0 &&
|
||||
previousBuildsForSelectedProfile[0].metrics?.buildDuration
|
||||
) {
|
||||
Log.log(
|
||||
`🕒 Previous build for "${buildProfile.profileName}" profile completed in ${Math.floor(
|
||||
previousBuildsForSelectedProfile[0].metrics.buildDuration / 60000
|
||||
)} minutes.`
|
||||
);
|
||||
}
|
||||
|
||||
await runBuildAndSubmitAsync({
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
projectDir,
|
||||
flags: {
|
||||
requestedPlatform:
|
||||
platform === Platform.ANDROID ? RequestedPlatform.Android : RequestedPlatform.Ios,
|
||||
nonInteractive: false,
|
||||
freezeCredentials: false,
|
||||
wait: true,
|
||||
clearCache: false,
|
||||
json: false,
|
||||
autoSubmit: false,
|
||||
localBuildOptions: {},
|
||||
repack: false,
|
||||
profile: flags.profile ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
||||
},
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
downloadSimBuildAutoConfirm: true,
|
||||
envOverride: env,
|
||||
});
|
||||
await this.startDevServerAsync({ projectDir, platform });
|
||||
}
|
||||
|
||||
private async selectPlatformAsync(platform?: Platform): Promise<Platform> {
|
||||
if (platform) {
|
||||
return platform;
|
||||
}
|
||||
const { resolvedPlatform } = await promptAsync({
|
||||
type: 'select',
|
||||
message: 'Select platform',
|
||||
name: 'resolvedPlatform',
|
||||
choices: [
|
||||
{ title: 'Android', value: Platform.ANDROID },
|
||||
{ title: 'iOS', value: Platform.IOS },
|
||||
],
|
||||
});
|
||||
return resolvedPlatform;
|
||||
}
|
||||
|
||||
private async validateBuildRunProfileAsync({
|
||||
platform,
|
||||
buildProfile,
|
||||
buildProfileName,
|
||||
}: {
|
||||
platform: Platform;
|
||||
buildProfile: BuildProfile;
|
||||
buildProfileName: string;
|
||||
}): Promise<void> {
|
||||
if (buildProfile.developmentClient !== true) {
|
||||
Errors.error(
|
||||
`Profile "${buildProfileName}" must specify "developmentClient: true" to create a dev client build. Select a different profile or update the profile in eas.json.`,
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
if (buildProfile.distribution !== 'internal') {
|
||||
Errors.error(
|
||||
`Profile "${buildProfileName}" must specify "distribution: internal" in order to work with eas build:dev command. Select a different profile or update the profile in eas.json.`,
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
|
||||
if (platform === Platform.IOS) {
|
||||
const iosProfile = buildProfile as BuildProfile<Platform.IOS>;
|
||||
if (iosProfile.simulator !== true && iosProfile.withoutCredentials !== true) {
|
||||
Errors.error(
|
||||
`Profile "${buildProfileName}" must specify "ios.simulator: true" or "withoutCredentials: true" to create an iOS simulator build. Select a different profile or update the profile in eas.json.`,
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const androidProfile = buildProfile as BuildProfile<Platform.ANDROID>;
|
||||
if (
|
||||
androidProfile.distribution !== 'internal' &&
|
||||
androidProfile.withoutCredentials !== true
|
||||
) {
|
||||
Errors.error(
|
||||
`Profile "${buildProfileName}" must specify "distribution: internal" or "withoutCredentials: true" to create an Android emulator build. Select a different profile or update the profile in eas.json.`,
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureValidBuildRunProfileExistsAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
selectedBuildProfileName,
|
||||
vcsClient,
|
||||
}: {
|
||||
projectDir: string;
|
||||
platform: Platform;
|
||||
selectedBuildProfileName?: string;
|
||||
vcsClient: Client;
|
||||
}): Promise<ProfileData<'build'>> {
|
||||
if (
|
||||
!!selectedBuildProfileName ||
|
||||
(await doesBuildProfileExistAsync({
|
||||
projectDir,
|
||||
profileName: DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
||||
}))
|
||||
) {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
const [buildProfile] = await getProfilesAsync({
|
||||
type: 'build',
|
||||
easJsonAccessor,
|
||||
platforms: [platform],
|
||||
profileName: selectedBuildProfileName ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
||||
projectDir,
|
||||
});
|
||||
await this.validateBuildRunProfileAsync({
|
||||
buildProfileName: selectedBuildProfileName ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
||||
platform,
|
||||
buildProfile: buildProfile.profile,
|
||||
});
|
||||
} else {
|
||||
const createBuildProfile = await confirmAsync({
|
||||
message: `We want to go ahead and generate "${DEFAULT_EAS_BUILD_RUN_PROFILE_NAME}" build profile for you, that matches eas build:dev criteria. Do you want to proceed?`,
|
||||
});
|
||||
if (!createBuildProfile) {
|
||||
Errors.error(
|
||||
'Come back later or specify different build compliant with eas build:dev requirements by using "--profile" flag.',
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
await createBuildProfileAsync({
|
||||
projectDir,
|
||||
profileName: DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
||||
profileContents: {
|
||||
developmentClient: true,
|
||||
distribution: 'internal',
|
||||
ios: {
|
||||
simulator: true,
|
||||
},
|
||||
environment: 'development',
|
||||
},
|
||||
nonInteractive: false,
|
||||
vcsClient,
|
||||
});
|
||||
}
|
||||
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
const [buildProfile] = await getProfilesAsync({
|
||||
type: 'build',
|
||||
easJsonAccessor,
|
||||
platforms: [platform],
|
||||
profileName: selectedBuildProfileName ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
||||
projectDir,
|
||||
});
|
||||
return buildProfile;
|
||||
}
|
||||
|
||||
private async getBuildsAsync({
|
||||
graphqlClient,
|
||||
projectId,
|
||||
platform,
|
||||
fingerprint,
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
projectId: string;
|
||||
platform: Platform;
|
||||
fingerprint?: { hash: string };
|
||||
}): Promise<BuildFragment[]> {
|
||||
return await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
filter: {
|
||||
platform: toAppPlatform(platform),
|
||||
fingerprintHash: fingerprint?.hash,
|
||||
status: BuildStatus.Finished,
|
||||
simulator: platform === Platform.IOS ? true : undefined,
|
||||
distribution: platform === Platform.ANDROID ? DistributionType.Internal : undefined,
|
||||
developmentClient: true,
|
||||
},
|
||||
offset: 0,
|
||||
limit: 1,
|
||||
});
|
||||
}
|
||||
|
||||
private async startDevServerAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
}: {
|
||||
projectDir: string;
|
||||
platform: Platform;
|
||||
}): Promise<void> {
|
||||
Log.newLine();
|
||||
Log.log(
|
||||
`Starting development server: ${chalk.dim(
|
||||
`npx expo start --dev-client ${platform === Platform.IOS ? '--ios' : '--android'}`
|
||||
)}`
|
||||
);
|
||||
await expoCommandAsync(projectDir, [
|
||||
'start',
|
||||
'--dev-client',
|
||||
platform === Platform.IOS ? '--ios' : '--android',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ interface RawBuildFlags {
|
||||
message?: string;
|
||||
'build-logger-level'?: LoggerLevel;
|
||||
'freeze-credentials': boolean;
|
||||
repack: boolean;
|
||||
}
|
||||
|
||||
export default class Build extends EasCommand {
|
||||
@@ -77,10 +78,12 @@ export default class Build extends EasCommand {
|
||||
description: 'Clear cache before the build',
|
||||
}),
|
||||
'auto-submit': Flags.boolean({
|
||||
char: 's',
|
||||
default: false,
|
||||
description:
|
||||
'Submit on build complete using the submit profile with the same name as the build profile',
|
||||
exclusive: ['auto-submit-with-profile'],
|
||||
aliases: ['submit'],
|
||||
}),
|
||||
'auto-submit-with-profile': Flags.string({
|
||||
description: 'Submit on build complete using the submit profile with provided name',
|
||||
@@ -109,6 +112,11 @@ export default class Build extends EasCommand {
|
||||
default: false,
|
||||
description: 'Prevent the build from updating credentials in non-interactive mode',
|
||||
}),
|
||||
repack: Flags.boolean({
|
||||
default: false,
|
||||
hidden: true,
|
||||
description: 'Use the golden dev client build repack flow as it works for onboarding',
|
||||
}),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
@@ -136,6 +144,7 @@ export default class Build extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(Build, {
|
||||
nonInteractive: flags.nonInteractive,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||
@@ -151,15 +160,15 @@ export default class Build extends EasCommand {
|
||||
|
||||
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
|
||||
|
||||
await runBuildAndSubmitAsync(
|
||||
await runBuildAndSubmitAsync({
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
projectDir,
|
||||
flagsWithPlatform,
|
||||
flags: flagsWithPlatform,
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync
|
||||
);
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
});
|
||||
}
|
||||
|
||||
private sanitizeFlags(
|
||||
@@ -223,6 +232,7 @@ export default class Build extends EasCommand {
|
||||
message,
|
||||
buildLoggerLevel: flags['build-logger-level'],
|
||||
freezeCredentials: flags['freeze-credentials'],
|
||||
repack: flags.repack,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ export default class BuildInspect extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildInspect, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const outputDirectory = path.resolve(process.cwd(), flags.output);
|
||||
@@ -98,12 +99,12 @@ export default class BuildInspect extends EasCommand {
|
||||
await this.copyToOutputDirAsync(tmpWorkingdir, outputDirectory);
|
||||
} else {
|
||||
try {
|
||||
await runBuildAndSubmitAsync(
|
||||
await runBuildAndSubmitAsync({
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
projectDir,
|
||||
{
|
||||
flags: {
|
||||
nonInteractive: false,
|
||||
freezeCredentials: false,
|
||||
wait: true,
|
||||
@@ -120,10 +121,11 @@ export default class BuildInspect extends EasCommand {
|
||||
workingdir: tmpWorkingdir,
|
||||
artifactsDir: path.join(tmpWorkingdir, 'artifacts'),
|
||||
},
|
||||
repack: false,
|
||||
},
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync
|
||||
);
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
});
|
||||
if (!flags.verbose) {
|
||||
Log.log(chalk.green('Build successful'));
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
|
||||
import GitClient from '../../vcs/clients/git';
|
||||
|
||||
/**
|
||||
* This command will be run on the EAS Build workers, when building
|
||||
@@ -63,17 +63,24 @@ export default class BuildInternal extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildInternal, {
|
||||
nonInteractive: true,
|
||||
vcsClientOverride: new GitNoCommitClient(),
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
if (vcsClient instanceof GitClient) {
|
||||
// `build:internal` is run on EAS workers and the repo may have been changed
|
||||
// by pre-install hooks or other scripts. We don't want to require committing changes
|
||||
// to continue the build.
|
||||
vcsClient.requireCommit = false;
|
||||
}
|
||||
|
||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||
|
||||
await runBuildAndSubmitAsync(
|
||||
await runBuildAndSubmitAsync({
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
projectDir,
|
||||
{
|
||||
flags: {
|
||||
requestedPlatform: flags.platform as RequestedPlatform,
|
||||
profile: flags.profile,
|
||||
nonInteractive: true,
|
||||
@@ -86,9 +93,10 @@ export default class BuildInternal extends EasCommand {
|
||||
localBuildMode: LocalBuildMode.INTERNAL,
|
||||
},
|
||||
submitProfile: flags['auto-submit-with-profile'] ?? flags.profile,
|
||||
repack: false,
|
||||
},
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync
|
||||
);
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -21,34 +21,50 @@ export default class BuildList extends EasCommand {
|
||||
|
||||
static override flags = {
|
||||
platform: Flags.enum({
|
||||
options: [RequestedPlatform.All, RequestedPlatform.Android, RequestedPlatform.Ios],
|
||||
options: Object.values(RequestedPlatform),
|
||||
char: 'p',
|
||||
}),
|
||||
status: Flags.enum({
|
||||
options: [
|
||||
BuildStatus.NEW,
|
||||
BuildStatus.IN_QUEUE,
|
||||
BuildStatus.IN_PROGRESS,
|
||||
BuildStatus.PENDING_CANCEL,
|
||||
BuildStatus.ERRORED,
|
||||
BuildStatus.FINISHED,
|
||||
BuildStatus.CANCELED,
|
||||
],
|
||||
options: Object.values(BuildStatus),
|
||||
description: 'Filter only builds with the specified status',
|
||||
}),
|
||||
distribution: Flags.enum({
|
||||
options: [
|
||||
BuildDistributionType.STORE,
|
||||
BuildDistributionType.INTERNAL,
|
||||
BuildDistributionType.SIMULATOR,
|
||||
],
|
||||
options: Object.values(BuildDistributionType),
|
||||
description: 'Filter only builds with the specified distribution type',
|
||||
}),
|
||||
channel: Flags.string(),
|
||||
appVersion: Flags.string(),
|
||||
appBuildVersion: Flags.string(),
|
||||
sdkVersion: Flags.string(),
|
||||
runtimeVersion: Flags.string(),
|
||||
appIdentifier: Flags.string(),
|
||||
buildProfile: Flags.string(),
|
||||
gitCommitHash: Flags.string(),
|
||||
'app-version': Flags.string({
|
||||
aliases: ['appVersion'],
|
||||
description: 'Filter only builds created with the specified main app version',
|
||||
}),
|
||||
'app-build-version': Flags.string({
|
||||
aliases: ['appBuildVersion'],
|
||||
description: 'Filter only builds created with the specified app build version',
|
||||
}),
|
||||
'sdk-version': Flags.string({
|
||||
aliases: ['sdkVersion'],
|
||||
description: 'Filter only builds created with the specified Expo SDK version',
|
||||
}),
|
||||
'runtime-version': Flags.string({
|
||||
aliases: ['runtimeVersion'],
|
||||
description: 'Filter only builds created with the specified runtime version',
|
||||
}),
|
||||
'app-identifier': Flags.string({
|
||||
aliases: ['appIdentifier'],
|
||||
description: 'Filter only builds created with the specified app identifier',
|
||||
}),
|
||||
'build-profile': Flags.string({
|
||||
char: 'e',
|
||||
aliases: ['profile', 'buildProfile'],
|
||||
description: 'Filter only builds created with the specified build profile',
|
||||
}),
|
||||
'git-commit-hash': Flags.string({
|
||||
aliases: ['gitCommitHash'],
|
||||
description: 'Filter only builds created with the specified git commit hash',
|
||||
}),
|
||||
'fingerprint-hash': Flags.string({
|
||||
description: 'Filter only builds with the specified fingerprint hash',
|
||||
}),
|
||||
...EasPaginatedQueryFlags,
|
||||
limit: getLimitFlagWithCustomValues({ defaultTo: 10, limit: BUILDS_LIMIT }),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
@@ -59,7 +75,7 @@ export default class BuildList extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
@@ -86,7 +102,7 @@ export default class BuildList extends EasCommand {
|
||||
process.exit(1);
|
||||
}
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BuildList, {
|
||||
nonInteractive,
|
||||
@@ -109,14 +125,15 @@ export default class BuildList extends EasCommand {
|
||||
status: graphqlBuildStatus,
|
||||
distribution: graphqlBuildDistribution,
|
||||
channel: flags.channel,
|
||||
appVersion: flags.appVersion,
|
||||
appBuildVersion: flags.appBuildVersion,
|
||||
sdkVersion: flags.sdkVersion,
|
||||
runtimeVersion: flags.runtimeVersion,
|
||||
appIdentifier: flags.appIdentifier,
|
||||
buildProfile: flags.buildProfile,
|
||||
gitCommitHash: flags.gitCommitHash,
|
||||
appVersion: flags['app-version'],
|
||||
appBuildVersion: flags['app-build-version'],
|
||||
sdkVersion: flags['sdk-version'],
|
||||
runtimeVersion: flags['runtime-version'],
|
||||
appIdentifier: flags['app-identifier'],
|
||||
buildProfile: flags['build-profile'],
|
||||
gitCommitHash: flags['git-commit-hash'],
|
||||
simulator: flags.simulator,
|
||||
fingerprintHash: flags['fingerprint-hash'],
|
||||
},
|
||||
paginatedQueryOptions,
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import chalk from 'chalk';
|
||||
|
||||
import { handleDeprecatedEasJsonAsync } from '.';
|
||||
import { waitForBuildEndAsync } from '../../build/build';
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../../build/evaluateConfigWithEnvVarsAsync';
|
||||
import { ensureIosCredentialsForBuildResignAsync } from '../../build/ios/credentials';
|
||||
import { prepareCredentialsToResign } from '../../build/ios/prepareJob';
|
||||
import { listAndSelectBuildOnAppAsync } from '../../build/queries';
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
BuildStatus,
|
||||
BuildWorkflow,
|
||||
DistributionType,
|
||||
EnvironmentVariableEnvironment,
|
||||
IosJobOverridesInput,
|
||||
ProjectArchiveSourceType,
|
||||
StatuspageServiceName,
|
||||
@@ -42,9 +44,11 @@ interface BuildResignFlags {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
platform?: Platform;
|
||||
profile?: string;
|
||||
targetProfile?: string;
|
||||
sourceProfile?: string;
|
||||
maybeBuildId?: string;
|
||||
wait: boolean;
|
||||
environment?: EnvironmentVariableEnvironment;
|
||||
}
|
||||
interface RawBuildResignFlags {
|
||||
json: boolean;
|
||||
@@ -52,9 +56,11 @@ interface RawBuildResignFlags {
|
||||
offset: number | undefined;
|
||||
limit: number | undefined;
|
||||
platform: 'android' | 'ios' | undefined;
|
||||
profile: string | undefined;
|
||||
'target-profile': string | undefined;
|
||||
'source-profile': string | undefined;
|
||||
wait: boolean;
|
||||
id: string | undefined;
|
||||
environment: EnvironmentVariableEnvironment | undefined;
|
||||
}
|
||||
|
||||
export default class BuildResign extends EasCommand {
|
||||
@@ -65,10 +71,16 @@ export default class BuildResign extends EasCommand {
|
||||
char: 'p',
|
||||
options: ['android', 'ios'],
|
||||
}),
|
||||
profile: Flags.string({
|
||||
'target-profile': Flags.string({
|
||||
char: 'e',
|
||||
description:
|
||||
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
'Name of the target build profile from eas.json. Credentials and environment variables from this profile will be used when re-signing. Defaults to "production" if defined in eas.json.',
|
||||
helpValue: 'PROFILE_NAME',
|
||||
aliases: ['profile'],
|
||||
}),
|
||||
'source-profile': Flags.string({
|
||||
description:
|
||||
'Name of the source build profile from eas.json. Used to filter builds eligible for re-signing.',
|
||||
helpValue: 'PROFILE_NAME',
|
||||
}),
|
||||
wait: Flags.boolean({
|
||||
@@ -108,6 +120,7 @@ export default class BuildResign extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildResign, {
|
||||
nonInteractive: flags.nonInteractive,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const maybeBuild = flags.maybeBuildId
|
||||
@@ -130,12 +143,28 @@ export default class BuildResign extends EasCommand {
|
||||
const buildProfile = await EasJsonUtils.getBuildProfileAsync(
|
||||
easJsonAccessor,
|
||||
platform,
|
||||
flags.profile ?? 'production'
|
||||
flags.targetProfile ?? 'production'
|
||||
);
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env });
|
||||
|
||||
const { exp, projectId, env } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile,
|
||||
buildProfileName: flags.targetProfile ?? 'production',
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: buildProfile.env },
|
||||
});
|
||||
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
const build = await this.ensureBuildSelectedAsync(
|
||||
{ graphqlClient, projectId, platform, nonInteractive, limit, offset },
|
||||
{
|
||||
graphqlClient,
|
||||
projectId,
|
||||
platform,
|
||||
nonInteractive,
|
||||
limit,
|
||||
offset,
|
||||
buildProfile: flags.sourceProfile,
|
||||
},
|
||||
maybeBuild
|
||||
);
|
||||
const credentialsCtx = new CredentialsContext({
|
||||
@@ -145,7 +174,7 @@ export default class BuildResign extends EasCommand {
|
||||
user: actor,
|
||||
graphqlClient,
|
||||
analytics,
|
||||
env: buildProfile.env,
|
||||
env,
|
||||
easJsonCliConfig,
|
||||
vcsClient,
|
||||
});
|
||||
@@ -165,7 +194,7 @@ export default class BuildResign extends EasCommand {
|
||||
projectDir,
|
||||
exp,
|
||||
xcodeBuildContext,
|
||||
env: buildProfile.env,
|
||||
env,
|
||||
vcsClient,
|
||||
});
|
||||
const credentialsResult = await ensureIosCredentialsForBuildResignAsync(
|
||||
@@ -228,9 +257,11 @@ export default class BuildResign extends EasCommand {
|
||||
offset: flags.offset,
|
||||
limit: flags.limit,
|
||||
platform: flags.platform as Platform | undefined,
|
||||
profile: flags.profile,
|
||||
sourceProfile: flags['source-profile'],
|
||||
targetProfile: flags['target-profile'],
|
||||
maybeBuildId: flags.id,
|
||||
wait: flags.wait,
|
||||
environment: flags.environment,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -242,6 +273,7 @@ export default class BuildResign extends EasCommand {
|
||||
nonInteractive,
|
||||
limit,
|
||||
offset,
|
||||
buildProfile,
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
projectId: string;
|
||||
@@ -249,6 +281,7 @@ export default class BuildResign extends EasCommand {
|
||||
nonInteractive: boolean;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
buildProfile?: string;
|
||||
},
|
||||
maybeBuild?: BuildFragment
|
||||
): Promise<BuildFragment> {
|
||||
@@ -268,6 +301,7 @@ export default class BuildResign extends EasCommand {
|
||||
distribution: DistributionType.Internal,
|
||||
platform: toAppPlatform(platform),
|
||||
status: BuildStatus.Finished,
|
||||
buildProfile,
|
||||
},
|
||||
});
|
||||
if (!build) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
import assert from 'assert';
|
||||
import { pathExists } from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import { getLatestBuildAsync, listAndSelectBuildOnAppAsync } from '../../build/queries';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
@@ -17,13 +16,12 @@ import Log from '../../log';
|
||||
import { appPlatformDisplayNames } from '../../platform';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { RunArchiveFlags, runAsync } from '../../run/run';
|
||||
import { RunArchiveFlags, getEasBuildRunCachedAppPath, runAsync } from '../../run/run';
|
||||
import { isRunnableOnSimulatorOrEmulator } from '../../run/utils';
|
||||
import {
|
||||
downloadAndMaybeExtractAppAsync,
|
||||
extractAppFromLocalArchiveAsync,
|
||||
} from '../../utils/download';
|
||||
import { getEasBuildRunCacheDirectoryPath } from '../../utils/paths';
|
||||
|
||||
interface RawRunFlags {
|
||||
latest?: boolean;
|
||||
@@ -79,8 +77,7 @@ export default class Run extends EasCommand {
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
|
||||
@@ -90,7 +87,7 @@ export default class Run extends EasCommand {
|
||||
const queryOptions = getPaginatedQueryOptions(flags);
|
||||
const {
|
||||
loggedIn: { graphqlClient },
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
} = await this.getContextAsync(Run, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
@@ -246,17 +243,6 @@ async function maybeGetBuildAsync(
|
||||
}
|
||||
}
|
||||
|
||||
function getEasBuildRunCachedAppPath(
|
||||
projectId: string,
|
||||
buildId: string,
|
||||
platform: AppPlatform
|
||||
): string {
|
||||
return path.join(
|
||||
getEasBuildRunCacheDirectoryPath(),
|
||||
`${projectId}_${buildId}.${platform === AppPlatform.Ios ? 'app' : 'apk'}`
|
||||
);
|
||||
}
|
||||
|
||||
async function getPathToSimulatorBuildAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
projectId: string,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { EasJsonAccessor } from '@expo/eas-json';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync';
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../../commandUtils/flags';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
@@ -53,6 +54,7 @@ export default class BuildVersionGetView extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildVersionGetView, {
|
||||
nonInteractive: true,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
if (!flags.platform && flags['non-interactive']) {
|
||||
@@ -74,8 +76,12 @@ export default class BuildVersionGetView extends EasCommand {
|
||||
});
|
||||
const results: { [key in Platform]?: string } = {};
|
||||
for (const { profile, platform } of buildProfiles) {
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
|
||||
env: profile.env,
|
||||
const { exp, projectId, env } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: profile,
|
||||
buildProfileName: flags.profile ?? 'production',
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: profile.env },
|
||||
});
|
||||
|
||||
validateAppConfigForRemoteVersionSource(exp, platform);
|
||||
@@ -89,6 +95,7 @@ export default class BuildVersionGetView extends EasCommand {
|
||||
platform,
|
||||
vcsClient,
|
||||
nonInteractive: flags['non-interactive'],
|
||||
env,
|
||||
});
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
graphqlClient,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync';
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
@@ -53,6 +54,7 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildVersionSetView, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const platform = await selectPlatformAsync(flags.platform);
|
||||
@@ -64,7 +66,13 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
flags.profile ?? undefined
|
||||
);
|
||||
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: profile.env });
|
||||
const { exp, projectId, env } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: profile,
|
||||
buildProfileName: flags.profile ?? 'production',
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: profile.env },
|
||||
});
|
||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
validateAppConfigForRemoteVersionSource(exp, platform);
|
||||
@@ -78,6 +86,7 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
platform,
|
||||
vcsClient,
|
||||
nonInteractive: false,
|
||||
env,
|
||||
});
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
graphqlClient,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../../build/android/version';
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync';
|
||||
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../../build/ios/version';
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
@@ -72,6 +73,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildVersionSyncView, {
|
||||
nonInteractive: true,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
|
||||
@@ -87,8 +89,12 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
projectDir,
|
||||
});
|
||||
for (const profileInfo of buildProfiles) {
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
|
||||
env: profileInfo.profile.env,
|
||||
const { exp, projectId, env } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: profileInfo.profile,
|
||||
buildProfileName: profileInfo.profileName,
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: profileInfo.profile.env },
|
||||
});
|
||||
|
||||
validateAppConfigForRemoteVersionSource(exp, profileInfo.platform);
|
||||
@@ -103,6 +109,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
platform: profileInfo.platform,
|
||||
vcsClient,
|
||||
nonInteractive: false,
|
||||
env,
|
||||
});
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
graphqlClient,
|
||||
@@ -113,7 +120,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, profileInfo.platform, vcsClient);
|
||||
if (!remoteVersions?.buildVersion) {
|
||||
Log.warn(
|
||||
`Skipping versions sync for ${platformDisplayName}. There are no versions configured on Expo servers, use "eas build:version:set" or run a build to initialize it.`
|
||||
`Skipping versions sync for ${platformDisplayName}. There are no versions configured on EAS, use "eas build:version:set" or run a build to initialize it.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -128,7 +135,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
continue;
|
||||
}
|
||||
if (profileInfo.platform === Platform.ANDROID) {
|
||||
this.syncAndroidAsync({
|
||||
await this.syncAndroidAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
profile: profileInfo.profile as BuildProfile<Platform.ANDROID>,
|
||||
@@ -137,7 +144,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
vcsClient,
|
||||
});
|
||||
} else {
|
||||
this.syncIosAsync({
|
||||
await this.syncIosAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
profile: profileInfo.profile as BuildProfile<Platform.IOS>,
|
||||
@@ -199,7 +206,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
||||
const isMultiFlavor =
|
||||
buildGradle.android?.productFlavors || buildGradle.android?.flavorDimensions;
|
||||
buildGradle.android?.productFlavors ?? buildGradle.android?.flavorDimensions;
|
||||
if (isMultiFlavor) {
|
||||
throw new Error(
|
||||
"Versions in native code can't be automatically synced in multi-flavor Android projects. If you are using EAS Build with app version source set to remote, the correct values will be injected at the build time."
|
||||
|
||||
Loaded 100 of 406 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user