mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
329
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d1bf434ec | ||
|
|
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 |
No files matched your search
@@ -5,3 +5,4 @@
|
|||||||
packages/eas-cli/src/graphql/generated.ts
|
packages/eas-cli/src/graphql/generated.ts
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
|
ts-declarations
|
||||||
@@ -30,12 +30,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
'no-underscore-dangle': ['error', { allow: ['__typename'] }],
|
'no-underscore-dangle': ['error', { allow: ['__typename'] }],
|
||||||
'@typescript-eslint/explicit-function-return-type': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
allowExpressions: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'async-protect/async-suffix': 'error',
|
'async-protect/async-suffix': 'error',
|
||||||
'node/no-sync': '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
|
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: ${{ 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: |
|
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.
|
❌ It looks like a changelog entry is missing for this PR. Add it manually to CHANGELOG.md.
|
||||||
🤖 To use the bot, simply comment on this PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`.
|
|
||||||
⏩ If this PR doesn't require a changelog entry, such as if it's an internal change that doesn't affect the user experience, you can add the "no changelog" label to the PR.
|
⏩ 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 }}
|
||||||
+418
@@ -8,10 +8,428 @@ This is the log of notable changes to EAS CLI and related packages.
|
|||||||
|
|
||||||
### 🎉 New features
|
### 🎉 New features
|
||||||
|
|
||||||
|
- 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))
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
### 🧹 Chores
|
### 🧹 Chores
|
||||||
|
|
||||||
|
- Fix tests for VisionOS type.
|
||||||
|
- 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))
|
- 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
|
## [10.0.2](https://github.com/expo/eas-cli/releases/tag/v10.0.2) - 2024-06-17
|
||||||
|
|||||||
-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",
|
"npmClient": "yarn",
|
||||||
"version": "10.0.2",
|
"version": "14.5.0",
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -23,7 +23,7 @@
|
|||||||
"@types/jest": "29.5.11",
|
"@types/jest": "29.5.11",
|
||||||
"@types/node": "20.11.0",
|
"@types/node": "20.11.0",
|
||||||
"eslint": "8.56.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-async-protect": "3.1.0",
|
||||||
"eslint-plugin-node": "11.1.0",
|
"eslint-plugin-node": "11.1.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
|
|||||||
+579
-169
File diff suppressed because it is too large.
Load diff
@@ -7,6 +7,7 @@ documents:
|
|||||||
- 'src/commands/**/*.ts'
|
- 'src/commands/**/*.ts'
|
||||||
- 'src/branch/**/*.ts'
|
- 'src/branch/**/*.ts'
|
||||||
- 'src/channel/**/*.ts'
|
- 'src/channel/**/*.ts'
|
||||||
|
- 'src/worker/**/*.ts'
|
||||||
generates:
|
generates:
|
||||||
src/graphql/generated.ts:
|
src/graphql/generated.ts:
|
||||||
plugins:
|
plugins:
|
||||||
|
|||||||
Generated
+16473
-635
File diff suppressed because it is too large.
Load diff
@@ -1,34 +1,34 @@
|
|||||||
{
|
{
|
||||||
"name": "eas-cli",
|
"name": "eas-cli",
|
||||||
"description": "EAS command line tool",
|
"description": "EAS command line tool",
|
||||||
"version": "10.0.2",
|
"version": "14.5.0",
|
||||||
"author": "Expo <[email protected]>",
|
"author": "Expo <[email protected]>",
|
||||||
"bin": {
|
"bin": {
|
||||||
"eas": "./bin/run"
|
"eas": "./bin/run"
|
||||||
},
|
},
|
||||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/apple-utils": "1.7.0",
|
"@expo/apple-utils": "2.1.5",
|
||||||
"@expo/code-signing-certificates": "0.0.5",
|
"@expo/code-signing-certificates": "0.0.5",
|
||||||
"@expo/config": "8.5.4",
|
"@expo/config": "10.0.6",
|
||||||
"@expo/config-plugins": "7.8.4",
|
"@expo/config-plugins": "9.0.12",
|
||||||
"@expo/config-types": "50.0.0",
|
"@expo/eas-build-job": "1.0.165",
|
||||||
"@expo/eas-build-job": "1.0.117",
|
"@expo/eas-json": "14.5.0",
|
||||||
"@expo/eas-json": "10.0.0",
|
"@expo/env": "^1.0.0",
|
||||||
"@expo/json-file": "8.2.37",
|
"@expo/json-file": "8.3.3",
|
||||||
"@expo/logger": "1.0.117",
|
"@expo/logger": "1.0.117",
|
||||||
"@expo/multipart-body-parser": "1.1.0",
|
"@expo/multipart-body-parser": "2.0.0",
|
||||||
"@expo/osascript": "2.0.33",
|
"@expo/osascript": "2.1.4",
|
||||||
"@expo/package-manager": "1.1.2",
|
"@expo/package-manager": "1.7.0",
|
||||||
"@expo/pkcs12": "0.0.8",
|
"@expo/pkcs12": "0.1.3",
|
||||||
"@expo/plist": "0.0.20",
|
"@expo/plist": "0.2.0",
|
||||||
"@expo/plugin-help": "5.1.23",
|
"@expo/plugin-help": "5.1.23",
|
||||||
"@expo/plugin-warn-if-update-available": "2.5.1",
|
"@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/results": "1.0.0",
|
||||||
"@expo/rudder-sdk-node": "1.1.1",
|
"@expo/rudder-sdk-node": "1.1.1",
|
||||||
"@expo/spawn-async": "1.7.0",
|
"@expo/spawn-async": "1.7.2",
|
||||||
"@expo/steps": "1.0.118",
|
"@expo/steps": "1.0.166",
|
||||||
"@expo/timeago.js": "1.0.0",
|
"@expo/timeago.js": "1.0.0",
|
||||||
"@oclif/core": "^1.26.2",
|
"@oclif/core": "^1.26.2",
|
||||||
"@oclif/plugin-autocomplete": "^2.3.10",
|
"@oclif/plugin-autocomplete": "^2.3.10",
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
"chalk": "4.1.2",
|
"chalk": "4.1.2",
|
||||||
"cli-progress": "3.12.0",
|
"cli-progress": "3.12.0",
|
||||||
"dateformat": "4.6.3",
|
"dateformat": "4.6.3",
|
||||||
|
"diff": "7.0.0",
|
||||||
"dotenv": "16.3.1",
|
"dotenv": "16.3.1",
|
||||||
"env-paths": "2.2.0",
|
"env-paths": "2.2.0",
|
||||||
"envinfo": "7.11.0",
|
"envinfo": "7.11.0",
|
||||||
@@ -63,7 +64,8 @@
|
|||||||
"log-symbols": "4.1.0",
|
"log-symbols": "4.1.0",
|
||||||
"mime": "3.0.0",
|
"mime": "3.0.0",
|
||||||
"minimatch": "5.1.2",
|
"minimatch": "5.1.2",
|
||||||
"nanoid": "3.3.4",
|
"minizlib": "3.0.1",
|
||||||
|
"nanoid": "3.3.8",
|
||||||
"node-fetch": "2.6.7",
|
"node-fetch": "2.6.7",
|
||||||
"node-forge": "1.3.1",
|
"node-forge": "1.3.1",
|
||||||
"nullthrows": "1.1.1",
|
"nullthrows": "1.1.1",
|
||||||
@@ -78,20 +80,25 @@
|
|||||||
"semver": "7.5.4",
|
"semver": "7.5.4",
|
||||||
"slash": "3.0.0",
|
"slash": "3.0.0",
|
||||||
"tar": "6.2.1",
|
"tar": "6.2.1",
|
||||||
|
"tar-stream": "3.1.7",
|
||||||
"terminal-link": "2.1.1",
|
"terminal-link": "2.1.1",
|
||||||
"tslib": "2.6.2",
|
"tslib": "2.6.2",
|
||||||
"turndown": "7.1.2",
|
"turndown": "7.1.2",
|
||||||
"untildify": "4.0.0",
|
"untildify": "4.0.0",
|
||||||
"uuid": "9.0.1",
|
"uuid": "9.0.1",
|
||||||
"wrap-ansi": "7.0.0"
|
"wrap-ansi": "7.0.0",
|
||||||
|
"yaml": "2.6.0",
|
||||||
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "5.0.0",
|
"@graphql-codegen/cli": "5.0.0",
|
||||||
"@graphql-codegen/introspection": "4.0.0",
|
"@graphql-codegen/introspection": "4.0.0",
|
||||||
"@graphql-codegen/typescript": "4.0.1",
|
"@graphql-codegen/typescript": "4.0.1",
|
||||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||||
|
"@tsconfig/node18": "18.2.4",
|
||||||
"@types/cli-progress": "3.11.5",
|
"@types/cli-progress": "3.11.5",
|
||||||
"@types/dateformat": "3.0.1",
|
"@types/dateformat": "3.0.1",
|
||||||
|
"@types/diff": "6.0.0",
|
||||||
"@types/envinfo": "7.8.3",
|
"@types/envinfo": "7.8.3",
|
||||||
"@types/express": "4.17.15",
|
"@types/express": "4.17.15",
|
||||||
"@types/fs-extra": "11.0.4",
|
"@types/fs-extra": "11.0.4",
|
||||||
@@ -105,12 +112,13 @@
|
|||||||
"@types/prompts": "2.4.2",
|
"@types/prompts": "2.4.2",
|
||||||
"@types/semver": "7.5.6",
|
"@types/semver": "7.5.6",
|
||||||
"@types/tar": "6.1.10",
|
"@types/tar": "6.1.10",
|
||||||
|
"@types/tar-stream": "3.1.3",
|
||||||
"@types/tough-cookie": "4.0.2",
|
"@types/tough-cookie": "4.0.2",
|
||||||
"@types/uuid": "9.0.7",
|
"@types/uuid": "9.0.7",
|
||||||
"@types/wrap-ansi": "3.0.0",
|
"@types/wrap-ansi": "3.0.0",
|
||||||
"axios": "0.27.2",
|
"axios": "0.28.1",
|
||||||
"eslint-plugin-graphql": "4.0.0",
|
"eslint-plugin-graphql": "4.0.0",
|
||||||
"express": "4.19.2",
|
"express": "4.20.0",
|
||||||
"memfs": "3.4.13",
|
"memfs": "3.4.13",
|
||||||
"mockdate": "3.0.5",
|
"mockdate": "3.0.5",
|
||||||
"nock": "13.4.0",
|
"nock": "13.4.0",
|
||||||
@@ -172,16 +180,22 @@
|
|||||||
"secret": {
|
"secret": {
|
||||||
"description": "manage project and account secrets"
|
"description": "manage project and account secrets"
|
||||||
},
|
},
|
||||||
|
"env": {
|
||||||
|
"description": "manage project and account environment variables"
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"description": "manage individual updates"
|
"description": "manage individual updates"
|
||||||
},
|
},
|
||||||
"webhook": {
|
"webhook": {
|
||||||
"description": "manage webhooks"
|
"description": "manage webhooks"
|
||||||
|
},
|
||||||
|
"workflow": {
|
||||||
|
"description": "manage workflows"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warn-if-update-available": {
|
"warn-if-update-available": {
|
||||||
"timeoutInDays": 0.5,
|
"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": {
|
"update": {
|
||||||
"node": {
|
"node": {
|
||||||
|
|||||||
@@ -1,6 +1,20 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-06/schema",
|
"$schema": "http://json-schema.org/draft-06/schema",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"configVersion": {
|
||||||
|
"enum": [0],
|
||||||
|
"description": "The EAS metadata store configuration version"
|
||||||
|
},
|
||||||
|
"apple": {
|
||||||
|
"$ref": "#/definitions/AppleConfig"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"configVersion",
|
||||||
|
"apple"
|
||||||
|
],
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"apple": {
|
"apple": {
|
||||||
"AppleKidsAge": {
|
"AppleKidsAge": {
|
||||||
@@ -323,7 +337,8 @@
|
|||||||
"gambling",
|
"gambling",
|
||||||
"unrestrictedWebAccess",
|
"unrestrictedWebAccess",
|
||||||
"kidsAgeBand",
|
"kidsAgeBand",
|
||||||
"seventeenPlus"
|
"ageRatingOverride",
|
||||||
|
"koreaAgeRatingOverride"
|
||||||
],
|
],
|
||||||
"defaultSnippets": [
|
"defaultSnippets": [
|
||||||
{
|
{
|
||||||
@@ -345,7 +360,9 @@
|
|||||||
"gambling": false,
|
"gambling": false,
|
||||||
"unrestrictedWebAccess": false,
|
"unrestrictedWebAccess": false,
|
||||||
"kidsAgeBand": null,
|
"kidsAgeBand": null,
|
||||||
"seventeenPlus": false
|
"ageRatingOverride": "NONE",
|
||||||
|
"koreaAgeRatingOverride": "NONE",
|
||||||
|
"lootBox": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -367,7 +384,9 @@
|
|||||||
"unrestrictedWebAccess": false,
|
"unrestrictedWebAccess": false,
|
||||||
"gambling": false,
|
"gambling": false,
|
||||||
"kidsAgeBand": null,
|
"kidsAgeBand": null,
|
||||||
"seventeenPlus": false
|
"ageRatingOverride": "${13|NONE,SEVENTEEN_PLUS,UNRATED|}",
|
||||||
|
"koreaAgeRatingOverride": "${14|NONE,FIFTEEN_PLUS,NINETEEN_PLUS|}",
|
||||||
|
"lootBox": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -442,8 +461,24 @@
|
|||||||
},
|
},
|
||||||
"seventeenPlus": {
|
"seventeenPlus": {
|
||||||
"type": "boolean",
|
"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",
|
"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)"
|
"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
|
"maxLength": 4000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"AppleAgeRatingOverride": {
|
||||||
|
"enum": [
|
||||||
|
"NONE",
|
||||||
|
"SEVENTEEN_PLUS",
|
||||||
|
"UNRATED"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"AppleKoreaAgeRatingOverride": {
|
||||||
|
"enum": [
|
||||||
|
"NONE",
|
||||||
|
"FIFTEEN_PLUS",
|
||||||
|
"NINETEEN_PLUS"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AppleConfig": {
|
"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
|
// 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."`;
|
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 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 BuildVersionGetView from '../../commands/build/version/get';
|
||||||
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
|
import { AppVersionSourceUpdateOption } from '../../project/remoteVersionSource';
|
||||||
|
import * as prompts from '../../prompts';
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
|
|
||||||
jest.mock('../../project/applicationIdentifier');
|
jest.mock('../../project/applicationIdentifier');
|
||||||
@@ -12,16 +20,7 @@ jest.mock('fs');
|
|||||||
jest.mock('../../log');
|
jest.mock('../../log');
|
||||||
jest.mock('../../utils/json');
|
jest.mock('../../utils/json');
|
||||||
jest.mock('../../graphql/queries/AppVersionQuery');
|
jest.mock('../../graphql/queries/AppVersionQuery');
|
||||||
|
jest.mock('../../prompts');
|
||||||
function withRemoteVersionSource(easJson: EasJson): EasJson {
|
|
||||||
return {
|
|
||||||
...easJson,
|
|
||||||
cli: {
|
|
||||||
...easJson.cli,
|
|
||||||
appVersionSource: AppVersionSource.REMOTE,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe(BuildVersionGetView, () => {
|
describe(BuildVersionGetView, () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -49,6 +48,29 @@ describe(BuildVersionGetView, () => {
|
|||||||
expect(printJsonOnlyOutput).not.toHaveBeenCalled();
|
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 () => {
|
test('reading version for platform android when no remote version is set', async () => {
|
||||||
const ctx = mockCommandContext(BuildVersionGetView, {
|
const ctx = mockCommandContext(BuildVersionGetView, {
|
||||||
easJson: withRemoteVersionSource(getMockEasJson()),
|
easJson: withRemoteVersionSource(getMockEasJson()),
|
||||||
@@ -145,7 +167,9 @@ describe(BuildVersionGetView, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('reading version when appVersionSource is set to local ', async () => {
|
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 () => ({
|
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||||
buildVersion: '100',
|
buildVersion: '100',
|
||||||
storeVersion: '1.0.0',
|
storeVersion: '1.0.0',
|
||||||
@@ -158,4 +182,51 @@ describe(BuildVersionGetView, () => {
|
|||||||
);
|
);
|
||||||
await expect(cmd.run()).rejects.toThrowErrorMatchingSnapshot();
|
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 fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
@@ -8,12 +7,15 @@ import {
|
|||||||
mockCommandContext,
|
mockCommandContext,
|
||||||
mockProjectId,
|
mockProjectId,
|
||||||
mockTestCommand,
|
mockTestCommand,
|
||||||
|
withLocalVersionSource,
|
||||||
|
withRemoteVersionSource,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import BuildVersionSetView from '../../commands/build/version/set';
|
import BuildVersionSetView from '../../commands/build/version/set';
|
||||||
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
|
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
|
||||||
import { AppQuery } from '../../graphql/queries/AppQuery';
|
import { AppQuery } from '../../graphql/queries/AppQuery';
|
||||||
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
|
import { AppVersionSourceUpdateOption } from '../../project/remoteVersionSource';
|
||||||
import * as prompts from '../../prompts';
|
import * as prompts from '../../prompts';
|
||||||
|
|
||||||
jest.mock('../../project/applicationIdentifier');
|
jest.mock('../../project/applicationIdentifier');
|
||||||
@@ -25,16 +27,6 @@ jest.mock('../../log');
|
|||||||
jest.mock('../../prompts');
|
jest.mock('../../prompts');
|
||||||
jest.mock('../../utils/json');
|
jest.mock('../../utils/json');
|
||||||
|
|
||||||
function withRemoteVersionSource(easJson: EasJson): EasJson {
|
|
||||||
return {
|
|
||||||
...easJson,
|
|
||||||
cli: {
|
|
||||||
...easJson.cli,
|
|
||||||
appVersionSource: AppVersionSource.REMOTE,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe(BuildVersionSetView, () => {
|
describe(BuildVersionSetView, () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks();
|
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 () => {
|
test('printing current remote version before prompting for a new one', async () => {
|
||||||
const ctx = mockCommandContext(BuildVersionSetView, {
|
const ctx = mockCommandContext(BuildVersionSetView, {
|
||||||
easJson: withRemoteVersionSource(getMockEasJson()),
|
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 () => {
|
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(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => false);
|
jest.mocked(prompts.confirmAsync).mockImplementation(async () => false);
|
||||||
@@ -125,10 +147,26 @@ describe(BuildVersionSetView, () => {
|
|||||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
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, {});
|
const ctx = mockCommandContext(BuildVersionSetView, {});
|
||||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
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);
|
jest.mocked(prompts.confirmAsync).mockImplementation(async () => true);
|
||||||
|
|
||||||
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
|
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
|
||||||
@@ -137,4 +175,33 @@ describe(BuildVersionSetView, () => {
|
|||||||
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
|
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
|
||||||
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
|
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 { Workflow } from '@expo/eas-build-job';
|
||||||
import { AppVersionSource, EasJson } from '@expo/eas-json';
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
@@ -9,6 +8,8 @@ import {
|
|||||||
mockCommandContext,
|
mockCommandContext,
|
||||||
mockProjectId,
|
mockProjectId,
|
||||||
mockTestCommand,
|
mockTestCommand,
|
||||||
|
withLocalVersionSource,
|
||||||
|
withRemoteVersionSource,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../build/android/version';
|
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../build/android/version';
|
||||||
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../build/ios/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 { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
||||||
import { getAppBuildGradleAsync } from '../../project/android/gradleUtils';
|
import { getAppBuildGradleAsync } from '../../project/android/gradleUtils';
|
||||||
import { resolveTargetsAsync } from '../../project/ios/target';
|
import { resolveTargetsAsync } from '../../project/ios/target';
|
||||||
|
import { AppVersionSourceUpdateOption } from '../../project/remoteVersionSource';
|
||||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||||
import * as prompts from '../../prompts';
|
import * as prompts from '../../prompts';
|
||||||
|
|
||||||
@@ -37,16 +39,6 @@ jest.mock('../../log');
|
|||||||
jest.mock('../../prompts');
|
jest.mock('../../prompts');
|
||||||
jest.mock('../../utils/json');
|
jest.mock('../../utils/json');
|
||||||
|
|
||||||
function withRemoteVersionSource(easJson: EasJson): EasJson {
|
|
||||||
return {
|
|
||||||
...easJson,
|
|
||||||
cli: {
|
|
||||||
...easJson.cli,
|
|
||||||
appVersionSource: AppVersionSource.REMOTE,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe(BuildVersionSyncView, () => {
|
describe(BuildVersionSyncView, () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
@@ -79,6 +71,34 @@ describe(BuildVersionSyncView, () => {
|
|||||||
expect(syncAndroidAsync).not.toHaveBeenCalled();
|
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 () => {
|
test('syncing version for bare project on platform android', async () => {
|
||||||
const ctx = mockCommandContext(BuildVersionSyncView, {
|
const ctx = mockCommandContext(BuildVersionSyncView, {
|
||||||
easJson: withRemoteVersionSource(getMockEasJson()),
|
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 () => {
|
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(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => false);
|
jest.mocked(prompts.confirmAsync).mockImplementation(async () => false);
|
||||||
@@ -189,10 +211,26 @@ describe(BuildVersionSyncView, () => {
|
|||||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
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, {});
|
const ctx = mockCommandContext(BuildVersionSyncView, {});
|
||||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
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);
|
jest.mocked(prompts.confirmAsync).mockImplementation(async () => true);
|
||||||
|
|
||||||
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
|
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
|
||||||
@@ -201,4 +239,33 @@ describe(BuildVersionSyncView, () => {
|
|||||||
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
|
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
|
||||||
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
|
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 { ExpoConfig } from '@expo/config';
|
||||||
import { EasJson } from '@expo/eas-json';
|
import { AppVersionSource, EasJson } from '@expo/eas-json';
|
||||||
import { Command, Config } from '@oclif/core';
|
import { Command, Config } from '@oclif/core';
|
||||||
import { vol } from 'memfs';
|
import { vol } from 'memfs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
@@ -148,3 +148,23 @@ export const getError = <TError = any>(call: () => unknown): TError | NoErrorThr
|
|||||||
return error as TError;
|
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> {
|
export class SelectBranch implements EASUpdateAction<UpdateBranchBasicInfoFragment | null> {
|
||||||
constructor(
|
constructor(
|
||||||
private options: {
|
private readonly options: {
|
||||||
filterPredicate?: (branchInfo: UpdateBranchBasicInfoFragment) => boolean;
|
filterPredicate?: (branchInfo: UpdateBranchBasicInfoFragment) => boolean;
|
||||||
printedType?: string;
|
printedType?: string;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
@@ -91,8 +91,8 @@ export class SelectBranch implements EASUpdateAction<UpdateBranchBasicInfoFragme
|
|||||||
queryParams: QueryParams
|
queryParams: QueryParams
|
||||||
): Promise<Connection<UpdateBranchBasicInfoFragment>> =>
|
): Promise<Connection<UpdateBranchBasicInfoFragment>> =>
|
||||||
filterPredicate
|
filterPredicate
|
||||||
? this.filterQueryAsync(ctx, queryParams, filterPredicate)
|
? await this.filterQueryAsync(ctx, queryParams, filterPredicate)
|
||||||
: this.queryAsync(ctx, queryParams);
|
: await this.queryAsync(ctx, queryParams);
|
||||||
|
|
||||||
const getTitleAsync = async (branchInfo: UpdateBranchBasicInfoFragment): Promise<string> =>
|
const getTitleAsync = async (branchInfo: UpdateBranchBasicInfoFragment): Promise<string> =>
|
||||||
branchInfo.name;
|
branchInfo.name;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
import { print } from 'graphql';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
import { BranchNotFoundError } from './utils';
|
import { BranchNotFoundError } from './utils';
|
||||||
@@ -8,10 +9,11 @@ import { withErrorHandlingAsync } from '../graphql/client';
|
|||||||
import {
|
import {
|
||||||
CreateUpdateBranchForAppMutation,
|
CreateUpdateBranchForAppMutation,
|
||||||
CreateUpdateBranchForAppMutationVariables,
|
CreateUpdateBranchForAppMutationVariables,
|
||||||
UpdateBranch,
|
UpdateBranchBasicInfoFragment,
|
||||||
UpdateBranchFragment,
|
UpdateBranchFragment,
|
||||||
} from '../graphql/generated';
|
} from '../graphql/generated';
|
||||||
import { BranchQuery } from '../graphql/queries/BranchQuery';
|
import { BranchQuery } from '../graphql/queries/BranchQuery';
|
||||||
|
import { UpdateBranchBasicInfoFragmentNode } from '../graphql/types/UpdateBranchBasicInfo';
|
||||||
import Log from '../log';
|
import Log from '../log';
|
||||||
import { formatBranch, getBranchDescription } from '../update/utils';
|
import { formatBranch, getBranchDescription } from '../update/utils';
|
||||||
import { printJsonOnlyOutput } from '../utils/json';
|
import { printJsonOnlyOutput } from '../utils/json';
|
||||||
@@ -84,7 +86,9 @@ export async function listAndRenderBranchesOnAppAsync(
|
|||||||
queryBranchesOnProjectAsync(graphqlClient, limit, offset, projectId),
|
queryBranchesOnProjectAsync(graphqlClient, limit, offset, projectId),
|
||||||
promptOptions: {
|
promptOptions: {
|
||||||
title: 'Load more branches?',
|
title: 'Load more branches?',
|
||||||
renderListItems: branches => renderPageOfBranches(branches, paginatedQueryOptions),
|
renderListItems: branches => {
|
||||||
|
renderPageOfBranches(branches, paginatedQueryOptions);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -124,7 +128,7 @@ function renderPageOfBranches(
|
|||||||
export async function createUpdateBranchOnAppAsync(
|
export async function createUpdateBranchOnAppAsync(
|
||||||
graphqlClient: ExpoGraphqlClient,
|
graphqlClient: ExpoGraphqlClient,
|
||||||
{ appId, name }: CreateUpdateBranchForAppMutationVariables
|
{ appId, name }: CreateUpdateBranchForAppMutationVariables
|
||||||
): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
|
): Promise<UpdateBranchBasicInfoFragment> {
|
||||||
const result = await withErrorHandlingAsync(
|
const result = await withErrorHandlingAsync(
|
||||||
graphqlClient
|
graphqlClient
|
||||||
.mutation<CreateUpdateBranchForAppMutation, CreateUpdateBranchForAppMutationVariables>(
|
.mutation<CreateUpdateBranchForAppMutation, CreateUpdateBranchForAppMutationVariables>(
|
||||||
@@ -133,15 +137,17 @@ export async function createUpdateBranchOnAppAsync(
|
|||||||
updateBranch {
|
updateBranch {
|
||||||
createUpdateBranchForApp(appId: $appId, name: $name) {
|
createUpdateBranchForApp(appId: $appId, name: $name) {
|
||||||
id
|
id
|
||||||
name
|
...UpdateBranchBasicInfoFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
${print(UpdateBranchBasicInfoFragmentNode)}
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
appId,
|
appId,
|
||||||
name,
|
name,
|
||||||
}
|
},
|
||||||
|
{ additionalTypenames: ['UpdateBranch'] }
|
||||||
)
|
)
|
||||||
.toPromise()
|
.toPromise()
|
||||||
);
|
);
|
||||||
@@ -161,22 +167,21 @@ export async function ensureBranchExistsAsync(
|
|||||||
appId: string;
|
appId: string;
|
||||||
branchName: string;
|
branchName: string;
|
||||||
}
|
}
|
||||||
): Promise<{ branchId: string; createdBranch: boolean }> {
|
): Promise<{ branch: UpdateBranchBasicInfoFragment; createdBranch: boolean }> {
|
||||||
try {
|
try {
|
||||||
const updateBranch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
const updateBranch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||||
appId,
|
appId,
|
||||||
name: branchName,
|
name: branchName,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { id } = updateBranch;
|
return { branch: updateBranch, createdBranch: false };
|
||||||
return { branchId: id, createdBranch: false };
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof BranchNotFoundError) {
|
if (error instanceof BranchNotFoundError) {
|
||||||
const newUpdateBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
|
const newUpdateBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
|
||||||
appId,
|
appId,
|
||||||
name: branchName,
|
name: branchName,
|
||||||
});
|
});
|
||||||
return { branchId: newUpdateBranch.id, createdBranch: true };
|
return { branch: newUpdateBranch, createdBranch: true };
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { Client } from '../vcs/vcs';
|
import { Client } from '../vcs/vcs';
|
||||||
|
|
||||||
export async function getDefaultBranchNameAsync(vcsClient: Client): Promise<string> {
|
export async function getDefaultBranchNameAsync(vcsClient: Client): Promise<string | null> {
|
||||||
return (
|
return await vcsClient.getBranchNameAsync();
|
||||||
(await vcsClient.getBranchNameAsync()) || `branch-${Math.random().toString(36).substring(2, 4)}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BranchNotFoundError extends Error {
|
export class BranchNotFoundError extends Error {
|
||||||
|
|||||||
@@ -47,14 +47,14 @@ describe(selectBuildToCancelAsync.name, () => {
|
|||||||
it('does not return build id when confirmation is rejected', async () => {
|
it('does not return build id when confirmation is rejected', async () => {
|
||||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||||
jest.mocked(confirmAsync).mockResolvedValueOnce(false);
|
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 () => {
|
it('returns build id when confirmation is confirmed', async () => {
|
||||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||||
jest.mocked(selectAsync).mockResolvedValueOnce(selectedBuildId);
|
jest.mocked(selectAsync).mockResolvedValueOnce(selectedBuildId);
|
||||||
jest.mocked(confirmAsync).mockResolvedValueOnce(true);
|
jest.mocked(confirmAsync).mockResolvedValueOnce(true);
|
||||||
expect(selectBuildToCancelAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
await expect(selectBuildToCancelAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
||||||
selectedBuildId
|
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());
|
||||||
|
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());
|
||||||
|
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 () => {
|
it('does not return build id when confirmation is rejected', async () => {
|
||||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||||
jest.mocked(confirmAsync).mockResolvedValueOnce(false);
|
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 () => {
|
it('returns build id when confirmation is confirmed', async () => {
|
||||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||||
jest.mocked(selectAsync).mockResolvedValueOnce(selectedBuildId);
|
jest.mocked(selectAsync).mockResolvedValueOnce(selectedBuildId);
|
||||||
jest.mocked(confirmAsync).mockResolvedValueOnce(true);
|
jest.mocked(confirmAsync).mockResolvedValueOnce(true);
|
||||||
expect(selectBuildToDeleteAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
await expect(selectBuildToDeleteAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
||||||
selectedBuildId
|
selectedBuildId
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,23 +1,43 @@
|
|||||||
import { ExpoConfig } from '@expo/config';
|
import { ExpoConfig } from '@expo/config';
|
||||||
import { Platform } from '@expo/eas-build-job';
|
import { Platform } from '@expo/eas-build-job';
|
||||||
import { BuildProfile } from '@expo/eas-json';
|
import { BuildProfile } from '@expo/eas-json';
|
||||||
|
import { AndroidBuildProfile } from '@expo/eas-json/build/build/types';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { vol } from 'memfs';
|
import { vol } from 'memfs';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
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 { getAppBuildGradleAsync, resolveConfigValue } from '../../../project/android/gradleUtils';
|
||||||
import { resolveVcsClient } from '../../../vcs';
|
import { resolveVcsClient } from '../../../vcs';
|
||||||
|
import { Client } from '../../../vcs/vcs';
|
||||||
import {
|
import {
|
||||||
BumpStrategy,
|
BumpStrategy,
|
||||||
bumpVersionAsync,
|
bumpVersionAsync,
|
||||||
bumpVersionInAppJsonAsync,
|
bumpVersionInAppJsonAsync,
|
||||||
maybeResolveVersionsAsync,
|
maybeResolveVersionsAsync,
|
||||||
|
resolveRemoteVersionCodeAsync,
|
||||||
} from '../version';
|
} from '../version';
|
||||||
|
|
||||||
const fsReal = jest.requireActual('fs').promises as typeof fs;
|
const fsReal = jest.requireActual('fs').promises as typeof fs;
|
||||||
jest.mock('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();
|
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({
|
function initBareWorkflowProject({
|
||||||
versionCode = 123,
|
versionCode = 123,
|
||||||
versionName = '3.0.0',
|
versionName = '3.0.0',
|
||||||
}: {
|
}: {
|
||||||
versionCode?: number;
|
versionCode?: number | null;
|
||||||
versionName?: string;
|
versionName?: string | null;
|
||||||
} = {}): ExpoConfig {
|
} = {}): ExpoConfig {
|
||||||
const fakeExp: ExpoConfig = {
|
const fakeExp: ExpoConfig = {
|
||||||
name: 'myproject',
|
name: 'myproject',
|
||||||
@@ -239,8 +407,8 @@ function initBareWorkflowProject({
|
|||||||
'./android/app/build.gradle': `android {
|
'./android/app/build.gradle': `android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.expo.testapp"
|
applicationId "com.expo.testapp"
|
||||||
versionCode ${versionCode}
|
${versionCode ? `versionCode "${versionCode}"` : ''}
|
||||||
versionName "${versionName}"
|
${versionName ? `versionName "${versionName}"` : ''}
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
'./android/app/src/main/AndroidManifest.xml': 'fake',
|
'./android/app/src/main/AndroidManifest.xml': 'fake',
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export async function prepareAndroidBuildAsync(
|
|||||||
? false
|
? false
|
||||||
: ctx.buildProfile.autoIncrement,
|
: ctx.buildProfile.autoIncrement,
|
||||||
vcsClient: ctx.vcsClient,
|
vcsClient: ctx.vcsClient,
|
||||||
env: ctx.buildProfile.env,
|
env: ctx.env,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
prepareJobAsync: async (
|
prepareJobAsync: async (
|
||||||
@@ -151,6 +151,7 @@ async function ensureAndroidCredentialsAsync(
|
|||||||
ctx.android.gradleContext
|
ctx.android.gradleContext
|
||||||
);
|
);
|
||||||
const provider = new AndroidCredentialsProvider(ctx.credentialsCtx, {
|
const provider = new AndroidCredentialsProvider(ctx.credentialsCtx, {
|
||||||
|
name: ctx.buildProfile.keystoreName,
|
||||||
app: {
|
app: {
|
||||||
account: nullthrows(
|
account: nullthrows(
|
||||||
ctx.user.accounts.find(a => a.name === ctx.accountName),
|
ctx.user.accounts.find(a => a.name === ctx.accountName),
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ export async function prepareJobAsync(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
loggerLevel: ctx.loggerLevel,
|
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;
|
return sanitizeBuildJob(job) as Android.Job;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export async function bumpVersionAsync({
|
|||||||
|
|
||||||
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
||||||
const isMultiFlavor =
|
const isMultiFlavor =
|
||||||
buildGradle.android?.productFlavors || buildGradle.android?.flavorDimensions;
|
buildGradle.android?.productFlavors ?? buildGradle.android?.flavorDimensions;
|
||||||
if (isMultiFlavor) {
|
if (isMultiFlavor) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Automatic version bumping is not supported for multi-flavor Android projects.'
|
'Automatic version bumping is not supported for multi-flavor Android projects.'
|
||||||
@@ -99,7 +99,7 @@ export async function maybeResolveVersionsAsync(
|
|||||||
exp: ExpoConfig,
|
exp: ExpoConfig,
|
||||||
buildProfile: BuildProfile<Platform.ANDROID>,
|
buildProfile: BuildProfile<Platform.ANDROID>,
|
||||||
vcsClient: Client
|
vcsClient: Client
|
||||||
): Promise<{ appVersion?: string; appBuildVersion?: string }> {
|
): Promise<{ appVersion?: string; appBuildVersion?: string; isVersionInitialized?: boolean }> {
|
||||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
|
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
|
||||||
if (workflow === Workflow.GENERIC) {
|
if (workflow === Workflow.GENERIC) {
|
||||||
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
||||||
@@ -113,6 +113,11 @@ export async function maybeResolveVersionsAsync(
|
|||||||
resolveConfigValue(buildGradle, 'versionName', parsedGradleCommand?.flavor) ?? '1.0.0',
|
resolveConfigValue(buildGradle, 'versionName', parsedGradleCommand?.flavor) ?? '1.0.0',
|
||||||
appBuildVersion:
|
appBuildVersion:
|
||||||
resolveConfigValue(buildGradle, 'versionCode', parsedGradleCommand?.flavor) ?? '1',
|
resolveConfigValue(buildGradle, 'versionCode', parsedGradleCommand?.flavor) ?? '1',
|
||||||
|
isVersionInitialized: !resolveConfigValue(
|
||||||
|
buildGradle,
|
||||||
|
'versionCode',
|
||||||
|
parsedGradleCommand?.flavor
|
||||||
|
),
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return {};
|
return {};
|
||||||
@@ -204,14 +209,18 @@ export async function resolveRemoteVersionCodeAsync(
|
|||||||
|
|
||||||
const localVersions = await maybeResolveVersionsAsync(projectDir, exp, buildProfile, vcsClient);
|
const localVersions = await maybeResolveVersionsAsync(projectDir, exp, buildProfile, vcsClient);
|
||||||
let currentBuildVersion: string;
|
let currentBuildVersion: string;
|
||||||
|
let shouldInitializeVersionCode = false;
|
||||||
if (remoteVersions?.buildVersion) {
|
if (remoteVersions?.buildVersion) {
|
||||||
currentBuildVersion = remoteVersions.buildVersion;
|
currentBuildVersion = remoteVersions.buildVersion;
|
||||||
} else {
|
} else {
|
||||||
if (localVersions.appBuildVersion) {
|
if (localVersions.appBuildVersion) {
|
||||||
Log.warn(
|
Log.log(
|
||||||
'No remote versions are configured for this project, versionCode will be initialized based on the value from the local project.'
|
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;
|
currentBuildVersion = localVersions.appBuildVersion;
|
||||||
|
shouldInitializeVersionCode = localVersions.isVersionInitialized ?? false;
|
||||||
} else {
|
} else {
|
||||||
Log.error(
|
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.`
|
`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) {
|
if (!buildProfile.autoIncrement && remoteVersions?.buildVersion) {
|
||||||
return currentBuildVersion;
|
return currentBuildVersion;
|
||||||
} else if (!buildProfile.autoIncrement && !remoteVersions?.buildVersion) {
|
} else if (
|
||||||
|
(!buildProfile.autoIncrement && !remoteVersions?.buildVersion) ||
|
||||||
|
shouldInitializeVersionCode
|
||||||
|
) {
|
||||||
const spinner = ora(
|
const spinner = ora(
|
||||||
`Initializing versionCode with ${chalk.bold(currentBuildVersion)}.`
|
`Initializing versionCode with ${chalk.bold(currentBuildVersion)}.`
|
||||||
).start();
|
).start();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
ArchiveSource,
|
ArchiveSource,
|
||||||
ArchiveSourceType,
|
ArchiveSourceType,
|
||||||
BuildJob,
|
BuildJob,
|
||||||
|
FingerprintSource,
|
||||||
Metadata,
|
Metadata,
|
||||||
Platform,
|
Platform,
|
||||||
} from '@expo/eas-build-job';
|
} from '@expo/eas-build-job';
|
||||||
@@ -56,12 +57,16 @@ import { BuildQuery } from '../graphql/queries/BuildQuery';
|
|||||||
import Log, { learnMore, link } from '../log';
|
import Log, { learnMore, link } from '../log';
|
||||||
import { Ora, ora } from '../ora';
|
import { Ora, ora } from '../ora';
|
||||||
import {
|
import {
|
||||||
|
RequestedPlatform,
|
||||||
appPlatformDisplayNames,
|
appPlatformDisplayNames,
|
||||||
appPlatformEmojis,
|
appPlatformEmojis,
|
||||||
requestedPlatformDisplayNames,
|
requestedPlatformDisplayNames,
|
||||||
} from '../platform';
|
} from '../platform';
|
||||||
|
import { maybeUploadFingerprintAsync } from '../project/maybeUploadFingerprintAsync';
|
||||||
|
import { resolveRuntimeVersionAsync } from '../project/resolveRuntimeVersionAsync';
|
||||||
import { uploadFileAtPathToGCSAsync } from '../uploads';
|
import { uploadFileAtPathToGCSAsync } from '../uploads';
|
||||||
import { formatBytes } from '../utils/files';
|
import { formatBytes } from '../utils/files';
|
||||||
|
import { createFingerprintAsync } from '../utils/fingerprintCli';
|
||||||
import { printJsonOnlyOutput } from '../utils/json';
|
import { printJsonOnlyOutput } from '../utils/json';
|
||||||
import { createProgressTracker } from '../utils/progress';
|
import { createProgressTracker } from '../utils/progress';
|
||||||
import { sleepAsync } from '../utils/promise';
|
import { sleepAsync } from '../utils/promise';
|
||||||
@@ -124,7 +129,9 @@ export async function prepareBuildRequestForPlatformAsync<
|
|||||||
|
|
||||||
await withAnalyticsAsync(
|
await withAnalyticsAsync(
|
||||||
ctx.analytics,
|
ctx.analytics,
|
||||||
async () => await builder.syncProjectConfigurationAsync(ctx),
|
async () => {
|
||||||
|
await builder.syncProjectConfigurationAsync(ctx);
|
||||||
|
},
|
||||||
{
|
{
|
||||||
attemptEvent: BuildEvent.CONFIGURE_PROJECT_ATTEMPT,
|
attemptEvent: BuildEvent.CONFIGURE_PROJECT_ATTEMPT,
|
||||||
successEvent: BuildEvent.CONFIGURE_PROJECT_SUCCESS,
|
successEvent: BuildEvent.CONFIGURE_PROJECT_SUCCESS,
|
||||||
@@ -135,9 +142,15 @@ export async function prepareBuildRequestForPlatformAsync<
|
|||||||
|
|
||||||
if (await ctx.vcsClient.isCommitRequiredAsync()) {
|
if (await ctx.vcsClient.isCommitRequiredAsync()) {
|
||||||
Log.addNewLineIfNone();
|
Log.addNewLineIfNone();
|
||||||
|
const platformToRequestedPlatform: Record<Platform, RequestedPlatform> = {
|
||||||
|
[Platform.ANDROID]: RequestedPlatform.Android,
|
||||||
|
[Platform.IOS]: RequestedPlatform.Ios,
|
||||||
|
};
|
||||||
await reviewAndCommitChangesAsync(
|
await reviewAndCommitChangesAsync(
|
||||||
ctx.vcsClient,
|
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 }
|
{ nonInteractive: ctx.nonInteractive }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -162,7 +175,9 @@ export async function prepareBuildRequestForPlatformAsync<
|
|||||||
}
|
}
|
||||||
assert(projectArchive);
|
assert(projectArchive);
|
||||||
|
|
||||||
const metadata = await collectMetadataAsync(ctx);
|
const runtimeAndFingerprintMetadata =
|
||||||
|
await computeAndMaybeUploadRuntimeAndFingerprintMetadataAsync(ctx);
|
||||||
|
const metadata = await collectMetadataAsync(ctx, runtimeAndFingerprintMetadata);
|
||||||
const buildParams = resolveBuildParamsInput(ctx, metadata);
|
const buildParams = resolveBuildParamsInput(ctx, metadata);
|
||||||
const job = await builder.prepareJobAsync(ctx, {
|
const job = await builder.prepareJobAsync(ctx, {
|
||||||
projectArchive,
|
projectArchive,
|
||||||
@@ -171,7 +186,7 @@ export async function prepareBuildRequestForPlatformAsync<
|
|||||||
|
|
||||||
return async () => {
|
return async () => {
|
||||||
if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN) {
|
if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN) {
|
||||||
await runLocalBuildAsync(job, metadata, ctx.localBuildOptions);
|
await runLocalBuildAsync(job, metadata, ctx.localBuildOptions, ctx.env);
|
||||||
return undefined;
|
return undefined;
|
||||||
} else if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL) {
|
} else if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL) {
|
||||||
await BuildMutation.updateBuildMetadataAsync(ctx.graphqlClient, {
|
await BuildMutation.updateBuildMetadataAsync(ctx.graphqlClient, {
|
||||||
@@ -473,8 +488,8 @@ async function handleSingleBuildProgressAsync(
|
|||||||
spinner.succeed('Build finished');
|
spinner.succeed('Build finished');
|
||||||
return { refetch: false };
|
return { refetch: false };
|
||||||
case BuildStatus.New:
|
case BuildStatus.New:
|
||||||
spinner.text = `Build is waiting to enter the queue. Check your concurrency limit at ${link(
|
spinner.text = `Build concurrency limit reached for your account. Build will enter queue once a concurrency becomes available. Add additional concurrencies at ${link(
|
||||||
formatAccountSubscriptionsUrl(accountName)
|
formatAccountBillingUrl(accountName)
|
||||||
)}.`;
|
)}.`;
|
||||||
break;
|
break;
|
||||||
case BuildStatus.InQueue: {
|
case BuildStatus.InQueue: {
|
||||||
@@ -496,7 +511,7 @@ async function handleSingleBuildProgressAsync(
|
|||||||
Log.log('Start builds sooner in the priority queue.');
|
Log.log('Start builds sooner in the priority queue.');
|
||||||
Log.log(
|
Log.log(
|
||||||
`Sign up for EAS Production or Enterprise at ${link(
|
`Sign up for EAS Production or Enterprise at ${link(
|
||||||
formatAccountSubscriptionsUrl(accountName)
|
formatAccountBillingUrl(accountName)
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -643,9 +658,105 @@ function formatEstimatedWaitTime(estimatedWaitTimeLeftSeconds: number): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatAccountSubscriptionsUrl(accountName: string): string {
|
function formatAccountBillingUrl(accountName: string): string {
|
||||||
return new URL(
|
return new URL(`/accounts/${accountName}/settings/billing`, getExpoWebsiteBaseUrl()).toString();
|
||||||
`/accounts/${accountName}/settings/subscriptions`,
|
}
|
||||||
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,4 +1,4 @@
|
|||||||
import { EasJson, EasJsonAccessor } from '@expo/eas-json';
|
import { AppVersionSource, EasJson, EasJsonAccessor } from '@expo/eas-json';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
|
||||||
@@ -55,6 +55,7 @@ async function configureAsync({
|
|||||||
const EAS_JSON_DEFAULT: EasJson = {
|
const EAS_JSON_DEFAULT: EasJson = {
|
||||||
cli: {
|
cli: {
|
||||||
version: `>= ${easCliVersion}`,
|
version: `>= ${easCliVersion}`,
|
||||||
|
appVersionSource: AppVersionSource.REMOTE,
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
development: {
|
development: {
|
||||||
@@ -64,7 +65,9 @@ const EAS_JSON_DEFAULT: EasJson = {
|
|||||||
preview: {
|
preview: {
|
||||||
distribution: 'internal',
|
distribution: 'internal',
|
||||||
},
|
},
|
||||||
production: {},
|
production: {
|
||||||
|
autoIncrement: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
submit: {
|
submit: {
|
||||||
production: {},
|
production: {},
|
||||||
|
|||||||
@@ -63,4 +63,5 @@ export interface BuildContext<T extends Platform> {
|
|||||||
vcsClient: Client;
|
vcsClient: Client;
|
||||||
loggerLevel?: LoggerLevel;
|
loggerLevel?: LoggerLevel;
|
||||||
repack: boolean;
|
repack: boolean;
|
||||||
|
env: Record<string, string>;
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|||||||
import { createAndroidContextAsync } from './android/build';
|
import { createAndroidContextAsync } from './android/build';
|
||||||
import { BuildContext, CommonContext } from './context';
|
import { BuildContext, CommonContext } from './context';
|
||||||
import { createIosContextAsync } from './ios/build';
|
import { createIosContextAsync } from './ios/build';
|
||||||
import { LocalBuildOptions } from './local';
|
import { LocalBuildMode, LocalBuildOptions } from './local';
|
||||||
import { resolveBuildResourceClassAsync } from './utils/resourceClass';
|
import { resolveBuildResourceClassAsync } from './utils/resourceClass';
|
||||||
import { Analytics, AnalyticsEventProperties, BuildEvent } from '../analytics/AnalyticsManager';
|
import { Analytics, AnalyticsEventProperties, BuildEvent } from '../analytics/AnalyticsManager';
|
||||||
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
|
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
|
||||||
@@ -43,6 +43,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
|||||||
buildLoggerLevel,
|
buildLoggerLevel,
|
||||||
freezeCredentials,
|
freezeCredentials,
|
||||||
repack,
|
repack,
|
||||||
|
env,
|
||||||
}: {
|
}: {
|
||||||
buildProfileName: string;
|
buildProfileName: string;
|
||||||
buildProfile: BuildProfile<T>;
|
buildProfile: BuildProfile<T>;
|
||||||
@@ -64,8 +65,11 @@ export async function createBuildContextAsync<T extends Platform>({
|
|||||||
buildLoggerLevel?: LoggerLevel;
|
buildLoggerLevel?: LoggerLevel;
|
||||||
freezeCredentials: boolean;
|
freezeCredentials: boolean;
|
||||||
repack: boolean;
|
repack: boolean;
|
||||||
|
env: Record<string, string>;
|
||||||
}): Promise<BuildContext<T>> {
|
}): Promise<BuildContext<T>> {
|
||||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env });
|
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
|
||||||
|
env,
|
||||||
|
});
|
||||||
const projectName = exp.slug;
|
const projectName = exp.slug;
|
||||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||||
const workflow = await resolveWorkflowAsync(projectDir, platform, vcsClient);
|
const workflow = await resolveWorkflowAsync(projectDir, platform, vcsClient);
|
||||||
@@ -87,7 +91,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
|||||||
user: actor,
|
user: actor,
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
analytics,
|
analytics,
|
||||||
env: buildProfile.env,
|
env,
|
||||||
easJsonCliConfig,
|
easJsonCliConfig,
|
||||||
vcsClient,
|
vcsClient,
|
||||||
freezeCredentials,
|
freezeCredentials,
|
||||||
@@ -108,6 +112,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
|||||||
...devClientProperties,
|
...devClientProperties,
|
||||||
no_wait: noWait,
|
no_wait: noWait,
|
||||||
run_from_ci: runFromCI,
|
run_from_ci: runFromCI,
|
||||||
|
local: localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN,
|
||||||
};
|
};
|
||||||
analytics.logEvent(BuildEvent.BUILD_COMMAND, analyticsEventProperties);
|
analytics.logEvent(BuildEvent.BUILD_COMMAND, analyticsEventProperties);
|
||||||
|
|
||||||
@@ -146,6 +151,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
|||||||
requiredPackageManager,
|
requiredPackageManager,
|
||||||
loggerLevel: buildLoggerLevel,
|
loggerLevel: buildLoggerLevel,
|
||||||
repack,
|
repack,
|
||||||
|
env,
|
||||||
};
|
};
|
||||||
if (platform === Platform.ANDROID) {
|
if (platform === Platform.ANDROID) {
|
||||||
const common = commonContext as CommonContext<Platform.ANDROID>;
|
const common = commonContext as CommonContext<Platform.ANDROID>;
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import { Env } from '@expo/eas-build-job';
|
||||||
|
import { BuildProfile } from '@expo/eas-json';
|
||||||
|
|
||||||
|
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
|
import { EnvironmentVariableEnvironment } from '../graphql/generated';
|
||||||
|
import { EnvironmentVariablesQuery } from '../graphql/queries/EnvironmentVariablesQuery';
|
||||||
|
import Log, { learnMore } from '../log';
|
||||||
|
|
||||||
|
function isEnvironment(env: string): env is EnvironmentVariableEnvironment {
|
||||||
|
return Object.values(EnvironmentVariableEnvironment).includes(
|
||||||
|
env as EnvironmentVariableEnvironment
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
ArchiveSourceType,
|
||||||
BuildMode,
|
BuildMode,
|
||||||
BuildTrigger,
|
BuildTrigger,
|
||||||
|
FingerprintSourceType,
|
||||||
Metadata,
|
Metadata,
|
||||||
Workflow,
|
Workflow,
|
||||||
} from '@expo/eas-build-job';
|
} from '@expo/eas-build-job';
|
||||||
@@ -15,8 +16,10 @@ import {
|
|||||||
BuildMetadataInput,
|
BuildMetadataInput,
|
||||||
BuildWorkflow,
|
BuildWorkflow,
|
||||||
DistributionType,
|
DistributionType,
|
||||||
|
FingerprintSourceInput,
|
||||||
BuildMode as GraphQLBuildMode,
|
BuildMode as GraphQLBuildMode,
|
||||||
BuildTrigger as GraphQLBuildTrigger,
|
BuildTrigger as GraphQLBuildTrigger,
|
||||||
|
FingerprintSourceType as GraphQLFingeprintSourceType,
|
||||||
ProjectArchiveSourceInput,
|
ProjectArchiveSourceInput,
|
||||||
ProjectArchiveSourceType,
|
ProjectArchiveSourceType,
|
||||||
WorkerLoggerLevel,
|
WorkerLoggerLevel,
|
||||||
@@ -42,6 +45,8 @@ export function transformProjectArchive(archiveSource: ArchiveSource): ProjectAr
|
|||||||
export function transformMetadata(metadata: Metadata): BuildMetadataInput {
|
export function transformMetadata(metadata: Metadata): BuildMetadataInput {
|
||||||
return {
|
return {
|
||||||
...metadata,
|
...metadata,
|
||||||
|
fingerprintSource:
|
||||||
|
metadata.fingerprintSource && transformFingerprintSource(metadata.fingerprintSource),
|
||||||
credentialsSource:
|
credentialsSource:
|
||||||
metadata.credentialsSource && transformCredentialsSource(metadata.credentialsSource),
|
metadata.credentialsSource && transformCredentialsSource(metadata.credentialsSource),
|
||||||
distribution: metadata.distribution && transformDistribution(metadata.distribution),
|
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(
|
function transformCredentialsSource(
|
||||||
credentialsSource: Metadata['credentialsSource']
|
credentialsSource: Metadata['credentialsSource']
|
||||||
): BuildCredentialsSource {
|
): BuildCredentialsSource {
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import { ExpoConfig } from '@expo/config';
|
import { ExpoConfig } from '@expo/config';
|
||||||
import { IOSConfig } from '@expo/config-plugins';
|
import { IOSConfig } from '@expo/config-plugins';
|
||||||
|
import { IosBuildProfile } from '@expo/eas-json/build/build/types';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { vol } from 'memfs';
|
import { vol } from 'memfs';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
import { instance, mock } from 'ts-mockito';
|
||||||
import type { XCBuildConfiguration } from 'xcode';
|
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 { readPlistAsync } from '../../../utils/plist';
|
||||||
import { resolveVcsClient } from '../../../vcs';
|
import { resolveVcsClient } from '../../../vcs';
|
||||||
|
import { Client } from '../../../vcs/vcs';
|
||||||
import {
|
import {
|
||||||
BumpStrategy,
|
BumpStrategy,
|
||||||
bumpVersionAsync,
|
bumpVersionAsync,
|
||||||
@@ -15,9 +22,23 @@ import {
|
|||||||
getInfoPlistPath,
|
getInfoPlistPath,
|
||||||
readBuildNumberAsync,
|
readBuildNumberAsync,
|
||||||
readShortVersionAsync,
|
readShortVersionAsync,
|
||||||
|
resolveRemoteBuildNumberAsync,
|
||||||
} from '../version';
|
} from '../version';
|
||||||
|
|
||||||
jest.mock('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();
|
const vcsClient = resolveVcsClient();
|
||||||
|
|
||||||
@@ -300,25 +321,6 @@ describe(readShortVersionAsync, () => {
|
|||||||
);
|
);
|
||||||
expect(buildNumber).toBe('1.0.0');
|
expect(buildNumber).toBe('1.0.0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails when build number is invalid', async () => {
|
|
||||||
const exp = initBareWorkflowProject({
|
|
||||||
appVersion: '$(CURRENT_PROJECT_VERSION)',
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
readShortVersionAsync(
|
|
||||||
'/app',
|
|
||||||
exp,
|
|
||||||
{
|
|
||||||
CURRENT_PROJECT_VERSION: '0.0.7.1.028',
|
|
||||||
},
|
|
||||||
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', () => {
|
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({
|
function initBareWorkflowProject({
|
||||||
appVersion = '1.0.0',
|
appVersion = '1.0.0',
|
||||||
buildNumber = '1',
|
buildNumber = '1',
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
export async function createIosContextAsync(
|
export async function createIosContextAsync(
|
||||||
ctx: CommonContext<Platform.IOS>
|
ctx: CommonContext<Platform.IOS>
|
||||||
): Promise<IosBuildContext> {
|
): Promise<IosBuildContext> {
|
||||||
const { buildProfile } = ctx;
|
const { buildProfile, env } = ctx;
|
||||||
|
|
||||||
if (ctx.workflow === Workflow.MANAGED) {
|
if (ctx.workflow === Workflow.MANAGED) {
|
||||||
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx);
|
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx);
|
||||||
@@ -47,7 +47,7 @@ export async function createIosContextAsync(
|
|||||||
projectDir: ctx.projectDir,
|
projectDir: ctx.projectDir,
|
||||||
exp: ctx.exp,
|
exp: ctx.exp,
|
||||||
xcodeBuildContext,
|
xcodeBuildContext,
|
||||||
env: buildProfile.env,
|
env,
|
||||||
vcsClient: ctx.vcsClient,
|
vcsClient: ctx.vcsClient,
|
||||||
});
|
});
|
||||||
const applicationTarget = findApplicationTarget(targets);
|
const applicationTarget = findApplicationTarget(targets);
|
||||||
@@ -77,7 +77,7 @@ export async function prepareIosBuildAsync(
|
|||||||
return await prepareBuildRequestForPlatformAsync({
|
return await prepareBuildRequestForPlatformAsync({
|
||||||
ctx,
|
ctx,
|
||||||
ensureCredentialsAsync: async (ctx: BuildContext<Platform.IOS>) => {
|
ensureCredentialsAsync: async (ctx: BuildContext<Platform.IOS>) => {
|
||||||
return ensureIosCredentialsAsync(ctx, ctx.ios.targets);
|
return await ensureIosCredentialsAsync(ctx, ctx.ios.targets);
|
||||||
},
|
},
|
||||||
syncProjectConfigurationAsync: async () => {
|
syncProjectConfigurationAsync: async () => {
|
||||||
await syncProjectConfigurationAsync({
|
await syncProjectConfigurationAsync({
|
||||||
@@ -89,7 +89,7 @@ export async function prepareIosBuildAsync(
|
|||||||
? false
|
? false
|
||||||
: ctx.buildProfile.autoIncrement,
|
: ctx.buildProfile.autoIncrement,
|
||||||
vcsClient: ctx.vcsClient,
|
vcsClient: ctx.vcsClient,
|
||||||
env: ctx.buildProfile.env,
|
env: ctx.env,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
prepareJobAsync: async (
|
prepareJobAsync: async (
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ export async function prepareJobAsync(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
loggerLevel: ctx.loggerLevel,
|
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;
|
return sanitizeBuildJob(job) as Ios.Job;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { Target } from '../../credentials/ios/types';
|
|||||||
import { AppPlatform } from '../../graphql/generated';
|
import { AppPlatform } from '../../graphql/generated';
|
||||||
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
|
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
|
||||||
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
||||||
import Log from '../../log';
|
import Log, { learnMore } from '../../log';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { findApplicationTarget } from '../../project/ios/target';
|
import { findApplicationTarget } from '../../project/ios/target';
|
||||||
import { getNextBuildNumber, isValidBuildNumber } from '../../project/ios/versions';
|
import { getNextBuildNumber, isValidBuildNumber } from '../../project/ios/versions';
|
||||||
@@ -23,7 +23,7 @@ import { Client } from '../../vcs/vcs';
|
|||||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||||
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
|
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
|
||||||
|
|
||||||
const SHORT_VERSION_REGEX = /^\d+.\d+.\d+$/;
|
const SHORT_VERSION_REGEX = /^\d+(\.\d+){0,2}$/;
|
||||||
|
|
||||||
export enum BumpStrategy {
|
export enum BumpStrategy {
|
||||||
APP_VERSION,
|
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)) {
|
if (shortVersion && !SHORT_VERSION_REGEX.test(shortVersion)) {
|
||||||
throw new Error(
|
if (workflow === Workflow.MANAGED) {
|
||||||
`CFBundleShortVersionString (version field in app.json/app.config.js) must be a period-separated list of three non-negative integers. Current value: ${shortVersion}`
|
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 &&
|
infoPlist.CFBundleShortVersionString &&
|
||||||
evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings);
|
evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings);
|
||||||
|
|
||||||
validateShortVersion(shortVersion);
|
validateShortVersion({ shortVersion, workflow });
|
||||||
return shortVersion;
|
return shortVersion;
|
||||||
} else {
|
} else {
|
||||||
validateShortVersion(exp.version);
|
validateShortVersion({ shortVersion: exp.version, workflow });
|
||||||
return exp.version;
|
return exp.version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,14 +330,18 @@ export async function resolveRemoteBuildNumberAsync(
|
|||||||
vcsClient
|
vcsClient
|
||||||
);
|
);
|
||||||
let currentBuildVersion: string;
|
let currentBuildVersion: string;
|
||||||
|
let shouldInitializeBuildNumber = false;
|
||||||
if (remoteVersions?.buildVersion) {
|
if (remoteVersions?.buildVersion) {
|
||||||
currentBuildVersion = remoteVersions.buildVersion;
|
currentBuildVersion = remoteVersions.buildVersion;
|
||||||
} else {
|
} else {
|
||||||
if (localBuildNumber) {
|
if (localBuildNumber) {
|
||||||
Log.warn(
|
Log.log(
|
||||||
'No remote versions are configured for this project, buildNumber will be initialized based on the value from the local project.'
|
chalk.green(
|
||||||
|
'No remote versions are configured for this project, buildNumber will be initialized based on the value from the local project.'
|
||||||
|
)
|
||||||
);
|
);
|
||||||
currentBuildVersion = localBuildNumber;
|
currentBuildVersion = localBuildNumber;
|
||||||
|
shouldInitializeBuildNumber = localBuildNumber === '1';
|
||||||
} else {
|
} else {
|
||||||
Log.error(
|
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.`
|
`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) {
|
if (!buildProfile.autoIncrement && remoteVersions?.buildVersion) {
|
||||||
return currentBuildVersion;
|
return currentBuildVersion;
|
||||||
} else if (!buildProfile.autoIncrement && !remoteVersions?.buildVersion) {
|
} else if (
|
||||||
|
(!buildProfile.autoIncrement && !remoteVersions?.buildVersion) ||
|
||||||
|
shouldInitializeBuildNumber
|
||||||
|
) {
|
||||||
const spinner = ora(
|
const spinner = ora(
|
||||||
`Initializing buildNumber with ${chalk.bold(currentBuildVersion)}.`
|
`Initializing buildNumber with ${chalk.bold(currentBuildVersion)}.`
|
||||||
).start();
|
).start();
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import spawnAsync from '@expo/spawn-async';
|
|||||||
import { ChildProcess } from 'child_process';
|
import { ChildProcess } from 'child_process';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
|
||||||
|
import Log from '../log';
|
||||||
import { ora } from '../ora';
|
import { ora } from '../ora';
|
||||||
|
|
||||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||||
const PLUGIN_PACKAGE_VERSION = '1.0.118';
|
const PLUGIN_PACKAGE_VERSION = '1.0.163';
|
||||||
|
|
||||||
export enum LocalBuildMode {
|
export enum LocalBuildMode {
|
||||||
/**
|
/**
|
||||||
@@ -39,7 +40,8 @@ export interface LocalBuildOptions {
|
|||||||
export async function runLocalBuildAsync(
|
export async function runLocalBuildAsync(
|
||||||
job: Job,
|
job: Job,
|
||||||
metadata: Metadata,
|
metadata: Metadata,
|
||||||
options: LocalBuildOptions
|
options: LocalBuildOptions,
|
||||||
|
env: Record<string, string>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { command, args } = await getCommandAndArgsAsync(job, metadata);
|
const { command, args } = await getCommandAndArgsAsync(job, metadata);
|
||||||
let spinner;
|
let spinner;
|
||||||
@@ -54,18 +56,26 @@ export async function runLocalBuildAsync(
|
|||||||
};
|
};
|
||||||
process.on('SIGINT', interruptHandler);
|
process.on('SIGINT', interruptHandler);
|
||||||
try {
|
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, {
|
const spawnPromise = spawnAsync(command, args, {
|
||||||
stdio: options.verbose ? 'inherit' : 'pipe',
|
stdio: options.verbose ? 'inherit' : 'pipe',
|
||||||
env: {
|
env: mergedEnv,
|
||||||
...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 } : {}),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
childProcess = spawnPromise.child;
|
childProcess = spawnPromise.child;
|
||||||
await spawnPromise;
|
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 { IosEnterpriseProvisioning } from '@expo/eas-json';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import resolveFrom from 'resolve-from';
|
import resolveFrom from 'resolve-from';
|
||||||
@@ -10,13 +10,17 @@ import { LocalBuildMode } from './local';
|
|||||||
import { BuildDistributionType } from './types';
|
import { BuildDistributionType } from './types';
|
||||||
import Log from '../log';
|
import Log from '../log';
|
||||||
import { getUsername, isExpoUpdatesInstalled } from '../project/projectUtils';
|
import { getUsername, isExpoUpdatesInstalled } from '../project/projectUtils';
|
||||||
import { resolveRuntimeVersionAsync } from '../project/resolveRuntimeVersionAsync';
|
|
||||||
import { readChannelSafelyAsync as readAndroidChannelSafelyAsync } from '../update/android/UpdatesModule';
|
import { readChannelSafelyAsync as readAndroidChannelSafelyAsync } from '../update/android/UpdatesModule';
|
||||||
import { readChannelSafelyAsync as readIosChannelSafelyAsync } from '../update/ios/UpdatesModule';
|
import { readChannelSafelyAsync as readIosChannelSafelyAsync } from '../update/ios/UpdatesModule';
|
||||||
import { easCliVersion } from '../utils/easCli';
|
import { easCliVersion } from '../utils/easCli';
|
||||||
|
|
||||||
export async function collectMetadataAsync<T extends Platform>(
|
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> {
|
): Promise<Metadata> {
|
||||||
const channelObject = await resolveChannelAsync(ctx);
|
const channelObject = await resolveChannelAsync(ctx);
|
||||||
const distribution = ctx.buildProfile.distribution ?? BuildDistributionType.STORE;
|
const distribution = ctx.buildProfile.distribution ?? BuildDistributionType.STORE;
|
||||||
@@ -27,15 +31,9 @@ export async function collectMetadataAsync<T extends Platform>(
|
|||||||
workflow: ctx.workflow,
|
workflow: ctx.workflow,
|
||||||
credentialsSource: ctx.buildProfile.credentialsSource,
|
credentialsSource: ctx.buildProfile.credentialsSource,
|
||||||
sdkVersion: ctx.exp.sdkVersion,
|
sdkVersion: ctx.exp.sdkVersion,
|
||||||
runtimeVersion:
|
runtimeVersion: runtimeAndFingerprintMetadata?.runtimeVersion,
|
||||||
(await resolveRuntimeVersionAsync({
|
fingerprintHash: runtimeAndFingerprintMetadata?.fingerprintHash,
|
||||||
exp: ctx.exp,
|
fingerprintSource: runtimeAndFingerprintMetadata?.fingerprintSource,
|
||||||
platform: ctx.platform,
|
|
||||||
workflow: ctx.workflow,
|
|
||||||
projectDir: ctx.projectDir,
|
|
||||||
env: ctx.buildProfile.env,
|
|
||||||
cwd: ctx.projectDir,
|
|
||||||
})) ?? undefined,
|
|
||||||
reactNativeVersion: await getReactNativeVersionAsync(ctx.projectDir),
|
reactNativeVersion: await getReactNativeVersionAsync(ctx.projectDir),
|
||||||
...channelObject,
|
...channelObject,
|
||||||
distribution,
|
distribution,
|
||||||
@@ -64,6 +62,7 @@ export async function collectMetadataAsync<T extends Platform>(
|
|||||||
requiredPackageManager: ctx.requiredPackageManager ?? undefined,
|
requiredPackageManager: ctx.requiredPackageManager ?? undefined,
|
||||||
selectedImage: ctx.buildProfile.image,
|
selectedImage: ctx.buildProfile.image,
|
||||||
customNodeVersion: ctx.buildProfile.node,
|
customNodeVersion: ctx.buildProfile.node,
|
||||||
|
environment: ctx.buildProfile.environment,
|
||||||
simulator: 'simulator' in ctx.buildProfile && ctx.buildProfile.simulator,
|
simulator: 'simulator' in ctx.buildProfile && ctx.buildProfile.simulator,
|
||||||
};
|
};
|
||||||
return sanitizeMetadata(metadata);
|
return sanitizeMetadata(metadata);
|
||||||
|
|||||||
@@ -50,8 +50,9 @@ export async function listAndRenderBuildsOnAppAsync(
|
|||||||
}),
|
}),
|
||||||
promptOptions: {
|
promptOptions: {
|
||||||
title: 'Load more builds?',
|
title: 'Load more builds?',
|
||||||
renderListItems: builds =>
|
renderListItems: builds => {
|
||||||
renderPageOfBuilds({ builds, projectDisplayName, paginatedQueryOptions }),
|
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 { Env, Platform, Workflow } from '@expo/eas-build-job';
|
||||||
import {
|
import {
|
||||||
AppVersionSource,
|
AppVersionSource,
|
||||||
BuildProfile,
|
|
||||||
EasJson,
|
EasJson,
|
||||||
EasJsonAccessor,
|
EasJsonAccessor,
|
||||||
EasJsonUtils,
|
EasJsonUtils,
|
||||||
@@ -19,6 +18,7 @@ import { BuildRequestSender, MaybeBuildFragment, waitForBuildEndAsync } from './
|
|||||||
import { ensureProjectConfiguredAsync } from './configure';
|
import { ensureProjectConfiguredAsync } from './configure';
|
||||||
import { BuildContext } from './context';
|
import { BuildContext } from './context';
|
||||||
import { createBuildContextAsync } from './createContext';
|
import { createBuildContextAsync } from './createContext';
|
||||||
|
import { evaluateConfigWithEnvVarsAsync } from './evaluateConfigWithEnvVarsAsync';
|
||||||
import { prepareIosBuildAsync } from './ios/build';
|
import { prepareIosBuildAsync } from './ios/build';
|
||||||
import { LocalBuildMode, LocalBuildOptions } from './local';
|
import { LocalBuildMode, LocalBuildOptions } from './local';
|
||||||
import { ensureExpoDevClientInstalledForDevClientBuildsAsync } from './utils/devClient';
|
import { ensureExpoDevClientInstalledForDevClientBuildsAsync } from './utils/devClient';
|
||||||
@@ -57,8 +57,9 @@ import {
|
|||||||
validateAppVersionRuntimePolicySupportAsync,
|
validateAppVersionRuntimePolicySupportAsync,
|
||||||
} from '../project/projectUtils';
|
} from '../project/projectUtils';
|
||||||
import {
|
import {
|
||||||
|
ensureAppVersionSourceIsSetAsync,
|
||||||
validateAppConfigForRemoteVersionSource,
|
validateAppConfigForRemoteVersionSource,
|
||||||
validateBuildProfileVersionSettings,
|
validateBuildProfileVersionSettingsAsync,
|
||||||
} from '../project/remoteVersionSource';
|
} from '../project/remoteVersionSource';
|
||||||
import { confirmAsync } from '../prompts';
|
import { confirmAsync } from '../prompts';
|
||||||
import { runAsync } from '../run/run';
|
import { runAsync } from '../run/run';
|
||||||
@@ -129,19 +130,30 @@ export async function runBuildAndSubmitAsync(
|
|||||||
profileName: flags.profile ?? undefined,
|
profileName: flags.profile ?? undefined,
|
||||||
projectDir,
|
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) {
|
for (const buildProfile of buildProfiles) {
|
||||||
if (buildProfile.profile.image && ['default', 'stable'].includes(buildProfile.profile.image)) {
|
if (buildProfile.profile.image && ['default', 'stable'].includes(buildProfile.profile.image)) {
|
||||||
Log.warn(
|
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/`
|
`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/`
|
||||||
);
|
);
|
||||||
|
} else if (
|
||||||
|
buildProfile.profile.image &&
|
||||||
|
[
|
||||||
|
'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 deprecated and will be removed on September 1st, 2024. ${learnMore(
|
||||||
|
'https://expo.dev/changelog/2024/07-12-eas-build-upcoming-android-images-updates'
|
||||||
|
)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +167,12 @@ export async function runBuildAndSubmitAsync(
|
|||||||
const customBuildConfigMetadataByPlatform: { [p in AppPlatform]?: CustomBuildConfigMetadata } =
|
const customBuildConfigMetadataByPlatform: { [p in AppPlatform]?: CustomBuildConfigMetadata } =
|
||||||
{};
|
{};
|
||||||
for (const buildProfile of buildProfiles) {
|
for (const buildProfile of buildProfiles) {
|
||||||
validateBuildProfileVersionSettings(buildProfile, easJsonCliConfig);
|
await validateBuildProfileVersionSettingsAsync(
|
||||||
|
buildProfile,
|
||||||
|
easJsonCliConfig,
|
||||||
|
projectDir,
|
||||||
|
flags
|
||||||
|
);
|
||||||
const maybeMetadata = await validateCustomBuildConfigAsync({
|
const maybeMetadata = await validateCustomBuildConfigAsync({
|
||||||
projectDir,
|
projectDir,
|
||||||
profile: buildProfile.profile,
|
profile: buildProfile.profile,
|
||||||
@@ -174,6 +191,15 @@ export async function runBuildAndSubmitAsync(
|
|||||||
|
|
||||||
for (const buildProfile of buildProfiles) {
|
for (const buildProfile of buildProfiles) {
|
||||||
const platform = toAppPlatform(buildProfile.platform);
|
const platform = toAppPlatform(buildProfile.platform);
|
||||||
|
|
||||||
|
const { env } = await evaluateConfigWithEnvVarsAsync({
|
||||||
|
buildProfile: buildProfile.profile,
|
||||||
|
buildProfileName: buildProfile.profileName,
|
||||||
|
graphqlClient,
|
||||||
|
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||||
|
opts: { env: buildProfile.profile.env },
|
||||||
|
});
|
||||||
|
|
||||||
const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({
|
const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({
|
||||||
projectDir,
|
projectDir,
|
||||||
flags,
|
flags,
|
||||||
@@ -186,6 +212,7 @@ export async function runBuildAndSubmitAsync(
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
getDynamicPrivateProjectConfigAsync,
|
getDynamicPrivateProjectConfigAsync,
|
||||||
customBuildConfigMetadata: customBuildConfigMetadataByPlatform[platform],
|
customBuildConfigMetadata: customBuildConfigMetadataByPlatform[platform],
|
||||||
|
env,
|
||||||
});
|
});
|
||||||
if (maybeBuild) {
|
if (maybeBuild) {
|
||||||
startedBuilds.push({ build: maybeBuild, buildProfile });
|
startedBuilds.push({ build: maybeBuild, buildProfile });
|
||||||
@@ -233,7 +260,6 @@ export async function runBuildAndSubmitAsync(
|
|||||||
buildCtx: nullthrows(buildCtxByPlatform[startedBuild.build.platform]),
|
buildCtx: nullthrows(buildCtxByPlatform[startedBuild.build.platform]),
|
||||||
moreBuilds: startedBuilds.length > 1,
|
moreBuilds: startedBuilds.length > 1,
|
||||||
projectDir,
|
projectDir,
|
||||||
buildProfile: startedBuild.buildProfile.profile,
|
|
||||||
submitProfile,
|
submitProfile,
|
||||||
nonInteractive: flags.nonInteractive,
|
nonInteractive: flags.nonInteractive,
|
||||||
selectedSubmitProfileName: flags.submitProfile,
|
selectedSubmitProfileName: flags.submitProfile,
|
||||||
@@ -322,6 +348,7 @@ async function prepareAndStartBuildAsync({
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
getDynamicPrivateProjectConfigAsync,
|
getDynamicPrivateProjectConfigAsync,
|
||||||
customBuildConfigMetadata,
|
customBuildConfigMetadata,
|
||||||
|
env,
|
||||||
}: {
|
}: {
|
||||||
projectDir: string;
|
projectDir: string;
|
||||||
flags: BuildFlags;
|
flags: BuildFlags;
|
||||||
@@ -334,6 +361,7 @@ async function prepareAndStartBuildAsync({
|
|||||||
vcsClient: Client;
|
vcsClient: Client;
|
||||||
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
|
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
|
||||||
customBuildConfigMetadata?: CustomBuildConfigMetadata;
|
customBuildConfigMetadata?: CustomBuildConfigMetadata;
|
||||||
|
env: Env;
|
||||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
||||||
const buildCtx = await createBuildContextAsync({
|
const buildCtx = await createBuildContextAsync({
|
||||||
buildProfileName: buildProfile.profileName,
|
buildProfileName: buildProfile.profileName,
|
||||||
@@ -353,9 +381,10 @@ async function prepareAndStartBuildAsync({
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
getDynamicPrivateProjectConfigAsync,
|
getDynamicPrivateProjectConfigAsync,
|
||||||
customBuildConfigMetadata,
|
customBuildConfigMetadata,
|
||||||
buildLoggerLevel: flags.buildLoggerLevel,
|
buildLoggerLevel: flags.buildLoggerLevel ?? (Log.isDebug ? LoggerLevel.DEBUG : undefined),
|
||||||
freezeCredentials: flags.freezeCredentials,
|
freezeCredentials: flags.freezeCredentials,
|
||||||
repack: flags.repack,
|
repack: flags.repack,
|
||||||
|
env,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (moreBuilds) {
|
if (moreBuilds) {
|
||||||
@@ -394,6 +423,25 @@ async function prepareAndStartBuildAsync({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await validateAppVersionRuntimePolicySupportAsync(buildCtx.projectDir, buildCtx.exp);
|
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) {
|
if (easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE) {
|
||||||
validateAppConfigForRemoteVersionSource(buildCtx.exp, buildProfile.platform);
|
validateAppConfigForRemoteVersionSource(buildCtx.exp, buildProfile.platform);
|
||||||
}
|
}
|
||||||
@@ -430,7 +478,6 @@ async function prepareAndStartSubmissionAsync({
|
|||||||
buildCtx,
|
buildCtx,
|
||||||
moreBuilds,
|
moreBuilds,
|
||||||
projectDir,
|
projectDir,
|
||||||
buildProfile,
|
|
||||||
submitProfile,
|
submitProfile,
|
||||||
selectedSubmitProfileName,
|
selectedSubmitProfileName,
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
@@ -439,7 +486,6 @@ async function prepareAndStartSubmissionAsync({
|
|||||||
buildCtx: BuildContext<Platform>;
|
buildCtx: BuildContext<Platform>;
|
||||||
moreBuilds: boolean;
|
moreBuilds: boolean;
|
||||||
projectDir: string;
|
projectDir: string;
|
||||||
buildProfile: BuildProfile;
|
|
||||||
submitProfile: SubmitProfile;
|
submitProfile: SubmitProfile;
|
||||||
selectedSubmitProfileName?: string;
|
selectedSubmitProfileName?: string;
|
||||||
nonInteractive: boolean;
|
nonInteractive: boolean;
|
||||||
@@ -451,7 +497,7 @@ async function prepareAndStartSubmissionAsync({
|
|||||||
profile: submitProfile,
|
profile: submitProfile,
|
||||||
archiveFlags: { id: build.id },
|
archiveFlags: { id: build.id },
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
env: buildProfile.env,
|
env: buildCtx.env,
|
||||||
credentialsCtx: buildCtx.credentialsCtx,
|
credentialsCtx: buildCtx.credentialsCtx,
|
||||||
applicationIdentifier: buildCtx.android?.applicationId ?? buildCtx.ios?.bundleIdentifier,
|
applicationIdentifier: buildCtx.android?.applicationId ?? buildCtx.ios?.bundleIdentifier,
|
||||||
actor: buildCtx.user,
|
actor: buildCtx.user,
|
||||||
|
|||||||
@@ -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 {
|
export enum BuildStatus {
|
||||||
NEW = 'new',
|
NEW = 'new',
|
||||||
IN_QUEUE = 'in-queue',
|
IN_QUEUE = 'in-queue',
|
||||||
@@ -14,3 +20,20 @@ export enum BuildDistributionType {
|
|||||||
/** @deprecated Use simulator flag instead */
|
/** @deprecated Use simulator flag instead */
|
||||||
SIMULATOR = 'simulator',
|
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;
|
||||||
|
}
|
||||||
@@ -62,7 +62,9 @@ export function printBuildResults(builds: (BuildFragment | null)[]): void {
|
|||||||
assert(build, 'Build should be defined');
|
assert(build, 'Build should be defined');
|
||||||
printBuildResult(build);
|
printBuildResult(build);
|
||||||
} else {
|
} 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
|
// to the build details page and let people press the button to download there
|
||||||
const qrcodeUrl =
|
const qrcodeUrl =
|
||||||
build.platform === AppPlatform.Ios ? getInternalDistributionInstallUrl(build) : logsUrl;
|
build.platform === AppPlatform.Ios ? getInternalDistributionInstallUrl(build) : logsUrl;
|
||||||
qrcodeTerminal.generate(qrcodeUrl, { small: true }, code =>
|
qrcodeTerminal.generate(qrcodeUrl, { small: true }, code => {
|
||||||
Log.log(`${indentString(code, 2)}\n`)
|
Log.log(`${indentString(code, 2)}\n`);
|
||||||
);
|
});
|
||||||
Log.log(
|
Log.log(
|
||||||
`${appPlatformEmojis[build.platform]} Open this link on your ${
|
`${appPlatformEmojis[build.platform]} Open this link on your ${
|
||||||
appPlatformDisplayNames[build.platform]
|
appPlatformDisplayNames[build.platform]
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export type LocalFile = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function makeProjectMetadataFileAsync(archivePath: string): Promise<LocalFile> {
|
export async function makeProjectMetadataFileAsync(archivePath: string): Promise<LocalFile> {
|
||||||
|
await fs.mkdirp(getTmpDirectory());
|
||||||
const metadataLocation = path.join(getTmpDirectory(), `${uuidv4()}-eas-build-metadata.json`);
|
const metadataLocation = path.join(getTmpDirectory(), `${uuidv4()}-eas-build-metadata.json`);
|
||||||
const archiveContent: string[] = [];
|
const archiveContent: string[] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -50,3 +50,25 @@ export function getUpdateGroupUrl(
|
|||||||
getExpoWebsiteBaseUrl()
|
getExpoWebsiteBaseUrl()
|
||||||
).toString();
|
).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';
|
import { isPNGAsync } from '../utils/image';
|
||||||
|
|
||||||
export function checkNodeEnvVariable(ctx: CommonContext<Platform>): void {
|
export function checkNodeEnvVariable(ctx: CommonContext<Platform>): void {
|
||||||
if (ctx.buildProfile.env?.NODE_ENV === 'production') {
|
if (ctx.env?.NODE_ENV === 'production') {
|
||||||
Log.warn(
|
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();
|
Log.newLine();
|
||||||
}
|
}
|
||||||
@@ -27,24 +27,28 @@ export async function checkGoogleServicesFileAsync<T extends Platform>(
|
|||||||
if (!googleServicesFilePath) {
|
if (!googleServicesFilePath) {
|
||||||
return;
|
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 rootDir = path.normalize(await ctx.vcsClient.getRootPathAsync());
|
||||||
const absGoogleServicesFilePath = path.resolve(ctx.projectDir, googleServicesFilePath);
|
const absGoogleServicesFilePath = path.resolve(ctx.projectDir, googleServicesFilePath);
|
||||||
if (
|
if (
|
||||||
(await fs.pathExists(absGoogleServicesFilePath)) &&
|
(await fs.pathExists(absGoogleServicesFilePath)) &&
|
||||||
(!isInsideDirectory(absGoogleServicesFilePath, rootDir) ||
|
(!isInsideDirectory(absGoogleServicesFilePath, rootDir) ||
|
||||||
(await ctx.vcsClient.isFileIgnoredAsync(path.relative(rootDir, absGoogleServicesFilePath))))
|
(await ctx.vcsClient.isFileIgnoredAsync(
|
||||||
|
path.relative(rootDir, absGoogleServicesFilePath)
|
||||||
|
))) &&
|
||||||
|
!googleServicesEnvVar
|
||||||
) {
|
) {
|
||||||
Log.warn(
|
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.`
|
`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(
|
Log.warn(
|
||||||
`Use EAS Secret to pass all values that you don't want to include in your version control. ${learnMore(
|
`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/build-reference/variables/#using-secrets-in-environment-variables'
|
'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();
|
Log.newLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ import {
|
|||||||
|
|
||||||
describe(assertVersion, () => {
|
describe(assertVersion, () => {
|
||||||
it('throws if the branch mapping is not the correct version', () => {
|
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', () => {
|
it('asserts the correct version', () => {
|
||||||
assertVersion(testChannelBasicInfo, 0);
|
assertVersion(testChannelBasicInfo, 0);
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ export const testChannelObject: UpdateChannelObject = {
|
|||||||
branchMapping:
|
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}',
|
'{"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],
|
updateBranches: [testUpdateBranch1, testUpdateBranch2],
|
||||||
|
isPaused: false,
|
||||||
__typename: 'UpdateChannel',
|
__typename: 'UpdateChannel',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
*/
|
*/
|
||||||
export class SelectChannel implements EASUpdateAction<UpdateChannelBasicInfoFragment | null> {
|
export class SelectChannel implements EASUpdateAction<UpdateChannelBasicInfoFragment | null> {
|
||||||
constructor(
|
constructor(
|
||||||
private options: {
|
private readonly options: {
|
||||||
filterPredicate?: (channelInfo: UpdateChannelBasicInfoFragment) => boolean;
|
filterPredicate?: (channelInfo: UpdateChannelBasicInfoFragment) => boolean;
|
||||||
printedType?: string;
|
printedType?: string;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
@@ -91,8 +91,8 @@ export class SelectChannel implements EASUpdateAction<UpdateChannelBasicInfoFrag
|
|||||||
queryParams: QueryParams
|
queryParams: QueryParams
|
||||||
): Promise<Connection<UpdateChannelBasicInfoFragment>> =>
|
): Promise<Connection<UpdateChannelBasicInfoFragment>> =>
|
||||||
filterPredicate
|
filterPredicate
|
||||||
? this.filterQueryAsync(ctx, queryParams, filterPredicate)
|
? await this.filterQueryAsync(ctx, queryParams, filterPredicate)
|
||||||
: this.queryAsync(ctx, queryParams);
|
: await this.queryAsync(ctx, queryParams);
|
||||||
|
|
||||||
const getTitleAsync = async (channelInfo: UpdateChannelBasicInfoFragment): Promise<string> =>
|
const getTitleAsync = async (channelInfo: UpdateChannelBasicInfoFragment): Promise<string> =>
|
||||||
channelInfo.name;
|
channelInfo.name;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
import { BranchQuery, UpdateBranchOnChannelObject } from '../graphql/queries/BranchQuery';
|
import { BranchQuery, UpdateBranchOnChannelObject } from '../graphql/queries/BranchQuery';
|
||||||
import { ChannelQuery, UpdateChannelObject } from '../graphql/queries/ChannelQuery';
|
import { ChannelQuery, UpdateChannelObject } from '../graphql/queries/ChannelQuery';
|
||||||
import { UpdateChannelBasicInfoFragmentNode } from '../graphql/types/UpdateChannelBasicInfo';
|
import { UpdateChannelBasicInfoFragmentNode } from '../graphql/types/UpdateChannelBasicInfo';
|
||||||
import Log from '../log';
|
import Log, { learnMore } from '../log';
|
||||||
import { getDisplayNameForProjectIdAsync } from '../project/projectUtils';
|
import { getDisplayNameForProjectIdAsync } from '../project/projectUtils';
|
||||||
import formatFields from '../utils/formatFields';
|
import formatFields from '../utils/formatFields';
|
||||||
import { printJsonOnlyOutput } from '../utils/json';
|
import { printJsonOnlyOutput } from '../utils/json';
|
||||||
@@ -89,7 +89,9 @@ export async function listAndRenderChannelsOnAppAsync(
|
|||||||
queryChannelsOnAppAsync(graphqlClient, { limit, offset, appId: projectId }),
|
queryChannelsOnAppAsync(graphqlClient, { limit, offset, appId: projectId }),
|
||||||
promptOptions: {
|
promptOptions: {
|
||||||
title: 'Load more channels?',
|
title: 'Load more channels?',
|
||||||
renderListItems: channels => renderPageOfChannels(channels, paginatedQueryOptions),
|
renderListItems: channels => {
|
||||||
|
renderPageOfChannels(channels, paginatedQueryOptions);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -108,7 +110,11 @@ export async function listAndRenderBranchesAndUpdatesOnChannelAsync(
|
|||||||
}
|
}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const channel = await ChannelQuery.viewUpdateChannelAsync(graphqlClient, { appId, channelName });
|
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) {
|
if (paginatedQueryOptions.nonInteractive) {
|
||||||
const branches = await queryBranchesAndUpdateGroupsOnChannelAsync(graphqlClient, {
|
const branches = await queryBranchesAndUpdateGroupsOnChannelAsync(graphqlClient, {
|
||||||
@@ -131,8 +137,9 @@ export async function listAndRenderBranchesAndUpdatesOnChannelAsync(
|
|||||||
}),
|
}),
|
||||||
promptOptions: {
|
promptOptions: {
|
||||||
title: 'Load more channels?',
|
title: 'Load more channels?',
|
||||||
renderListItems: branches =>
|
renderListItems: branches => {
|
||||||
renderPageOfBranchesOnChannel(channel, branches, paginatedQueryOptions),
|
renderPageOfBranchesOnChannel(channel, branches, paginatedQueryOptions);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -164,7 +171,11 @@ function renderPageOfChannels(
|
|||||||
printJsonOnlyOutput({ currentPage });
|
printJsonOnlyOutput({ currentPage });
|
||||||
} else {
|
} else {
|
||||||
for (const channel of currentPage) {
|
for (const channel of currentPage) {
|
||||||
renderChannelHeaderContent({ channelName: channel.name, channelId: channel.id });
|
renderChannelHeaderContent({
|
||||||
|
channelName: channel.name,
|
||||||
|
channelId: channel.id,
|
||||||
|
isPaused: channel.isPaused,
|
||||||
|
});
|
||||||
Log.addNewLineIfNone();
|
Log.addNewLineIfNone();
|
||||||
logChannelDetails(channel);
|
logChannelDetails(channel);
|
||||||
|
|
||||||
@@ -193,9 +204,11 @@ function renderPageOfBranchesOnChannel(
|
|||||||
function renderChannelHeaderContent({
|
function renderChannelHeaderContent({
|
||||||
channelName,
|
channelName,
|
||||||
channelId,
|
channelId,
|
||||||
|
isPaused,
|
||||||
}: {
|
}: {
|
||||||
channelName: string;
|
channelName: string;
|
||||||
channelId: string;
|
channelId: string;
|
||||||
|
isPaused: boolean;
|
||||||
}): void {
|
}): void {
|
||||||
Log.addNewLineIfNone();
|
Log.addNewLineIfNone();
|
||||||
Log.log(chalk.bold('Channel:'));
|
Log.log(chalk.bold('Channel:'));
|
||||||
@@ -203,6 +216,7 @@ function renderChannelHeaderContent({
|
|||||||
formatFields([
|
formatFields([
|
||||||
{ label: 'Name', value: channelName },
|
{ label: 'Name', value: channelName },
|
||||||
{ label: 'ID', value: channelId },
|
{ label: 'ID', value: channelId },
|
||||||
|
{ label: 'Status', value: isPaused ? 'Paused' : 'Active' },
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
Log.addNewLineIfNone();
|
Log.addNewLineIfNone();
|
||||||
@@ -289,7 +303,6 @@ export async function doesChannelExistAsync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* Creates a channel and links it to a branch with the same name.
|
* Creates a channel and links it to a branch with the same name.
|
||||||
*
|
*
|
||||||
* @param appId the app ID, also known as the project ID
|
* @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 }
|
}: { appId: string; channelName: string; shouldPrintJson?: boolean }
|
||||||
): Promise<ChannelBasicInfo> {
|
): Promise<ChannelBasicInfo> {
|
||||||
let branchId: string;
|
let branchId: string;
|
||||||
let branchMessage: string;
|
let hasCreatedBranch = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||||
@@ -313,7 +326,6 @@ export async function createAndLinkChannelAsync(
|
|||||||
name: channelName,
|
name: channelName,
|
||||||
});
|
});
|
||||||
branchId = branch.id;
|
branchId = branch.id;
|
||||||
branchMessage = `We found a branch with the same name`;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof BranchNotFoundError) {
|
if (error instanceof BranchNotFoundError) {
|
||||||
const newBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
|
const newBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
|
||||||
@@ -321,7 +333,7 @@ export async function createAndLinkChannelAsync(
|
|||||||
name: channelName,
|
name: channelName,
|
||||||
});
|
});
|
||||||
branchId = newBranch.id;
|
branchId = newBranch.id;
|
||||||
branchMessage = `We also went ahead and made a branch with the same name`;
|
hasCreatedBranch = true;
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -344,26 +356,25 @@ export async function createAndLinkChannelAsync(
|
|||||||
if (shouldPrintJson) {
|
if (shouldPrintJson) {
|
||||||
printJsonOnlyOutput(newChannel);
|
printJsonOnlyOutput(newChannel);
|
||||||
} else {
|
} else {
|
||||||
|
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, appId);
|
||||||
|
const learnMoreLink = learnMore('https://docs.expo.dev/eas-update/eas-cli/');
|
||||||
|
|
||||||
Log.addNewLineIfNone();
|
Log.addNewLineIfNone();
|
||||||
Log.withTick(
|
if (hasCreatedBranch) {
|
||||||
`Created a new channel on project ${chalk.bold(
|
Log.withTick(
|
||||||
await getDisplayNameForProjectIdAsync(graphqlClient, appId)
|
`Created update channel ${chalk.bold(`"${newChannel.name}"`)} and branch ${chalk.bold(
|
||||||
)}`
|
`"${newChannel.name}"`
|
||||||
);
|
)} on ${chalk.bold(projectDisplayName)} project. ${learnMoreLink}`
|
||||||
Log.log(
|
);
|
||||||
formatFields([
|
} else {
|
||||||
{ label: 'Name', value: newChannel.name },
|
Log.withTick(
|
||||||
{ label: 'ID', value: newChannel.id },
|
`Created update channel ${chalk.bold(`"${newChannel.name}"`)} on ${chalk.bold(
|
||||||
])
|
projectDisplayName
|
||||||
);
|
)} project and connected it with existing ${chalk.bold(
|
||||||
Log.addNewLineIfNone();
|
`"${newChannel.name}"`
|
||||||
Log.withTick(`${branchMessage} and have pointed the channel at it.`);
|
)} branch. ${learnMoreLink}`
|
||||||
Log.log(
|
);
|
||||||
formatFields([
|
}
|
||||||
{ label: 'Name', value: newChannel.name },
|
|
||||||
{ label: 'ID', value: branchId },
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return newChannel;
|
return newChannel;
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import nullthrows from 'nullthrows';
|
|||||||
|
|
||||||
import AnalyticsContextField from './context/AnalyticsContextField';
|
import AnalyticsContextField from './context/AnalyticsContextField';
|
||||||
import ContextField from './context/ContextField';
|
import ContextField from './context/ContextField';
|
||||||
|
import DynamicLoggedInContextField from './context/DynamicLoggedInContextField';
|
||||||
import {
|
import {
|
||||||
DynamicPrivateProjectConfigContextField,
|
DynamicPrivateProjectConfigContextField,
|
||||||
DynamicPublicProjectConfigContextField,
|
DynamicPublicProjectConfigContextField,
|
||||||
@@ -15,6 +16,8 @@ import MaybeLoggedInContextField from './context/MaybeLoggedInContextField';
|
|||||||
import { OptionalPrivateProjectConfigContextField } from './context/OptionalPrivateProjectConfigContextField';
|
import { OptionalPrivateProjectConfigContextField } from './context/OptionalPrivateProjectConfigContextField';
|
||||||
import { PrivateProjectConfigContextField } from './context/PrivateProjectConfigContextField';
|
import { PrivateProjectConfigContextField } from './context/PrivateProjectConfigContextField';
|
||||||
import ProjectDirContextField from './context/ProjectDirContextField';
|
import ProjectDirContextField from './context/ProjectDirContextField';
|
||||||
|
import { ProjectIdContextField } from './context/ProjectIdContextField';
|
||||||
|
import { ServerSideEnvironmentVariablesContextField } from './context/ServerSideEnvironmentVariablesContextField';
|
||||||
import SessionManagementContextField from './context/SessionManagementContextField';
|
import SessionManagementContextField from './context/SessionManagementContextField';
|
||||||
import VcsClientContextField from './context/VcsClientContextField';
|
import VcsClientContextField from './context/VcsClientContextField';
|
||||||
import { EasCommandError } from './errors';
|
import { EasCommandError } from './errors';
|
||||||
@@ -23,6 +26,7 @@ import {
|
|||||||
CommandEvent,
|
CommandEvent,
|
||||||
createAnalyticsAsync,
|
createAnalyticsAsync,
|
||||||
} from '../analytics/AnalyticsManager';
|
} from '../analytics/AnalyticsManager';
|
||||||
|
import { EnvironmentVariableEnvironment } from '../graphql/generated';
|
||||||
import Log from '../log';
|
import Log from '../log';
|
||||||
import SessionManager from '../user/SessionManager';
|
import SessionManager from '../user/SessionManager';
|
||||||
import { Client } from '../vcs/vcs';
|
import { Client } from '../vcs/vcs';
|
||||||
@@ -43,8 +47,26 @@ export type ContextOutput<
|
|||||||
[P in keyof T]: T[P];
|
[P in keyof T]: T[P];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GetContextType<Type> = {
|
||||||
|
[Property in keyof Type]: any;
|
||||||
|
};
|
||||||
|
|
||||||
const BASE_GRAPHQL_ERROR_MESSAGE: string = 'GraphQL request failed.';
|
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 {
|
export default abstract class EasCommand extends Command {
|
||||||
protected static readonly ContextOptions = {
|
protected static readonly ContextOptions = {
|
||||||
/**
|
/**
|
||||||
@@ -61,8 +83,16 @@ export default abstract class EasCommand extends Command {
|
|||||||
MaybeLoggedIn: {
|
MaybeLoggedIn: {
|
||||||
maybeLoggedIn: new MaybeLoggedInContextField(),
|
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.
|
* 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: {
|
SessionManagment: {
|
||||||
sessionManager: new SessionManagementContextField(),
|
sessionManager: new SessionManagementContextField(),
|
||||||
@@ -72,7 +102,7 @@ export default abstract class EasCommand extends Command {
|
|||||||
* run within a project directory, null otherwise.
|
* run within a project directory, null otherwise.
|
||||||
*/
|
*/
|
||||||
OptionalProjectConfig: {
|
OptionalProjectConfig: {
|
||||||
privateProjectConfig: new OptionalPrivateProjectConfigContextField(),
|
optionalPrivateProjectConfig: new OptionalPrivateProjectConfigContextField(),
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Require this command to be run in a project directory. Return the project directory in the context.
|
* 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: {
|
Vcs: {
|
||||||
vcsClient: new VcsClientContextField(),
|
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,
|
} = object,
|
||||||
>(
|
>(
|
||||||
commandClass: { contextDefinition: ContextInput<C> },
|
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>> {
|
): Promise<ContextOutput<C>> {
|
||||||
const contextDefinition = commandClass.contextDefinition;
|
const contextDefinition = commandClass.contextDefinition;
|
||||||
|
|
||||||
@@ -155,6 +207,7 @@ export default abstract class EasCommand extends Command {
|
|||||||
sessionManager: this.sessionManager,
|
sessionManager: this.sessionManager,
|
||||||
analytics: this.analytics,
|
analytics: this.analytics,
|
||||||
vcsClientOverride,
|
vcsClientOverride,
|
||||||
|
withServerSideEnvironment,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -187,13 +240,13 @@ export default abstract class EasCommand extends Command {
|
|||||||
action: `eas ${this.id}`,
|
action: `eas ${this.id}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.runAsync();
|
return await this.runAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line async-protect/async-suffix
|
// eslint-disable-next-line async-protect/async-suffix
|
||||||
override async finally(err: Error): Promise<any> {
|
override async finally(err: Error): Promise<any> {
|
||||||
await this.analytics.flushAsync();
|
await this.analytics.flushAsync();
|
||||||
return super.finally(err);
|
return await super.finally(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override catch(err: Error): Promise<any> {
|
protected override catch(err: Error): Promise<any> {
|
||||||
|
|||||||
@@ -38,7 +38,12 @@ export async function fetchBuildsAsync({
|
|||||||
}: {
|
}: {
|
||||||
graphqlClient: ExpoGraphqlClient;
|
graphqlClient: ExpoGraphqlClient;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
filters?: { statuses?: BuildStatus[]; platform?: RequestedPlatform; profile?: string };
|
filters?: {
|
||||||
|
statuses?: BuildStatus[];
|
||||||
|
platform?: RequestedPlatform;
|
||||||
|
profile?: string;
|
||||||
|
hasFingerprint?: boolean;
|
||||||
|
};
|
||||||
}): Promise<BuildFragment[]> {
|
}): Promise<BuildFragment[]> {
|
||||||
let builds: BuildFragment[];
|
let builds: BuildFragment[];
|
||||||
const queryFilters: InputMaybe<BuildFilter> = {};
|
const queryFilters: InputMaybe<BuildFilter> = {};
|
||||||
@@ -48,6 +53,9 @@ export async function fetchBuildsAsync({
|
|||||||
if (filters?.profile) {
|
if (filters?.profile) {
|
||||||
queryFilters['buildProfile'] = filters.profile;
|
queryFilters['buildProfile'] = filters.profile;
|
||||||
}
|
}
|
||||||
|
if (filters?.hasFingerprint) {
|
||||||
|
queryFilters['hasFingerprint'] = filters.hasFingerprint;
|
||||||
|
}
|
||||||
if (!filters?.statuses) {
|
if (!filters?.statuses) {
|
||||||
builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||||
appId: projectId,
|
appId: projectId,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Analytics } from '../../analytics/AnalyticsManager';
|
import { Analytics } from '../../analytics/AnalyticsManager';
|
||||||
|
import { EnvironmentVariableEnvironment } from '../../graphql/generated';
|
||||||
import SessionManager from '../../user/SessionManager';
|
import SessionManager from '../../user/SessionManager';
|
||||||
import { Client } from '../../vcs/vcs';
|
import { Client } from '../../vcs/vcs';
|
||||||
|
|
||||||
@@ -7,6 +8,10 @@ export interface ContextOptions {
|
|||||||
analytics: Analytics;
|
analytics: Analytics;
|
||||||
nonInteractive: boolean;
|
nonInteractive: boolean;
|
||||||
vcsClientOverride?: Client;
|
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> {
|
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 ContextField, { ContextOptions } from './ContextField';
|
||||||
|
import { createGraphqlClient } from './contextUtils/createGraphqlClient';
|
||||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||||
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
||||||
|
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
|
||||||
import {
|
import {
|
||||||
ExpoConfigOptions,
|
ExpoConfigOptions,
|
||||||
getPrivateExpoConfig,
|
getPrivateExpoConfigAsync,
|
||||||
getPublicExpoConfig,
|
getPublicExpoConfigAsync,
|
||||||
} from '../../project/expoConfig';
|
} from '../../project/expoConfig';
|
||||||
|
|
||||||
export type DynamicConfigContextFn = (options?: ExpoConfigOptions) => Promise<{
|
export type DynamicConfigContextFn = (options?: ExpoConfigOptions) => Promise<{
|
||||||
@@ -19,15 +21,34 @@ export class DynamicPublicProjectConfigContextField extends ContextField<Dynamic
|
|||||||
async getValueAsync({
|
async getValueAsync({
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
sessionManager,
|
sessionManager,
|
||||||
|
withServerSideEnvironment,
|
||||||
}: ContextOptions): Promise<DynamicConfigContextFn> {
|
}: ContextOptions): Promise<DynamicConfigContextFn> {
|
||||||
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||||
return async (options?: ExpoConfigOptions) => {
|
return async (options?: ExpoConfigOptions) => {
|
||||||
const expBefore = getPublicExpoConfig(projectDir, options);
|
const expBefore = await getPublicExpoConfigAsync(projectDir, options);
|
||||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
env: options?.env,
|
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 {
|
return {
|
||||||
exp,
|
exp,
|
||||||
projectDir,
|
projectDir,
|
||||||
@@ -41,15 +62,34 @@ export class DynamicPrivateProjectConfigContextField extends ContextField<Dynami
|
|||||||
async getValueAsync({
|
async getValueAsync({
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
sessionManager,
|
sessionManager,
|
||||||
|
withServerSideEnvironment,
|
||||||
}: ContextOptions): Promise<DynamicConfigContextFn> {
|
}: ContextOptions): Promise<DynamicConfigContextFn> {
|
||||||
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||||
return async (options?: ExpoConfigOptions) => {
|
return async (options?: ExpoConfigOptions) => {
|
||||||
const expBefore = getPrivateExpoConfig(projectDir, options);
|
const expBefore = await getPrivateExpoConfigAsync(projectDir, options);
|
||||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
env: options?.env,
|
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 {
|
return {
|
||||||
exp,
|
exp,
|
||||||
projectDir,
|
projectDir,
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
import ContextField, { ContextOptions } from './ContextField';
|
import ContextField, { ContextOptions } from './ContextField';
|
||||||
import { ExpoGraphqlClient, createGraphqlClient } from './contextUtils/createGraphqlClient';
|
import { ExpoGraphqlClient, createGraphqlClient } from './contextUtils/createGraphqlClient';
|
||||||
|
import { LoggedInAuthenticationInfo } from '../../user/SessionManager';
|
||||||
import { Actor } from '../../user/User';
|
import { Actor } from '../../user/User';
|
||||||
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
|
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
|
||||||
import FeatureGating from '../gating/FeatureGating';
|
import FeatureGating from '../gating/FeatureGating';
|
||||||
|
|
||||||
export default class LoggedInContextField extends ContextField<{
|
type LoggedInContextType = {
|
||||||
actor: Actor;
|
actor: Actor;
|
||||||
featureGating: FeatureGating;
|
featureGating: FeatureGating;
|
||||||
graphqlClient: ExpoGraphqlClient;
|
graphqlClient: ExpoGraphqlClient;
|
||||||
}> {
|
authenticationInfo: LoggedInAuthenticationInfo;
|
||||||
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<{
|
};
|
||||||
actor: Actor;
|
|
||||||
featureGating: FeatureGating;
|
export default class LoggedInContextField extends ContextField<LoggedInContextType> {
|
||||||
graphqlClient: ExpoGraphqlClient;
|
async getValueAsync({
|
||||||
}> {
|
nonInteractive,
|
||||||
|
sessionManager,
|
||||||
|
}: ContextOptions): Promise<LoggedInContextType> {
|
||||||
const { actor, authenticationInfo } = await sessionManager.ensureLoggedInAsync({
|
const { actor, authenticationInfo } = await sessionManager.ensureLoggedInAsync({
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
});
|
});
|
||||||
@@ -25,6 +28,7 @@ export default class LoggedInContextField extends ContextField<{
|
|||||||
actor,
|
actor,
|
||||||
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
|
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
|
authenticationInfo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,16 +4,18 @@ import { Actor } from '../../user/User';
|
|||||||
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
|
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
|
||||||
import FeatureGating from '../gating/FeatureGating';
|
import FeatureGating from '../gating/FeatureGating';
|
||||||
|
|
||||||
export default class MaybeLoggedInContextField extends ContextField<{
|
type MaybeLoggedInContextType = {
|
||||||
actor: Actor | null;
|
actor: Actor | null;
|
||||||
featureGating: FeatureGating;
|
featureGating: FeatureGating;
|
||||||
graphqlClient: ExpoGraphqlClient;
|
graphqlClient: ExpoGraphqlClient;
|
||||||
}> {
|
authenticationInfo: {
|
||||||
async getValueAsync({ sessionManager }: ContextOptions): Promise<{
|
accessToken: string | null;
|
||||||
actor: Actor | null;
|
sessionSecret: string | null;
|
||||||
featureGating: FeatureGating;
|
};
|
||||||
graphqlClient: ExpoGraphqlClient;
|
};
|
||||||
}> {
|
|
||||||
|
export default class MaybeLoggedInContextField extends ContextField<MaybeLoggedInContextType> {
|
||||||
|
async getValueAsync({ sessionManager }: ContextOptions): Promise<MaybeLoggedInContextType> {
|
||||||
const authenticationInfo = {
|
const authenticationInfo = {
|
||||||
accessToken: sessionManager.getAccessToken(),
|
accessToken: sessionManager.getAccessToken(),
|
||||||
sessionSecret: sessionManager.getSessionSecret(),
|
sessionSecret: sessionManager.getSessionSecret(),
|
||||||
@@ -27,6 +29,7 @@ export default class MaybeLoggedInContextField extends ContextField<{
|
|||||||
actor: actor ?? null,
|
actor: actor ?? null,
|
||||||
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
|
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
|
||||||
graphqlClient,
|
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 { InvalidEasJsonError } from '@expo/eas-json/build/errors';
|
||||||
|
|
||||||
import ContextField, { ContextOptions } from './ContextField';
|
import ContextField, { ContextOptions } from './ContextField';
|
||||||
|
import { createGraphqlClient } from './contextUtils/createGraphqlClient';
|
||||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||||
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
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<
|
export class OptionalPrivateProjectConfigContextField extends ContextField<
|
||||||
| {
|
| {
|
||||||
@@ -14,7 +16,11 @@ export class OptionalPrivateProjectConfigContextField extends ContextField<
|
|||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
> {
|
> {
|
||||||
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<
|
async getValueAsync({
|
||||||
|
nonInteractive,
|
||||||
|
sessionManager,
|
||||||
|
withServerSideEnvironment,
|
||||||
|
}: ContextOptions): Promise<
|
||||||
| {
|
| {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
exp: ExpoConfig;
|
exp: ExpoConfig;
|
||||||
@@ -35,11 +41,24 @@ export class OptionalPrivateProjectConfigContextField extends ContextField<
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const expBefore = getPrivateExpoConfig(projectDir);
|
const expBefore = await getPrivateExpoConfigAsync(projectDir);
|
||||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||||
nonInteractive,
|
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 {
|
return {
|
||||||
exp,
|
exp,
|
||||||
projectDir,
|
projectDir,
|
||||||
|
|||||||
@@ -1,26 +1,45 @@
|
|||||||
import { ExpoConfig } from '@expo/config-types';
|
import { ExpoConfig } from '@expo/config';
|
||||||
|
|
||||||
import ContextField, { ContextOptions } from './ContextField';
|
import ContextField, { ContextOptions } from './ContextField';
|
||||||
|
import { createGraphqlClient } from './contextUtils/createGraphqlClient';
|
||||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||||
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
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<{
|
export class PrivateProjectConfigContextField extends ContextField<{
|
||||||
projectId: string;
|
projectId: string;
|
||||||
exp: ExpoConfig;
|
exp: ExpoConfig;
|
||||||
projectDir: string;
|
projectDir: string;
|
||||||
}> {
|
}> {
|
||||||
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<{
|
async getValueAsync({
|
||||||
|
nonInteractive,
|
||||||
|
sessionManager,
|
||||||
|
withServerSideEnvironment,
|
||||||
|
}: ContextOptions): Promise<{
|
||||||
projectId: string;
|
projectId: string;
|
||||||
exp: ExpoConfig;
|
exp: ExpoConfig;
|
||||||
projectDir: string;
|
projectDir: string;
|
||||||
}> {
|
}> {
|
||||||
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||||
const expBefore = getPrivateExpoConfig(projectDir);
|
const expBefore = await getPrivateExpoConfigAsync(projectDir);
|
||||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||||
nonInteractive,
|
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 {
|
return {
|
||||||
projectId,
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-1
@@ -6,6 +6,7 @@ import { Role } from '../../../../graphql/generated';
|
|||||||
import { AppQuery } from '../../../../graphql/queries/AppQuery';
|
import { AppQuery } from '../../../../graphql/queries/AppQuery';
|
||||||
import { learnMore } from '../../../../log';
|
import { learnMore } from '../../../../log';
|
||||||
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||||
|
import { isExpoInstalled } from '../../../../project/projectUtils';
|
||||||
import SessionManager from '../../../../user/SessionManager';
|
import SessionManager from '../../../../user/SessionManager';
|
||||||
import { findProjectRootAsync } from '../findProjectDirAndVerifyProjectSetupAsync';
|
import { findProjectRootAsync } from '../findProjectDirAndVerifyProjectSetupAsync';
|
||||||
import { getProjectIdAsync } from '../getProjectIdAsync';
|
import { getProjectIdAsync } from '../getProjectIdAsync';
|
||||||
@@ -21,6 +22,7 @@ jest.mock('../../../../ora', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
jest.mock('../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync');
|
jest.mock('../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync');
|
||||||
|
jest.mock('../../../../project/projectUtils');
|
||||||
|
|
||||||
describe(getProjectIdAsync, () => {
|
describe(getProjectIdAsync, () => {
|
||||||
let sessionManager: SessionManager;
|
let sessionManager: SessionManager;
|
||||||
@@ -71,6 +73,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
jest.mocked(findProjectRootAsync).mockResolvedValue('/app');
|
jest.mocked(findProjectRootAsync).mockResolvedValue('/app');
|
||||||
|
jest.mocked(isExpoInstalled).mockReturnValue(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets the project ID from app config if exists', async () => {
|
it('gets the project ID from app config if exists', async () => {
|
||||||
@@ -222,7 +225,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
config: { expo: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } } },
|
config: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } },
|
||||||
});
|
});
|
||||||
jest
|
jest
|
||||||
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function createGraphqlClient(authInfo: {
|
|||||||
return !!(
|
return !!(
|
||||||
err &&
|
err &&
|
||||||
!operation.context.noRetry &&
|
!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.`);
|
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))) {
|
if (maybeRepoRoot !== projectDir && (await isEasCliInDependenciesAsync(maybeRepoRoot))) {
|
||||||
printCliVersionWarning = true;
|
printCliVersionWarning = true;
|
||||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your monorepo dependencies.`);
|
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
|
* Determine the project root directory and ensure some constraints about the project setup
|
||||||
* like CLI version and dependencies.
|
* like CLI version and dependencies.
|
||||||
@@ -113,6 +115,9 @@ export async function findProjectRootAsync({
|
|||||||
export async function findProjectDirAndVerifyProjectSetupAsync(): Promise<string> {
|
export async function findProjectDirAndVerifyProjectSetupAsync(): Promise<string> {
|
||||||
const projectDir = await findProjectRootAsync();
|
const projectDir = await findProjectRootAsync();
|
||||||
await applyCliConfigAsync(projectDir);
|
await applyCliConfigAsync(projectDir);
|
||||||
await ensureEasCliIsNotInDependenciesAsync(projectDir);
|
if (!ranEnsureEasCliIsNotInDependencies) {
|
||||||
|
ranEnsureEasCliIsNotInDependencies = true;
|
||||||
|
await ensureEasCliIsNotInDependenciesAsync(projectDir);
|
||||||
|
}
|
||||||
return projectDir;
|
return projectDir;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { getProjectConfigDescription } from '@expo/config';
|
import { ExpoConfig, getProjectConfigDescription } from '@expo/config';
|
||||||
import { ExpoConfig } from '@expo/config-types';
|
|
||||||
import { Env } from '@expo/eas-build-job';
|
import { Env } from '@expo/eas-build-job';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
@@ -8,9 +7,11 @@ import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync
|
|||||||
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||||
import Log, { learnMore } from '../../../log';
|
import Log, { learnMore } from '../../../log';
|
||||||
import { ora } from '../../../ora';
|
import { ora } from '../../../ora';
|
||||||
import { createOrModifyExpoConfigAsync, getPrivateExpoConfig } from '../../../project/expoConfig';
|
import {
|
||||||
|
createOrModifyExpoConfigAsync,
|
||||||
|
getPrivateExpoConfigAsync,
|
||||||
|
} from '../../../project/expoConfig';
|
||||||
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||||
import { toAppPrivacy } from '../../../project/projectUtils';
|
|
||||||
import SessionManager from '../../../user/SessionManager';
|
import SessionManager from '../../../user/SessionManager';
|
||||||
import { Actor, getActorUsername } from '../../../user/User';
|
import { Actor, getActorUsername } from '../../../user/User';
|
||||||
|
|
||||||
@@ -18,13 +19,15 @@ import { Actor, getActorUsername } from '../../../user/User';
|
|||||||
* Save an EAS project ID to the appropriate field in the app config.
|
* 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 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(
|
export async function saveProjectIdToAppConfigAsync(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
options: { env?: Env } = {}
|
options: { env?: Env } = {}
|
||||||
): Promise<void> {
|
): 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(
|
const result = await createOrModifyExpoConfigAsync(
|
||||||
projectDir,
|
projectDir,
|
||||||
{
|
{
|
||||||
@@ -193,7 +196,6 @@ export async function validateOrSetProjectIdAsync({
|
|||||||
{
|
{
|
||||||
accountName: getAccountNameForEASProjectSync(exp, actor),
|
accountName: getAccountNameForEASProjectSync(exp, actor),
|
||||||
projectName: exp.slug,
|
projectName: exp.slug,
|
||||||
privacy: toAppPrivacy(exp.privacy),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
nonInteractive: options.nonInteractive,
|
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 { 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 = {
|
export const EasNonInteractiveAndJsonFlags = {
|
||||||
json: Flags.boolean({
|
json: Flags.boolean({
|
||||||
description: 'Enable JSON output, non-JSON messages will be printed to stderr.',
|
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 = {
|
export const EASNonInteractiveFlag = {
|
||||||
'non-interactive': Flags.boolean({
|
'non-interactive': Flags.boolean({
|
||||||
description: 'Run the command in non-interactive mode.',
|
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.',
|
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 {
|
public static overrideKeyForEachInTest(key: FeatureGateKey, enabled: boolean): void {
|
||||||
beforeEach(() => FeatureGateTestOverrides.setOverride(key, enabled));
|
beforeEach(() => {
|
||||||
afterEach(() => FeatureGateTestOverrides.removeOverride(key));
|
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 EasCommand from '../../commandUtils/EasCommand';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
|
import { confirmAsync } from '../../prompts';
|
||||||
|
import { getActorDisplayName } from '../../user/User';
|
||||||
|
|
||||||
export default class AccountLogin extends EasCommand {
|
export default class AccountLogin extends EasCommand {
|
||||||
static override description = 'log in with your Expo account';
|
static override description = 'log in with your Expo account';
|
||||||
@@ -17,6 +20,7 @@ export default class AccountLogin extends EasCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
|
...this.ContextOptions.MaybeLoggedIn,
|
||||||
...this.ContextOptions.SessionManagment,
|
...this.ContextOptions.SessionManagment,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -25,7 +29,28 @@ export default class AccountLogin extends EasCommand {
|
|||||||
flags: { sso },
|
flags: { sso },
|
||||||
} = await this.parse(AccountLogin);
|
} = 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 });
|
await sessionManager.showLoginPromptAsync({ sso });
|
||||||
Log.log('Logged in');
|
Log.log('Logged in');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,13 @@ export default class AccountView extends EasCommand {
|
|||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const {
|
const {
|
||||||
maybeLoggedIn: { actor },
|
maybeLoggedIn: { actor, authenticationInfo },
|
||||||
} = await this.getContextAsync(AccountView, { nonInteractive: true });
|
} = await this.getContextAsync(AccountView, { nonInteractive: true });
|
||||||
if (actor) {
|
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
|
// personal account is included, only show if more accounts that personal account
|
||||||
// but do show personal account in list if there are more
|
// 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> {
|
async runAsync(): Promise<void> {
|
||||||
const { STATUS: status } = (await this.parse(AnalyticsView)).args;
|
const { STATUS: status } = (await this.parse(AnalyticsView)).args;
|
||||||
if (status) {
|
if (status) {
|
||||||
setAnalyticsEnabledAsync(status === 'on');
|
await setAnalyticsEnabledAsync(status === 'on');
|
||||||
Log.withTick(`${status === 'on' ? 'Enabling' : 'Disabling'} analytics.`);
|
Log.withTick(`${status === 'on' ? 'Enabling' : 'Disabling'} analytics.`);
|
||||||
} else {
|
} else {
|
||||||
const analyticsEnabled = await getAnalyticsEnabledAsync();
|
const analyticsEnabled = await getAnalyticsEnabledAsync();
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default class BranchCreate extends EasCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
...this.ContextOptions.Vcs,
|
...this.ContextOptions.Vcs,
|
||||||
};
|
};
|
||||||
@@ -36,7 +36,7 @@ export default class BranchCreate extends EasCommand {
|
|||||||
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(BranchCreate);
|
} = await this.parse(BranchCreate);
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
vcsClient,
|
vcsClient,
|
||||||
} = await this.getContextAsync(BranchCreate, {
|
} = await this.getContextAsync(BranchCreate, {
|
||||||
@@ -58,7 +58,7 @@ export default class BranchCreate extends EasCommand {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
message: 'Provide a branch name:',
|
message: 'Provide a branch name:',
|
||||||
initial: await getDefaultBranchNameAsync(vcsClient),
|
initial: (await getDefaultBranchNameAsync(vcsClient)) ?? undefined,
|
||||||
validate: value => (value ? true : validationMessage),
|
validate: value => (value ? true : validationMessage),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export default class BranchDelete extends EasCommand {
|
|||||||
static override description = 'delete a branch';
|
static override description = 'delete a branch';
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ export default class BranchDelete extends EasCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(BranchDelete, { nonInteractive });
|
} = await this.getContextAsync(BranchDelete, { nonInteractive });
|
||||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ export default class BranchList extends EasCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
};
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(BranchList);
|
const { flags } = await this.parse(BranchList);
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(BranchList, {
|
} = await this.getContextAsync(BranchList, {
|
||||||
nonInteractive: flags['non-interactive'],
|
nonInteractive: flags['non-interactive'],
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default class BranchRename extends EasCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ export default class BranchRename extends EasCommand {
|
|||||||
flags: { json: jsonFlag, from: currentName, to: newName, 'non-interactive': nonInteractive },
|
flags: { json: jsonFlag, from: currentName, to: newName, 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(BranchRename);
|
} = await this.parse(BranchRename);
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(BranchRename, {
|
} = await this.getContextAsync(BranchRename, {
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default class BranchView extends EasCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ export default class BranchView extends EasCommand {
|
|||||||
} = await this.parse(BranchView);
|
} = await this.parse(BranchView);
|
||||||
const { 'non-interactive': nonInteractive } = flags;
|
const { 'non-interactive': nonInteractive } = flags;
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(BranchView, {
|
} = await this.getContextAsync(BranchView, {
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ async function cancelBuildAsync(
|
|||||||
)
|
)
|
||||||
.toPromise()
|
.toPromise()
|
||||||
);
|
);
|
||||||
return data.build!.cancel;
|
return data.build.cancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function selectBuildToCancelAsync(
|
export async function selectBuildToCancelAsync(
|
||||||
@@ -110,7 +110,7 @@ export default class BuildCancel extends EasCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
...this.ContextOptions.Vcs,
|
...this.ContextOptions.Vcs,
|
||||||
};
|
};
|
||||||
@@ -128,7 +128,7 @@ export default class BuildCancel extends EasCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(BuildCancel, {
|
} = await this.getContextAsync(BuildCancel, {
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export default class BuildConfigure extends EasCommand {
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
} = await this.getContextAsync(BuildConfigure, {
|
} = await this.getContextAsync(BuildConfigure, {
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
|
withServerSideEnvironment: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
Log.log(
|
Log.log(
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ async function deleteBuildAsync(
|
|||||||
)
|
)
|
||||||
.toPromise()
|
.toPromise()
|
||||||
);
|
);
|
||||||
return data.build!.deleteBuild;
|
return data.build.deleteBuild;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function selectBuildToDeleteAsync(
|
export async function selectBuildToDeleteAsync(
|
||||||
@@ -94,7 +94,7 @@ export default class BuildDelete extends EasCommand {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
...this.ContextOptions.Vcs,
|
...this.ContextOptions.Vcs,
|
||||||
};
|
};
|
||||||
@@ -112,7 +112,7 @@ export default class BuildDelete extends EasCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(BuildDelete, { nonInteractive });
|
} = await this.getContextAsync(BuildDelete, { nonInteractive });
|
||||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ export default class Build extends EasCommand {
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
} = await this.getContextAsync(Build, {
|
} = await this.getContextAsync(Build, {
|
||||||
nonInteractive: flags.nonInteractive,
|
nonInteractive: flags.nonInteractive,
|
||||||
|
withServerSideEnvironment: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export default class BuildInspect extends EasCommand {
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
} = await this.getContextAsync(BuildInspect, {
|
} = await this.getContextAsync(BuildInspect, {
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
|
withServerSideEnvironment: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const outputDirectory = path.resolve(process.cwd(), flags.output);
|
const outputDirectory = path.resolve(process.cwd(), flags.output);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import EasCommand from '../../commandUtils/EasCommand';
|
|||||||
import { RequestedPlatform } from '../../platform';
|
import { RequestedPlatform } from '../../platform';
|
||||||
import { enableJsonOutput } from '../../utils/json';
|
import { enableJsonOutput } from '../../utils/json';
|
||||||
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
|
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
|
||||||
|
import NoVcsClient from '../../vcs/clients/noVcs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This command will be run on the EAS Build workers, when building
|
* This command will be run on the EAS Build workers, when building
|
||||||
@@ -63,7 +64,8 @@ export default class BuildInternal extends EasCommand {
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
} = await this.getContextAsync(BuildInternal, {
|
} = await this.getContextAsync(BuildInternal, {
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
vcsClientOverride: new GitNoCommitClient(),
|
vcsClientOverride: process.env.EAS_NO_VCS ? new NoVcsClient() : new GitNoCommitClient(),
|
||||||
|
withServerSideEnvironment: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ export default class BuildList extends EasCommand {
|
|||||||
aliases: ['gitCommitHash'],
|
aliases: ['gitCommitHash'],
|
||||||
description: 'Filter only builds created with the specified git commit hash',
|
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,
|
...EasPaginatedQueryFlags,
|
||||||
limit: getLimitFlagWithCustomValues({ defaultTo: 10, limit: BUILDS_LIMIT }),
|
limit: getLimitFlagWithCustomValues({ defaultTo: 10, limit: BUILDS_LIMIT }),
|
||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
@@ -72,7 +75,7 @@ export default class BuildList extends EasCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
...this.ContextOptions.Vcs,
|
...this.ContextOptions.Vcs,
|
||||||
};
|
};
|
||||||
@@ -99,7 +102,7 @@ export default class BuildList extends EasCommand {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(BuildList, {
|
} = await this.getContextAsync(BuildList, {
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
@@ -130,6 +133,7 @@ export default class BuildList extends EasCommand {
|
|||||||
buildProfile: flags['build-profile'],
|
buildProfile: flags['build-profile'],
|
||||||
gitCommitHash: flags['git-commit-hash'],
|
gitCommitHash: flags['git-commit-hash'],
|
||||||
simulator: flags.simulator,
|
simulator: flags.simulator,
|
||||||
|
fingerprintHash: flags['fingerprint-hash'],
|
||||||
},
|
},
|
||||||
paginatedQueryOptions,
|
paginatedQueryOptions,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import chalk from 'chalk';
|
|||||||
|
|
||||||
import { handleDeprecatedEasJsonAsync } from '.';
|
import { handleDeprecatedEasJsonAsync } from '.';
|
||||||
import { waitForBuildEndAsync } from '../../build/build';
|
import { waitForBuildEndAsync } from '../../build/build';
|
||||||
|
import { evaluateConfigWithEnvVarsAsync } from '../../build/evaluateConfigWithEnvVarsAsync';
|
||||||
import { ensureIosCredentialsForBuildResignAsync } from '../../build/ios/credentials';
|
import { ensureIosCredentialsForBuildResignAsync } from '../../build/ios/credentials';
|
||||||
import { prepareCredentialsToResign } from '../../build/ios/prepareJob';
|
import { prepareCredentialsToResign } from '../../build/ios/prepareJob';
|
||||||
import { listAndSelectBuildOnAppAsync } from '../../build/queries';
|
import { listAndSelectBuildOnAppAsync } from '../../build/queries';
|
||||||
@@ -21,6 +22,7 @@ import {
|
|||||||
BuildStatus,
|
BuildStatus,
|
||||||
BuildWorkflow,
|
BuildWorkflow,
|
||||||
DistributionType,
|
DistributionType,
|
||||||
|
EnvironmentVariableEnvironment,
|
||||||
IosJobOverridesInput,
|
IosJobOverridesInput,
|
||||||
ProjectArchiveSourceType,
|
ProjectArchiveSourceType,
|
||||||
StatuspageServiceName,
|
StatuspageServiceName,
|
||||||
@@ -46,6 +48,7 @@ interface BuildResignFlags {
|
|||||||
sourceProfile?: string;
|
sourceProfile?: string;
|
||||||
maybeBuildId?: string;
|
maybeBuildId?: string;
|
||||||
wait: boolean;
|
wait: boolean;
|
||||||
|
environment?: EnvironmentVariableEnvironment;
|
||||||
}
|
}
|
||||||
interface RawBuildResignFlags {
|
interface RawBuildResignFlags {
|
||||||
json: boolean;
|
json: boolean;
|
||||||
@@ -57,6 +60,7 @@ interface RawBuildResignFlags {
|
|||||||
'source-profile': string | undefined;
|
'source-profile': string | undefined;
|
||||||
wait: boolean;
|
wait: boolean;
|
||||||
id: string | undefined;
|
id: string | undefined;
|
||||||
|
environment: EnvironmentVariableEnvironment | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class BuildResign extends EasCommand {
|
export default class BuildResign extends EasCommand {
|
||||||
@@ -116,6 +120,7 @@ export default class BuildResign extends EasCommand {
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
} = await this.getContextAsync(BuildResign, {
|
} = await this.getContextAsync(BuildResign, {
|
||||||
nonInteractive: flags.nonInteractive,
|
nonInteractive: flags.nonInteractive,
|
||||||
|
withServerSideEnvironment: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const maybeBuild = flags.maybeBuildId
|
const maybeBuild = flags.maybeBuildId
|
||||||
@@ -140,7 +145,15 @@ export default class BuildResign extends EasCommand {
|
|||||||
platform,
|
platform,
|
||||||
flags.targetProfile ?? '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 account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||||
const build = await this.ensureBuildSelectedAsync(
|
const build = await this.ensureBuildSelectedAsync(
|
||||||
{
|
{
|
||||||
@@ -161,7 +174,7 @@ export default class BuildResign extends EasCommand {
|
|||||||
user: actor,
|
user: actor,
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
analytics,
|
analytics,
|
||||||
env: buildProfile.env,
|
env,
|
||||||
easJsonCliConfig,
|
easJsonCliConfig,
|
||||||
vcsClient,
|
vcsClient,
|
||||||
});
|
});
|
||||||
@@ -181,7 +194,7 @@ export default class BuildResign extends EasCommand {
|
|||||||
projectDir,
|
projectDir,
|
||||||
exp,
|
exp,
|
||||||
xcodeBuildContext,
|
xcodeBuildContext,
|
||||||
env: buildProfile.env,
|
env,
|
||||||
vcsClient,
|
vcsClient,
|
||||||
});
|
});
|
||||||
const credentialsResult = await ensureIosCredentialsForBuildResignAsync(
|
const credentialsResult = await ensureIosCredentialsForBuildResignAsync(
|
||||||
@@ -248,6 +261,7 @@ export default class BuildResign extends EasCommand {
|
|||||||
targetProfile: flags['target-profile'],
|
targetProfile: flags['target-profile'],
|
||||||
maybeBuildId: flags.id,
|
maybeBuildId: flags.id,
|
||||||
wait: flags.wait,
|
wait: flags.wait,
|
||||||
|
environment: flags.environment,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,8 +79,7 @@ export default class Run extends EasCommand {
|
|||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.ProjectDir,
|
|
||||||
...this.ContextOptions.Vcs,
|
...this.ContextOptions.Vcs,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,7 +89,7 @@ export default class Run extends EasCommand {
|
|||||||
const queryOptions = getPaginatedQueryOptions(flags);
|
const queryOptions = getPaginatedQueryOptions(flags);
|
||||||
const {
|
const {
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
} = await this.getContextAsync(Run, {
|
} = await this.getContextAsync(Run, {
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { EasJsonAccessor } from '@expo/eas-json';
|
|||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync';
|
||||||
import EasCommand from '../../../commandUtils/EasCommand';
|
import EasCommand from '../../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../../commandUtils/flags';
|
||||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||||
@@ -53,6 +54,7 @@ export default class BuildVersionGetView extends EasCommand {
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
} = await this.getContextAsync(BuildVersionGetView, {
|
} = await this.getContextAsync(BuildVersionGetView, {
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
|
withServerSideEnvironment: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!flags.platform && flags['non-interactive']) {
|
if (!flags.platform && flags['non-interactive']) {
|
||||||
@@ -74,8 +76,12 @@ export default class BuildVersionGetView extends EasCommand {
|
|||||||
});
|
});
|
||||||
const results: { [key in Platform]?: string } = {};
|
const results: { [key in Platform]?: string } = {};
|
||||||
for (const { profile, platform } of buildProfiles) {
|
for (const { profile, platform } of buildProfiles) {
|
||||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
|
const { exp, projectId, env } = await evaluateConfigWithEnvVarsAsync({
|
||||||
env: profile.env,
|
buildProfile: profile,
|
||||||
|
buildProfileName: flags.profile ?? 'production',
|
||||||
|
graphqlClient,
|
||||||
|
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||||
|
opts: { env: profile.env },
|
||||||
});
|
});
|
||||||
|
|
||||||
validateAppConfigForRemoteVersionSource(exp, platform);
|
validateAppConfigForRemoteVersionSource(exp, platform);
|
||||||
@@ -89,6 +95,7 @@ export default class BuildVersionGetView extends EasCommand {
|
|||||||
platform,
|
platform,
|
||||||
vcsClient,
|
vcsClient,
|
||||||
nonInteractive: flags['non-interactive'],
|
nonInteractive: flags['non-interactive'],
|
||||||
|
env,
|
||||||
});
|
});
|
||||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
|||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync';
|
||||||
import EasCommand from '../../../commandUtils/EasCommand';
|
import EasCommand from '../../../commandUtils/EasCommand';
|
||||||
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
||||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||||
@@ -53,6 +54,7 @@ export default class BuildVersionSetView extends EasCommand {
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
} = await this.getContextAsync(BuildVersionSetView, {
|
} = await this.getContextAsync(BuildVersionSetView, {
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
|
withServerSideEnvironment: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const platform = await selectPlatformAsync(flags.platform);
|
const platform = await selectPlatformAsync(flags.platform);
|
||||||
@@ -64,7 +66,13 @@ export default class BuildVersionSetView extends EasCommand {
|
|||||||
flags.profile ?? undefined
|
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);
|
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||||
|
|
||||||
validateAppConfigForRemoteVersionSource(exp, platform);
|
validateAppConfigForRemoteVersionSource(exp, platform);
|
||||||
@@ -78,6 +86,7 @@ export default class BuildVersionSetView extends EasCommand {
|
|||||||
platform,
|
platform,
|
||||||
vcsClient,
|
vcsClient,
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
|
env,
|
||||||
});
|
});
|
||||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Flags } from '@oclif/core';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../../build/android/version';
|
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../../build/android/version';
|
||||||
|
import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync';
|
||||||
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../../build/ios/version';
|
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../../build/ios/version';
|
||||||
import EasCommand from '../../../commandUtils/EasCommand';
|
import EasCommand from '../../../commandUtils/EasCommand';
|
||||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||||
@@ -72,6 +73,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
} = await this.getContextAsync(BuildVersionSyncView, {
|
} = await this.getContextAsync(BuildVersionSyncView, {
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
|
withServerSideEnvironment: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
|
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
|
||||||
@@ -87,8 +89,12 @@ export default class BuildVersionSyncView extends EasCommand {
|
|||||||
projectDir,
|
projectDir,
|
||||||
});
|
});
|
||||||
for (const profileInfo of buildProfiles) {
|
for (const profileInfo of buildProfiles) {
|
||||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
|
const { exp, projectId, env } = await evaluateConfigWithEnvVarsAsync({
|
||||||
env: profileInfo.profile.env,
|
buildProfile: profileInfo.profile,
|
||||||
|
buildProfileName: profileInfo.profileName,
|
||||||
|
graphqlClient,
|
||||||
|
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||||
|
opts: { env: profileInfo.profile.env },
|
||||||
});
|
});
|
||||||
|
|
||||||
validateAppConfigForRemoteVersionSource(exp, profileInfo.platform);
|
validateAppConfigForRemoteVersionSource(exp, profileInfo.platform);
|
||||||
@@ -103,6 +109,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
|||||||
platform: profileInfo.platform,
|
platform: profileInfo.platform,
|
||||||
vcsClient,
|
vcsClient,
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
|
env,
|
||||||
});
|
});
|
||||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
@@ -113,7 +120,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
|||||||
const workflow = await resolveWorkflowAsync(projectDir, profileInfo.platform, vcsClient);
|
const workflow = await resolveWorkflowAsync(projectDir, profileInfo.platform, vcsClient);
|
||||||
if (!remoteVersions?.buildVersion) {
|
if (!remoteVersions?.buildVersion) {
|
||||||
Log.warn(
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -128,7 +135,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (profileInfo.platform === Platform.ANDROID) {
|
if (profileInfo.platform === Platform.ANDROID) {
|
||||||
this.syncAndroidAsync({
|
await this.syncAndroidAsync({
|
||||||
projectDir,
|
projectDir,
|
||||||
exp,
|
exp,
|
||||||
profile: profileInfo.profile as BuildProfile<Platform.ANDROID>,
|
profile: profileInfo.profile as BuildProfile<Platform.ANDROID>,
|
||||||
@@ -137,7 +144,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.syncIosAsync({
|
await this.syncIosAsync({
|
||||||
projectDir,
|
projectDir,
|
||||||
exp,
|
exp,
|
||||||
profile: profileInfo.profile as BuildProfile<Platform.IOS>,
|
profile: profileInfo.profile as BuildProfile<Platform.IOS>,
|
||||||
@@ -199,7 +206,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
|||||||
if (workflow === Workflow.GENERIC) {
|
if (workflow === Workflow.GENERIC) {
|
||||||
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
||||||
const isMultiFlavor =
|
const isMultiFlavor =
|
||||||
buildGradle.android?.productFlavors || buildGradle.android?.flavorDimensions;
|
buildGradle.android?.productFlavors ?? buildGradle.android?.flavorDimensions;
|
||||||
if (isMultiFlavor) {
|
if (isMultiFlavor) {
|
||||||
throw new Error(
|
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."
|
"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."
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default class BuildView extends EasCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
...this.ContextOptions.Vcs,
|
...this.ContextOptions.Vcs,
|
||||||
};
|
};
|
||||||
@@ -29,7 +29,7 @@ export default class BuildView extends EasCommand {
|
|||||||
flags,
|
flags,
|
||||||
} = await this.parse(BuildView);
|
} = await this.parse(BuildView);
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(BuildView, {
|
} = await this.getContextAsync(BuildView, {
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default class ChannelCreate extends EasCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ export default class ChannelCreate extends EasCommand {
|
|||||||
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(ChannelCreate);
|
} = await this.parse(ChannelCreate);
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(ChannelCreate, {
|
} = await this.getContextAsync(ChannelCreate, {
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default class ChannelDelete extends EasCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export default class ChannelDelete extends EasCommand {
|
|||||||
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(ChannelDelete);
|
} = await this.parse(ChannelDelete);
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(ChannelDelete, {
|
} = await this.getContextAsync(ChannelDelete, {
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export default class ChannelEdit extends EasCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ export default class ChannelEdit extends EasCommand {
|
|||||||
flags: { branch: branchFlag, json, 'non-interactive': nonInteractive },
|
flags: { branch: branchFlag, json, 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(ChannelEdit);
|
} = await this.parse(ChannelEdit);
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(ChannelEdit, {
|
} = await this.getContextAsync(ChannelEdit, {
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default class ChannelList extends EasCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static override contextDefinition = {
|
static override contextDefinition = {
|
||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectId,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ export default class ChannelList extends EasCommand {
|
|||||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||||
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
||||||
const {
|
const {
|
||||||
privateProjectConfig: { projectId },
|
projectId,
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(ChannelList, {
|
} = await this.getContextAsync(ChannelList, {
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { Flags } from '@oclif/core';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { print } from 'graphql';
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
import { selectChannelOnAppAsync } from '../../channel/queries';
|
||||||
|
import EasCommand from '../../commandUtils/EasCommand';
|
||||||
|
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
|
import { withErrorHandlingAsync } from '../../graphql/client';
|
||||||
|
import {
|
||||||
|
PauseUpdateChannelMutation,
|
||||||
|
PauseUpdateChannelMutationVariables,
|
||||||
|
UpdateChannelBasicInfoFragment,
|
||||||
|
} from '../../graphql/generated';
|
||||||
|
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
|
||||||
|
import { UpdateChannelBasicInfoFragmentNode } from '../../graphql/types/UpdateChannelBasicInfo';
|
||||||
|
import Log from '../../log';
|
||||||
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
|
|
||||||
|
export async function pauseUpdateChannelAsync(
|
||||||
|
graphqlClient: ExpoGraphqlClient,
|
||||||
|
{ channelId }: PauseUpdateChannelMutationVariables
|
||||||
|
): Promise<UpdateChannelBasicInfoFragment> {
|
||||||
|
const data = await withErrorHandlingAsync(
|
||||||
|
graphqlClient
|
||||||
|
.mutation<PauseUpdateChannelMutation, PauseUpdateChannelMutationVariables>(
|
||||||
|
gql`
|
||||||
|
mutation PauseUpdateChannel($channelId: ID!) {
|
||||||
|
updateChannel {
|
||||||
|
pauseUpdateChannel(channelId: $channelId) {
|
||||||
|
id
|
||||||
|
...UpdateChannelBasicInfoFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${print(UpdateChannelBasicInfoFragmentNode)}
|
||||||
|
`,
|
||||||
|
{ channelId }
|
||||||
|
)
|
||||||
|
.toPromise()
|
||||||
|
);
|
||||||
|
const channel = data.updateChannel.pauseUpdateChannel;
|
||||||
|
if (!channel) {
|
||||||
|
throw new Error(`Could not find a channel with id: ${channelId}`);
|
||||||
|
}
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ChannelPause extends EasCommand {
|
||||||
|
static override description = 'pause a channel to stop it from sending updates';
|
||||||
|
|
||||||
|
static override args = [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
required: false,
|
||||||
|
description: 'Name of the channel to edit',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
static override flags = {
|
||||||
|
branch: Flags.string({
|
||||||
|
description: 'Name of the branch to point to',
|
||||||
|
}),
|
||||||
|
...EasNonInteractiveAndJsonFlags,
|
||||||
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...this.ContextOptions.ProjectId,
|
||||||
|
...this.ContextOptions.LoggedIn,
|
||||||
|
};
|
||||||
|
|
||||||
|
async runAsync(): Promise<void> {
|
||||||
|
const {
|
||||||
|
args,
|
||||||
|
flags: { json, 'non-interactive': nonInteractive },
|
||||||
|
} = await this.parse(ChannelPause);
|
||||||
|
const {
|
||||||
|
projectId,
|
||||||
|
loggedIn: { graphqlClient },
|
||||||
|
} = await this.getContextAsync(ChannelPause, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
|
if (json) {
|
||||||
|
enableJsonOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingChannel = args.name
|
||||||
|
? await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||||
|
appId: projectId,
|
||||||
|
channelName: args.name,
|
||||||
|
})
|
||||||
|
: await selectChannelOnAppAsync(graphqlClient, {
|
||||||
|
projectId,
|
||||||
|
selectionPromptTitle: 'Select a channel to edit',
|
||||||
|
paginatedQueryOptions: { json, nonInteractive, offset: 0 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const channel = await pauseUpdateChannelAsync(graphqlClient, {
|
||||||
|
channelId: existingChannel.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (json) {
|
||||||
|
printJsonOnlyOutput(channel);
|
||||||
|
} else {
|
||||||
|
Log.withTick(chalk`Channel {bold ${channel.name}} is now paused.\n`);
|
||||||
|
Log.addNewLineIfNone();
|
||||||
|
Log.log(
|
||||||
|
chalk`Users with builds on channel {bold ${channel.name}} will no longer receive updates.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { Flags } from '@oclif/core';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { print } from 'graphql';
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
import { selectChannelOnAppAsync } from '../../channel/queries';
|
||||||
|
import EasCommand from '../../commandUtils/EasCommand';
|
||||||
|
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
|
import { withErrorHandlingAsync } from '../../graphql/client';
|
||||||
|
import {
|
||||||
|
ResumeUpdateChannelMutation,
|
||||||
|
ResumeUpdateChannelMutationVariables,
|
||||||
|
UpdateChannelBasicInfoFragment,
|
||||||
|
} from '../../graphql/generated';
|
||||||
|
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
|
||||||
|
import { UpdateChannelBasicInfoFragmentNode } from '../../graphql/types/UpdateChannelBasicInfo';
|
||||||
|
import Log from '../../log';
|
||||||
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
|
|
||||||
|
export async function resumeUpdateChannelAsync(
|
||||||
|
graphqlClient: ExpoGraphqlClient,
|
||||||
|
{ channelId }: ResumeUpdateChannelMutationVariables
|
||||||
|
): Promise<UpdateChannelBasicInfoFragment> {
|
||||||
|
const data = await withErrorHandlingAsync(
|
||||||
|
graphqlClient
|
||||||
|
.mutation<ResumeUpdateChannelMutation, ResumeUpdateChannelMutationVariables>(
|
||||||
|
gql`
|
||||||
|
mutation ResumeUpdateChannel($channelId: ID!) {
|
||||||
|
updateChannel {
|
||||||
|
resumeUpdateChannel(channelId: $channelId) {
|
||||||
|
id
|
||||||
|
...UpdateChannelBasicInfoFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${print(UpdateChannelBasicInfoFragmentNode)}
|
||||||
|
`,
|
||||||
|
{ channelId }
|
||||||
|
)
|
||||||
|
.toPromise()
|
||||||
|
);
|
||||||
|
const channel = data.updateChannel.resumeUpdateChannel;
|
||||||
|
if (!channel) {
|
||||||
|
throw new Error(`Could not find a channel with id: ${channelId}`);
|
||||||
|
}
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ChannelResume extends EasCommand {
|
||||||
|
static override description = 'resume a channel to start sending updates';
|
||||||
|
|
||||||
|
static override args = [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
required: false,
|
||||||
|
description: 'Name of the channel to edit',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
static override flags = {
|
||||||
|
branch: Flags.string({
|
||||||
|
description: 'Name of the branch to point to',
|
||||||
|
}),
|
||||||
|
...EasNonInteractiveAndJsonFlags,
|
||||||
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...this.ContextOptions.ProjectId,
|
||||||
|
...this.ContextOptions.LoggedIn,
|
||||||
|
};
|
||||||
|
|
||||||
|
async runAsync(): Promise<void> {
|
||||||
|
const {
|
||||||
|
args,
|
||||||
|
flags: { json, 'non-interactive': nonInteractive },
|
||||||
|
} = await this.parse(ChannelResume);
|
||||||
|
const {
|
||||||
|
projectId,
|
||||||
|
loggedIn: { graphqlClient },
|
||||||
|
} = await this.getContextAsync(ChannelResume, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
|
if (json) {
|
||||||
|
enableJsonOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingChannel = args.name
|
||||||
|
? await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||||
|
appId: projectId,
|
||||||
|
channelName: args.name,
|
||||||
|
})
|
||||||
|
: await selectChannelOnAppAsync(graphqlClient, {
|
||||||
|
projectId,
|
||||||
|
selectionPromptTitle: 'Select a channel to edit',
|
||||||
|
paginatedQueryOptions: { json, nonInteractive, offset: 0 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const channel = await resumeUpdateChannelAsync(graphqlClient, {
|
||||||
|
channelId: existingChannel.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (json) {
|
||||||
|
printJsonOnlyOutput(channel);
|
||||||
|
} else {
|
||||||
|
Log.withTick(chalk`Channel {bold ${channel.name}} is now active.\n`);
|
||||||
|
Log.addNewLineIfNone();
|
||||||
|
Log.log(chalk`Users with builds on channel {bold ${channel.name}} will now receive updates.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -138,6 +138,7 @@ export default class ChannelRollout extends EasCommand {
|
|||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
} = await this.getContextAsync(ChannelRollout, {
|
} = await this.getContextAsync(ChannelRollout, {
|
||||||
nonInteractive: argsAndFlags.nonInteractive,
|
nonInteractive: argsAndFlags.nonInteractive,
|
||||||
|
withServerSideEnvironment: null,
|
||||||
});
|
});
|
||||||
if (argsAndFlags.json) {
|
if (argsAndFlags.json) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
|
|||||||
Loaded 100 of 382 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user