mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-14 10:08:06 -10:00
Compare commits
251
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e1093607b | ||
|
|
37a68c5237 | ||
|
|
6823e4f66c | ||
|
|
ffa2f034f9 | ||
|
|
8243f5f0cb | ||
|
|
01676a4f88 | ||
|
|
7edc10feaf | ||
|
|
fba26e6b50 | ||
|
|
69014fe21d | ||
|
|
217a1f4fdb | ||
|
|
0863b9a477 | ||
|
|
081a254d9b | ||
|
|
ba7eb7ea07 | ||
|
|
f5df6e6e77 | ||
|
|
c0c27559eb | ||
|
|
2e002ad709 | ||
|
|
2e211f4904 | ||
|
|
981f7c9727 | ||
|
|
24245d76cf | ||
|
|
d42cbe0146 | ||
|
|
2cf6dd49db | ||
|
|
09c5038c58 | ||
|
|
d33ebe1ae4 | ||
|
|
41fd76c154 | ||
|
|
dadf58fa70 | ||
|
|
a7f939fb5f | ||
|
|
e54b5a243c | ||
|
|
d2a89c1d77 | ||
|
|
9c4ff98d68 | ||
|
|
d7dfef0c36 | ||
|
|
fdb30eeacc | ||
|
|
d4c6440753 | ||
|
|
87b0cf41a8 | ||
|
|
7847b5297c | ||
|
|
539ef8f3ad | ||
|
|
819259ab1a | ||
|
|
47e1989c9c | ||
|
|
fa3262951b | ||
|
|
66ce64e0a3 | ||
|
|
5c553cee7b | ||
|
|
2fb079d4bb | ||
|
|
d15cd90c81 | ||
|
|
0fbd3e47f8 | ||
|
|
12efaf12cc | ||
|
|
a404bb383d | ||
|
|
feaa8cbf22 | ||
|
|
542ea7a791 | ||
|
|
7d80b0540f | ||
|
|
cc4e0ebda1 | ||
|
|
1621fb3a9b | ||
|
|
51b5946854 | ||
|
|
b8ed63cadc | ||
|
|
8fc0648419 | ||
|
|
4fbc6aa3ff | ||
|
|
83bf6de727 | ||
|
|
a2219beaba | ||
|
|
90a2612792 | ||
|
|
d94b57c1b7 | ||
|
|
d63fc1f01e | ||
|
|
57e7481a68 | ||
|
|
7bb18e2489 | ||
|
|
359464d55c | ||
|
|
e993e9a370 | ||
|
|
75046b0b35 | ||
|
|
3d55a4df40 | ||
|
|
b32ff2e733 | ||
|
|
6245fb418d | ||
|
|
50943f333a | ||
|
|
e930383a10 | ||
|
|
8c31825908 | ||
|
|
fbc39240a5 | ||
|
|
f31054c5a5 | ||
|
|
7af8b62a7b | ||
|
|
56d492cbac | ||
|
|
4f53ce353c | ||
|
|
736552b358 | ||
|
|
b4a3f5b526 | ||
|
|
28592f27fa | ||
|
|
63502586f8 | ||
|
|
714532a532 | ||
|
|
8cbed45be9 | ||
|
|
3466a0c3dc | ||
|
|
b28943d8f1 | ||
|
|
e01a70943b | ||
|
|
56510f0925 | ||
|
|
1540b1354e | ||
|
|
61455420b1 | ||
|
|
d3fdc1a95b | ||
|
|
a28439a485 | ||
|
|
8af8569f16 | ||
|
|
9b49fe3a9f | ||
|
|
2c8f9a67c8 | ||
|
|
9597ea2832 | ||
|
|
5976dc7346 | ||
|
|
75b310e7e0 | ||
|
|
63009b0437 | ||
|
|
7994676f3e | ||
|
|
987c23fc2d | ||
|
|
2bf5819075 | ||
|
|
fdbeb862ec | ||
|
|
9a05359dcb | ||
|
|
ce76d4d7d0 | ||
|
|
3c58bd4515 | ||
|
|
f4f4756e07 | ||
|
|
31b3d81b1a | ||
|
|
5cf526592c | ||
|
|
8646dc81d9 | ||
|
|
54ae76e85e | ||
|
|
63381b8ee4 | ||
|
|
52ab2d5792 | ||
|
|
ade09fe6dc | ||
|
|
71261b5b26 | ||
|
|
37cec329d4 | ||
|
|
fc88d65691 | ||
|
|
4699ae51f0 | ||
|
|
aabe1ff806 | ||
|
|
c8bd95535e | ||
|
|
71184cbcd5 | ||
|
|
1629e2b585 | ||
|
|
4a82ac90d3 | ||
|
|
5745639bb2 | ||
|
|
17ecdb2a95 | ||
|
|
42b105c869 | ||
|
|
ac04891b28 | ||
|
|
14a454edfa | ||
|
|
c0651a880c | ||
|
|
c39f76392d | ||
|
|
f2ed434828 | ||
|
|
9344fba85f | ||
|
|
e5bfcf4e90 | ||
|
|
40a7b144ea | ||
|
|
97d1e25908 | ||
|
|
2b23c11d6e | ||
|
|
1c61465c5f | ||
|
|
0516b507bc | ||
|
|
2e14870b60 | ||
|
|
559baf9150 | ||
|
|
bd0a696b4e | ||
|
|
230e364379 | ||
|
|
eaebb72813 | ||
|
|
cf39201de3 | ||
|
|
bb3e815716 | ||
|
|
beec6ad305 | ||
|
|
84b47924b7 | ||
|
|
3ff62a9c79 | ||
|
|
2751a7e0de | ||
|
|
7139e4f576 | ||
|
|
ecbe49abaa | ||
|
|
6af86b545f | ||
|
|
a9a77b6fcb | ||
|
|
82fccc8193 | ||
|
|
5f800b5be6 | ||
|
|
3817e2d387 | ||
|
|
eb4c5e073d | ||
|
|
eefc133eda | ||
|
|
7f9969429d | ||
|
|
26ff083b7c | ||
|
|
6414361d4c | ||
|
|
28ce6b7dc7 | ||
|
|
4772f1aea5 | ||
|
|
2a1fd290b9 | ||
|
|
e83565c518 | ||
|
|
f38a739858 | ||
|
|
3fcbcf5f6b | ||
|
|
30936911ad | ||
|
|
a0606adde9 | ||
|
|
f0e3b648a1 | ||
|
|
3ed5e7821e | ||
|
|
466339b90e | ||
|
|
1d07e08a70 | ||
|
|
c21270e062 | ||
|
|
afb548599e | ||
|
|
0b54169631 | ||
|
|
af13f3987e | ||
|
|
3ea71e57cb | ||
|
|
261dbb4be4 | ||
|
|
5dd02e2b40 | ||
|
|
9447cbc478 | ||
|
|
562bd3fe9c | ||
|
|
d6840de401 | ||
|
|
d9bd20a47b | ||
|
|
13da8d4bfc | ||
|
|
a99fe9aef5 | ||
|
|
61109ea2c1 | ||
|
|
f889929f5f | ||
|
|
4bb314192e | ||
|
|
1a170283db | ||
|
|
ca803eed3f | ||
|
|
4b26472975 | ||
|
|
70b854abb1 | ||
|
|
2576a97704 | ||
|
|
15e50b1536 | ||
|
|
d6f3784cc0 | ||
|
|
a6f7d37d71 | ||
|
|
675c36a157 | ||
|
|
b2961a275b | ||
|
|
57928d1fd6 | ||
|
|
ff9d533248 | ||
|
|
e1ebcc0b8a | ||
|
|
628a7262cf | ||
|
|
05be162ec1 | ||
|
|
ff2fa425bd | ||
|
|
a621e395fc | ||
|
|
2260f74434 | ||
|
|
6dc9253d2d | ||
|
|
b6570bdfcf | ||
|
|
4d1675c96f | ||
|
|
00d9009967 | ||
|
|
b68840b76b | ||
|
|
6d8be295bf | ||
|
|
40c1cbe9f6 | ||
|
|
efae679089 | ||
|
|
6a2a3fe037 | ||
|
|
917987a41e | ||
|
|
bbe8fb9698 | ||
|
|
d656103357 | ||
|
|
4b7ad9f23a | ||
|
|
4db3979ab5 | ||
|
|
63e0bf2a24 | ||
|
|
a740aa174b | ||
|
|
f9ef83ba52 | ||
|
|
de8956c2ec | ||
|
|
41622c0cd3 | ||
|
|
d54abee7c3 | ||
|
|
cdd046b647 | ||
|
|
4577cf957d | ||
|
|
84b56f86eb | ||
|
|
b2580d0cc2 | ||
|
|
0f18ea01a9 | ||
|
|
fe9927862c | ||
|
|
bc2f6e60f4 | ||
|
|
6ac1e54f67 | ||
|
|
479e8d0b5d | ||
|
|
7ed62ec7f1 | ||
|
|
87000c36c1 | ||
|
|
a9d08f39ad | ||
|
|
55c156dd00 | ||
|
|
736d414cd6 | ||
|
|
85de580d84 | ||
|
|
1a65b2cc4e | ||
|
|
b3fca978d0 | ||
|
|
ba4c4e5954 | ||
|
|
383caa0354 | ||
|
|
2e43d4bba0 | ||
|
|
b995d1d713 | ||
|
|
d307f2343b | ||
|
|
9eeaee544a | ||
|
|
d9092e15f2 | ||
|
|
f90f085dcc | ||
|
|
61bb001059 | ||
|
|
9ef86f51fa |
No files matched your search
@@ -5,3 +5,4 @@
|
||||
packages/eas-cli/src/graphql/generated.ts
|
||||
|
||||
node_modules
|
||||
ts-declarations
|
||||
@@ -30,12 +30,6 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'no-underscore-dangle': ['error', { allow: ['__typename'] }],
|
||||
'@typescript-eslint/explicit-function-return-type': [
|
||||
'warn',
|
||||
{
|
||||
allowExpressions: true,
|
||||
},
|
||||
],
|
||||
'async-protect/async-suffix': 'error',
|
||||
'node/no-sync': 'error',
|
||||
},
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
rules:
|
||||
- patterns: ['**/*']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela']
|
||||
- patterns: ['packages/eas-cli/src/build/**']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela']
|
||||
- patterns: ['packages/eas-cli/src/commands/build/**']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela']
|
||||
- patterns: ['packages/eas-cli/src/submit/**']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela']
|
||||
- patterns: ['packages/eas-cli/src/commands/submit.ts']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela', 'radoslawkrzemien']
|
||||
- patterns: ['packages/eas-cli/src/update/**']
|
||||
mentions: ['EvanBacon', 'byCedric', 'wschurman']
|
||||
- patterns: ['packages/eas-cli/src/commands/update/**']
|
||||
mentions: ['EvanBacon', 'byCedric', 'wschurman']
|
||||
- patterns: ['packages/eas-cli/schema/**']
|
||||
mentions: ['bycedric']
|
||||
- patterns: ['packages/eas-cli/src/metadata/**']
|
||||
mentions: ['bycedric']
|
||||
- patterns: ['packages/eas-cli/src/commands/metadata/**']
|
||||
mentions: ['bycedric']
|
||||
- patterns: ['packages/eas-json/**']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela']
|
||||
@@ -1,48 +0,0 @@
|
||||
name: Changelog bot
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
pr-commented:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.issue.pull_request && github.event.issue.user.login == github.event.comment.user.login && startsWith(github.event.comment.body, '/changelog-entry ') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.EXPO_BOT_PAT }}
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Checkout Pull Request
|
||||
run: gh pr checkout ${{ github.event.issue.number }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install dependencies
|
||||
working-directory: ./scripts
|
||||
run: yarn install --frozen-lockfile --check-files
|
||||
- name: Add changelog entry
|
||||
working-directory: ./scripts
|
||||
run: |
|
||||
PARAMS=${PR_COMMENT_BODY#/changelog-entry }
|
||||
if [[ ! "$PARAMS" =~ " " ]]; then
|
||||
echo "Invalid /changelog-entry command"
|
||||
exit 1
|
||||
fi
|
||||
CATEGORY=$( cut -d ' ' -f 1 <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
|
||||
MESSAGE=$( cut -d ' ' -f 2- <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
|
||||
yarn changelog-entry "$CATEGORY" "$MESSAGE"
|
||||
env:
|
||||
GITHUB_PR_AUTHOR: ${{ github.event.issue.user.login }}
|
||||
GITHUB_PR_NUMBER: ${{ github.event.issue.number }}
|
||||
PR_COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
- name: Commit and push
|
||||
run: |
|
||||
# use the last commit author name and email
|
||||
git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)"
|
||||
git config --global user.email "$(git --no-pager log --format=format:'%ae' -n 1)"
|
||||
|
||||
git add CHANGELOG.md
|
||||
git commit -m "update CHANGELOG.md"
|
||||
git push
|
||||
@@ -36,6 +36,5 @@ jobs:
|
||||
refresh-message-position: true
|
||||
message: ${{ contains(github.event.pull_request.labels.*.name, 'no changelog') && '⏩ The changelog entry check has been skipped since the "no changelog" label is present.' || '✅ Thank you for adding the changelog entry!' }}
|
||||
message-failure: |
|
||||
❌ It looks like a changelog entry is missing for this PR. You have two options: you can add it manually, or you can use the changelog bot to do it for you.
|
||||
🤖 To use the bot, simply comment on this PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`.
|
||||
❌ It looks like a changelog entry is missing for this PR. Add it manually to CHANGELOG.md.
|
||||
⏩ If this PR doesn't require a changelog entry, such as if it's an internal change that doesn't affect the user experience, you can add the "no changelog" label to the PR.
|
||||
@@ -0,0 +1,14 @@
|
||||
name: codemention
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, ready_for_review]
|
||||
jobs:
|
||||
codemention:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: tobyhs/[email protected]
|
||||
with:
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -69,6 +69,9 @@ jobs:
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
- run: brew install python-setuptools
|
||||
env:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: yarn build
|
||||
- name: Build tarballs
|
||||
@@ -228,14 +231,8 @@ jobs:
|
||||
needs: publish-to-npm
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
eas-json:
|
||||
- 'packages/eas-json/**'
|
||||
- uses: actions/checkout@v2
|
||||
- name: Open a PR with `@expo/eas-json` version bump in universe
|
||||
if: steps.filter.outputs.eas-json == 'true'
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
repo: expo/universe
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: ['16', '18', '20']
|
||||
node: ['18', '20', '22']
|
||||
include:
|
||||
- node: '20'
|
||||
coverage: true
|
||||
|
||||
+328
@@ -10,6 +10,334 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🐛 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))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Implement new `worker` deploy API flow. ([#2601](https://github.com/expo/eas-cli/pull/2601) by [@kitten](https://github.com/kitten)))
|
||||
|
||||
## [12.5.1](https://github.com/expo/eas-cli/releases/tag/v12.5.1) - 2024-09-27
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Support assigning dev domain names in non interactive mode. ([#2595](https://github.com/expo/eas-cli/pull/2595) by [@byCedric](https://github.com/byCedric))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Simplify the output of `eas deploy --json`. ([#2596](https://github.com/expo/eas-cli/pull/2596) by [@byCedric](https://github.com/byCedric))
|
||||
- Support deploying Expo Router server exports without client directory. ([#2597](https://github.com/expo/eas-cli/pull/2597) by [@byCedric](https://github.com/byCedric))
|
||||
- Add exit option to `eas credentials` interactive menu. ([#2570](https://github.com/expo/eas-cli/pull/2570) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [12.5.0](https://github.com/expo/eas-cli/releases/tag/v12.5.0) - 2024-09-23
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Log command execution to assist in debugging local builds. ([#2526](https://github.com/expo/eas-cli/pull/2526) by [@trajano](https://github.com/trajano))
|
||||
- Allow submitting builds in progress ([#2543](https://github.com/expo/eas-cli/pull/2543) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
- Use `EAS_DANGEROUS_OVERRIDE_ANDROID_APPLICATION_ID` and `EAS_DANGEROUS_OVERRIDE_IOS_BUNDLE_IDENTIFIER` environment variables as overrides of the Android application ID and iOS bundle identifier in managed workflow too. ([#2576](https://github.com/expo/eas-cli/pull/2576) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
- Add destination branch arguments to update:republish command. ([#2575](https://github.com/expo/eas-cli/pull/2575) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Avoid malforming `app.json` with empty `.expo` object. ([#2573](https://github.com/expo/eas-cli/pull/2573) by [@byCedric](https://github.com/byCedric))
|
||||
- Fix typo causing `worker:deploy` asset upload errors not to be shown properly. ([#2579](https://github.com/expo/eas-cli/pull/2579) by [@kitten](https://github.com/kitten))
|
||||
|
||||
## [12.4.1](https://github.com/expo/eas-cli/releases/tag/v12.4.1) - 2024-09-14
|
||||
|
||||
## [12.4.0](https://github.com/expo/eas-cli/releases/tag/v12.4.0) - 2024-09-14
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `worker:alias` command to assign aliases from the CLI. ([#2548](https://github.com/expo/eas-cli/pull/2548) by [@byCedric](https://github.com/byCedric))
|
||||
- Add `worker --prod` flag to deploy to production from the CLI. ([#2550](https://github.com/expo/eas-cli/pull/2550) by [@byCedric](https://github.com/byCedric))
|
||||
- Add `worker --alias` flag to assign custom aliases when deploying. ([#2551](https://github.com/expo/eas-cli/pull/2551) by [@byCedric](https://github.com/byCedric)))
|
||||
- Add `worker --id` flag to use a custom deployment identifier. ([#2552](https://github.com/expo/eas-cli/pull/2552) by [@byCedric](https://github.com/byCedric)))
|
||||
- Add `worker --environment` flag to deploy with EAS environment variables. ([#2557](https://github.com/expo/eas-cli/pull/2557) by [@kitten](https://github.com/kitten)))
|
||||
- Add `worker --export-dir` flag to select exported directory. ([#2560](https://github.com/expo/eas-cli/pull/2560) by [@byCedric](https://github.com/byCedric)))
|
||||
- Add `worker --json` flag to allow integrating with 3rd parties and custom tooling. ([#2561](https://github.com/expo/eas-cli/pull/2561) by [@byCedric](https://github.com/byCedric)))
|
||||
- Add `worker:alias --json` flag to allow integrating with 3rd parties and custom tooling. ([#2562](https://github.com/expo/eas-cli/pull/2562) by [@byCedric](https://github.com/byCedric)))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Avoid merging `expo.extra` plugin-generated data with `expo.extra.eas.projectId` in `eas init`. ([#2554](https://github.com/expo/eas-cli/pull/2554) by [@byCedric](https://github.com/byCedric)))
|
||||
- Restore "export not found" error and hide recent export timestamps. ([#2566](https://github.com/expo/eas-cli/pull/2566) by [@byCedric](https://github.com/byCedric)))
|
||||
- Check if export is available before validating project ID in `eas worker`. ([#2569](https://github.com/expo/eas-cli/pull/2569) by [@byCedric](https://github.com/byCedric)))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Make error message for invalid CFBundleShortVersionString more descriptive and actionable. Improve CFBundleShortVersionString validation regex. ([#2542](https://github.com/expo/eas-cli/pull/2542) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Add missing `--non-interactive` argument to `worker:deploy` command. ([#2544](https://github.com/expo/eas-cli/pull/2544) by [@kitten](https://github.com/kitten))
|
||||
- Source `@expo/env` dotenv files for worker deployments. ([#2545](https://github.com/expo/eas-cli/pull/2545) by [@kitten](https://github.com/kitten))
|
||||
- Support `worker --production` and clean up command output. ([#2555](https://github.com/expo/eas-cli/pull/2555) by [@byCedric](https://github.com/byCedric)))
|
||||
- Unify both `worker` and `worker:alias` command output. ([#2558](https://github.com/expo/eas-cli/pull/2558) by [@byCedric](https://github.com/byCedric)))
|
||||
- Share similar table/json output in both `worker` and `worker:alias` command outputs. ([#2563](https://github.com/expo/eas-cli/pull/2563) by [@byCedric](https://github.com/byCedric)))
|
||||
- Polish the project URL prompt when setting up new projects. ([#2564](https://github.com/expo/eas-cli/pull/2564) by [@byCedric](https://github.com/byCedric)))
|
||||
- Always assume `static` exports in `eas deploy` and add modified time. ([#2565](https://github.com/expo/eas-cli/pull/2565) by [@byCedric](https://github.com/byCedric)))
|
||||
- Update the `eas worker --help` `--environment` description. ([#2567](https://github.com/expo/eas-cli/pull/2567) by [@byCedric](https://github.com/byCedric)))
|
||||
- Remove the cursor space after selecting project dev domain. ([#2568](https://github.com/expo/eas-cli/pull/2568) by [@byCedric](https://github.com/byCedric)))
|
||||
- Reword the dev domain prompt to mention "preview URL". ([#2572](https://github.com/expo/eas-cli/pull/2572) by [@byCedric](https://github.com/byCedric)))
|
||||
|
||||
## [12.3.0](https://github.com/expo/eas-cli/releases/tag/v12.3.0) - 2024-09-09
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `--non-interactive` and `--force` support when `--id` is not passed to the `eas init` command. ([#1983](https://github.com/expo/eas-cli/pull/1983) by [@mymattcarroll](https://github.com/mymattcarroll))
|
||||
|
||||
## [12.1.1](https://github.com/expo/eas-cli/releases/tag/v12.1.1) - 2024-09-09
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fixed an issue where extensions retrieved the main app's entitlements instead of their own. ([#2532](https://github.com/expo/eas-cli/pull/2532) by [@HarrisHan](https://github.com/HarrisHan))
|
||||
|
||||
## [12.1.0](https://github.com/expo/eas-cli/releases/tag/v12.1.0) - 2024-09-06
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- **Internal/Experimental:** Add EAS Worker command ([#2447](https://github.com/expo/eas-cli/pull/2447) by [@kitten](https://github.com/kitten))
|
||||
- Upload fingeprint source as part of eas update command. ([#2533](https://github.com/expo/eas-cli/pull/2533) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
## [12.0.0](https://github.com/expo/eas-cli/releases/tag/v12.0.0) - 2024-09-04
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Prompt the users to set `appVersionSource`, while mentioning that `remote` is the default. ([#2411](https://github.com/expo/eas-cli/pull/2411) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add support for syncing Journaling Suggestions, Managed App Installation UI, and 5G Network Slicing capabilities. ([#2525](https://github.com/expo/eas-cli/pull/2525) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [11.0.3](https://github.com/expo/eas-cli/releases/tag/v11.0.3) - 2024-08-31
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Revert config-related packages to SDK 50 version from SDK 51 version to fix broken entitlements behavior for older SDKs. ([#2524](https://github.com/expo/eas-cli/pull/2524) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [11.0.2](https://github.com/expo/eas-cli/releases/tag/v11.0.2) - 2024-08-27
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Remove unncessary static project config context definition from `eas build` command context, to fix resolving dynamic projest ID and slug for dynamic app configs. ([#2521](https://github.com/expo/eas-cli/pull/2521) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [11.0.1](https://github.com/expo/eas-cli/releases/tag/v11.0.1) - 2024-08-26
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Make `eas config` command not require authentication when running in `--eas-json-only` mode. ([#2517](https://github.com/expo/eas-cli/pull/2517) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [11.0.0](https://github.com/expo/eas-cli/releases/tag/v11.0.0) - 2024-08-26
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Remove long-deprecated `eas update` flags. ([#2501](https://github.com/expo/eas-cli/pull/2501) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add new rollout update type for `eas update` and `eas update:edit`. ([#2502](https://github.com/expo/eas-cli/pull/2502), [#2503](https://github.com/expo/eas-cli/pull/2503) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade packages to SDK 51 release. ([#2498](https://github.com/expo/eas-cli/pull/2498) by [@wschurman](https://github.com/wschurman))
|
||||
- Enable typescript linting and various lint rules. ([#2505](https://github.com/expo/eas-cli/pull/2505), [#2507](https://github.com/expo/eas-cli/pull/2507), [#2508](https://github.com/expo/eas-cli/pull/2508), [#2509](https://github.com/expo/eas-cli/pull/2509), [#2510](https://github.com/expo/eas-cli/pull/2510) by [@wschurman](https://github.com/wschurman))
|
||||
- Include debug info in fingerprint metadata during build. ([#2513](https://github.com/expo/eas-cli/pull/2513) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
## [10.2.4](https://github.com/expo/eas-cli/releases/tag/v10.2.4) - 2024-08-19
|
||||
|
||||
## [10.2.3](https://github.com/expo/eas-cli/releases/tag/v10.2.3) - 2024-08-13
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add support for `EAS_DANGEROUS_OVERRIDE_IOS_BUNDLE_IDENTIFIER` for bare workflow iOS builds. ([#2469](https://github.com/expo/eas-cli/pull/2469) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Update images list in `eas.schema.json` and warn users when using the deprecated Android images. ([#2450](https://github.com/expo/eas-cli/pull/2450) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [10.2.2](https://github.com/expo/eas-cli/releases/tag/v10.2.2) - 2024-07-31
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Pass correct path to `vcsClient.isFileIgnoredAsync` check for monorepos to validate that custom build config file is not ignored. ([#2470](https://github.com/expo/eas-cli/pull/2470) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Bump asset upload timeout from 90 to 180 seconds. ([#2466](https://github.com/expo/eas-cli/pull/2466) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [10.2.1](https://github.com/expo/eas-cli/releases/tag/v10.2.1) - 2024-07-18
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Indicate if a user is logged in using `EXPO_TOKEN` when running `eas whoami` command. ([#2461](https://github.com/expo/eas-cli/pull/2461) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Throw error when `eas login` command is run with `EXPO_TOKEN` environment variable set. ([#2461](https://github.com/expo/eas-cli/pull/2461) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Check if user is already logged in when running `eas login` command. ([#2461](https://github.com/expo/eas-cli/pull/2461) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [10.2.0](https://github.com/expo/eas-cli/releases/tag/v10.2.0) - 2024-07-15
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Added flag `--emit-metadata` to emit `eas-update-metadata.json` in the bundle folder with detailed information about the generated updates ([#2451](https://github.com/expo/eas-cli/pull/2451) by [@rainst](https://github.com/rainst))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Bump `@expo/apple-utils` version to use the fallback Apple Developer Portal domain on every internal server error. ([#2459](https://github.com/expo/eas-cli/pull/2459) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [10.1.1](https://github.com/expo/eas-cli/releases/tag/v10.1.1) - 2024-07-04
|
||||
|
||||
## [10.1.0](https://github.com/expo/eas-cli/releases/tag/v10.1.0) - 2024-07-02
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Save local fingeprint sources during build. ([#2422](https://github.com/expo/eas-cli/pull/2422) by [@kadikraman](https://github.com/kadikraman))
|
||||
|
||||
## [10.0.3](https://github.com/expo/eas-cli/releases/tag/v10.0.3) - 2024-06-26
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Track usage of `--local` build mode in analytics. ([#2445](https://github.com/expo/eas-cli/pull/2445) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Remove any mentions of deleted Xcode < 15 images. ([#2438](https://github.com/expo/eas-cli/pull/2438) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [10.0.2](https://github.com/expo/eas-cli/releases/tag/v10.0.2) - 2024-06-17
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix parsing of `build.gradle` file by `gradle-to-js` parser by filtering out empty single line comments. ([#2435](https://github.com/expo/eas-cli/pull/2435) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [10.0.1](https://github.com/expo/eas-cli/releases/tag/v10.0.1) - 2024-06-17
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add clarification to private-key flag in command line help. ([#2432](https://github.com/expo/eas-cli/pull/2432) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [10.0.0](https://github.com/expo/eas-cli/releases/tag/v10.0.0) - 2024-06-13
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Drop support for Node 16. ([#2413](https://github.com/expo/eas-cli/pull/2413) by [@byCedric](https://github.com/byCedric))
|
||||
- Update [`eas-build`](https://github.com/expo/eas-build) dependencies to the version requiring Node 18 as minimal Node version. ([#2416](https://github.com/expo/eas-cli/pull/2416) by [@expo-bot](https://github.com/expo-bot))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Resolve correct submit profile configuration for `eas build` command with `--auto-submit-with-profile` flag. ([#2425](https://github.com/expo/eas-cli/pull/2425) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Correctly parse the EXPO_APPLE_PROVIER_ID environment variable. ([#2349](https://github.com/expo/eas-cli/pull/2349) by [@louix](https://github.com/louix))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Update lockfile to only include `@types/[email protected]`. ([#2412](https://github.com/expo/eas-cli/pull/2412) by [@byCedric](https://github.com/byCedric))
|
||||
- Update test workflow Node versions to 18, 20, and 22. ([#2413](https://github.com/expo/eas-cli/pull/2413) by [@byCedric](https://github.com/byCedric))
|
||||
|
||||
## [9.2.0](https://github.com/expo/eas-cli/releases/tag/v9.2.0) - 2024-06-06
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `target-profile` and `source-profile` flags to the `eas build:resign` command. ([#2410](https://github.com/expo/eas-cli/pull/2410) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Display build profile in the output of `eas build:list`. ([#2408](https://github.com/expo/eas-cli/pull/2408) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Use the correct app config for no GitHub flow in `init:onboarding`. ([#2397](https://github.com/expo/eas-cli/pull/2397) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Disallow picking expired builds as submit archive source. ([#2406](https://github.com/expo/eas-cli/pull/2406) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Print network error message if present. ([#2407](https://github.com/expo/eas-cli/pull/2407) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Make flags for `eas build:list` command more aligned with flags for rest of the commands. ([#2409](https://github.com/expo/eas-cli/pull/2409) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [9.1.0](https://github.com/expo/eas-cli/releases/tag/v9.1.0) - 2024-05-23
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Make `eas init:onboarding` command public. ([#2399](https://github.com/expo/eas-cli/pull/2399) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [9.0.10](https://github.com/expo/eas-cli/releases/tag/v9.0.10) - 2024-05-22
|
||||
|
||||
## [9.0.9](https://github.com/expo/eas-cli/releases/tag/v9.0.9) - 2024-05-22
|
||||
|
||||
## [9.0.8](https://github.com/expo/eas-cli/releases/tag/v9.0.8) - 2024-05-21
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies. ([#2387](https://github.com/expo/eas-cli/pull/2387) by [@expo-bot](https://github.com/expo-bot))
|
||||
- Improve displaying of device registration QR code. ([#2391](https://github.com/expo/eas-cli/pull/2391) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [9.0.7](https://github.com/expo/eas-cli/releases/tag/v9.0.7) - 2024-05-17
|
||||
|
||||
## [9.0.6](https://github.com/expo/eas-cli/releases/tag/v9.0.6) - 2024-05-15
|
||||
|
||||
## [9.0.5](https://github.com/expo/eas-cli/releases/tag/v9.0.5) - 2024-05-13
|
||||
|
||||
## [9.0.4](https://github.com/expo/eas-cli/releases/tag/v9.0.4) - 2024-05-13
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add loader and progress information to the `diagnostics` command. ([#2378](https://github.com/expo/eas-cli/pull/2378) by [@simek](https://github.com/simek))
|
||||
|
||||
## [9.0.3](https://github.com/expo/eas-cli/releases/tag/v9.0.3) - 2024-05-10
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies. ([#2372](https://github.com/expo/eas-cli/pull/2372) by [@expo-bot](https://github.com/expo-bot))
|
||||
|
||||
## [9.0.2](https://github.com/expo/eas-cli/releases/tag/v9.0.2) - 2024-05-09
|
||||
|
||||
## [9.0.1](https://github.com/expo/eas-cli/releases/tag/v9.0.1) - 2024-05-09
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Update image tags in `eas.schema.json`. ([#2363](https://github.com/expo/eas-cli/pull/2363) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [9.0.0](https://github.com/expo/eas-cli/releases/tag/v9.0.0) - 2024-05-08
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Allow modification of provisioning profile in CI, add --freeze-credentials flag. ([#2347](https://github.com/expo/eas-cli/pull/2347) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Pass env from process.env and build profile to expo-updates CLI calls where applicable. ([#2359](https://github.com/expo/eas-cli/pull/2359) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Remove more classic updates code. ([#2357](https://github.com/expo/eas-cli/pull/2357) by [@wschurman](https://github.com/wschurman))
|
||||
- Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies. ([#2360](https://github.com/expo/eas-cli/pull/2360) by [@expo-bot](https://github.com/expo-bot))
|
||||
- Don't pass custom global `"expoCli"` version set in `eas.json` to EAS Build process and warn users when setting it. ([#2361](https://github.com/expo/eas-cli/pull/2361) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [8.0.0](https://github.com/expo/eas-cli/releases/tag/v8.0.0) - 2024-05-01
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Stop creating a channel on `eas update` and `eas update:roll-back-to-embedded` unless the `--channel` flag is specified. ([#2346](https://github.com/expo/eas-cli/pull/2346) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Improve login info message for other login options. ([#2352](https://github.com/expo/eas-cli/pull/2352) by [@wschurman](https://github.com/wschurman))
|
||||
- Show the -s, --sso option in the login command help. ([#2353](https://github.com/expo/eas-cli/pull/2353) by [@lzkb](https://github.com/lzkb))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies. ([#2351](https://github.com/expo/eas-cli/pull/2351) by [@expo-bot](https://github.com/expo-bot))
|
||||
- Don't prompt users to set push notifications by default if they don't have the `expo-notifications` installed. ([#2343](https://github.com/expo/eas-cli/pull/2343) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [7.8.5](https://github.com/expo/eas-cli/releases/tag/v7.8.5) - 2024-04-26
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Add explicit workflow arg to expo-update CLI calls. ([#2340](https://github.com/expo/eas-cli/pull/2340) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Improve error message if the server returns `UNAUTHORIZED_ERROR`. ([#2345](https://github.com/expo/eas-cli/pull/2345) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Fill in min expo-updates version for expo-updates CLI. ([#2344](https://github.com/expo/eas-cli/pull/2344) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
## [7.8.4](https://github.com/expo/eas-cli/releases/tag/v7.8.4) - 2024-04-24
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Update the list of available Android images. ([#2337](https://github.com/expo/eas-cli/pull/2337) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
- Make multi-select for revoking distribution certificates more readable. ([#2342](https://github.com/expo/eas-cli/pull/2342) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Improve error message displayed when EAS CLI version doesn't satisfy minimal version required specified in eas.json. ([#2341](https://github.com/expo/eas-cli/pull/2341) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [7.8.3](https://github.com/expo/eas-cli/releases/tag/v7.8.3) - 2024-04-23
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Don't prompt to set `android.package` and `ios.bundleIdentifier` values when running in non-interactive mode. ([#2336](https://github.com/expo/eas-cli/pull/2336) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Amend credential removal wording. ([#2334](https://github.com/expo/eas-cli/pull/2334) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
* @szdziedzic @khamilowicz @sjchmiela
|
||||
|
||||
packages/eas-cli/src/build @szdziedzic @khamilowicz @sjchmiela
|
||||
packages/eas-cli/src/commands/build @szdziedzic @khamilowicz @sjchmiela
|
||||
|
||||
packages/eas-cli/src/submit @szdziedzic @khamilowicz @sjchmiela
|
||||
packages/eas-cli/src/commands/submit.ts @szdziedzic @khamilowicz @sjchmiela
|
||||
|
||||
packages/eas-cli/src/update @EvanBacon @byCedric @wschurman
|
||||
packages/eas-cli/src/commands/update @EvanBacon @byCedric @wschurman
|
||||
|
||||
packages/eas-cli/schema @bycedric
|
||||
packages/eas-cli/src/metadata @bycedric
|
||||
packages/eas-cli/src/commands/metadata @bycedric
|
||||
|
||||
packages/eas-json @szdziedzic @khamilowicz @sjchmiela
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"version": "7.8.2",
|
||||
"version": "12.5.1",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
"@types/jest": "29.5.11",
|
||||
"@types/node": "20.11.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-universe": "12.0.0",
|
||||
"eslint-config-universe": "13.0.0",
|
||||
"eslint-plugin-async-protect": "3.1.0",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"jest": "29.7.0",
|
||||
|
||||
+187
-103
@@ -39,7 +39,7 @@ An example of **eas.json** that enforces `eas-cli` in version `1.0.0` or newer:
|
||||
}
|
||||
```
|
||||
|
||||
Learn more: https://docs.expo.dev/build-reference/eas-json/
|
||||
Learn more: https://docs.expo.dev/build/eas-json/
|
||||
|
||||
# Usage
|
||||
|
||||
@@ -94,14 +94,17 @@ eas --help COMMAND
|
||||
* [`eas diagnostics`](#eas-diagnostics)
|
||||
* [`eas help [COMMAND]`](#eas-help-command)
|
||||
* [`eas init`](#eas-init)
|
||||
* [`eas init:onboarding [TARGET_PROJECT_DIRECTORY]`](#eas-initonboarding-target_project_directory)
|
||||
* [`eas login`](#eas-login)
|
||||
* [`eas logout`](#eas-logout)
|
||||
* [`eas metadata:lint`](#eas-metadatalint)
|
||||
* [`eas metadata:pull`](#eas-metadatapull)
|
||||
* [`eas metadata:push`](#eas-metadatapush)
|
||||
* [`eas onboarding [TARGET_PROJECT_DIRECTORY]`](#eas-onboarding-target_project_directory)
|
||||
* [`eas open`](#eas-open)
|
||||
* [`eas project:info`](#eas-projectinfo)
|
||||
* [`eas project:init`](#eas-projectinit)
|
||||
* [`eas project:onboarding [TARGET_PROJECT_DIRECTORY]`](#eas-projectonboarding-target_project_directory)
|
||||
* [`eas secret:create`](#eas-secretcreate)
|
||||
* [`eas secret:delete`](#eas-secretdelete)
|
||||
* [`eas secret:list`](#eas-secretlist)
|
||||
@@ -128,7 +131,10 @@ log in with your Expo account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas account:login
|
||||
$ eas account:login [-s]
|
||||
|
||||
FLAGS
|
||||
-s, --sso Login with SSO
|
||||
|
||||
DESCRIPTION
|
||||
log in with your Expo account
|
||||
@@ -137,7 +143,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/account/login.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -154,7 +160,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -171,7 +177,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/account/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -185,7 +191,7 @@ DESCRIPTION
|
||||
display or change analytics settings
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -237,7 +243,7 @@ DESCRIPTION
|
||||
create a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -258,7 +264,7 @@ DESCRIPTION
|
||||
delete a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -278,7 +284,7 @@ DESCRIPTION
|
||||
list all branches
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -298,7 +304,7 @@ DESCRIPTION
|
||||
rename a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -321,7 +327,7 @@ DESCRIPTION
|
||||
view a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -331,7 +337,7 @@ start a build
|
||||
USAGE
|
||||
$ eas build [-p android|ios|all] [-e <value>] [--local] [--output <value>] [--wait] [--clear-cache]
|
||||
[--auto-submit | --auto-submit-with-profile <value>] [-m <value>] [--build-logger-level
|
||||
trace|debug|info|warn|error|fatal] [--json --non-interactive]
|
||||
trace|debug|info|warn|error|fatal] [--freeze-credentials] [--json --non-interactive]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to
|
||||
@@ -345,6 +351,8 @@ FLAGS
|
||||
--build-logger-level=(trace|debug|info|warn|error|fatal) The level of logs to output during the build process.
|
||||
Defaults to "info".
|
||||
--clear-cache Clear cache before the build
|
||||
--freeze-credentials Prevent the build from updating credentials in
|
||||
non-interactive mode
|
||||
--json Enable JSON output, non-JSON messages will be printed to
|
||||
stderr.
|
||||
--local Run build locally [experimental]
|
||||
@@ -356,7 +364,7 @@ DESCRIPTION
|
||||
start a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -375,7 +383,7 @@ DESCRIPTION
|
||||
cancel a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -392,7 +400,7 @@ DESCRIPTION
|
||||
configure the project to support EAS Build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:delete [BUILD_ID]`
|
||||
|
||||
@@ -411,7 +419,7 @@ DESCRIPTION
|
||||
delete a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -446,7 +454,7 @@ DESCRIPTION
|
||||
inspect the state of the project at specific build stages, useful for troubleshooting
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -454,20 +462,27 @@ list all builds for your project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:list [--platform all|android|ios] [--status
|
||||
$ eas build:list [-p android|ios|all] [--status
|
||||
new|in-queue|in-progress|pending-cancel|errored|finished|canceled] [--distribution store|internal|simulator]
|
||||
[--channel <value>] [--appVersion <value>] [--appBuildVersion <value>] [--sdkVersion <value>] [--runtimeVersion
|
||||
<value>] [--appIdentifier <value>] [--buildProfile <value>] [--gitCommitHash <value>] [--offset <value>] [--limit
|
||||
<value>] [--json --non-interactive] [--simulator]
|
||||
[--channel <value>] [--app-version <value>] [--app-build-version <value>] [--sdk-version <value>] [--runtime-version
|
||||
<value>] [--app-identifier <value>] [-e <value>] [--git-commit-hash <value>] [--offset <value>] [--limit <value>]
|
||||
[--json --non-interactive] [--simulator]
|
||||
|
||||
FLAGS
|
||||
--appBuildVersion=<value>
|
||||
--appIdentifier=<value>
|
||||
--appVersion=<value>
|
||||
--buildProfile=<value>
|
||||
-e, --build-profile=<value> Filter only builds created with the
|
||||
specified build profile
|
||||
-p, --platform=(android|ios|all)
|
||||
--app-build-version=<value> Filter only builds created with the
|
||||
specified app build version
|
||||
--app-identifier=<value> Filter only builds created with the
|
||||
specified app identifier
|
||||
--app-version=<value> Filter only builds created with the
|
||||
specified main app version
|
||||
--channel=<value>
|
||||
--distribution=(store|internal|simulator)
|
||||
--gitCommitHash=<value>
|
||||
--distribution=(store|internal|simulator) Filter only builds with the specified
|
||||
distribution type
|
||||
--git-commit-hash=<value> Filter only builds created with the
|
||||
specified git commit hash
|
||||
--json Enable JSON output, non-JSON messages
|
||||
will be printed to stderr.
|
||||
--limit=<value> The number of items to fetch each query.
|
||||
@@ -475,19 +490,21 @@ FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--offset=<value> Start queries from specified index. Use
|
||||
for paginating results. Defaults to 0.
|
||||
--platform=(all|android|ios)
|
||||
--runtimeVersion=<value>
|
||||
--sdkVersion=<value>
|
||||
--runtime-version=<value> Filter only builds created with the
|
||||
specified runtime version
|
||||
--sdk-version=<value> Filter only builds created with the
|
||||
specified Expo SDK version
|
||||
--simulator Filter only iOS simulator builds. Can
|
||||
only be used with --platform flag set to
|
||||
"ios"
|
||||
--status=(new|in-queue|in-progress|pending-cancel|errored|finished|canceled)
|
||||
--status=(new|in-queue|in-progress|pending-cancel|errored|finished|canceled) Filter only builds with the specified
|
||||
status
|
||||
|
||||
DESCRIPTION
|
||||
list all builds for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:resign`
|
||||
|
||||
@@ -495,25 +512,28 @@ re-sign a build archive
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:resign [-p android|ios] [-e <value>] [--wait] [--id <value>] [--offset <value>] [--limit <value>]
|
||||
[--json --non-interactive]
|
||||
$ eas build:resign [-p android|ios] [-e <value>] [--source-profile <value>] [--wait] [--id <value>] [--offset
|
||||
<value>] [--limit <value>] [--json --non-interactive]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined in
|
||||
eas.json.
|
||||
-e, --target-profile=PROFILE_NAME Name of the target build profile from eas.json. Credentials and environment
|
||||
variables from this profile will be used when re-signing. Defaults to "production"
|
||||
if defined in eas.json.
|
||||
-p, --platform=(android|ios)
|
||||
--id=<value> ID of the build to re-sign.
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--limit=<value> The number of items to fetch each query. Defaults to 50 and is capped at 100.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
|
||||
--[no-]wait Wait for build(s) to complete.
|
||||
--id=<value> ID of the build to re-sign.
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--limit=<value> The number of items to fetch each query. Defaults to 50 and is capped at 100.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
|
||||
--source-profile=PROFILE_NAME Name of the source build profile from eas.json. Used to filter builds eligible for
|
||||
re-signing.
|
||||
--[no-]wait Wait for build(s) to complete.
|
||||
|
||||
DESCRIPTION
|
||||
re-sign a build archive
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
|
||||
## `eas build:run`
|
||||
|
||||
@@ -539,7 +559,7 @@ DESCRIPTION
|
||||
run simulator/emulator builds from eas-cli
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/run.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/run.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -589,7 +609,7 @@ DESCRIPTION
|
||||
get the latest version from EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
|
||||
## `eas build:version:set`
|
||||
|
||||
@@ -608,7 +628,7 @@ DESCRIPTION
|
||||
update version of an app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
|
||||
## `eas build:version:sync`
|
||||
|
||||
@@ -627,7 +647,7 @@ DESCRIPTION
|
||||
update a version in native code with a value stored on EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -644,7 +664,7 @@ DESCRIPTION
|
||||
view a build for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/build/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -665,7 +685,7 @@ DESCRIPTION
|
||||
create a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -687,7 +707,7 @@ DESCRIPTION
|
||||
point a channel at a new branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -707,7 +727,7 @@ DESCRIPTION
|
||||
list all channels
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:rollout [CHANNEL]`
|
||||
|
||||
@@ -732,14 +752,15 @@ FLAGS
|
||||
--action=create
|
||||
--private-key-path=<value> File containing the PEM-encoded private key corresponding to the certificate
|
||||
in expo-updates' configuration. Defaults to a file named "private-key.pem" in
|
||||
the certificate's directory.
|
||||
the certificate's directory. Only relevant if you are using code signing:
|
||||
https://docs.expo.dev/eas-update/code-signing/
|
||||
--runtime-version=<value> Runtime version to target. Use with --action=create
|
||||
|
||||
DESCRIPTION
|
||||
Roll a new branch out on a channel incrementally.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -762,7 +783,7 @@ DESCRIPTION
|
||||
view a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -783,7 +804,7 @@ DESCRIPTION
|
||||
display project configuration (app.json + eas.json)
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -800,7 +821,7 @@ DESCRIPTION
|
||||
manage credentials
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
|
||||
## `eas credentials:configure-build`
|
||||
|
||||
@@ -818,7 +839,7 @@ DESCRIPTION
|
||||
Set up credentials for building your project.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -832,7 +853,7 @@ DESCRIPTION
|
||||
register new Apple Devices to use for internal distribution
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/device/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -852,7 +873,7 @@ DESCRIPTION
|
||||
remove a registered device from your account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -873,7 +894,7 @@ DESCRIPTION
|
||||
list all registered devices for your account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/device/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:rename`
|
||||
|
||||
@@ -894,7 +915,7 @@ DESCRIPTION
|
||||
rename a registered device
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -908,7 +929,7 @@ DESCRIPTION
|
||||
view a device for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/device/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -922,7 +943,7 @@ DESCRIPTION
|
||||
display environment info
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -950,10 +971,11 @@ create or link an EAS project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas init [--force --id <value>] [--non-interactive ]
|
||||
$ eas init [--id <value>] [--force] [--non-interactive]
|
||||
|
||||
FLAGS
|
||||
--force Whether to overwrite any existing project ID
|
||||
--force Whether to create a new project/link an existing project without additional prompts or overwrite
|
||||
any existing project ID when running with --id flag
|
||||
--id=<value> ID of the EAS project to link
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
@@ -964,13 +986,32 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
## `eas init:onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
continue onboarding process started on the https://expo.new website.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas init:onboarding [TARGET_PROJECT_DIRECTORY]
|
||||
|
||||
DESCRIPTION
|
||||
continue onboarding process started on the https://expo.new website.
|
||||
|
||||
ALIASES
|
||||
$ eas init:onboarding
|
||||
$ eas onboarding
|
||||
```
|
||||
|
||||
## `eas login`
|
||||
|
||||
log in with your Expo account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas login
|
||||
$ eas login [-s]
|
||||
|
||||
FLAGS
|
||||
-s, --sso Login with SSO
|
||||
|
||||
DESCRIPTION
|
||||
log in with your Expo account
|
||||
@@ -1010,7 +1051,7 @@ DESCRIPTION
|
||||
validate the local store configuration
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
|
||||
## `eas metadata:pull`
|
||||
|
||||
@@ -1027,7 +1068,7 @@ DESCRIPTION
|
||||
generate the local store configuration from the app stores
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -1044,7 +1085,23 @@ DESCRIPTION
|
||||
sync the local store configuration to the app stores
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
continue onboarding process started on the https://expo.new website.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas onboarding [TARGET_PROJECT_DIRECTORY]
|
||||
|
||||
DESCRIPTION
|
||||
continue onboarding process started on the https://expo.new website.
|
||||
|
||||
ALIASES
|
||||
$ eas init:onboarding
|
||||
$ eas onboarding
|
||||
```
|
||||
|
||||
## `eas open`
|
||||
|
||||
@@ -1058,7 +1115,7 @@ DESCRIPTION
|
||||
open the project page in a web browser
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/open.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/open.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -1072,7 +1129,7 @@ DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/project/info.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -1080,10 +1137,11 @@ create or link an EAS project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas project:init [--force --id <value>] [--non-interactive ]
|
||||
$ eas project:init [--id <value>] [--force] [--non-interactive]
|
||||
|
||||
FLAGS
|
||||
--force Whether to overwrite any existing project ID
|
||||
--force Whether to create a new project/link an existing project without additional prompts or overwrite
|
||||
any existing project ID when running with --id flag
|
||||
--id=<value> ID of the EAS project to link
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
@@ -1094,7 +1152,25 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/project/init.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
continue onboarding process started on the https://expo.new website.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas project:onboarding [TARGET_PROJECT_DIRECTORY]
|
||||
|
||||
DESCRIPTION
|
||||
continue onboarding process started on the https://expo.new website.
|
||||
|
||||
ALIASES
|
||||
$ eas init:onboarding
|
||||
$ eas onboarding
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
@@ -1117,7 +1193,7 @@ DESCRIPTION
|
||||
create an environment secret on the current project or owner account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -1135,7 +1211,7 @@ DESCRIPTION
|
||||
delete an environment secret by ID
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -1149,7 +1225,7 @@ DESCRIPTION
|
||||
list environment secrets available for your current app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
|
||||
## `eas secret:push`
|
||||
|
||||
@@ -1169,7 +1245,7 @@ DESCRIPTION
|
||||
read environment secrets from env file and store on the server
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/secret/push.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/secret/push.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -1200,7 +1276,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -1208,9 +1284,9 @@ publish an update group
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update [--branch <value>] [--channel <value>] [-m <value>] [--republish | --input-dir <value> |
|
||||
--skip-bundler] [--group <value> | | ] [--clear-cache] [-p android|ios|all] [--auto] [--private-key-path <value>]
|
||||
[--json --non-interactive]
|
||||
$ eas update [--branch <value>] [--channel <value>] [-m <value>] [--input-dir <value>] [--skip-bundler]
|
||||
[--clear-cache] [--emit-metadata] [-p android|ios|all] [--auto] [--private-key-path <value>] [--json
|
||||
--non-interactive]
|
||||
|
||||
FLAGS
|
||||
-m, --message=<value> A short message describing the update
|
||||
@@ -1219,21 +1295,22 @@ FLAGS
|
||||
--branch=<value> Branch to publish the update group on
|
||||
--channel=<value> Channel that the published update should affect
|
||||
--clear-cache Clear the bundler cache before publishing
|
||||
--group=<value> Update group to republish (deprecated, see republish command)
|
||||
--emit-metadata Emit "eas-update-metadata.json" in the bundle folder with detailed information about
|
||||
the generated updates
|
||||
--input-dir=<value> [default: dist] Location of the bundle
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--private-key-path=<value> File containing the PEM-encoded private key corresponding to the certificate in
|
||||
expo-updates' configuration. Defaults to a file named "private-key.pem" in the
|
||||
certificate's directory.
|
||||
--republish Republish an update group (deprecated, see republish command)
|
||||
certificate's directory. Only relevant if you are using code signing:
|
||||
https://docs.expo.dev/eas-update/code-signing/
|
||||
--skip-bundler Skip running Expo CLI to bundle the app before publishing
|
||||
|
||||
DESCRIPTION
|
||||
publish an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -1251,7 +1328,7 @@ DESCRIPTION
|
||||
configure the project to support EAS Update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -1272,7 +1349,7 @@ DESCRIPTION
|
||||
delete all the updates in an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -1294,7 +1371,7 @@ DESCRIPTION
|
||||
view the recent updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:republish`
|
||||
|
||||
@@ -1302,26 +1379,31 @@ roll back to an existing update
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:republish [--channel <value> | --branch <value> | --group <value>] [-m <value>] [-p android|ios|all]
|
||||
[--private-key-path <value>] [--json --non-interactive]
|
||||
$ eas update:republish [--channel <value> | --branch <value> | --group <value>] [--destination-channel <value> |
|
||||
--destination-branch <value>] [-m <value>] [-p android|ios|all] [--private-key-path <value>] [--json
|
||||
--non-interactive]
|
||||
|
||||
FLAGS
|
||||
-m, --message=<value> Short message describing the republished update group
|
||||
-p, --platform=(android|ios|all) [default: all]
|
||||
--branch=<value> Branch name to select an update group to republish from
|
||||
--channel=<value> Channel name to select an update group to republish from
|
||||
--destination-branch=<value> Branch name to republish to if republishing to a different branch
|
||||
--destination-channel=<value> Channel name to select a branch to republish to if republishing to a different
|
||||
branch
|
||||
--group=<value> Update group ID to republish
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--private-key-path=<value> File containing the PEM-encoded private key corresponding to the certificate in
|
||||
expo-updates' configuration. Defaults to a file named "private-key.pem" in the
|
||||
certificate's directory.
|
||||
certificate's directory. Only relevant if you are using code signing:
|
||||
https://docs.expo.dev/eas-update/code-signing/
|
||||
|
||||
DESCRIPTION
|
||||
roll back to an existing update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
|
||||
## `eas update:roll-back-to-embedded`
|
||||
|
||||
@@ -1342,13 +1424,14 @@ FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--private-key-path=<value> File containing the PEM-encoded private key corresponding to the certificate in
|
||||
expo-updates' configuration. Defaults to a file named "private-key.pem" in the
|
||||
certificate's directory.
|
||||
certificate's directory. Only relevant if you are using code signing:
|
||||
https://docs.expo.dev/eas-update/code-signing/
|
||||
|
||||
DESCRIPTION
|
||||
roll back to the embedded update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
|
||||
## `eas update:rollback`
|
||||
|
||||
@@ -1361,13 +1444,14 @@ USAGE
|
||||
FLAGS
|
||||
--private-key-path=<value> File containing the PEM-encoded private key corresponding to the certificate in
|
||||
expo-updates' configuration. Defaults to a file named "private-key.pem" in the
|
||||
certificate's directory.
|
||||
certificate's directory. Only relevant if you are using code signing:
|
||||
https://docs.expo.dev/eas-update/code-signing/
|
||||
|
||||
DESCRIPTION
|
||||
roll back to an embedded update or an existing update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -1387,7 +1471,7 @@ DESCRIPTION
|
||||
update group details
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/update/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -1408,7 +1492,7 @@ DESCRIPTION
|
||||
create a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -1428,7 +1512,7 @@ DESCRIPTION
|
||||
delete a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -1446,7 +1530,7 @@ DESCRIPTION
|
||||
list webhooks
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -1468,7 +1552,7 @@ DESCRIPTION
|
||||
update a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -1485,7 +1569,7 @@ DESCRIPTION
|
||||
view a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v7.8.2/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ documents:
|
||||
- 'src/commands/**/*.ts'
|
||||
- 'src/branch/**/*.ts'
|
||||
- 'src/channel/**/*.ts'
|
||||
- 'src/worker/**/*.ts'
|
||||
generates:
|
||||
src/graphql/generated.ts:
|
||||
plugins:
|
||||
|
||||
Generated
+12619
-1497
File diff suppressed because it is too large.
Load diff
@@ -1,22 +1,23 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "7.8.2",
|
||||
"version": "12.5.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
},
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/apple-utils": "1.7.0",
|
||||
"@expo/apple-utils": "1.8.0",
|
||||
"@expo/code-signing-certificates": "0.0.5",
|
||||
"@expo/config": "8.5.4",
|
||||
"@expo/config-plugins": "7.8.4",
|
||||
"@expo/config-types": "50.0.0",
|
||||
"@expo/eas-build-job": "1.0.95",
|
||||
"@expo/eas-json": "7.8.0",
|
||||
"@expo/eas-build-job": "1.0.136",
|
||||
"@expo/eas-json": "12.5.0",
|
||||
"@expo/env": "^0.3.0",
|
||||
"@expo/json-file": "8.2.37",
|
||||
"@expo/logger": "1.0.57",
|
||||
"@expo/logger": "1.0.117",
|
||||
"@expo/multipart-body-parser": "1.1.0",
|
||||
"@expo/osascript": "2.0.33",
|
||||
"@expo/package-manager": "1.1.2",
|
||||
@@ -28,7 +29,7 @@
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.7.0",
|
||||
"@expo/steps": "1.0.95",
|
||||
"@expo/steps": "1.0.136",
|
||||
"@expo/timeago.js": "1.0.0",
|
||||
"@oclif/core": "^1.26.2",
|
||||
"@oclif/plugin-autocomplete": "^2.3.10",
|
||||
@@ -63,6 +64,7 @@
|
||||
"log-symbols": "4.1.0",
|
||||
"mime": "3.0.0",
|
||||
"minimatch": "5.1.2",
|
||||
"minizlib": "3.0.1",
|
||||
"nanoid": "3.3.4",
|
||||
"node-fetch": "2.6.7",
|
||||
"node-forge": "1.3.1",
|
||||
@@ -78,12 +80,14 @@
|
||||
"semver": "7.5.4",
|
||||
"slash": "3.0.0",
|
||||
"tar": "6.2.1",
|
||||
"tar-stream": "3.1.7",
|
||||
"terminal-link": "2.1.1",
|
||||
"tslib": "2.6.2",
|
||||
"turndown": "7.1.2",
|
||||
"untildify": "4.0.0",
|
||||
"uuid": "9.0.1",
|
||||
"wrap-ansi": "7.0.0"
|
||||
"wrap-ansi": "7.0.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
@@ -105,12 +109,13 @@
|
||||
"@types/prompts": "2.4.2",
|
||||
"@types/semver": "7.5.6",
|
||||
"@types/tar": "6.1.10",
|
||||
"@types/tar-stream": "3.1.3",
|
||||
"@types/tough-cookie": "4.0.2",
|
||||
"@types/uuid": "9.0.7",
|
||||
"@types/wrap-ansi": "3.0.0",
|
||||
"axios": "0.27.2",
|
||||
"axios": "0.28.1",
|
||||
"eslint-plugin-graphql": "4.0.0",
|
||||
"express": "4.19.2",
|
||||
"express": "4.20.0",
|
||||
"memfs": "3.4.13",
|
||||
"mockdate": "3.0.5",
|
||||
"nock": "13.4.0",
|
||||
@@ -121,7 +126,7 @@
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"files": [
|
||||
"/bin",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
module.exports = {
|
||||
extends: ['universe/shared/typescript-analysis'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.d.ts'],
|
||||
parserOptions: {
|
||||
project: './packages/eas-cli/tsconfig.json',
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/explicit-function-return-type': [
|
||||
'warn',
|
||||
{
|
||||
allowExpressions: true,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/prefer-nullish-coalescing': ['warn', { ignorePrimitives: true }],
|
||||
'@typescript-eslint/no-confusing-void-expression': 'warn',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/no-misused-promises': [
|
||||
'error',
|
||||
{
|
||||
checksVoidReturn: false,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'no-void': ['warn', { allowAsStatement: true }],
|
||||
'no-return-await': 'off',
|
||||
'@typescript-eslint/return-await': ['error', 'always'],
|
||||
'@typescript-eslint/no-confusing-non-null-assertion': 'warn',
|
||||
'@typescript-eslint/no-extra-non-null-assertion': 'warn',
|
||||
'@typescript-eslint/prefer-as-const': 'warn',
|
||||
'@typescript-eslint/prefer-includes': 'warn',
|
||||
'@typescript-eslint/prefer-readonly': 'warn',
|
||||
'@typescript-eslint/prefer-string-starts-ends-with': 'warn',
|
||||
'@typescript-eslint/prefer-ts-expect-error': 'warn',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'warn',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`BuildVersionGetView reading version aborts when the appVersionSource is not specified and the user chooses to set it to LOCAL 1`] = `"Aborting..."`;
|
||||
|
||||
exports[`BuildVersionGetView reading version when appVersionSource is set to local 1`] = `"This project is not configured for using remote version source. Add [1m{"cli": { "appVersionSource": "remote" }}[22m in eas.json or re-run this command without "--non-interactive" flag."`;
|
||||
@@ -1,10 +1,18 @@
|
||||
import { AppVersionSource, EasJson } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { getMockEasJson, mockCommandContext, mockProjectId, mockTestCommand } from './utils';
|
||||
import {
|
||||
getMockEasJson,
|
||||
mockCommandContext,
|
||||
mockProjectId,
|
||||
mockTestCommand,
|
||||
withLocalVersionSource,
|
||||
withRemoteVersionSource,
|
||||
} from './utils';
|
||||
import BuildVersionGetView from '../../commands/build/version/get';
|
||||
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
||||
import Log from '../../log';
|
||||
import { AppVersionSourceUpdateOption } from '../../project/remoteVersionSource';
|
||||
import * as prompts from '../../prompts';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
|
||||
jest.mock('../../project/applicationIdentifier');
|
||||
@@ -12,16 +20,7 @@ jest.mock('fs');
|
||||
jest.mock('../../log');
|
||||
jest.mock('../../utils/json');
|
||||
jest.mock('../../graphql/queries/AppVersionQuery');
|
||||
|
||||
function withRemoteVersionSource(easJson: EasJson): EasJson {
|
||||
return {
|
||||
...easJson,
|
||||
cli: {
|
||||
...easJson.cli,
|
||||
appVersionSource: AppVersionSource.REMOTE,
|
||||
},
|
||||
};
|
||||
}
|
||||
jest.mock('../../prompts');
|
||||
|
||||
describe(BuildVersionGetView, () => {
|
||||
afterEach(() => {
|
||||
@@ -49,6 +48,29 @@ describe(BuildVersionGetView, () => {
|
||||
expect(printJsonOnlyOutput).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('reading version for platform android when the appVersionSource is not specified and the user chooses to set it to REMOTE', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||
buildVersion: '100',
|
||||
storeVersion: '1.0.0',
|
||||
}));
|
||||
const cmd = mockTestCommand(BuildVersionGetView, ['--platform=android'], ctx);
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_REMOTE);
|
||||
|
||||
await cmd.run();
|
||||
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
|
||||
ctx.loggedIn.graphqlClient,
|
||||
mockProjectId,
|
||||
'ANDROID',
|
||||
'eas.test.com'
|
||||
);
|
||||
expect(Log.log).toHaveBeenCalledWith(`Android versionCode - ${chalk.bold('100')}`);
|
||||
expect(enableJsonOutput).not.toHaveBeenCalled();
|
||||
expect(printJsonOnlyOutput).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('reading version for platform android when no remote version is set', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {
|
||||
easJson: withRemoteVersionSource(getMockEasJson()),
|
||||
@@ -145,7 +167,9 @@ describe(BuildVersionGetView, () => {
|
||||
});
|
||||
|
||||
test('reading version when appVersionSource is set to local ', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {});
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {
|
||||
easJson: withLocalVersionSource(getMockEasJson()),
|
||||
});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||
buildVersion: '100',
|
||||
storeVersion: '1.0.0',
|
||||
@@ -158,4 +182,51 @@ describe(BuildVersionGetView, () => {
|
||||
);
|
||||
await expect(cmd.run()).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('reading version aborts when the appVersionSource is not specified and the user chooses to set it to LOCAL', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||
buildVersion: '100',
|
||||
storeVersion: '1.0.0',
|
||||
}));
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_LOCAL);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionGetView, ['--platform=android'], ctx);
|
||||
await expect(cmd.run()).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('reading version aborts when the appVersionSource is not specified and the user chooses to configure it manually', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||
buildVersion: '100',
|
||||
storeVersion: '1.0.0',
|
||||
}));
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.ABORT);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionGetView, ['--platform=android'], ctx);
|
||||
await expect(cmd.run()).rejects.toThrowError('Aborted.');
|
||||
});
|
||||
|
||||
test('reading version sets appVersionSource to LOCAL and aborts when the appVersionSource is not specified and is run in non-interactive mode', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionGetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||
buildVersion: '100',
|
||||
storeVersion: '1.0.0',
|
||||
}));
|
||||
|
||||
const cmd = mockTestCommand(
|
||||
BuildVersionGetView,
|
||||
['--non-interactive', '--json', '--platform=android'],
|
||||
ctx
|
||||
);
|
||||
await expect(cmd.run()).rejects.toThrowError(
|
||||
`This project is not configured for using remote version source. Add ${chalk.bold(
|
||||
'{"cli": { "appVersionSource": "remote" }}'
|
||||
)} in eas.json or re-run this command without "--non-interactive" flag.`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AppVersionSource, EasJson } from '@expo/eas-json';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
@@ -8,12 +7,15 @@ import {
|
||||
mockCommandContext,
|
||||
mockProjectId,
|
||||
mockTestCommand,
|
||||
withLocalVersionSource,
|
||||
withRemoteVersionSource,
|
||||
} from './utils';
|
||||
import BuildVersionSetView from '../../commands/build/version/set';
|
||||
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
|
||||
import { AppQuery } from '../../graphql/queries/AppQuery';
|
||||
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
||||
import Log from '../../log';
|
||||
import { AppVersionSourceUpdateOption } from '../../project/remoteVersionSource';
|
||||
import * as prompts from '../../prompts';
|
||||
|
||||
jest.mock('../../project/applicationIdentifier');
|
||||
@@ -25,16 +27,6 @@ jest.mock('../../log');
|
||||
jest.mock('../../prompts');
|
||||
jest.mock('../../utils/json');
|
||||
|
||||
function withRemoteVersionSource(easJson: EasJson): EasJson {
|
||||
return {
|
||||
...easJson,
|
||||
cli: {
|
||||
...easJson.cli,
|
||||
appVersionSource: AppVersionSource.REMOTE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe(BuildVersionSetView, () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -67,6 +59,34 @@ describe(BuildVersionSetView, () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('setting version for platform android when the appVersionSource is not specified and the user chooses to set it to REMOTE', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {});
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(prompts.promptAsync).mockImplementationOnce(async () => ({
|
||||
version: '1000',
|
||||
}));
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_REMOTE);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
|
||||
await cmd.run();
|
||||
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
|
||||
ctx.loggedIn.graphqlClient,
|
||||
mockProjectId,
|
||||
'ANDROID',
|
||||
'eas.test.com'
|
||||
);
|
||||
expect(AppVersionMutation.createAppVersionAsync).toHaveBeenCalledWith(
|
||||
ctx.loggedIn.graphqlClient,
|
||||
expect.objectContaining({
|
||||
buildVersion: '1000',
|
||||
storeVersion: '1.0.0',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('printing current remote version before prompting for a new one', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {
|
||||
easJson: withRemoteVersionSource(getMockEasJson()),
|
||||
@@ -115,7 +135,9 @@ describe(BuildVersionSetView, () => {
|
||||
});
|
||||
|
||||
test('setting version aborts when appVersionSource is set to local and users refuse auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {});
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {
|
||||
easJson: withLocalVersionSource(getMockEasJson()),
|
||||
});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => false);
|
||||
@@ -125,10 +147,26 @@ describe(BuildVersionSetView, () => {
|
||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('setting version when appVersionSource is set to local and user allows auto configuration', async () => {
|
||||
test('setting version aborts when the appVersionSource is not specified and the user chooses to set it to LOCAL, and they refuse auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_LOCAL);
|
||||
jest.mocked(prompts.confirmAsync).mockImplementationOnce(async () => false);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
|
||||
await expect(cmd.run()).rejects.toThrowError('Aborting...');
|
||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('setting version when appVersionSource is set to local and user allows auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {
|
||||
easJson: withLocalVersionSource(getMockEasJson()),
|
||||
});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => true);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
|
||||
@@ -137,4 +175,33 @@ describe(BuildVersionSetView, () => {
|
||||
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
|
||||
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
|
||||
});
|
||||
|
||||
test('setting version when the appVersionSource is not specified and the user chooses to set it to LOCAL, and they allow auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_LOCAL);
|
||||
jest.mocked(prompts.confirmAsync).mockImplementationOnce(async () => true);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
|
||||
await cmd.run();
|
||||
|
||||
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
|
||||
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
|
||||
});
|
||||
|
||||
test('setting version aborts when the appVersionSource is not specified and the user chooses to configure manually', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSetView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.ABORT);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
|
||||
await expect(cmd.run()).rejects.toThrowError('Aborted.');
|
||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Workflow } from '@expo/eas-build-job';
|
||||
import { AppVersionSource, EasJson } from '@expo/eas-json';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
@@ -9,6 +8,8 @@ import {
|
||||
mockCommandContext,
|
||||
mockProjectId,
|
||||
mockTestCommand,
|
||||
withLocalVersionSource,
|
||||
withRemoteVersionSource,
|
||||
} from './utils';
|
||||
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../build/android/version';
|
||||
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../build/ios/version';
|
||||
@@ -19,6 +20,7 @@ import { AppQuery } from '../../graphql/queries/AppQuery';
|
||||
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
||||
import { getAppBuildGradleAsync } from '../../project/android/gradleUtils';
|
||||
import { resolveTargetsAsync } from '../../project/ios/target';
|
||||
import { AppVersionSourceUpdateOption } from '../../project/remoteVersionSource';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import * as prompts from '../../prompts';
|
||||
|
||||
@@ -37,16 +39,6 @@ jest.mock('../../log');
|
||||
jest.mock('../../prompts');
|
||||
jest.mock('../../utils/json');
|
||||
|
||||
function withRemoteVersionSource(easJson: EasJson): EasJson {
|
||||
return {
|
||||
...easJson,
|
||||
cli: {
|
||||
...easJson.cli,
|
||||
appVersionSource: AppVersionSource.REMOTE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe(BuildVersionSyncView, () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -79,6 +71,34 @@ describe(BuildVersionSyncView, () => {
|
||||
expect(syncAndroidAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('syncing version for managed project on platform android when appVersionSource is not set and the user chooses to set it to REMOTE', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {});
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
|
||||
buildVersion: '1000',
|
||||
storeVersion: '0.0.1',
|
||||
}));
|
||||
jest.mocked(prompts.promptAsync).mockImplementationOnce(async () => ({
|
||||
version: '1000',
|
||||
}));
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_REMOTE);
|
||||
jest.mocked(resolveWorkflowAsync).mockImplementation(async () => Workflow.MANAGED);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
|
||||
const syncAndroidAsync = jest.spyOn(cmd, 'syncAndroidAsync' as any);
|
||||
|
||||
await cmd.run();
|
||||
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
|
||||
ctx.loggedIn.graphqlClient,
|
||||
mockProjectId,
|
||||
'ANDROID',
|
||||
'eas.test.com'
|
||||
);
|
||||
expect(syncAndroidAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('syncing version for bare project on platform android', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {
|
||||
easJson: withRemoteVersionSource(getMockEasJson()),
|
||||
@@ -179,7 +199,9 @@ describe(BuildVersionSyncView, () => {
|
||||
});
|
||||
|
||||
test('syncing version aborts when appVersionSource is set to local and users refuse auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {});
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {
|
||||
easJson: withLocalVersionSource(getMockEasJson()),
|
||||
});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => false);
|
||||
@@ -189,10 +211,26 @@ describe(BuildVersionSyncView, () => {
|
||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('syncing version when appVersionSource is set to local and user allows auto configuration', async () => {
|
||||
test('syncing version aborts when appVersionSource is not set and the user chooses to set it to LOCAL, and they refuse auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_LOCAL);
|
||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => false);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
|
||||
await expect(cmd.run()).rejects.toThrowError('Aborting...');
|
||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('syncing version when appVersionSource is set to local and user allows auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {
|
||||
easJson: withLocalVersionSource(getMockEasJson()),
|
||||
});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => true);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
|
||||
@@ -201,4 +239,33 @@ describe(BuildVersionSyncView, () => {
|
||||
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
|
||||
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
|
||||
});
|
||||
|
||||
test('syncing version when appVersionSource is not set and the user chooses to set it to LOCAL and they allow auto configuration', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.SET_TO_LOCAL);
|
||||
jest.mocked(prompts.confirmAsync).mockImplementation(async () => true);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
|
||||
await cmd.run();
|
||||
|
||||
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
|
||||
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
|
||||
});
|
||||
|
||||
test('syncing version aborts when appVersionSource is not set and the user chooses to configure manually', async () => {
|
||||
const ctx = mockCommandContext(BuildVersionSyncView, {});
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(prompts.selectAsync)
|
||||
.mockImplementationOnce(async () => AppVersionSourceUpdateOption.ABORT);
|
||||
|
||||
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=android'], ctx);
|
||||
await expect(cmd.run()).rejects.toThrowError('Aborted.');
|
||||
expect(AppVersionMutation.createAppVersionAsync).not.toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { EasJson } from '@expo/eas-json';
|
||||
import { AppVersionSource, EasJson } from '@expo/eas-json';
|
||||
import { Command, Config } from '@oclif/core';
|
||||
import { vol } from 'memfs';
|
||||
import path from 'path';
|
||||
@@ -24,6 +24,7 @@ export function getMockAppFragment(): AppFragment {
|
||||
return {
|
||||
id: mockProjectId,
|
||||
slug: 'testapp',
|
||||
name: 'testapp',
|
||||
fullName: '@testuser/testpp',
|
||||
ownerAccount: {
|
||||
id: 'test-account-id',
|
||||
@@ -147,3 +148,23 @@ export const getError = <TError = any>(call: () => unknown): TError | NoErrorThr
|
||||
return error as TError;
|
||||
}
|
||||
};
|
||||
|
||||
export function withRemoteVersionSource(easJson: EasJson): EasJson {
|
||||
return {
|
||||
...easJson,
|
||||
cli: {
|
||||
...easJson.cli,
|
||||
appVersionSource: AppVersionSource.REMOTE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function withLocalVersionSource(easJson: EasJson): EasJson {
|
||||
return {
|
||||
...easJson,
|
||||
cli: {
|
||||
...easJson.cli,
|
||||
appVersionSource: AppVersionSource.LOCAL,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
*/
|
||||
export class SelectBranch implements EASUpdateAction<UpdateBranchBasicInfoFragment | null> {
|
||||
constructor(
|
||||
private options: {
|
||||
private readonly options: {
|
||||
filterPredicate?: (branchInfo: UpdateBranchBasicInfoFragment) => boolean;
|
||||
printedType?: string;
|
||||
pageSize?: number;
|
||||
@@ -91,8 +91,8 @@ export class SelectBranch implements EASUpdateAction<UpdateBranchBasicInfoFragme
|
||||
queryParams: QueryParams
|
||||
): Promise<Connection<UpdateBranchBasicInfoFragment>> =>
|
||||
filterPredicate
|
||||
? this.filterQueryAsync(ctx, queryParams, filterPredicate)
|
||||
: this.queryAsync(ctx, queryParams);
|
||||
? await this.filterQueryAsync(ctx, queryParams, filterPredicate)
|
||||
: await this.queryAsync(ctx, queryParams);
|
||||
|
||||
const getTitleAsync = async (branchInfo: UpdateBranchBasicInfoFragment): Promise<string> =>
|
||||
branchInfo.name;
|
||||
|
||||
@@ -84,7 +84,9 @@ export async function listAndRenderBranchesOnAppAsync(
|
||||
queryBranchesOnProjectAsync(graphqlClient, limit, offset, projectId),
|
||||
promptOptions: {
|
||||
title: 'Load more branches?',
|
||||
renderListItems: branches => renderPageOfBranches(branches, paginatedQueryOptions),
|
||||
renderListItems: branches => {
|
||||
renderPageOfBranches(branches, paginatedQueryOptions);
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -47,14 +47,14 @@ describe(selectBuildToCancelAsync.name, () => {
|
||||
it('does not return build id when confirmation is rejected', async () => {
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||
jest.mocked(confirmAsync).mockResolvedValueOnce(false);
|
||||
expect(selectBuildToCancelAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(null);
|
||||
await expect(selectBuildToCancelAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(null);
|
||||
});
|
||||
|
||||
it('returns build id when confirmation is confirmed', async () => {
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||
jest.mocked(selectAsync).mockResolvedValueOnce(selectedBuildId);
|
||||
jest.mocked(confirmAsync).mockResolvedValueOnce(true);
|
||||
expect(selectBuildToCancelAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
||||
await expect(selectBuildToCancelAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
||||
selectedBuildId
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { AppVersionSource, EasJsonAccessor } from '@expo/eas-json';
|
||||
import fs from 'fs-extra';
|
||||
import { vol } from 'memfs';
|
||||
|
||||
import { easCliVersion } from '../../utils/easCli';
|
||||
import GitClient from '../../vcs/clients/git';
|
||||
import { Client } from '../../vcs/vcs';
|
||||
import { ensureProjectConfiguredAsync } from '../configure';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('../../vcs/vcs');
|
||||
jest.mock('../../vcs/clients/git');
|
||||
|
||||
beforeEach(async () => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
describe(ensureProjectConfiguredAsync, () => {
|
||||
it('returns false and does not configure if eas.json exists', async () => {
|
||||
vol.fromJSON({
|
||||
'./eas.json': JSON.stringify({
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
},
|
||||
build: {
|
||||
development: {
|
||||
developmentClient: true,
|
||||
distribution: 'internal',
|
||||
},
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
},
|
||||
production: {},
|
||||
},
|
||||
submit: {
|
||||
production: {},
|
||||
},
|
||||
}),
|
||||
});
|
||||
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeTruthy();
|
||||
const vcsClientMock = jest.mocked(new GitClient());
|
||||
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 () => {
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||
jest.mocked(confirmAsync).mockResolvedValueOnce(false);
|
||||
expect(selectBuildToDeleteAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(null);
|
||||
await expect(selectBuildToDeleteAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(null);
|
||||
});
|
||||
|
||||
it('returns build id when confirmation is confirmed', async () => {
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||
jest.mocked(selectAsync).mockResolvedValueOnce(selectedBuildId);
|
||||
jest.mocked(confirmAsync).mockResolvedValueOnce(true);
|
||||
expect(selectBuildToDeleteAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
||||
await expect(selectBuildToDeleteAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
||||
selectedBuildId
|
||||
);
|
||||
});
|
||||
|
||||
@@ -107,6 +107,7 @@ export async function prepareAndroidBuildAsync(
|
||||
? false
|
||||
: ctx.buildProfile.autoIncrement,
|
||||
vcsClient: ctx.vcsClient,
|
||||
env: ctx.env,
|
||||
});
|
||||
},
|
||||
prepareJobAsync: async (
|
||||
|
||||
@@ -15,7 +15,6 @@ export function transformJob(job: Android.Job): AndroidJobInput {
|
||||
triggeredBy: transformBuildTrigger(job.triggeredBy),
|
||||
projectArchive: transformProjectArchive(job.projectArchive),
|
||||
projectRootDirectory: job.projectRootDirectory,
|
||||
releaseChannel: job.releaseChannel,
|
||||
updates: job.updates,
|
||||
secrets: job.secrets,
|
||||
builderEnvironment: job.builderEnvironment,
|
||||
|
||||
@@ -56,6 +56,15 @@ export async function prepareJobAsync(
|
||||
? getCustomBuildConfigPathForJob(buildProfile.config)
|
||||
: undefined;
|
||||
|
||||
let buildMode;
|
||||
if (ctx.repack) {
|
||||
buildMode = BuildMode.REPACK;
|
||||
} else if (buildProfile.config) {
|
||||
buildMode = BuildMode.CUSTOM;
|
||||
} else {
|
||||
buildMode = BuildMode.BUILD;
|
||||
}
|
||||
|
||||
const job: Android.Job = {
|
||||
type: ctx.workflow,
|
||||
platform: Platform.ANDROID,
|
||||
@@ -68,7 +77,6 @@ export async function prepareJobAsync(
|
||||
bun: buildProfile.bun,
|
||||
yarn: buildProfile.yarn,
|
||||
ndk: buildProfile.ndk,
|
||||
expoCli: buildProfile.expoCli,
|
||||
env: buildProfile.env,
|
||||
},
|
||||
cache: {
|
||||
@@ -79,7 +87,6 @@ export async function prepareJobAsync(
|
||||
secrets: {
|
||||
...buildCredentials,
|
||||
},
|
||||
releaseChannel: buildProfile.releaseChannel,
|
||||
updates: { channel: buildProfile.channel },
|
||||
developmentClient: buildProfile.developmentClient,
|
||||
gradleCommand: buildProfile.gradleCommand,
|
||||
@@ -95,13 +102,18 @@ export async function prepareJobAsync(
|
||||
experimental: {
|
||||
prebuildCommand: buildProfile.prebuildCommand,
|
||||
},
|
||||
mode: buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
|
||||
mode: buildMode,
|
||||
triggeredBy: BuildTrigger.EAS_CLI,
|
||||
...(maybeCustomBuildConfigPath && {
|
||||
customBuildConfig: {
|
||||
path: maybeCustomBuildConfigPath,
|
||||
},
|
||||
}),
|
||||
...(ctx.repack && {
|
||||
customBuildConfig: {
|
||||
path: '__eas/repack.yml',
|
||||
},
|
||||
}),
|
||||
loggerLevel: ctx.loggerLevel,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { AndroidConfig } from '@expo/config-plugins';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { Env, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { AndroidVersionAutoIncrement } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
@@ -18,11 +18,13 @@ export async function syncProjectConfigurationAsync({
|
||||
exp,
|
||||
localAutoIncrement,
|
||||
vcsClient,
|
||||
env,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
localAutoIncrement?: AndroidVersionAutoIncrement;
|
||||
vcsClient: Client;
|
||||
env: Env | undefined;
|
||||
}): Promise<void> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
|
||||
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
|
||||
@@ -30,7 +32,7 @@ export async function syncProjectConfigurationAsync({
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
await cleanUpOldEasBuildGradleScriptAsync(projectDir);
|
||||
if (isExpoUpdatesInstalled(projectDir)) {
|
||||
await syncUpdatesConfigurationAsync(projectDir, exp);
|
||||
await syncUpdatesConfigurationAsync({ projectDir, exp, workflow, env });
|
||||
}
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
|
||||
} else {
|
||||
|
||||
@@ -44,7 +44,7 @@ export async function bumpVersionAsync({
|
||||
|
||||
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
||||
const isMultiFlavor =
|
||||
buildGradle.android?.productFlavors || buildGradle.android?.flavorDimensions;
|
||||
buildGradle.android?.productFlavors ?? buildGradle.android?.flavorDimensions;
|
||||
if (isMultiFlavor) {
|
||||
throw new Error(
|
||||
'Automatic version bumping is not supported for multi-flavor Android projects.'
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
ArchiveSource,
|
||||
ArchiveSourceType,
|
||||
BuildJob,
|
||||
FingerprintSource,
|
||||
Metadata,
|
||||
Platform,
|
||||
} from '@expo/eas-build-job';
|
||||
@@ -56,10 +57,13 @@ import { BuildQuery } from '../graphql/queries/BuildQuery';
|
||||
import Log, { learnMore, link } from '../log';
|
||||
import { Ora, ora } from '../ora';
|
||||
import {
|
||||
RequestedPlatform,
|
||||
appPlatformDisplayNames,
|
||||
appPlatformEmojis,
|
||||
requestedPlatformDisplayNames,
|
||||
} from '../platform';
|
||||
import { maybeUploadFingerprintAsync } from '../project/maybeUploadFingerprintAsync';
|
||||
import { resolveRuntimeVersionAsync } from '../project/resolveRuntimeVersionAsync';
|
||||
import { uploadFileAtPathToGCSAsync } from '../uploads';
|
||||
import { formatBytes } from '../utils/files';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
@@ -124,7 +128,9 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
|
||||
await withAnalyticsAsync(
|
||||
ctx.analytics,
|
||||
async () => await builder.syncProjectConfigurationAsync(ctx),
|
||||
async () => {
|
||||
await builder.syncProjectConfigurationAsync(ctx);
|
||||
},
|
||||
{
|
||||
attemptEvent: BuildEvent.CONFIGURE_PROJECT_ATTEMPT,
|
||||
successEvent: BuildEvent.CONFIGURE_PROJECT_SUCCESS,
|
||||
@@ -135,9 +141,15 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
|
||||
if (await ctx.vcsClient.isCommitRequiredAsync()) {
|
||||
Log.addNewLineIfNone();
|
||||
const platformToRequestedPlatform: Record<Platform, RequestedPlatform> = {
|
||||
[Platform.ANDROID]: RequestedPlatform.Android,
|
||||
[Platform.IOS]: RequestedPlatform.Ios,
|
||||
};
|
||||
await reviewAndCommitChangesAsync(
|
||||
ctx.vcsClient,
|
||||
`[EAS Build] Run EAS Build for ${requestedPlatformDisplayNames[ctx.platform as Platform]}`,
|
||||
`[EAS Build] Run EAS Build for ${
|
||||
requestedPlatformDisplayNames[platformToRequestedPlatform[ctx.platform]]
|
||||
}`,
|
||||
{ nonInteractive: ctx.nonInteractive }
|
||||
);
|
||||
}
|
||||
@@ -162,7 +174,8 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
}
|
||||
assert(projectArchive);
|
||||
|
||||
const metadata = await collectMetadataAsync(ctx);
|
||||
const runtimeMetadata = await createAndMaybeUploadFingerprintAsync(ctx);
|
||||
const metadata = await collectMetadataAsync(ctx, runtimeMetadata);
|
||||
const buildParams = resolveBuildParamsInput(ctx, metadata);
|
||||
const job = await builder.prepareJobAsync(ctx, {
|
||||
projectArchive,
|
||||
@@ -171,7 +184,7 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
|
||||
return async () => {
|
||||
if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN) {
|
||||
await runLocalBuildAsync(job, metadata, ctx.localBuildOptions);
|
||||
await runLocalBuildAsync(job, metadata, ctx.localBuildOptions, ctx.env);
|
||||
return undefined;
|
||||
} else if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL) {
|
||||
await BuildMutation.updateBuildMetadataAsync(ctx.graphqlClient, {
|
||||
@@ -275,20 +288,23 @@ async function uploadProjectAsync<TPlatform extends Platform>(
|
||||
}
|
||||
|
||||
projectTarballPath = projectTarball.path;
|
||||
const bucketKey = await uploadFileAtPathToGCSAsync(
|
||||
ctx.graphqlClient,
|
||||
UploadSessionType.EasBuildGcsProjectSources,
|
||||
projectTarball.path,
|
||||
createProgressTracker({
|
||||
total: projectTarball.size,
|
||||
message: ratio =>
|
||||
`Uploading to EAS Build (${formatBytes(projectTarball.size * ratio)} / ${formatBytes(
|
||||
projectTarball.size
|
||||
)})`,
|
||||
completedMessage: (duration: string) => `Uploaded to EAS ${chalk.dim(duration)}`,
|
||||
})
|
||||
);
|
||||
const { metadataLocation } = await uploadMetadataFileAsync<TPlatform>(projectTarball, ctx);
|
||||
const [bucketKey, { metadataLocation }] = await Promise.all([
|
||||
uploadFileAtPathToGCSAsync(
|
||||
ctx.graphqlClient,
|
||||
UploadSessionType.EasBuildGcsProjectSources,
|
||||
projectTarball.path,
|
||||
createProgressTracker({
|
||||
total: projectTarball.size,
|
||||
message: ratio =>
|
||||
`Uploading to EAS Build (${formatBytes(
|
||||
projectTarball.size * ratio
|
||||
)} / ${formatBytes(projectTarball.size)})`,
|
||||
completedMessage: (duration: string) => `Uploaded to EAS ${chalk.dim(duration)}`,
|
||||
})
|
||||
),
|
||||
uploadMetadataFileAsync<TPlatform>(projectTarball, ctx),
|
||||
]);
|
||||
|
||||
if (metadataLocation) {
|
||||
return { bucketKey, metadataLocation };
|
||||
}
|
||||
@@ -327,15 +343,7 @@ async function uploadMetadataFileAsync<TPlatform extends Platform>(
|
||||
const metadataLocation = await uploadFileAtPathToGCSAsync(
|
||||
ctx.graphqlClient,
|
||||
UploadSessionType.EasBuildGcsProjectMetadata,
|
||||
projectMetadataFile.path,
|
||||
createProgressTracker({
|
||||
total: projectMetadataFile.size,
|
||||
message: ratio =>
|
||||
`Uploading metadata to EAS Build (${formatBytes(
|
||||
projectMetadataFile!.size * ratio
|
||||
)} / ${formatBytes(projectMetadataFile!.size)})`,
|
||||
completedMessage: (duration: string) => `Uploaded to EAS ${chalk.dim(duration)}`,
|
||||
})
|
||||
projectMetadataFile.path
|
||||
);
|
||||
return { metadataLocation };
|
||||
} catch (err: any) {
|
||||
@@ -654,3 +662,40 @@ function formatAccountSubscriptionsUrl(accountName: string): string {
|
||||
getExpoWebsiteBaseUrl()
|
||||
).toString();
|
||||
}
|
||||
|
||||
async function createAndMaybeUploadFingerprintAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<{
|
||||
runtimeVersion?: string;
|
||||
fingerprintSource?: FingerprintSource;
|
||||
}> {
|
||||
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.fingerprint || !resolvedRuntimeVersion.runtimeVersion) {
|
||||
return {
|
||||
runtimeVersion: resolvedRuntimeVersion.runtimeVersion ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return await maybeUploadFingerprintAsync({
|
||||
runtimeVersion: resolvedRuntimeVersion.runtimeVersion,
|
||||
fingerprint: resolvedRuntimeVersion.fingerprint,
|
||||
graphqlClient: ctx.graphqlClient,
|
||||
localBuildMode: ctx.localBuildOptions.localBuildMode,
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EasJson, EasJsonAccessor } from '@expo/eas-json';
|
||||
import { AppVersionSource, EasJson, EasJsonAccessor } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
@@ -55,6 +55,7 @@ async function configureAsync({
|
||||
const EAS_JSON_DEFAULT: EasJson = {
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
appVersionSource: AppVersionSource.REMOTE,
|
||||
},
|
||||
build: {
|
||||
development: {
|
||||
@@ -64,7 +65,9 @@ const EAS_JSON_DEFAULT: EasJson = {
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
},
|
||||
production: {},
|
||||
production: {
|
||||
autoIncrement: true,
|
||||
},
|
||||
},
|
||||
submit: {
|
||||
production: {},
|
||||
|
||||
@@ -62,4 +62,6 @@ export interface BuildContext<T extends Platform> {
|
||||
requiredPackageManager: NodePackageManager['name'] | null;
|
||||
vcsClient: Client;
|
||||
loggerLevel?: LoggerLevel;
|
||||
repack: boolean;
|
||||
env: Record<string, string>;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import { createAndroidContextAsync } from './android/build';
|
||||
import { BuildContext, CommonContext } from './context';
|
||||
import { createIosContextAsync } from './ios/build';
|
||||
import { LocalBuildOptions } from './local';
|
||||
import { LocalBuildMode, LocalBuildOptions } from './local';
|
||||
import { resolveBuildResourceClassAsync } from './utils/resourceClass';
|
||||
import { Analytics, AnalyticsEventProperties, BuildEvent } from '../analytics/AnalyticsManager';
|
||||
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
|
||||
@@ -41,6 +41,9 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
customBuildConfigMetadata,
|
||||
buildLoggerLevel,
|
||||
freezeCredentials,
|
||||
repack,
|
||||
env,
|
||||
}: {
|
||||
buildProfileName: string;
|
||||
buildProfile: BuildProfile<T>;
|
||||
@@ -60,8 +63,13 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
|
||||
customBuildConfigMetadata?: CustomBuildConfigMetadata;
|
||||
buildLoggerLevel?: LoggerLevel;
|
||||
freezeCredentials: boolean;
|
||||
repack: boolean;
|
||||
env: Record<string, string>;
|
||||
}): Promise<BuildContext<T>> {
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env });
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
|
||||
env,
|
||||
});
|
||||
const projectName = exp.slug;
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
const workflow = await resolveWorkflowAsync(projectDir, platform, vcsClient);
|
||||
@@ -83,9 +91,10 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
user: actor,
|
||||
graphqlClient,
|
||||
analytics,
|
||||
env: buildProfile.env,
|
||||
env,
|
||||
easJsonCliConfig,
|
||||
vcsClient,
|
||||
freezeCredentials,
|
||||
});
|
||||
|
||||
const devClientProperties = getDevClientEventProperties({
|
||||
@@ -103,6 +112,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
...devClientProperties,
|
||||
no_wait: noWait,
|
||||
run_from_ci: runFromCI,
|
||||
local: localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN,
|
||||
};
|
||||
analytics.logEvent(BuildEvent.BUILD_COMMAND, analyticsEventProperties);
|
||||
|
||||
@@ -140,6 +150,8 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
developmentClient,
|
||||
requiredPackageManager,
|
||||
loggerLevel: buildLoggerLevel,
|
||||
repack,
|
||||
env,
|
||||
};
|
||||
if (platform === Platform.ANDROID) {
|
||||
const common = commonContext as CommonContext<Platform.ANDROID>;
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
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() ?? process.env.EAS_CURRENT_ENVIRONMENT;
|
||||
|
||||
if (!environment || !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>;
|
||||
|
||||
const envVarsWithSource: Record<string, 'build profile' | 'EAS server'> = {
|
||||
...Object.fromEntries(Object.keys(serverEnvVars).map(key => [key, 'EAS server'])),
|
||||
...(buildProfile.env
|
||||
? Object.fromEntries(Object.keys(buildProfile.env).map(key => [key, 'build profile']))
|
||||
: null),
|
||||
};
|
||||
Log.log(
|
||||
`Loaded "env" configuration for the "${buildProfileName}" profile and "${environment.toLowerCase()}" environment: ${
|
||||
Object.keys(envVarsWithSource).length > 0
|
||||
? Object.keys(envVarsWithSource)
|
||||
.map(key => `${key} (source: ${envVarsWithSource[key]})`)
|
||||
.join('\n')
|
||||
: 'no environment variables specified'
|
||||
}\n${learnMore('https://docs.expo.dev/build-reference/variables/')}`
|
||||
);
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ArchiveSourceType,
|
||||
BuildMode,
|
||||
BuildTrigger,
|
||||
FingerprintSourceType,
|
||||
Metadata,
|
||||
Workflow,
|
||||
} from '@expo/eas-build-job';
|
||||
@@ -15,8 +16,10 @@ import {
|
||||
BuildMetadataInput,
|
||||
BuildWorkflow,
|
||||
DistributionType,
|
||||
FingerprintSourceInput,
|
||||
BuildMode as GraphQLBuildMode,
|
||||
BuildTrigger as GraphQLBuildTrigger,
|
||||
FingerprintSourceType as GraphQLFingeprintSourceType,
|
||||
ProjectArchiveSourceInput,
|
||||
ProjectArchiveSourceType,
|
||||
WorkerLoggerLevel,
|
||||
@@ -42,6 +45,8 @@ export function transformProjectArchive(archiveSource: ArchiveSource): ProjectAr
|
||||
export function transformMetadata(metadata: Metadata): BuildMetadataInput {
|
||||
return {
|
||||
...metadata,
|
||||
fingerprintSource:
|
||||
metadata.fingerprintSource && transformFingerprintSource(metadata.fingerprintSource),
|
||||
credentialsSource:
|
||||
metadata.credentialsSource && transformCredentialsSource(metadata.credentialsSource),
|
||||
distribution: metadata.distribution && transformDistribution(metadata.distribution),
|
||||
@@ -52,6 +57,20 @@ export function transformMetadata(metadata: Metadata): BuildMetadataInput {
|
||||
};
|
||||
}
|
||||
|
||||
export function transformFingerprintSource(
|
||||
fingerprintSource: NonNullable<Metadata['fingerprintSource']>
|
||||
): FingerprintSourceInput | null {
|
||||
if (fingerprintSource.type !== FingerprintSourceType.GCS) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: GraphQLFingeprintSourceType.Gcs,
|
||||
bucketKey: fingerprintSource.bucketKey,
|
||||
isDebugFingerprint: fingerprintSource.isDebugFingerprint,
|
||||
};
|
||||
}
|
||||
|
||||
function transformCredentialsSource(
|
||||
credentialsSource: Metadata['credentialsSource']
|
||||
): BuildCredentialsSource {
|
||||
@@ -94,6 +113,7 @@ const buildModeToGraphQLBuildMode: Record<BuildMode, GraphQLBuildMode> = {
|
||||
[BuildMode.BUILD]: GraphQLBuildMode.Build,
|
||||
[BuildMode.CUSTOM]: GraphQLBuildMode.Custom,
|
||||
[BuildMode.RESIGN]: GraphQLBuildMode.Resign,
|
||||
[BuildMode.REPACK]: GraphQLBuildMode.Repack,
|
||||
};
|
||||
|
||||
export function transformBuildMode(buildMode: BuildMode): GraphQLBuildMode {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { learnMore } from '../../../log';
|
||||
import { readPlistAsync } from '../../../utils/plist';
|
||||
import { resolveVcsClient } from '../../../vcs';
|
||||
import {
|
||||
@@ -316,7 +317,9 @@ describe(readShortVersionAsync, () => {
|
||||
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'
|
||||
`The required format for "CFBundleShortVersionString" in Info.plist 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: 0.0.7.1.028. Edit the "CFBundleShortVersionString" in your Info.plist to match the required format. ${learnMore(
|
||||
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
export async function createIosContextAsync(
|
||||
ctx: CommonContext<Platform.IOS>
|
||||
): Promise<IosBuildContext> {
|
||||
const { buildProfile } = ctx;
|
||||
const { buildProfile, env } = ctx;
|
||||
|
||||
if (ctx.workflow === Workflow.MANAGED) {
|
||||
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx);
|
||||
@@ -47,7 +47,7 @@ export async function createIosContextAsync(
|
||||
projectDir: ctx.projectDir,
|
||||
exp: ctx.exp,
|
||||
xcodeBuildContext,
|
||||
env: buildProfile.env,
|
||||
env,
|
||||
vcsClient: ctx.vcsClient,
|
||||
});
|
||||
const applicationTarget = findApplicationTarget(targets);
|
||||
@@ -77,7 +77,7 @@ export async function prepareIosBuildAsync(
|
||||
return await prepareBuildRequestForPlatformAsync({
|
||||
ctx,
|
||||
ensureCredentialsAsync: async (ctx: BuildContext<Platform.IOS>) => {
|
||||
return ensureIosCredentialsAsync(ctx, ctx.ios.targets);
|
||||
return await ensureIosCredentialsAsync(ctx, ctx.ios.targets);
|
||||
},
|
||||
syncProjectConfigurationAsync: async () => {
|
||||
await syncProjectConfigurationAsync({
|
||||
@@ -89,6 +89,7 @@ export async function prepareIosBuildAsync(
|
||||
? false
|
||||
: ctx.buildProfile.autoIncrement,
|
||||
vcsClient: ctx.vcsClient,
|
||||
env: ctx.env,
|
||||
});
|
||||
},
|
||||
prepareJobAsync: async (
|
||||
|
||||
@@ -16,7 +16,6 @@ export function transformJob(job: Ios.Job): IosJobInput {
|
||||
triggeredBy: transformBuildTrigger(job.triggeredBy),
|
||||
projectArchive: transformProjectArchive(job.projectArchive),
|
||||
projectRootDirectory: nullthrows(job.projectRootDirectory),
|
||||
releaseChannel: job.releaseChannel,
|
||||
updates: job.updates,
|
||||
secrets: job.secrets ? transformIosSecrets(job.secrets) : undefined,
|
||||
builderEnvironment: job.builderEnvironment,
|
||||
|
||||
@@ -48,6 +48,15 @@ export async function prepareJobAsync(
|
||||
? getCustomBuildConfigPathForJob(buildProfile.config)
|
||||
: undefined;
|
||||
|
||||
let buildMode;
|
||||
if (ctx.repack) {
|
||||
buildMode = BuildMode.REPACK;
|
||||
} else if (buildProfile.config) {
|
||||
buildMode = BuildMode.CUSTOM;
|
||||
} else {
|
||||
buildMode = BuildMode.BUILD;
|
||||
}
|
||||
|
||||
const job: Ios.Job = {
|
||||
type: ctx.workflow,
|
||||
platform: Platform.IOS,
|
||||
@@ -62,7 +71,6 @@ export async function prepareJobAsync(
|
||||
bundler: buildProfile.bundler,
|
||||
cocoapods: buildProfile.cocoapods,
|
||||
fastlane: buildProfile.fastlane,
|
||||
expoCli: buildProfile.expoCli,
|
||||
env: buildProfile.env,
|
||||
},
|
||||
cache: {
|
||||
@@ -73,7 +81,6 @@ export async function prepareJobAsync(
|
||||
secrets: {
|
||||
buildCredentials,
|
||||
},
|
||||
releaseChannel: buildProfile.releaseChannel,
|
||||
updates: { channel: buildProfile.channel },
|
||||
developmentClient: buildProfile.developmentClient,
|
||||
simulator: buildProfile.simulator,
|
||||
@@ -90,13 +97,18 @@ export async function prepareJobAsync(
|
||||
experimental: {
|
||||
prebuildCommand: buildProfile.prebuildCommand,
|
||||
},
|
||||
mode: buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
|
||||
mode: buildMode,
|
||||
triggeredBy: BuildTrigger.EAS_CLI,
|
||||
...(maybeCustomBuildConfigPath && {
|
||||
customBuildConfig: {
|
||||
path: maybeCustomBuildConfigPath,
|
||||
},
|
||||
}),
|
||||
...(ctx.repack && {
|
||||
customBuildConfig: {
|
||||
path: '__eas/repack.yml',
|
||||
},
|
||||
}),
|
||||
loggerLevel: ctx.loggerLevel,
|
||||
};
|
||||
return sanitizeBuildJob(job) as Ios.Job;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { Env, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { IosVersionAutoIncrement } from '@expo/eas-json';
|
||||
|
||||
import { BumpStrategy, bumpVersionAsync, bumpVersionInAppJsonAsync } from './version';
|
||||
@@ -15,19 +15,21 @@ export async function syncProjectConfigurationAsync({
|
||||
targets,
|
||||
localAutoIncrement,
|
||||
vcsClient,
|
||||
env,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
targets: Target[];
|
||||
localAutoIncrement?: IosVersionAutoIncrement;
|
||||
vcsClient: Client;
|
||||
env: Env | undefined;
|
||||
}): Promise<void> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS, vcsClient);
|
||||
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
|
||||
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
if (isExpoUpdatesInstalled(projectDir)) {
|
||||
await syncUpdatesConfigurationAsync(vcsClient, projectDir, exp);
|
||||
await syncUpdatesConfigurationAsync({ vcsClient, projectDir, exp, workflow, env });
|
||||
}
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, targets });
|
||||
} else {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Target } from '../../credentials/ios/types';
|
||||
import { AppPlatform } from '../../graphql/generated';
|
||||
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
|
||||
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
|
||||
import Log from '../../log';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { findApplicationTarget } from '../../project/ios/target';
|
||||
import { getNextBuildNumber, isValidBuildNumber } from '../../project/ios/versions';
|
||||
@@ -23,7 +23,7 @@ import { Client } from '../../vcs/vcs';
|
||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
|
||||
|
||||
const SHORT_VERSION_REGEX = /^\d+.\d+.\d+$/;
|
||||
const SHORT_VERSION_REGEX = /^\d+(\.\d+){0,2}$/;
|
||||
|
||||
export enum BumpStrategy {
|
||||
APP_VERSION,
|
||||
@@ -100,11 +100,27 @@ export async function bumpVersionInAppJsonAsync({
|
||||
}
|
||||
}
|
||||
|
||||
function validateShortVersion(shortVersion: string | undefined): void {
|
||||
function validateShortVersion({
|
||||
shortVersion,
|
||||
workflow,
|
||||
}: {
|
||||
shortVersion: string | undefined;
|
||||
workflow: Workflow;
|
||||
}): void {
|
||||
if (shortVersion && !SHORT_VERSION_REGEX.test(shortVersion)) {
|
||||
throw new Error(
|
||||
`CFBundleShortVersionString (version field in app.json/app.config.js) must be a period-separated list of three non-negative integers. Current value: ${shortVersion}`
|
||||
);
|
||||
if (workflow === Workflow.MANAGED) {
|
||||
throw new Error(
|
||||
`The required format for "version" field from app.json/app.config.ts 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 required format. ${learnMore(
|
||||
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
`The required format for "CFBundleShortVersionString" in Info.plist 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 required format. ${learnMore(
|
||||
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,10 +138,10 @@ export async function readShortVersionAsync(
|
||||
infoPlist.CFBundleShortVersionString &&
|
||||
evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings);
|
||||
|
||||
validateShortVersion(shortVersion);
|
||||
validateShortVersion({ shortVersion, workflow });
|
||||
return shortVersion;
|
||||
} else {
|
||||
validateShortVersion(exp.version);
|
||||
validateShortVersion({ shortVersion: exp.version, workflow });
|
||||
return exp.version;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@ import spawnAsync from '@expo/spawn-async';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import semver from 'semver';
|
||||
|
||||
import Log from '../log';
|
||||
import { ora } from '../ora';
|
||||
|
||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||
const PLUGIN_PACKAGE_VERSION = '1.0.100';
|
||||
const PLUGIN_PACKAGE_VERSION = '1.0.135';
|
||||
|
||||
export enum LocalBuildMode {
|
||||
/**
|
||||
@@ -39,7 +40,8 @@ export interface LocalBuildOptions {
|
||||
export async function runLocalBuildAsync(
|
||||
job: Job,
|
||||
metadata: Metadata,
|
||||
options: LocalBuildOptions
|
||||
options: LocalBuildOptions,
|
||||
env: Record<string, string>
|
||||
): Promise<void> {
|
||||
const { command, args } = await getCommandAndArgsAsync(job, metadata);
|
||||
let spinner;
|
||||
@@ -54,18 +56,26 @@ export async function runLocalBuildAsync(
|
||||
};
|
||||
process.on('SIGINT', interruptHandler);
|
||||
try {
|
||||
const mergedEnv = {
|
||||
...env,
|
||||
...process.env,
|
||||
EAS_LOCAL_BUILD_WORKINGDIR: options.workingdir ?? process.env.EAS_LOCAL_BUILD_WORKINGDIR,
|
||||
...(options.skipCleanup || options.skipNativeBuild
|
||||
? { EAS_LOCAL_BUILD_SKIP_CLEANUP: '1' }
|
||||
: {}),
|
||||
...(options.skipNativeBuild ? { EAS_LOCAL_BUILD_SKIP_NATIVE_BUILD: '1' } : {}),
|
||||
...(options.artifactsDir ? { EAS_LOCAL_BUILD_ARTIFACTS_DIR: options.artifactsDir } : {}),
|
||||
...(options.artifactPath ? { EAS_LOCAL_BUILD_ARTIFACT_PATH: options.artifactPath } : {}),
|
||||
};
|
||||
// log command execution to assist in debugging local builds
|
||||
Log.debug('Running local build, using local-build-plugin', {
|
||||
command,
|
||||
args,
|
||||
env: mergedEnv,
|
||||
});
|
||||
const spawnPromise = spawnAsync(command, args, {
|
||||
stdio: options.verbose ? 'inherit' : 'pipe',
|
||||
env: {
|
||||
...process.env,
|
||||
EAS_LOCAL_BUILD_WORKINGDIR: options.workingdir ?? process.env.EAS_LOCAL_BUILD_WORKINGDIR,
|
||||
...(options.skipCleanup || options.skipNativeBuild
|
||||
? { EAS_LOCAL_BUILD_SKIP_CLEANUP: '1' }
|
||||
: {}),
|
||||
...(options.skipNativeBuild ? { EAS_LOCAL_BUILD_SKIP_NATIVE_BUILD: '1' } : {}),
|
||||
...(options.artifactsDir ? { EAS_LOCAL_BUILD_ARTIFACTS_DIR: options.artifactsDir } : {}),
|
||||
...(options.artifactPath ? { EAS_LOCAL_BUILD_ARTIFACT_PATH: options.artifactPath } : {}),
|
||||
},
|
||||
env: mergedEnv,
|
||||
});
|
||||
childProcess = spawnPromise.child;
|
||||
await spawnPromise;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Metadata, Platform, sanitizeMetadata } from '@expo/eas-build-job';
|
||||
import { FingerprintSource, Metadata, Platform, sanitizeMetadata } from '@expo/eas-build-job';
|
||||
import { IosEnterpriseProvisioning } from '@expo/eas-json';
|
||||
import fs from 'fs-extra';
|
||||
import resolveFrom from 'resolve-from';
|
||||
@@ -9,26 +9,19 @@ import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios
|
||||
import { LocalBuildMode } from './local';
|
||||
import { BuildDistributionType } from './types';
|
||||
import Log from '../log';
|
||||
import {
|
||||
getUsername,
|
||||
isClassicUpdatesSupportedAsync,
|
||||
isExpoUpdatesInstalled,
|
||||
} from '../project/projectUtils';
|
||||
import { resolveRuntimeVersionAsync } from '../project/resolveRuntimeVersionAsync';
|
||||
import {
|
||||
readChannelSafelyAsync as readAndroidChannelSafelyAsync,
|
||||
readReleaseChannelSafelyAsync as readAndroidReleaseChannelSafelyAsync,
|
||||
} from '../update/android/UpdatesModule';
|
||||
import {
|
||||
readChannelSafelyAsync as readIosChannelSafelyAsync,
|
||||
readReleaseChannelSafelyAsync as readIosReleaseChannelSafelyAsync,
|
||||
} from '../update/ios/UpdatesModule';
|
||||
import { getUsername, isExpoUpdatesInstalled } from '../project/projectUtils';
|
||||
import { readChannelSafelyAsync as readAndroidChannelSafelyAsync } from '../update/android/UpdatesModule';
|
||||
import { readChannelSafelyAsync as readIosChannelSafelyAsync } from '../update/ios/UpdatesModule';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
|
||||
export async function collectMetadataAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
ctx: BuildContext<T>,
|
||||
runtimeMetadata: {
|
||||
runtimeVersion?: string | undefined;
|
||||
fingerprintSource?: FingerprintSource | undefined;
|
||||
}
|
||||
): Promise<Metadata> {
|
||||
const channelOrReleaseChannel = await resolveChannelOrReleaseChannelAsync(ctx);
|
||||
const channelObject = await resolveChannelAsync(ctx);
|
||||
const distribution = ctx.buildProfile.distribution ?? BuildDistributionType.STORE;
|
||||
const metadata: Metadata = {
|
||||
trackingContext: ctx.analyticsEventProperties,
|
||||
@@ -37,9 +30,10 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
workflow: ctx.workflow,
|
||||
credentialsSource: ctx.buildProfile.credentialsSource,
|
||||
sdkVersion: ctx.exp.sdkVersion,
|
||||
runtimeVersion: (await resolveRuntimeVersionAsync(ctx)) ?? undefined,
|
||||
runtimeVersion: runtimeMetadata?.runtimeVersion,
|
||||
fingerprintSource: runtimeMetadata?.fingerprintSource,
|
||||
reactNativeVersion: await getReactNativeVersionAsync(ctx.projectDir),
|
||||
...channelOrReleaseChannel,
|
||||
...channelObject,
|
||||
distribution,
|
||||
appName: ctx.exp.name,
|
||||
appIdentifier: resolveAppIdentifier(ctx),
|
||||
@@ -66,6 +60,7 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
requiredPackageManager: ctx.requiredPackageManager ?? undefined,
|
||||
selectedImage: ctx.buildProfile.image,
|
||||
customNodeVersion: ctx.buildProfile.node,
|
||||
environment: ctx.buildProfile.environment,
|
||||
simulator: 'simulator' in ctx.buildProfile && ctx.buildProfile.simulator,
|
||||
};
|
||||
return sanitizeMetadata(metadata);
|
||||
@@ -117,9 +112,9 @@ function resolveAppIdentifier<T extends Platform>(ctx: BuildContext<T>): string
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveChannelOrReleaseChannelAsync<T extends Platform>(
|
||||
async function resolveChannelAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<{ channel: string } | { releaseChannel: string } | null> {
|
||||
): Promise<{ channel: string } | null> {
|
||||
if (!isExpoUpdatesInstalled(ctx.projectDir)) {
|
||||
return null;
|
||||
}
|
||||
@@ -131,31 +126,7 @@ async function resolveChannelOrReleaseChannelAsync<T extends Platform>(
|
||||
return { channel };
|
||||
}
|
||||
|
||||
if (!(await isClassicUpdatesSupportedAsync(ctx.projectDir))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ctx.buildProfile.releaseChannel) {
|
||||
return { releaseChannel: ctx.buildProfile.releaseChannel };
|
||||
}
|
||||
|
||||
const releaseChannel = await getNativeReleaseChannelAsync(ctx);
|
||||
return { releaseChannel };
|
||||
}
|
||||
|
||||
async function getNativeReleaseChannelAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<string> {
|
||||
switch (ctx.platform) {
|
||||
case Platform.ANDROID: {
|
||||
return (await readAndroidReleaseChannelSafelyAsync(ctx.projectDir)) ?? 'default';
|
||||
}
|
||||
case Platform.IOS: {
|
||||
return (await readIosReleaseChannelSafelyAsync(ctx.projectDir)) ?? 'default';
|
||||
}
|
||||
default:
|
||||
return 'default';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getNativeChannelAsync<T extends Platform>(
|
||||
|
||||
@@ -50,8 +50,9 @@ export async function listAndRenderBuildsOnAppAsync(
|
||||
}),
|
||||
promptOptions: {
|
||||
title: 'Load more builds?',
|
||||
renderListItems: builds =>
|
||||
renderPageOfBuilds({ builds, projectDisplayName, paginatedQueryOptions }),
|
||||
renderListItems: builds => {
|
||||
renderPageOfBuilds({ builds, projectDisplayName, paginatedQueryOptions });
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { Env, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import {
|
||||
AppVersionSource,
|
||||
BuildProfile,
|
||||
EasJson,
|
||||
EasJsonAccessor,
|
||||
EasJsonUtils,
|
||||
@@ -19,6 +18,7 @@ import { BuildRequestSender, MaybeBuildFragment, waitForBuildEndAsync } from './
|
||||
import { ensureProjectConfiguredAsync } from './configure';
|
||||
import { BuildContext } from './context';
|
||||
import { createBuildContextAsync } from './createContext';
|
||||
import { evaluateConfigWithEnvVarsAsync } from './evaluateConfigWithEnvVarsAsync';
|
||||
import { prepareIosBuildAsync } from './ios/build';
|
||||
import { LocalBuildMode, LocalBuildOptions } from './local';
|
||||
import { ensureExpoDevClientInstalledForDevClientBuildsAsync } from './utils/devClient';
|
||||
@@ -57,8 +57,9 @@ import {
|
||||
validateAppVersionRuntimePolicySupportAsync,
|
||||
} from '../project/projectUtils';
|
||||
import {
|
||||
ensureAppVersionSourceIsSetAsync,
|
||||
validateAppConfigForRemoteVersionSource,
|
||||
validateBuildProfileVersionSettings,
|
||||
validateBuildProfileVersionSettingsAsync,
|
||||
} from '../project/remoteVersionSource';
|
||||
import { confirmAsync } from '../prompts';
|
||||
import { runAsync } from '../run/run';
|
||||
@@ -71,7 +72,6 @@ import {
|
||||
} from '../submit/submit';
|
||||
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
|
||||
import { ensureEASUpdateIsConfiguredAsync } from '../update/configure';
|
||||
import { validateBuildProfileConfigMatchesProjectConfigAsync } from '../update/utils';
|
||||
import { Actor } from '../user/User';
|
||||
import { downloadAndMaybeExtractAppAsync } from '../utils/download';
|
||||
import { truthy } from '../utils/expodash/filter';
|
||||
@@ -95,6 +95,8 @@ export interface BuildFlags {
|
||||
resourceClass?: ResourceClass;
|
||||
message?: string;
|
||||
buildLoggerLevel?: LoggerLevel;
|
||||
freezeCredentials: boolean;
|
||||
repack: boolean;
|
||||
}
|
||||
|
||||
export async function runBuildAndSubmitAsync(
|
||||
@@ -105,7 +107,9 @@ export async function runBuildAndSubmitAsync(
|
||||
flags: BuildFlags,
|
||||
actor: Actor,
|
||||
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn
|
||||
): Promise<void> {
|
||||
): Promise<{
|
||||
buildIds: string[];
|
||||
}> {
|
||||
await vcsClient.ensureRepoExistsAsync();
|
||||
await ensureRepoIsCleanAsync(vcsClient, flags.nonInteractive);
|
||||
|
||||
@@ -126,39 +130,28 @@ export async function runBuildAndSubmitAsync(
|
||||
profileName: flags.profile ?? undefined,
|
||||
projectDir,
|
||||
});
|
||||
Log.log(
|
||||
`Loaded "env" configuration for the "${buildProfiles[0].profileName}" profile: ${
|
||||
buildProfiles[0].profile.env
|
||||
? Object.keys(buildProfiles[0].profile.env).join(', ')
|
||||
: 'no environment variables specified'
|
||||
}. ${learnMore('https://docs.expo.dev/build-reference/variables/')}`
|
||||
);
|
||||
|
||||
for (const buildProfile of buildProfiles) {
|
||||
if (buildProfile.profile.image && ['default', 'stable'].includes(buildProfile.profile.image)) {
|
||||
Log.warn(
|
||||
`The "image" field in the build profile "${buildProfile.profileName}" is set to "${buildProfile.profile.image}". This tag is deprecated and will be removed in the future. Use other images or tags listed here: https://docs.expo.dev/build-reference/infrastructure/`
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
} else if (
|
||||
buildProfile.profile.image &&
|
||||
[
|
||||
'macos-monterey-12.4-xcode-13.4',
|
||||
'macos-monterey-12.3-xcode-13.3',
|
||||
'macos-monterey-12.1-xcode-13.2',
|
||||
'macos-ventura-13.4-xcode-14.3.1',
|
||||
'macos-ventura-13.3-xcode-14.3',
|
||||
'macos-monterey-12.6-xcode-14.2',
|
||||
'macos-monterey-12.6-xcode-14.1',
|
||||
'macos-monterey-12.6-xcode-14.0',
|
||||
'ubuntu-20.04-jdk-11-ndk-r19c',
|
||||
'ubuntu-20.04-jdk-8-ndk-r19c',
|
||||
'ubuntu-20.04-jdk-11-ndk-r21e',
|
||||
'ubuntu-20.04-jdk-8-ndk-r21e',
|
||||
'ubuntu-22.04-jdk-8-ndk-r21e',
|
||||
'ubuntu-20.04-jdk-11-ndk-r23b',
|
||||
].includes(buildProfile.profile.image)
|
||||
) {
|
||||
Log.warn(
|
||||
`The "image" field in the build profile "${buildProfile.profileName}" is set to "${
|
||||
buildProfile.profile.image
|
||||
}". This image is using Xcode in version lower then 15. Apple's new requirement states that starting on April 29th, 2024, apps uploaded to App Store Connect must be built with Xcode 15 for iOS 17, iPadOS 17, tvOS 17, or watchOS 10. Start using Xcode 15 image today by setting "macos-sonoma-14.4-xcode-15.3" or "latest" as your image value. This image will be removed from EAS Build on May 29th, 2024. ${learnMore(
|
||||
'https://expo.dev/changelog/2024/03-29-eas-build-upcoming-ios-images-updates'
|
||||
}". This image is deprecated and will be removed on September 1st, 2024. ${learnMore(
|
||||
'https://expo.dev/changelog/2024/07-12-eas-build-upcoming-android-images-updates'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
@@ -174,7 +167,12 @@ export async function runBuildAndSubmitAsync(
|
||||
const customBuildConfigMetadataByPlatform: { [p in AppPlatform]?: CustomBuildConfigMetadata } =
|
||||
{};
|
||||
for (const buildProfile of buildProfiles) {
|
||||
validateBuildProfileVersionSettings(buildProfile, easJsonCliConfig);
|
||||
await validateBuildProfileVersionSettingsAsync(
|
||||
buildProfile,
|
||||
easJsonCliConfig,
|
||||
projectDir,
|
||||
flags
|
||||
);
|
||||
const maybeMetadata = await validateCustomBuildConfigAsync({
|
||||
projectDir,
|
||||
profile: buildProfile.profile,
|
||||
@@ -193,6 +191,15 @@ export async function runBuildAndSubmitAsync(
|
||||
|
||||
for (const buildProfile of buildProfiles) {
|
||||
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({
|
||||
projectDir,
|
||||
flags,
|
||||
@@ -205,6 +212,7 @@ export async function runBuildAndSubmitAsync(
|
||||
vcsClient,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
customBuildConfigMetadata: customBuildConfigMetadataByPlatform[platform],
|
||||
env,
|
||||
});
|
||||
if (maybeBuild) {
|
||||
startedBuilds.push({ build: maybeBuild, buildProfile });
|
||||
@@ -213,7 +221,9 @@ export async function runBuildAndSubmitAsync(
|
||||
}
|
||||
|
||||
if (flags.localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN) {
|
||||
return;
|
||||
return {
|
||||
buildIds: startedBuilds.map(({ build }) => build.id),
|
||||
};
|
||||
}
|
||||
|
||||
if (flags.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL) {
|
||||
@@ -250,9 +260,9 @@ export async function runBuildAndSubmitAsync(
|
||||
buildCtx: nullthrows(buildCtxByPlatform[startedBuild.build.platform]),
|
||||
moreBuilds: startedBuilds.length > 1,
|
||||
projectDir,
|
||||
buildProfile: startedBuild.buildProfile.profile,
|
||||
submitProfile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
selectedSubmitProfileName: flags.submitProfile,
|
||||
});
|
||||
startedBuild.build = await BuildQuery.withSubmissionsByIdAsync(
|
||||
graphqlClient,
|
||||
@@ -269,14 +279,18 @@ export async function runBuildAndSubmitAsync(
|
||||
}
|
||||
|
||||
if (flags.localBuildOptions.localBuildMode) {
|
||||
return;
|
||||
return {
|
||||
buildIds: startedBuilds.map(({ build }) => build.id),
|
||||
};
|
||||
}
|
||||
|
||||
if (!flags.wait) {
|
||||
if (flags.json) {
|
||||
printJsonOnlyOutput(startedBuilds.map(buildInfo => buildInfo.build));
|
||||
}
|
||||
return;
|
||||
return {
|
||||
buildIds: startedBuilds.map(({ build }) => build.id),
|
||||
};
|
||||
}
|
||||
|
||||
const { accountName } = Object.values(buildCtxByPlatform)[0];
|
||||
@@ -317,6 +331,9 @@ export async function runBuildAndSubmitAsync(
|
||||
}
|
||||
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish(completedSubmissions);
|
||||
}
|
||||
return {
|
||||
buildIds: startedBuilds.map(({ build }) => build.id),
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareAndStartBuildAsync({
|
||||
@@ -331,6 +348,7 @@ async function prepareAndStartBuildAsync({
|
||||
vcsClient,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
customBuildConfigMetadata,
|
||||
env,
|
||||
}: {
|
||||
projectDir: string;
|
||||
flags: BuildFlags;
|
||||
@@ -343,6 +361,7 @@ async function prepareAndStartBuildAsync({
|
||||
vcsClient: Client;
|
||||
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
|
||||
customBuildConfigMetadata?: CustomBuildConfigMetadata;
|
||||
env: Env;
|
||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
||||
const buildCtx = await createBuildContextAsync({
|
||||
buildProfileName: buildProfile.profileName,
|
||||
@@ -363,6 +382,9 @@ async function prepareAndStartBuildAsync({
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
customBuildConfigMetadata,
|
||||
buildLoggerLevel: flags.buildLoggerLevel,
|
||||
freezeCredentials: flags.freezeCredentials,
|
||||
repack: flags.repack,
|
||||
env,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
@@ -375,12 +397,6 @@ async function prepareAndStartBuildAsync({
|
||||
);
|
||||
}
|
||||
|
||||
await validateBuildProfileConfigMatchesProjectConfigAsync(
|
||||
buildCtx.exp,
|
||||
buildProfile,
|
||||
buildCtx.projectId,
|
||||
flags.nonInteractive
|
||||
);
|
||||
if (buildProfile.profile.channel) {
|
||||
await validateExpoUpdatesInstalledAsProjectDependencyAsync({
|
||||
exp: buildCtx.exp,
|
||||
@@ -390,6 +406,7 @@ async function prepareAndStartBuildAsync({
|
||||
sdkVersion: buildCtx.exp.sdkVersion,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
buildProfile,
|
||||
env: buildProfile.profile.env,
|
||||
});
|
||||
if (isUsingEASUpdate(buildCtx.exp, buildCtx.projectId)) {
|
||||
const doesChannelExist = await doesChannelExistAsync(graphqlClient, {
|
||||
@@ -406,6 +423,25 @@ async function prepareAndStartBuildAsync({
|
||||
}
|
||||
|
||||
await validateAppVersionRuntimePolicySupportAsync(buildCtx.projectDir, buildCtx.exp);
|
||||
if (
|
||||
easJsonCliConfig?.appVersionSource === undefined &&
|
||||
buildProfile.profile.autoIncrement !== 'version'
|
||||
) {
|
||||
if (buildProfile.profile.autoIncrement !== true) {
|
||||
Log.warn(
|
||||
`The field "cli.appVersionSource" is not set, but it will be required in the future. ${learnMore(
|
||||
'https://docs.expo.dev/build-reference/app-versions/'
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
easJsonCliConfig = await ensureAppVersionSourceIsSetAsync(
|
||||
easJsonAccessor,
|
||||
easJsonCliConfig,
|
||||
flags.nonInteractive
|
||||
);
|
||||
}
|
||||
}
|
||||
if (easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE) {
|
||||
validateAppConfigForRemoteVersionSource(buildCtx.exp, buildProfile.platform);
|
||||
}
|
||||
@@ -442,16 +478,16 @@ async function prepareAndStartSubmissionAsync({
|
||||
buildCtx,
|
||||
moreBuilds,
|
||||
projectDir,
|
||||
buildProfile,
|
||||
submitProfile,
|
||||
selectedSubmitProfileName,
|
||||
nonInteractive,
|
||||
}: {
|
||||
build: BuildFragment;
|
||||
buildCtx: BuildContext<Platform>;
|
||||
moreBuilds: boolean;
|
||||
projectDir: string;
|
||||
buildProfile: BuildProfile;
|
||||
submitProfile: SubmitProfile;
|
||||
selectedSubmitProfileName?: string;
|
||||
nonInteractive: boolean;
|
||||
}): Promise<SubmissionFragment> {
|
||||
const platform = toPlatform(build.platform);
|
||||
@@ -461,7 +497,7 @@ async function prepareAndStartSubmissionAsync({
|
||||
profile: submitProfile,
|
||||
archiveFlags: { id: build.id },
|
||||
nonInteractive,
|
||||
env: buildProfile.env,
|
||||
env: buildCtx.env,
|
||||
credentialsCtx: buildCtx.credentialsCtx,
|
||||
applicationIdentifier: buildCtx.android?.applicationId ?? buildCtx.ios?.bundleIdentifier,
|
||||
actor: buildCtx.user,
|
||||
@@ -471,6 +507,7 @@ async function prepareAndStartSubmissionAsync({
|
||||
exp: buildCtx.exp,
|
||||
vcsClient: buildCtx.vcsClient,
|
||||
isVerboseFastlaneEnabled: false,
|
||||
specifiedProfile: selectedSubmitProfileName,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
@@ -534,6 +571,7 @@ async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
|
||||
buildProfile,
|
||||
nonInteractive,
|
||||
sdkVersion,
|
||||
env,
|
||||
}: {
|
||||
exp: ExpoConfig;
|
||||
projectId: string;
|
||||
@@ -542,6 +580,7 @@ async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
|
||||
buildProfile: ProfileData<'build'>;
|
||||
nonInteractive: boolean;
|
||||
sdkVersion?: string;
|
||||
env: Env | undefined;
|
||||
}): Promise<void> {
|
||||
if (isExpoUpdatesInstalledOrAvailable(projectDir, sdkVersion)) {
|
||||
return;
|
||||
@@ -569,6 +608,7 @@ async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
|
||||
projectDir,
|
||||
platform: RequestedPlatform.All,
|
||||
vcsClient,
|
||||
env,
|
||||
});
|
||||
Log.withTick('Installed expo-updates and configured EAS Update.');
|
||||
throw new Error('Command must be re-run to pick up new updates configuration.');
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { ResourceClass } from '@expo/eas-json';
|
||||
import { LoggerLevel } from '@expo/logger';
|
||||
|
||||
import { LocalBuildOptions } from './local';
|
||||
import { RequestedPlatform } from '../platform';
|
||||
|
||||
export enum BuildStatus {
|
||||
NEW = 'new',
|
||||
IN_QUEUE = 'in-queue',
|
||||
@@ -14,3 +20,20 @@ export enum BuildDistributionType {
|
||||
/** @deprecated Use simulator flag instead */
|
||||
SIMULATOR = 'simulator',
|
||||
}
|
||||
|
||||
export interface BuildFlags {
|
||||
requestedPlatform: RequestedPlatform;
|
||||
profile?: string;
|
||||
nonInteractive: boolean;
|
||||
wait: boolean;
|
||||
clearCache: boolean;
|
||||
json: boolean;
|
||||
autoSubmit: boolean;
|
||||
submitProfile?: string;
|
||||
localBuildOptions: LocalBuildOptions;
|
||||
resourceClass?: ResourceClass;
|
||||
message?: string;
|
||||
buildLoggerLevel?: LoggerLevel;
|
||||
freezeCredentials: boolean;
|
||||
repack: boolean;
|
||||
}
|
||||
@@ -39,6 +39,10 @@ export function formatGraphQLBuild(build: BuildFragment): string {
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Profile',
|
||||
value: build.buildProfile,
|
||||
},
|
||||
{
|
||||
label: 'Message',
|
||||
value: build.message,
|
||||
@@ -51,10 +55,6 @@ export function formatGraphQLBuild(build: BuildFragment): string {
|
||||
label: 'Enterprise Provisioning',
|
||||
value: build.iosEnterpriseProvisioning?.toLowerCase(),
|
||||
},
|
||||
{
|
||||
label: 'Release Channel',
|
||||
value: build.releaseChannel,
|
||||
},
|
||||
{
|
||||
label: 'Channel',
|
||||
value: build.channel,
|
||||
|
||||
@@ -62,7 +62,9 @@ export function printBuildResults(builds: (BuildFragment | null)[]): void {
|
||||
assert(build, 'Build should be defined');
|
||||
printBuildResult(build);
|
||||
} else {
|
||||
(builds.filter(i => i) as BuildFragment[]).forEach(build => printBuildResult(build));
|
||||
(builds.filter(i => i) as BuildFragment[]).forEach(build => {
|
||||
printBuildResult(build);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,9 +99,9 @@ function printBuildResult(build: BuildFragment): void {
|
||||
// to the build details page and let people press the button to download there
|
||||
const qrcodeUrl =
|
||||
build.platform === AppPlatform.Ios ? getInternalDistributionInstallUrl(build) : logsUrl;
|
||||
qrcodeTerminal.generate(qrcodeUrl, { small: true }, code =>
|
||||
Log.log(`${indentString(code, 2)}\n`)
|
||||
);
|
||||
qrcodeTerminal.generate(qrcodeUrl, { small: true }, code => {
|
||||
Log.log(`${indentString(code, 2)}\n`);
|
||||
});
|
||||
Log.log(
|
||||
`${appPlatformEmojis[build.platform]} Open this link on your ${
|
||||
appPlatformDisplayNames[build.platform]
|
||||
|
||||
@@ -100,44 +100,22 @@ export type LocalFile = {
|
||||
};
|
||||
|
||||
export async function makeProjectMetadataFileAsync(archivePath: string): Promise<LocalFile> {
|
||||
const spinner = ora('Creating project metadata file');
|
||||
const timerLabel = 'makeProjectMetadataFileAsync';
|
||||
const timer = setTimeout(
|
||||
() => {
|
||||
spinner.start();
|
||||
},
|
||||
Log.isDebug ? 1 : 1000
|
||||
);
|
||||
startTimer(timerLabel);
|
||||
|
||||
await fs.mkdirp(getTmpDirectory());
|
||||
const metadataLocation = path.join(getTmpDirectory(), `${uuidv4()}-eas-build-metadata.json`);
|
||||
const archiveContent: string[] = [];
|
||||
|
||||
try {
|
||||
await tar.list({
|
||||
file: archivePath,
|
||||
onentry: (entry: tar.ReadEntry) => {
|
||||
if (entry.type === 'File' && !entry.path.includes('.git/')) {
|
||||
archiveContent.push(entry.path);
|
||||
}
|
||||
},
|
||||
});
|
||||
await tar.list({
|
||||
file: archivePath,
|
||||
onentry: (entry: tar.ReadEntry) => {
|
||||
if (entry.type === 'File' && !entry.path.includes('.git/')) {
|
||||
archiveContent.push(entry.path);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await fs.writeJSON(metadataLocation, {
|
||||
archiveContent,
|
||||
});
|
||||
} catch (e) {
|
||||
clearTimeout(timer);
|
||||
if (spinner.isSpinning) {
|
||||
spinner.fail();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
clearTimeout(timer);
|
||||
|
||||
const duration = endTimer(timerLabel);
|
||||
const prettyTime = formatMilliseconds(duration);
|
||||
spinner.succeed(`Created project metadata file ${chalk.dim(prettyTime)}`);
|
||||
await fs.writeJSON(metadataLocation, {
|
||||
archiveContent,
|
||||
});
|
||||
|
||||
return { path: metadataLocation, size: await fs.stat(metadataLocation).then(stat => stat.size) };
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ import Log, { learnMore } from '../log';
|
||||
import { isPNGAsync } from '../utils/image';
|
||||
|
||||
export function checkNodeEnvVariable(ctx: CommonContext<Platform>): void {
|
||||
if (ctx.buildProfile.env?.NODE_ENV === 'production') {
|
||||
if (ctx.env?.NODE_ENV === 'production') {
|
||||
Log.warn(
|
||||
'You set NODE_ENV=production in the build profile. Remember that it will be available during the entire build process. In particular, it will make yarn/npm install only production packages.'
|
||||
'You set NODE_ENV=production in the build profile or environment variables. Remember that it will be available during the entire build process. In particular, it will make yarn/npm install only production packages.'
|
||||
);
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ import {
|
||||
|
||||
describe(assertVersion, () => {
|
||||
it('throws if the branch mapping is not the correct version', () => {
|
||||
expect(() => assertVersion(testChannelBasicInfo, 5)).toThrowError(BranchMappingValidationError);
|
||||
expect(() => {
|
||||
assertVersion(testChannelBasicInfo, 5);
|
||||
}).toThrowError(BranchMappingValidationError);
|
||||
});
|
||||
it('asserts the correct version', () => {
|
||||
assertVersion(testChannelBasicInfo, 0);
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
*/
|
||||
export class SelectChannel implements EASUpdateAction<UpdateChannelBasicInfoFragment | null> {
|
||||
constructor(
|
||||
private options: {
|
||||
private readonly options: {
|
||||
filterPredicate?: (channelInfo: UpdateChannelBasicInfoFragment) => boolean;
|
||||
printedType?: string;
|
||||
pageSize?: number;
|
||||
@@ -91,8 +91,8 @@ export class SelectChannel implements EASUpdateAction<UpdateChannelBasicInfoFrag
|
||||
queryParams: QueryParams
|
||||
): Promise<Connection<UpdateChannelBasicInfoFragment>> =>
|
||||
filterPredicate
|
||||
? this.filterQueryAsync(ctx, queryParams, filterPredicate)
|
||||
: this.queryAsync(ctx, queryParams);
|
||||
? await this.filterQueryAsync(ctx, queryParams, filterPredicate)
|
||||
: await this.queryAsync(ctx, queryParams);
|
||||
|
||||
const getTitleAsync = async (channelInfo: UpdateChannelBasicInfoFragment): Promise<string> =>
|
||||
channelInfo.name;
|
||||
|
||||
@@ -89,7 +89,9 @@ export async function listAndRenderChannelsOnAppAsync(
|
||||
queryChannelsOnAppAsync(graphqlClient, { limit, offset, appId: projectId }),
|
||||
promptOptions: {
|
||||
title: 'Load more channels?',
|
||||
renderListItems: channels => renderPageOfChannels(channels, paginatedQueryOptions),
|
||||
renderListItems: channels => {
|
||||
renderPageOfChannels(channels, paginatedQueryOptions);
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -131,8 +133,9 @@ export async function listAndRenderBranchesAndUpdatesOnChannelAsync(
|
||||
}),
|
||||
promptOptions: {
|
||||
title: 'Load more channels?',
|
||||
renderListItems: branches =>
|
||||
renderPageOfBranchesOnChannel(channel, branches, paginatedQueryOptions),
|
||||
renderListItems: branches => {
|
||||
renderPageOfBranchesOnChannel(channel, branches, paginatedQueryOptions);
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Command } from '@oclif/core';
|
||||
import { CombinedError } from '@urql/core';
|
||||
import chalk from 'chalk';
|
||||
import { GraphQLError } from 'graphql/error';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
@@ -186,13 +187,13 @@ export default abstract class EasCommand extends Command {
|
||||
action: `eas ${this.id}`,
|
||||
});
|
||||
|
||||
return this.runAsync();
|
||||
return await this.runAsync();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
override async finally(err: Error): Promise<any> {
|
||||
await this.analytics.flushAsync();
|
||||
return super.finally(err);
|
||||
return await super.finally(err);
|
||||
}
|
||||
|
||||
protected override catch(err: Error): Promise<any> {
|
||||
@@ -200,7 +201,7 @@ export default abstract class EasCommand extends Command {
|
||||
if (err instanceof EasCommandError) {
|
||||
Log.error(err.message);
|
||||
} else if (err instanceof CombinedError && err?.graphQLErrors) {
|
||||
const cleanMessage = err?.graphQLErrors
|
||||
const cleanGQLErrorsMessage = err?.graphQLErrors
|
||||
.map((graphQLError: GraphQLError) => {
|
||||
const messageLine = graphQLError.message.replace('[GraphQL] ', '');
|
||||
const requestIdLine = graphQLError.extensions?.requestId
|
||||
@@ -210,12 +211,21 @@ export default abstract class EasCommand extends Command {
|
||||
const defaultMsg = `${messageLine}${requestIdLine}`;
|
||||
|
||||
if (graphQLError.extensions?.errorCode === 'UNAUTHORIZED_ERROR') {
|
||||
return `You don't have the required permissions to perform this operation.\n\n${defaultMsg}`;
|
||||
return `${chalk.bold(
|
||||
`You don't have the required permissions to perform this operation.`
|
||||
)}\n\nThis can sometimes happen if you are logged in as incorrect user.\nRun ${chalk.bold(
|
||||
'eas whoami'
|
||||
)} to check the username you are logged in as.\nRun ${chalk.bold(
|
||||
'eas login'
|
||||
)} to change the account.\n\nOriginal error message: ${defaultMsg}`;
|
||||
}
|
||||
|
||||
return defaultMsg;
|
||||
})
|
||||
.join('\n');
|
||||
const cleanMessage = err.networkError
|
||||
? `${cleanGQLErrorsMessage}\n${err.networkError.message}`
|
||||
: cleanGQLErrorsMessage;
|
||||
Log.error(cleanMessage);
|
||||
baseMessage = BASE_GRAPHQL_ERROR_MESSAGE;
|
||||
} else {
|
||||
|
||||
+7
@@ -55,6 +55,7 @@ describe(getProjectIdAsync, () => {
|
||||
],
|
||||
isExpoAdmin: false,
|
||||
featureGates: {},
|
||||
preferences: {},
|
||||
},
|
||||
authenticationInfo: { accessToken: 'fake', sessionSecret: null },
|
||||
});
|
||||
@@ -79,6 +80,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@notnotbrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'notnotbrent' } as any,
|
||||
});
|
||||
@@ -99,6 +101,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@notnotbrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'notnotbrent' } as any,
|
||||
});
|
||||
@@ -123,6 +126,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
});
|
||||
@@ -171,6 +175,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
});
|
||||
@@ -195,6 +200,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@notnotbrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'notnotbrent' } as any,
|
||||
});
|
||||
@@ -264,6 +270,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@notnotbrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'notnotbrent' } as any,
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ export function createGraphqlClient(authInfo: {
|
||||
return !!(
|
||||
err &&
|
||||
!operation.context.noRetry &&
|
||||
(err.networkError || err.graphQLErrors.some(e => e?.extensions?.isTransient))
|
||||
(err.networkError ?? err.graphQLErrors.some(e => e?.extensions?.isTransient))
|
||||
);
|
||||
},
|
||||
}),
|
||||
|
||||
+5
-1
@@ -15,7 +15,11 @@ async function applyCliConfigAsync(projectDir: string): Promise<void> {
|
||||
const config = await EasJsonUtils.getCliConfigAsync(easJsonAccessor);
|
||||
if (config?.version && !semver.satisfies(easCliVersion, config.version)) {
|
||||
throw new Error(
|
||||
`You are on eas-cli@${easCliVersion} which does not satisfy the CLI version constraint in eas.json (${config.version})`
|
||||
`You are on eas-cli@${easCliVersion} which does not satisfy the CLI version constraint defined in eas.json (${
|
||||
config.version
|
||||
}).\n\nThis error probably means that you need update your eas-cli to a newer version.\nRun ${chalk.bold(
|
||||
'npm install -g eas-cli'
|
||||
)} to update the eas-cli to the latest version.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ExpoConfig } from '@expo/config-types';
|
||||
import { Env } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { createGraphqlClient } from './createGraphqlClient';
|
||||
import { ExpoGraphqlClient, createGraphqlClient } from './createGraphqlClient';
|
||||
import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import Log, { learnMore } from '../../../log';
|
||||
@@ -18,13 +18,15 @@ import { Actor, getActorUsername } from '../../../user/User';
|
||||
* Save an EAS project ID to the appropriate field in the app config.
|
||||
*
|
||||
* @deprecated Should not be used outside of context functions except in the init command.
|
||||
* @deprecated Starting from `@expo/config` from SDK 52, the `modifyConfigAsync` function is merging existing data. Once this is released, we can use that instead of manually merging.
|
||||
*/
|
||||
export async function saveProjectIdToAppConfigAsync(
|
||||
projectDir: string,
|
||||
projectId: string,
|
||||
options: { env?: Env } = {}
|
||||
): Promise<void> {
|
||||
const exp = getPrivateExpoConfig(projectDir, options);
|
||||
// NOTE(cedric): we disable plugins to avoid writing plugin-generated content to `expo.extra`
|
||||
const exp = getPrivateExpoConfig(projectDir, { skipPlugins: true, ...options });
|
||||
const result = await createOrModifyExpoConfigAsync(
|
||||
projectDir,
|
||||
{
|
||||
@@ -87,6 +89,23 @@ export async function getProjectIdAsync(
|
||||
});
|
||||
const graphqlClient = createGraphqlClient(authenticationInfo);
|
||||
|
||||
const projectId = await validateOrSetProjectIdAsync({ exp, graphqlClient, actor, options });
|
||||
return projectId;
|
||||
}
|
||||
|
||||
export async function validateOrSetProjectIdAsync({
|
||||
exp,
|
||||
graphqlClient,
|
||||
actor,
|
||||
options,
|
||||
cwd,
|
||||
}: {
|
||||
exp: ExpoConfig;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
actor: Actor;
|
||||
options: { env?: Env; nonInteractive: boolean };
|
||||
cwd?: string;
|
||||
}): Promise<string> {
|
||||
const localProjectId = exp.extra?.eas?.projectId;
|
||||
if (localProjectId) {
|
||||
if (typeof localProjectId !== 'string') {
|
||||
@@ -146,7 +165,9 @@ export async function getProjectIdAsync(
|
||||
return localProjectId;
|
||||
}
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const projectDir = await findProjectRootAsync({
|
||||
cwd,
|
||||
});
|
||||
if (!projectDir) {
|
||||
throw new Error('This command must be run inside a project directory.');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
|
||||
import {
|
||||
EnvironmentVariableEnvironment,
|
||||
EnvironmentVariableScope,
|
||||
EnvironmentVariableVisibility,
|
||||
} from '../graphql/generated';
|
||||
|
||||
// NOTE: not exactly true, but, provided mapToLowercase and upperCaseAsync
|
||||
// are used in tandem, it saves on unnecessary typying in commands
|
||||
async function upperCaseAsync<T>(input: string): Promise<T> {
|
||||
return input.toUpperCase() as T;
|
||||
}
|
||||
|
||||
function mapToLowercase<T extends string>(options: T[]): T[] {
|
||||
return options.map(option => option.toLowerCase()) as T[];
|
||||
}
|
||||
|
||||
export const EasNonInteractiveAndJsonFlags = {
|
||||
json: Flags.boolean({
|
||||
description: 'Enable JSON output, non-JSON messages will be printed to stderr.',
|
||||
@@ -10,6 +26,47 @@ export const EasNonInteractiveAndJsonFlags = {
|
||||
}),
|
||||
};
|
||||
|
||||
export const EASEnvironmentFlag = {
|
||||
environment: Flags.enum<EnvironmentVariableEnvironment>({
|
||||
description: "Environment variable's environment",
|
||||
parse: upperCaseAsync,
|
||||
options: mapToLowercase([
|
||||
EnvironmentVariableEnvironment.Development,
|
||||
EnvironmentVariableEnvironment.Preview,
|
||||
EnvironmentVariableEnvironment.Production,
|
||||
]),
|
||||
}),
|
||||
};
|
||||
|
||||
export const EASVariableFormatFlag = {
|
||||
format: Flags.enum({
|
||||
description: 'Output format',
|
||||
options: ['long', 'short'],
|
||||
default: 'short',
|
||||
}),
|
||||
};
|
||||
|
||||
export const EASVariableVisibilityFlag = {
|
||||
visibility: Flags.enum<EnvironmentVariableVisibility>({
|
||||
description: 'Visibility of the variable',
|
||||
options: mapToLowercase([
|
||||
EnvironmentVariableVisibility.Secret,
|
||||
EnvironmentVariableVisibility.Sensitive,
|
||||
EnvironmentVariableVisibility.Public,
|
||||
]),
|
||||
parse: upperCaseAsync,
|
||||
}),
|
||||
};
|
||||
|
||||
export const EASVariableScopeFlag = {
|
||||
scope: Flags.enum<EnvironmentVariableScope>({
|
||||
description: 'Scope for the variable',
|
||||
options: mapToLowercase([EnvironmentVariableScope.Shared, EnvironmentVariableScope.Project]),
|
||||
parse: upperCaseAsync,
|
||||
default: EnvironmentVariableScope.Project,
|
||||
}),
|
||||
};
|
||||
|
||||
export const EASNonInteractiveFlag = {
|
||||
'non-interactive': Flags.boolean({
|
||||
description: 'Run the command in non-interactive mode.',
|
||||
|
||||
@@ -63,7 +63,11 @@ export default class FeatureGating {
|
||||
}
|
||||
|
||||
public static overrideKeyForEachInTest(key: FeatureGateKey, enabled: boolean): void {
|
||||
beforeEach(() => FeatureGateTestOverrides.setOverride(key, enabled));
|
||||
afterEach(() => FeatureGateTestOverrides.removeOverride(key));
|
||||
beforeEach(() => {
|
||||
FeatureGateTestOverrides.setOverride(key, enabled);
|
||||
});
|
||||
afterEach(() => {
|
||||
FeatureGateTestOverrides.removeOverride(key);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log from '../../log';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { getActorDisplayName } from '../../user/User';
|
||||
|
||||
export default class AccountLogin extends EasCommand {
|
||||
static override description = 'log in with your Expo account';
|
||||
@@ -13,11 +16,11 @@ export default class AccountLogin extends EasCommand {
|
||||
description: 'Login with SSO',
|
||||
char: 's',
|
||||
default: false,
|
||||
hidden: true,
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.MaybeLoggedIn,
|
||||
...this.ContextOptions.SessionManagment,
|
||||
};
|
||||
|
||||
@@ -26,7 +29,28 @@ export default class AccountLogin extends EasCommand {
|
||||
flags: { sso },
|
||||
} = await this.parse(AccountLogin);
|
||||
|
||||
const { sessionManager } = await this.getContextAsync(AccountLogin, { nonInteractive: false });
|
||||
const {
|
||||
sessionManager,
|
||||
maybeLoggedIn: { actor },
|
||||
} = await this.getContextAsync(AccountLogin, { nonInteractive: false });
|
||||
|
||||
if (sessionManager.getAccessToken()) {
|
||||
throw new Error(
|
||||
'EXPO_TOKEN is set in your environment, and is being used for all EAS authentication. To use username/password authentication, unset EXPO_TOKEN in your environment and re-run the command.'
|
||||
);
|
||||
}
|
||||
|
||||
if (actor) {
|
||||
Log.warn(`You are already logged in as ${chalk.bold(getActorDisplayName(actor))}.`);
|
||||
|
||||
const shouldContinue = await confirmAsync({
|
||||
message: 'Do you want to continue?',
|
||||
});
|
||||
if (!shouldContinue) {
|
||||
Errors.error('Aborted', { exit: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
await sessionManager.showLoginPromptAsync({ sso });
|
||||
Log.log('Logged in');
|
||||
}
|
||||
|
||||
@@ -12,14 +12,19 @@ export default class AccountView extends EasCommand {
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.MaybeLoggedIn,
|
||||
...this.ContextOptions.SessionManagment,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
maybeLoggedIn: { actor },
|
||||
sessionManager,
|
||||
} = await this.getContextAsync(AccountView, { nonInteractive: true });
|
||||
if (actor) {
|
||||
Log.log(chalk.green(getActorDisplayName(actor)));
|
||||
const loggedInAs = sessionManager.getAccessToken()
|
||||
? `${getActorDisplayName(actor)} (authenticated using EXPO_TOKEN)`
|
||||
: getActorDisplayName(actor);
|
||||
Log.log(chalk.green(loggedInAs));
|
||||
|
||||
// personal account is included, only show if more accounts that personal account
|
||||
// but do show personal account in list if there are more
|
||||
|
||||
@@ -10,7 +10,7 @@ export default class AnalyticsView extends EasCommand {
|
||||
async runAsync(): Promise<void> {
|
||||
const { STATUS: status } = (await this.parse(AnalyticsView)).args;
|
||||
if (status) {
|
||||
setAnalyticsEnabledAsync(status === 'on');
|
||||
await setAnalyticsEnabledAsync(status === 'on');
|
||||
Log.withTick(`${status === 'on' ? 'Enabling' : 'Disabling'} analytics.`);
|
||||
} else {
|
||||
const analyticsEnabled = await getAnalyticsEnabledAsync();
|
||||
|
||||
@@ -39,7 +39,7 @@ async function cancelBuildAsync(
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.build!.cancel;
|
||||
return data.build.cancel;
|
||||
}
|
||||
|
||||
export async function selectBuildToCancelAsync(
|
||||
|
||||
@@ -74,14 +74,20 @@ export default class BuildConfigure extends EasCommand {
|
||||
if ([RequestedPlatform.Android, RequestedPlatform.All].includes(platform)) {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
await syncAndroidUpdatesConfigurationAsync(projectDir, exp);
|
||||
await syncAndroidUpdatesConfigurationAsync({ projectDir, exp, workflow, env: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
if ([RequestedPlatform.Ios, RequestedPlatform.All].includes(platform)) {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS, vcsClient);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
await syncIosUpdatesConfigurationAsync(vcsClient, projectDir, exp);
|
||||
await syncIosUpdatesConfigurationAsync({
|
||||
vcsClient,
|
||||
projectDir,
|
||||
exp,
|
||||
workflow,
|
||||
env: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ async function deleteBuildAsync(
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.build!.deleteBuild;
|
||||
return data.build.deleteBuild;
|
||||
}
|
||||
|
||||
export async function selectBuildToDeleteAsync(
|
||||
|
||||
@@ -35,6 +35,8 @@ interface RawBuildFlags {
|
||||
'resource-class'?: ResourceClass;
|
||||
message?: string;
|
||||
'build-logger-level'?: LoggerLevel;
|
||||
'freeze-credentials': boolean;
|
||||
repack: boolean;
|
||||
}
|
||||
|
||||
export default class Build extends EasCommand {
|
||||
@@ -104,6 +106,15 @@ export default class Build extends EasCommand {
|
||||
description: 'The level of logs to output during the build process. Defaults to "info".',
|
||||
options: Object.values(LoggerLevel),
|
||||
}),
|
||||
'freeze-credentials': Flags.boolean({
|
||||
default: false,
|
||||
description: 'Prevent the build from updating credentials in non-interactive mode',
|
||||
}),
|
||||
repack: Flags.boolean({
|
||||
default: false,
|
||||
hidden: true,
|
||||
description: 'Use the golden dev client build repack flow as it works for onboarding',
|
||||
}),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
@@ -217,6 +228,8 @@ export default class Build extends EasCommand {
|
||||
resourceClass: flags['resource-class'],
|
||||
message,
|
||||
buildLoggerLevel: flags['build-logger-level'],
|
||||
freezeCredentials: flags['freeze-credentials'],
|
||||
repack: flags.repack,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ export default class BuildInspect extends EasCommand {
|
||||
projectDir,
|
||||
{
|
||||
nonInteractive: false,
|
||||
freezeCredentials: false,
|
||||
wait: true,
|
||||
clearCache: false,
|
||||
json: false,
|
||||
@@ -119,6 +120,7 @@ export default class BuildInspect extends EasCommand {
|
||||
workingdir: tmpWorkingdir,
|
||||
artifactsDir: path.join(tmpWorkingdir, 'artifacts'),
|
||||
},
|
||||
repack: false,
|
||||
},
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync
|
||||
|
||||
@@ -77,6 +77,7 @@ export default class BuildInternal extends EasCommand {
|
||||
requestedPlatform: flags.platform as RequestedPlatform,
|
||||
profile: flags.profile,
|
||||
nonInteractive: true,
|
||||
freezeCredentials: false,
|
||||
wait: false,
|
||||
clearCache: false,
|
||||
json: true,
|
||||
@@ -85,6 +86,7 @@ export default class BuildInternal extends EasCommand {
|
||||
localBuildMode: LocalBuildMode.INTERNAL,
|
||||
},
|
||||
submitProfile: flags['auto-submit-with-profile'] ?? flags.profile,
|
||||
repack: false,
|
||||
},
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync
|
||||
|
||||
@@ -21,34 +21,47 @@ export default class BuildList extends EasCommand {
|
||||
|
||||
static override flags = {
|
||||
platform: Flags.enum({
|
||||
options: [RequestedPlatform.All, RequestedPlatform.Android, RequestedPlatform.Ios],
|
||||
options: Object.values(RequestedPlatform),
|
||||
char: 'p',
|
||||
}),
|
||||
status: Flags.enum({
|
||||
options: [
|
||||
BuildStatus.NEW,
|
||||
BuildStatus.IN_QUEUE,
|
||||
BuildStatus.IN_PROGRESS,
|
||||
BuildStatus.PENDING_CANCEL,
|
||||
BuildStatus.ERRORED,
|
||||
BuildStatus.FINISHED,
|
||||
BuildStatus.CANCELED,
|
||||
],
|
||||
options: Object.values(BuildStatus),
|
||||
description: 'Filter only builds with the specified status',
|
||||
}),
|
||||
distribution: Flags.enum({
|
||||
options: [
|
||||
BuildDistributionType.STORE,
|
||||
BuildDistributionType.INTERNAL,
|
||||
BuildDistributionType.SIMULATOR,
|
||||
],
|
||||
options: Object.values(BuildDistributionType),
|
||||
description: 'Filter only builds with the specified distribution type',
|
||||
}),
|
||||
channel: Flags.string(),
|
||||
appVersion: Flags.string(),
|
||||
appBuildVersion: Flags.string(),
|
||||
sdkVersion: Flags.string(),
|
||||
runtimeVersion: Flags.string(),
|
||||
appIdentifier: Flags.string(),
|
||||
buildProfile: Flags.string(),
|
||||
gitCommitHash: Flags.string(),
|
||||
'app-version': Flags.string({
|
||||
aliases: ['appVersion'],
|
||||
description: 'Filter only builds created with the specified main app version',
|
||||
}),
|
||||
'app-build-version': Flags.string({
|
||||
aliases: ['appBuildVersion'],
|
||||
description: 'Filter only builds created with the specified app build version',
|
||||
}),
|
||||
'sdk-version': Flags.string({
|
||||
aliases: ['sdkVersion'],
|
||||
description: 'Filter only builds created with the specified Expo SDK version',
|
||||
}),
|
||||
'runtime-version': Flags.string({
|
||||
aliases: ['runtimeVersion'],
|
||||
description: 'Filter only builds created with the specified runtime version',
|
||||
}),
|
||||
'app-identifier': Flags.string({
|
||||
aliases: ['appIdentifier'],
|
||||
description: 'Filter only builds created with the specified app identifier',
|
||||
}),
|
||||
'build-profile': Flags.string({
|
||||
char: 'e',
|
||||
aliases: ['profile', 'buildProfile'],
|
||||
description: 'Filter only builds created with the specified build profile',
|
||||
}),
|
||||
'git-commit-hash': Flags.string({
|
||||
aliases: ['gitCommitHash'],
|
||||
description: 'Filter only builds created with the specified git commit hash',
|
||||
}),
|
||||
...EasPaginatedQueryFlags,
|
||||
limit: getLimitFlagWithCustomValues({ defaultTo: 10, limit: BUILDS_LIMIT }),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
@@ -109,13 +122,13 @@ export default class BuildList extends EasCommand {
|
||||
status: graphqlBuildStatus,
|
||||
distribution: graphqlBuildDistribution,
|
||||
channel: flags.channel,
|
||||
appVersion: flags.appVersion,
|
||||
appBuildVersion: flags.appBuildVersion,
|
||||
sdkVersion: flags.sdkVersion,
|
||||
runtimeVersion: flags.runtimeVersion,
|
||||
appIdentifier: flags.appIdentifier,
|
||||
buildProfile: flags.buildProfile,
|
||||
gitCommitHash: flags.gitCommitHash,
|
||||
appVersion: flags['app-version'],
|
||||
appBuildVersion: flags['app-build-version'],
|
||||
sdkVersion: flags['sdk-version'],
|
||||
runtimeVersion: flags['runtime-version'],
|
||||
appIdentifier: flags['app-identifier'],
|
||||
buildProfile: flags['build-profile'],
|
||||
gitCommitHash: flags['git-commit-hash'],
|
||||
simulator: flags.simulator,
|
||||
},
|
||||
paginatedQueryOptions,
|
||||
|
||||
@@ -6,6 +6,7 @@ import chalk from 'chalk';
|
||||
|
||||
import { handleDeprecatedEasJsonAsync } from '.';
|
||||
import { waitForBuildEndAsync } from '../../build/build';
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../../build/evaluateConfigWithEnvVarsAsync';
|
||||
import { ensureIosCredentialsForBuildResignAsync } from '../../build/ios/credentials';
|
||||
import { prepareCredentialsToResign } from '../../build/ios/prepareJob';
|
||||
import { listAndSelectBuildOnAppAsync } from '../../build/queries';
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
BuildStatus,
|
||||
BuildWorkflow,
|
||||
DistributionType,
|
||||
EnvironmentVariableEnvironment,
|
||||
IosJobOverridesInput,
|
||||
ProjectArchiveSourceType,
|
||||
StatuspageServiceName,
|
||||
@@ -42,9 +44,11 @@ interface BuildResignFlags {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
platform?: Platform;
|
||||
profile?: string;
|
||||
targetProfile?: string;
|
||||
sourceProfile?: string;
|
||||
maybeBuildId?: string;
|
||||
wait: boolean;
|
||||
environment?: EnvironmentVariableEnvironment;
|
||||
}
|
||||
interface RawBuildResignFlags {
|
||||
json: boolean;
|
||||
@@ -52,9 +56,11 @@ interface RawBuildResignFlags {
|
||||
offset: number | undefined;
|
||||
limit: number | undefined;
|
||||
platform: 'android' | 'ios' | undefined;
|
||||
profile: string | undefined;
|
||||
'target-profile': string | undefined;
|
||||
'source-profile': string | undefined;
|
||||
wait: boolean;
|
||||
id: string | undefined;
|
||||
environment: EnvironmentVariableEnvironment | undefined;
|
||||
}
|
||||
|
||||
export default class BuildResign extends EasCommand {
|
||||
@@ -65,10 +71,16 @@ export default class BuildResign extends EasCommand {
|
||||
char: 'p',
|
||||
options: ['android', 'ios'],
|
||||
}),
|
||||
profile: Flags.string({
|
||||
'target-profile': Flags.string({
|
||||
char: 'e',
|
||||
description:
|
||||
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
'Name of the target build profile from eas.json. Credentials and environment variables from this profile will be used when re-signing. Defaults to "production" if defined in eas.json.',
|
||||
helpValue: 'PROFILE_NAME',
|
||||
aliases: ['profile'],
|
||||
}),
|
||||
'source-profile': Flags.string({
|
||||
description:
|
||||
'Name of the source build profile from eas.json. Used to filter builds eligible for re-signing.',
|
||||
helpValue: 'PROFILE_NAME',
|
||||
}),
|
||||
wait: Flags.boolean({
|
||||
@@ -130,12 +142,28 @@ export default class BuildResign extends EasCommand {
|
||||
const buildProfile = await EasJsonUtils.getBuildProfileAsync(
|
||||
easJsonAccessor,
|
||||
platform,
|
||||
flags.profile ?? 'production'
|
||||
flags.targetProfile ?? 'production'
|
||||
);
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env });
|
||||
|
||||
const { exp, projectId, env } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile,
|
||||
buildProfileName: flags.targetProfile ?? 'production',
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: buildProfile.env },
|
||||
});
|
||||
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
const build = await this.ensureBuildSelectedAsync(
|
||||
{ graphqlClient, projectId, platform, nonInteractive, limit, offset },
|
||||
{
|
||||
graphqlClient,
|
||||
projectId,
|
||||
platform,
|
||||
nonInteractive,
|
||||
limit,
|
||||
offset,
|
||||
buildProfile: flags.sourceProfile,
|
||||
},
|
||||
maybeBuild
|
||||
);
|
||||
const credentialsCtx = new CredentialsContext({
|
||||
@@ -145,7 +173,7 @@ export default class BuildResign extends EasCommand {
|
||||
user: actor,
|
||||
graphqlClient,
|
||||
analytics,
|
||||
env: buildProfile.env,
|
||||
env,
|
||||
easJsonCliConfig,
|
||||
vcsClient,
|
||||
});
|
||||
@@ -165,7 +193,7 @@ export default class BuildResign extends EasCommand {
|
||||
projectDir,
|
||||
exp,
|
||||
xcodeBuildContext,
|
||||
env: buildProfile.env,
|
||||
env,
|
||||
vcsClient,
|
||||
});
|
||||
const credentialsResult = await ensureIosCredentialsForBuildResignAsync(
|
||||
@@ -228,9 +256,11 @@ export default class BuildResign extends EasCommand {
|
||||
offset: flags.offset,
|
||||
limit: flags.limit,
|
||||
platform: flags.platform as Platform | undefined,
|
||||
profile: flags.profile,
|
||||
sourceProfile: flags['source-profile'],
|
||||
targetProfile: flags['target-profile'],
|
||||
maybeBuildId: flags.id,
|
||||
wait: flags.wait,
|
||||
environment: flags.environment,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -242,6 +272,7 @@ export default class BuildResign extends EasCommand {
|
||||
nonInteractive,
|
||||
limit,
|
||||
offset,
|
||||
buildProfile,
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
projectId: string;
|
||||
@@ -249,6 +280,7 @@ export default class BuildResign extends EasCommand {
|
||||
nonInteractive: boolean;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
buildProfile?: string;
|
||||
},
|
||||
maybeBuild?: BuildFragment
|
||||
): Promise<BuildFragment> {
|
||||
@@ -268,6 +300,7 @@ export default class BuildResign extends EasCommand {
|
||||
distribution: DistributionType.Internal,
|
||||
platform: toAppPlatform(platform),
|
||||
status: BuildStatus.Finished,
|
||||
buildProfile,
|
||||
},
|
||||
});
|
||||
if (!build) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { EasJsonAccessor } from '@expo/eas-json';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync';
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../../commandUtils/flags';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
@@ -74,8 +75,12 @@ export default class BuildVersionGetView extends EasCommand {
|
||||
});
|
||||
const results: { [key in Platform]?: string } = {};
|
||||
for (const { profile, platform } of buildProfiles) {
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
|
||||
env: profile.env,
|
||||
const { exp, projectId, env } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: profile,
|
||||
buildProfileName: flags.profile ?? 'production',
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: profile.env },
|
||||
});
|
||||
|
||||
validateAppConfigForRemoteVersionSource(exp, platform);
|
||||
@@ -88,6 +93,8 @@ export default class BuildVersionGetView extends EasCommand {
|
||||
buildProfile: profile,
|
||||
platform,
|
||||
vcsClient,
|
||||
nonInteractive: flags['non-interactive'],
|
||||
env,
|
||||
});
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
graphqlClient,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync';
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
@@ -64,7 +65,13 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
flags.profile ?? undefined
|
||||
);
|
||||
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: profile.env });
|
||||
const { exp, projectId, env } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: profile,
|
||||
buildProfileName: flags.profile ?? 'production',
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: profile.env },
|
||||
});
|
||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
validateAppConfigForRemoteVersionSource(exp, platform);
|
||||
@@ -77,6 +84,8 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
buildProfile: profile,
|
||||
platform,
|
||||
vcsClient,
|
||||
nonInteractive: false,
|
||||
env,
|
||||
});
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
graphqlClient,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../../build/android/version';
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync';
|
||||
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../../build/ios/version';
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
@@ -87,8 +88,12 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
projectDir,
|
||||
});
|
||||
for (const profileInfo of buildProfiles) {
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
|
||||
env: profileInfo.profile.env,
|
||||
const { exp, projectId, env } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: profileInfo.profile,
|
||||
buildProfileName: profileInfo.profileName,
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: profileInfo.profile.env },
|
||||
});
|
||||
|
||||
validateAppConfigForRemoteVersionSource(exp, profileInfo.platform);
|
||||
@@ -102,6 +107,8 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
buildProfile: profileInfo.profile,
|
||||
platform: profileInfo.platform,
|
||||
vcsClient,
|
||||
nonInteractive: false,
|
||||
env,
|
||||
});
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
graphqlClient,
|
||||
@@ -127,7 +134,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
continue;
|
||||
}
|
||||
if (profileInfo.platform === Platform.ANDROID) {
|
||||
this.syncAndroidAsync({
|
||||
await this.syncAndroidAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
profile: profileInfo.profile as BuildProfile<Platform.ANDROID>,
|
||||
@@ -136,7 +143,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
vcsClient,
|
||||
});
|
||||
} else {
|
||||
this.syncIosAsync({
|
||||
await this.syncIosAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
profile: profileInfo.profile as BuildProfile<Platform.IOS>,
|
||||
@@ -198,7 +205,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
||||
const isMultiFlavor =
|
||||
buildGradle.android?.productFlavors || buildGradle.android?.flavorDimensions;
|
||||
buildGradle.android?.productFlavors ?? buildGradle.android?.flavorDimensions;
|
||||
if (isMultiFlavor) {
|
||||
throw new Error(
|
||||
"Versions in native code can't be automatically synced in multi-flavor Android projects. If you are using EAS Build with app version source set to remote, the correct values will be injected at the build time."
|
||||
|
||||
@@ -117,7 +117,7 @@ export default class ChannelRollout extends EasCommand {
|
||||
required: false,
|
||||
}),
|
||||
'private-key-path': Flags.string({
|
||||
description: `File containing the PEM-encoded private key corresponding to the certificate in expo-updates' configuration. Defaults to a file named "private-key.pem" in the certificate's directory.`,
|
||||
description: `File containing the PEM-encoded private key corresponding to the certificate in expo-updates' configuration. Defaults to a file named "private-key.pem" in the certificate's directory. Only relevant if you are using code signing: https://docs.expo.dev/eas-update/code-signing/`,
|
||||
required: false,
|
||||
}),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
@@ -125,6 +125,7 @@ export default class ChannelRollout extends EasCommand {
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.Vcs,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
@@ -133,6 +134,7 @@ export default class ChannelRollout extends EasCommand {
|
||||
const argsAndFlags = this.sanitizeArgsAndFlags({ ...flags, ...args });
|
||||
const {
|
||||
privateProjectConfig: { exp, projectId, projectDir },
|
||||
vcsClient,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelRollout, {
|
||||
nonInteractive: argsAndFlags.nonInteractive,
|
||||
@@ -146,6 +148,7 @@ export default class ChannelRollout extends EasCommand {
|
||||
nonInteractive: argsAndFlags.nonInteractive,
|
||||
graphqlClient,
|
||||
app,
|
||||
vcsClient,
|
||||
};
|
||||
if (argsAndFlags.nonInteractive) {
|
||||
await new NonInteractiveRollout(argsAndFlags).runAsync(ctx);
|
||||
|
||||
@@ -4,7 +4,9 @@ import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../build/evaluateConfigWithEnvVarsAsync';
|
||||
import EasCommand from '../commandUtils/EasCommand';
|
||||
import { createGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../commandUtils/flags';
|
||||
import { toAppPlatform } from '../graphql/types/AppPlatform';
|
||||
import Log from '../log';
|
||||
@@ -33,6 +35,7 @@ export default class Config extends EasCommand {
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
...this.ContextOptions.SessionManagment,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
@@ -40,10 +43,15 @@ export default class Config extends EasCommand {
|
||||
if (flags.json) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
const { platform: maybePlatform, profile: maybeProfile } = flags;
|
||||
const { getDynamicPublicProjectConfigAsync, projectDir } = await this.getContextAsync(Config, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
const {
|
||||
platform: maybePlatform,
|
||||
profile: maybeProfile,
|
||||
'non-interactive': nonInteractive,
|
||||
} = flags;
|
||||
const { getDynamicPublicProjectConfigAsync, projectDir, sessionManager } =
|
||||
await this.getContextAsync(Config, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
const accessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
const profileName =
|
||||
@@ -80,8 +88,16 @@ export default class Config extends EasCommand {
|
||||
Log.log(JSON.stringify(profile, null, 2));
|
||||
}
|
||||
} else {
|
||||
const { exp: appConfig } = await getDynamicPublicProjectConfigAsync({
|
||||
env: profile.env,
|
||||
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
|
||||
nonInteractive,
|
||||
});
|
||||
const graphqlClient = createGraphqlClient(authenticationInfo);
|
||||
const { exp: appConfig } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: profile,
|
||||
buildProfileName: profileName,
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPublicProjectConfigAsync,
|
||||
opts: { env: profile.env },
|
||||
});
|
||||
|
||||
if (flags.json) {
|
||||
|
||||
@@ -58,7 +58,8 @@ export default class InitializeBuildCredentials extends EasCommand {
|
||||
privateProjectConfig ?? null,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
platform,
|
||||
buildProfile
|
||||
buildProfile,
|
||||
process.cwd()
|
||||
).runAsync();
|
||||
}
|
||||
}
|
||||
@@ -170,7 +170,7 @@ export default class DeviceDelete extends EasCommand {
|
||||
Log.log(
|
||||
formatDevice(device, {
|
||||
appleTeamName,
|
||||
appleTeamIdentifier: appleTeamIdentifier!,
|
||||
appleTeamIdentifier,
|
||||
})
|
||||
);
|
||||
Log.newLine();
|
||||
|
||||
@@ -159,7 +159,7 @@ export default class DeviceRename extends EasCommand {
|
||||
Log.log(
|
||||
formatDevice(device, {
|
||||
appleTeamName,
|
||||
appleTeamIdentifier: appleTeamIdentifier!,
|
||||
appleTeamIdentifier,
|
||||
})
|
||||
);
|
||||
Log.newLine();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
import envinfo from 'envinfo';
|
||||
|
||||
import EasCommand from '../commandUtils/EasCommand';
|
||||
import Log from '../log';
|
||||
import { ora } from '../ora';
|
||||
import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { Client } from '../vcs/vcs';
|
||||
@@ -20,6 +22,7 @@ export default class Diagnostics extends EasCommand {
|
||||
nonInteractive: true,
|
||||
});
|
||||
|
||||
const spinner = ora().start(`Gathering diagnostic information...`);
|
||||
const info = await envinfo.run(
|
||||
{
|
||||
System: ['OS', 'Shell'],
|
||||
@@ -40,9 +43,10 @@ export default class Diagnostics extends EasCommand {
|
||||
npmGlobalPackages: ['eas-cli', 'expo-cli'],
|
||||
},
|
||||
{
|
||||
title: `EAS CLI ${easCliVersion} environment info`,
|
||||
title: chalk.bold(`EAS CLI ${easCliVersion} environment info`),
|
||||
}
|
||||
);
|
||||
spinner.succeed('All needed information gathered!');
|
||||
|
||||
Log.log(info.trimEnd());
|
||||
await this.printWorkflowAsync(projectDir, vcsClient);
|
||||
@@ -56,7 +60,7 @@ export default class Diagnostics extends EasCommand {
|
||||
if (androidWorkflow === iosWorkflow) {
|
||||
Log.log(` Project workflow: ${androidWorkflow}`);
|
||||
} else {
|
||||
Log.log(` Project Workflow:`);
|
||||
Log.log(` Project workflow:`);
|
||||
Log.log(` Android: ${androidWorkflow}`);
|
||||
Log.log(` iOS: ${iosWorkflow}`);
|
||||
}
|
||||
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import {
|
||||
EASEnvironmentFlag,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableScopeFlag,
|
||||
EASVariableVisibilityFlag,
|
||||
} from '../../commandUtils/flags';
|
||||
import {
|
||||
EnvironmentVariableEnvironment,
|
||||
EnvironmentVariableScope,
|
||||
EnvironmentVariableVisibility,
|
||||
} from '../../graphql/generated';
|
||||
import { EnvironmentVariableMutation } from '../../graphql/mutations/EnvironmentVariableMutation';
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getOwnerAccountForProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import {
|
||||
promptVariableEnvironmentAsync,
|
||||
promptVariableNameAsync,
|
||||
promptVariableValueAsync,
|
||||
} from '../../utils/prompts';
|
||||
|
||||
type CreateFlags = {
|
||||
name?: string;
|
||||
value?: string;
|
||||
link?: boolean;
|
||||
force?: boolean;
|
||||
visibility?: EnvironmentVariableVisibility;
|
||||
scope?: EnvironmentVariableScope;
|
||||
environment?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
};
|
||||
|
||||
export default class EnvironmentVariableCreate extends EasCommand {
|
||||
static override description =
|
||||
'create an environment variable on the current project or owner account';
|
||||
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
name: Flags.string({
|
||||
description: 'Name of the variable',
|
||||
}),
|
||||
value: Flags.string({
|
||||
description: 'Text value or the variable',
|
||||
}),
|
||||
link: Flags.boolean({
|
||||
description: 'Link shared variable to the current project',
|
||||
}),
|
||||
force: Flags.boolean({
|
||||
description: 'Overwrite existing variable',
|
||||
default: false,
|
||||
}),
|
||||
...EASVariableVisibilityFlag,
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.Analytics,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(EnvironmentVariableCreate);
|
||||
|
||||
let {
|
||||
name,
|
||||
value,
|
||||
scope,
|
||||
'non-interactive': nonInteractive,
|
||||
environment,
|
||||
visibility,
|
||||
link,
|
||||
force,
|
||||
} = this.validateFlags(flags);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableCreate, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
const [projectDisplayName, ownerAccount] = await Promise.all([
|
||||
getDisplayNameForProjectIdAsync(graphqlClient, projectId),
|
||||
getOwnerAccountForProjectIdAsync(graphqlClient, projectId),
|
||||
]);
|
||||
|
||||
if (!name) {
|
||||
name = await promptVariableNameAsync(nonInteractive);
|
||||
}
|
||||
|
||||
let overwrite = false;
|
||||
visibility = visibility ?? EnvironmentVariableVisibility.Public;
|
||||
|
||||
if (!value) {
|
||||
value = await promptVariableValueAsync({
|
||||
nonInteractive,
|
||||
hidden: visibility !== EnvironmentVariableVisibility.Public,
|
||||
});
|
||||
}
|
||||
|
||||
if (scope === EnvironmentVariableScope.Project) {
|
||||
if (!environment) {
|
||||
environment = await promptVariableEnvironmentAsync(nonInteractive);
|
||||
}
|
||||
const existingVariables = await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
environment,
|
||||
});
|
||||
const existingVariable = existingVariables.find(variable => variable.name === name);
|
||||
|
||||
if (existingVariable) {
|
||||
if (existingVariable.scope === EnvironmentVariableScope.Shared) {
|
||||
if (!nonInteractive) {
|
||||
const confirmation = await confirmAsync({
|
||||
message: `Shared variable ${name} already exists on this project. Do you want to unlink it first?`,
|
||||
});
|
||||
|
||||
if (!confirmation) {
|
||||
Log.log('Aborting');
|
||||
throw new Error(`Shared variable ${name} already exists on this project.`);
|
||||
}
|
||||
} else if (!force) {
|
||||
throw new Error(
|
||||
`Shared variable ${name} already exists on this project. Use --force to overwrite it.`
|
||||
);
|
||||
}
|
||||
|
||||
await EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync(
|
||||
graphqlClient,
|
||||
existingVariable.id,
|
||||
projectId,
|
||||
environment
|
||||
);
|
||||
Log.withTick(
|
||||
`Unlinking shared variable ${chalk.bold(name)} on project ${chalk.bold(
|
||||
projectDisplayName
|
||||
)}.`
|
||||
);
|
||||
} else {
|
||||
if (!nonInteractive) {
|
||||
const confirmation = await confirmAsync({
|
||||
message: `Variable ${name} already exists on this project. Do you want to overwrite it?`,
|
||||
});
|
||||
|
||||
if (!confirmation) {
|
||||
Log.log('Aborting');
|
||||
throw new Error(
|
||||
`Variable ${name} already exists on this project. Use --force to overwrite it.`
|
||||
);
|
||||
}
|
||||
} else if (!force) {
|
||||
throw new Error(
|
||||
`Variable ${name} already exists on this project. Use --force to overwrite it.`
|
||||
);
|
||||
}
|
||||
overwrite = true;
|
||||
}
|
||||
}
|
||||
|
||||
const variable = await EnvironmentVariableMutation.createForAppAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
name,
|
||||
value,
|
||||
environment,
|
||||
visibility,
|
||||
overwrite,
|
||||
},
|
||||
projectId
|
||||
);
|
||||
if (!variable) {
|
||||
throw new Error(
|
||||
`Could not create variable with name ${name} on project ${projectDisplayName}`
|
||||
);
|
||||
}
|
||||
|
||||
Log.withTick(
|
||||
`Created a new variable ${chalk.bold(name)} on project ${chalk.bold(projectDisplayName)}.`
|
||||
);
|
||||
} else if (scope === EnvironmentVariableScope.Shared) {
|
||||
const sharedVariables = await EnvironmentVariablesQuery.sharedAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
});
|
||||
const existingVariable = sharedVariables.find(variable => variable.name === name);
|
||||
if (existingVariable) {
|
||||
throw new Error(
|
||||
`Shared variable with ${name} name already exists on this account.\n` +
|
||||
`Use a different name or delete the existing variable on website or by using eas env:delete --name ${name} --scope shared command.`
|
||||
);
|
||||
}
|
||||
|
||||
if (environment && !link) {
|
||||
const confirmation = await confirmAsync({
|
||||
message: `Unexpected argument: --environment can only be used with --link flag. Do you want to link the variable to the current project?`,
|
||||
});
|
||||
|
||||
if (!confirmation) {
|
||||
Log.log('Aborting');
|
||||
throw new Error('Unexpected argument: --environment can only be used with --link flag.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!environment && link) {
|
||||
environment = await promptVariableEnvironmentAsync(nonInteractive);
|
||||
}
|
||||
|
||||
const variable = await EnvironmentVariableMutation.createSharedVariableAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
name,
|
||||
value,
|
||||
visibility,
|
||||
},
|
||||
ownerAccount.id
|
||||
);
|
||||
|
||||
if (!variable) {
|
||||
throw new Error(
|
||||
`Could not create variable with name ${name} on account ${ownerAccount.name}`
|
||||
);
|
||||
}
|
||||
|
||||
Log.withTick(
|
||||
`Created a new variable ${chalk.bold(name)} on account ${chalk.bold(ownerAccount.name)}.`
|
||||
);
|
||||
|
||||
if (link && environment) {
|
||||
Log.withTick(
|
||||
`Linking shared variable ${chalk.bold(name)} to project ${chalk.bold(
|
||||
projectDisplayName
|
||||
)}.`
|
||||
);
|
||||
await EnvironmentVariableMutation.linkSharedEnvironmentVariableAsync(
|
||||
graphqlClient,
|
||||
variable.id,
|
||||
projectId,
|
||||
environment
|
||||
);
|
||||
Log.withTick(
|
||||
`Linked shared variable ${chalk.bold(name)} to project ${chalk.bold(projectDisplayName)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private validateFlags(flags: CreateFlags): CreateFlags {
|
||||
if (flags.scope !== EnvironmentVariableScope.Shared && flags.link) {
|
||||
throw new Error(
|
||||
`Unexpected argument: --link can only be used when creating shared variables`
|
||||
);
|
||||
}
|
||||
if (
|
||||
flags.scope === EnvironmentVariableScope.Shared &&
|
||||
flags.environment &&
|
||||
!flags.link &&
|
||||
flags['non-interactive']
|
||||
) {
|
||||
throw new Error(
|
||||
'Unexpected argument: --environment in non-interactive mode can only be used with --link flag.'
|
||||
);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import {
|
||||
EASEnvironmentFlag,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableScopeFlag,
|
||||
} from '../../commandUtils/flags';
|
||||
import { EnvironmentVariableEnvironment, EnvironmentVariableScope } from '../../graphql/generated';
|
||||
import { EnvironmentVariableMutation } from '../../graphql/mutations/EnvironmentVariableMutation';
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../log';
|
||||
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
|
||||
|
||||
type DeleteFlags = {
|
||||
name?: string;
|
||||
environment?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
scope?: EnvironmentVariableScope;
|
||||
};
|
||||
|
||||
export default class EnvironmentVariableDelete extends EasCommand {
|
||||
static override description = 'delete an environment variable by name';
|
||||
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
name: Flags.string({
|
||||
description: 'Name of the variable to delete',
|
||||
}),
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(EnvironmentVariableDelete);
|
||||
let { name, environment, 'non-interactive': nonInteractive, scope } = this.validateFlags(flags);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableDelete, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
if (scope === EnvironmentVariableScope.Project) {
|
||||
if (!environment) {
|
||||
environment = await promptVariableEnvironmentAsync(nonInteractive);
|
||||
}
|
||||
}
|
||||
|
||||
const variables =
|
||||
scope === EnvironmentVariableScope.Project && environment
|
||||
? await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
environment,
|
||||
})
|
||||
: await EnvironmentVariablesQuery.sharedAsync(graphqlClient, { appId: projectId });
|
||||
|
||||
if (!name) {
|
||||
({ name } = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'name',
|
||||
message: 'Pick the variable to be deleted:',
|
||||
choices: variables
|
||||
.filter(({ scope: variableScope }) => scope === variableScope)
|
||||
.map(variable => ({
|
||||
title: variable.name,
|
||||
value: variable.name,
|
||||
})),
|
||||
}));
|
||||
|
||||
if (!name) {
|
||||
throw new Error(
|
||||
`Environment variable wasn't selected. Run the command again and select existing variable or run it with ${chalk.bold(
|
||||
'--name VARIABLE_NAME'
|
||||
)} flag to fix the issue.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const selectedVariable = variables.find(variable => variable.name === name);
|
||||
|
||||
if (!selectedVariable) {
|
||||
throw new Error(`Variable "${name}" not found.`);
|
||||
}
|
||||
|
||||
if (!nonInteractive) {
|
||||
Log.addNewLineIfNone();
|
||||
Log.warn(`You are about to permanently delete variable ${selectedVariable.name}.`);
|
||||
Log.warn('This action is irreversible.');
|
||||
Log.newLine();
|
||||
const confirmed = await toggleConfirmAsync({
|
||||
message: `Are you sure you wish to proceed?${
|
||||
selectedVariable.scope === EnvironmentVariableScope.Shared
|
||||
? ' This variable is applied across your whole account and may affect multiple apps.'
|
||||
: ''
|
||||
}`,
|
||||
});
|
||||
if (!confirmed) {
|
||||
Log.error(`Canceled deletion of variable ${selectedVariable.name}.`);
|
||||
throw new Error(`Variable "${name}" not deleted.`);
|
||||
}
|
||||
}
|
||||
|
||||
await EnvironmentVariableMutation.deleteAsync(graphqlClient, selectedVariable.id);
|
||||
|
||||
Log.withTick(`️Deleted variable ${selectedVariable.name}".`);
|
||||
}
|
||||
|
||||
private validateFlags(flags: DeleteFlags): DeleteFlags {
|
||||
if (flags['non-interactive']) {
|
||||
if (!flags.name) {
|
||||
throw new Error(
|
||||
`Environment variable needs 'name' to be specified when running in non-interactive mode. Run the command with ${chalk.bold(
|
||||
'--name VARIABLE_NAME'
|
||||
)} flag to fix the issue`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EASEnvironmentFlag, EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import { EnvironmentVariableEnvironment } from '../../graphql/generated';
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../log';
|
||||
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
|
||||
|
||||
type ParsedFlags =
|
||||
| {
|
||||
nonInteractive: true;
|
||||
environment: EnvironmentVariableEnvironment;
|
||||
}
|
||||
| {
|
||||
nonInteractive: false;
|
||||
environment?: EnvironmentVariableEnvironment;
|
||||
};
|
||||
|
||||
interface RawFlags {
|
||||
environment?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
}
|
||||
|
||||
export default class EnvExec extends EasCommand {
|
||||
static override description =
|
||||
'execute a bash command with environment variables from the selected environment';
|
||||
|
||||
static override hidden = true;
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
...EASEnvironmentFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override args = [
|
||||
{
|
||||
name: 'BASH_COMMAND',
|
||||
required: true,
|
||||
description: 'bash command to execute with the environment variables from the environment',
|
||||
},
|
||||
];
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
flags,
|
||||
args: { BASH_COMMAND: command },
|
||||
} = await this.parse(EnvExec);
|
||||
|
||||
const parsedFlags = this.sanitizeFlags(flags);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvExec, {
|
||||
nonInteractive: parsedFlags.nonInteractive,
|
||||
});
|
||||
|
||||
const environment =
|
||||
parsedFlags.environment ?? (await promptVariableEnvironmentAsync(parsedFlags.nonInteractive));
|
||||
const environmentVariables = await this.loadEnvironmentVariablesAsync({
|
||||
graphqlClient,
|
||||
projectId,
|
||||
environment,
|
||||
});
|
||||
|
||||
await this.runCommandWithEnvVarsAsync({ command, environmentVariables });
|
||||
}
|
||||
|
||||
private sanitizeFlags(rawFlags: RawFlags): ParsedFlags {
|
||||
const environment = rawFlags.environment;
|
||||
if (rawFlags['non-interactive']) {
|
||||
if (!environment) {
|
||||
throw new Error(
|
||||
'You must specify an environment when running in non-interactive mode. Use the --environment flag.'
|
||||
);
|
||||
}
|
||||
return {
|
||||
nonInteractive: true,
|
||||
environment,
|
||||
};
|
||||
}
|
||||
return {
|
||||
nonInteractive: false,
|
||||
environment: rawFlags.environment,
|
||||
};
|
||||
}
|
||||
|
||||
private async runCommandWithEnvVarsAsync({
|
||||
command,
|
||||
environmentVariables,
|
||||
}: {
|
||||
command: string;
|
||||
environmentVariables: Record<string, string>;
|
||||
}): Promise<void> {
|
||||
Log.log(`Running command: ${chalk.bold(command)}`);
|
||||
const spawnPromise = spawnAsync('bash', ['-c', command], {
|
||||
stdio: ['inherit', 'pipe', 'pipe'],
|
||||
env: {
|
||||
...process.env,
|
||||
...environmentVariables,
|
||||
},
|
||||
});
|
||||
const {
|
||||
child: { stdout, stderr },
|
||||
} = spawnPromise;
|
||||
if (!stdout || !stderr) {
|
||||
throw new Error(`Failed to spawn ${command}`);
|
||||
}
|
||||
stdout.on('data', data => {
|
||||
for (const line of data.toString().trim().split('\n')) {
|
||||
Log.log(`${chalk.gray('[stdout]')} ${line}`);
|
||||
}
|
||||
});
|
||||
stderr.on('data', data => {
|
||||
for (const line of data.toString().trim().split('\n')) {
|
||||
Log.warn(`${chalk.gray('[stderr]')} ${line}`);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await spawnPromise;
|
||||
} catch (error) {
|
||||
Log.error(`❌ ${chalk.bold(command)} failed`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async loadEnvironmentVariablesAsync({
|
||||
graphqlClient,
|
||||
projectId,
|
||||
environment,
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
projectId: string;
|
||||
environment: EnvironmentVariableEnvironment;
|
||||
}): Promise<Record<string, string>> {
|
||||
const environmentVariablesQueryResult =
|
||||
await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
environment,
|
||||
});
|
||||
|
||||
const secretEnvironmentVariables = environmentVariablesQueryResult.filter(
|
||||
({ value }) => !value
|
||||
);
|
||||
if (secretEnvironmentVariables.length > 0) {
|
||||
Log.warn(`The following environment variables are secret and cannot be downloaded locally:`);
|
||||
for (const { name } of secretEnvironmentVariables) {
|
||||
Log.warn(`- ${name}`);
|
||||
}
|
||||
Log.warn('Proceeding with the rest of the environment variables.');
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
const nonSecretEnvironmentVariables = environmentVariablesQueryResult.filter(
|
||||
({ value }) => !!value
|
||||
);
|
||||
if (nonSecretEnvironmentVariables.length === 0) {
|
||||
throw new Error('No readable environment variables found for the selected environment.');
|
||||
}
|
||||
Log.log(
|
||||
`Loaded environment variables for the selected environment "${environment.toLowerCase()}":`
|
||||
);
|
||||
for (const { name } of nonSecretEnvironmentVariables) {
|
||||
Log.log(`- ${name}`);
|
||||
}
|
||||
Log.newLine();
|
||||
|
||||
const environmentVariables: Record<string, string> = {};
|
||||
for (const { name, value } of nonSecretEnvironmentVariables) {
|
||||
if (value) {
|
||||
environmentVariables[name] = value;
|
||||
}
|
||||
}
|
||||
return environmentVariables;
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import {
|
||||
EASEnvironmentFlag,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableFormatFlag,
|
||||
EASVariableScopeFlag,
|
||||
} from '../../commandUtils/flags';
|
||||
import {
|
||||
EnvironmentVariableEnvironment,
|
||||
EnvironmentVariableFragment,
|
||||
EnvironmentVariableScope,
|
||||
} from '../../graphql/generated';
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../log';
|
||||
import { formatVariable } from '../../utils/formatVariable';
|
||||
import { promptVariableEnvironmentAsync, promptVariableNameAsync } from '../../utils/prompts';
|
||||
|
||||
type GetFlags = {
|
||||
name?: string;
|
||||
environment?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
format?: string;
|
||||
scope: EnvironmentVariableScope;
|
||||
};
|
||||
|
||||
export default class EnvironmentVariableGet extends EasCommand {
|
||||
static override description = 'get environment variable';
|
||||
|
||||
static override hidden = true;
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
name: Flags.string({
|
||||
description: 'Name of the variable',
|
||||
}),
|
||||
...EASVariableFormatFlag,
|
||||
...EASVariableScopeFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
...EASEnvironmentFlag,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(EnvironmentVariableGet);
|
||||
|
||||
let {
|
||||
environment,
|
||||
name,
|
||||
'non-interactive': nonInteractive,
|
||||
format,
|
||||
scope,
|
||||
} = this.validateFlags(flags);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableGet, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
if (!name) {
|
||||
name = await promptVariableNameAsync(nonInteractive);
|
||||
}
|
||||
|
||||
if (!environment && scope === EnvironmentVariableScope.Project) {
|
||||
environment = await promptVariableEnvironmentAsync(nonInteractive);
|
||||
}
|
||||
const variable = await getVariableAsync(graphqlClient, scope, projectId, name, environment);
|
||||
|
||||
if (!variable) {
|
||||
Log.error(`Variable with name "${name}" not found`);
|
||||
return;
|
||||
}
|
||||
if (!variable.value) {
|
||||
throw new Error(
|
||||
`${chalk.bold(
|
||||
variable.name
|
||||
)} is a secret variable and cannot be displayed once it has been created.`
|
||||
);
|
||||
}
|
||||
|
||||
if (format === 'short') {
|
||||
Log.log(`${chalk.bold(variable.name)}=${variable.value}`);
|
||||
} else {
|
||||
Log.log(formatVariable(variable));
|
||||
}
|
||||
}
|
||||
|
||||
private validateFlags(flags: GetFlags): GetFlags {
|
||||
if (flags.environment && flags.scope === EnvironmentVariableScope.Shared) {
|
||||
throw new Error(`Unexpected argument: --environment can only be used with project variables`);
|
||||
}
|
||||
if (flags['non-interactive']) {
|
||||
if (!flags.name) {
|
||||
throw new Error('Variable name is required. Run the command with --name flag.');
|
||||
}
|
||||
if (!flags.scope) {
|
||||
throw new Error('Scope is required. Run the command with --scope flag.');
|
||||
}
|
||||
if (!flags.environment && flags.scope === EnvironmentVariableScope.Project) {
|
||||
throw new Error('Environment is required.');
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
|
||||
async function getVariableAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
scope: string,
|
||||
projectId: string,
|
||||
name: string | undefined,
|
||||
environment: string | undefined
|
||||
): Promise<EnvironmentVariableFragment | null> {
|
||||
if (!environment && scope === EnvironmentVariableScope.Project) {
|
||||
throw new Error('Environment is required.');
|
||||
}
|
||||
if (!name) {
|
||||
throw new Error("Variable name is required. Run the command with '--name VARIABLE_NAME' flag.");
|
||||
}
|
||||
if (environment && scope === EnvironmentVariableScope.Project) {
|
||||
const appVariables = await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
environment,
|
||||
filterNames: [name],
|
||||
});
|
||||
return appVariables[0];
|
||||
}
|
||||
|
||||
if (scope === EnvironmentVariableScope.Shared) {
|
||||
const sharedVariables = await EnvironmentVariablesQuery.sharedWithSensitiveAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
appId: projectId,
|
||||
filterNames: [name],
|
||||
}
|
||||
);
|
||||
return sharedVariables[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EASEnvironmentFlag, EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import { EnvironmentVariableMutation } from '../../graphql/mutations/EnvironmentVariableMutation';
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
|
||||
|
||||
export default class EnvironmentVariableLink extends EasCommand {
|
||||
static override description = 'link a shared environment variable to the current project';
|
||||
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
name: Flags.string({
|
||||
description: 'Name of the variable',
|
||||
}),
|
||||
...EASEnvironmentFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
flags: { name, 'non-interactive': nonInteractive, environment },
|
||||
} = await this.parse(EnvironmentVariableLink);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableLink, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
const variables = await EnvironmentVariablesQuery.sharedAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
});
|
||||
|
||||
if (!name) {
|
||||
name = await selectAsync(
|
||||
'Select shared variable',
|
||||
variables.map(variable => ({
|
||||
title: variable.name,
|
||||
value: variable.name,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
const selectedVariable = variables.find(variable => variable.name === name);
|
||||
|
||||
if (!selectedVariable) {
|
||||
throw new Error(`Shared variable ${name} doesn't exist`);
|
||||
}
|
||||
|
||||
if (!environment) {
|
||||
environment = await promptVariableEnvironmentAsync(nonInteractive);
|
||||
}
|
||||
|
||||
const linkedVariable = await EnvironmentVariableMutation.linkSharedEnvironmentVariableAsync(
|
||||
graphqlClient,
|
||||
selectedVariable.id,
|
||||
projectId,
|
||||
environment
|
||||
);
|
||||
if (!linkedVariable) {
|
||||
throw new Error(
|
||||
`Could not link variable with name ${selectedVariable.name} to project with id ${projectId}`
|
||||
);
|
||||
}
|
||||
|
||||
Log.withTick(
|
||||
`Linked variable ${chalk.bold(linkedVariable.name)} to project ${chalk.bold(
|
||||
projectDisplayName
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import {
|
||||
EASEnvironmentFlag,
|
||||
EASVariableFormatFlag,
|
||||
EASVariableScopeFlag,
|
||||
} from '../../commandUtils/flags';
|
||||
import {
|
||||
EnvironmentVariableEnvironment,
|
||||
EnvironmentVariableFragment,
|
||||
EnvironmentVariableScope,
|
||||
} from '../../graphql/generated';
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../log';
|
||||
import { formatVariable } from '../../utils/formatVariable';
|
||||
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
|
||||
|
||||
export default class EnvironmentValueList extends EasCommand {
|
||||
static override description = 'list environment variables for the current project';
|
||||
|
||||
static override hidden = true;
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
'include-sensitive': Flags.boolean({
|
||||
description: 'Display sensitive values in the output',
|
||||
default: false,
|
||||
}),
|
||||
...EASVariableFormatFlag,
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentFlag,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
flags: { environment, format, scope, 'include-sensitive': includeSensitive },
|
||||
} = await this.parse(EnvironmentValueList);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentValueList, {
|
||||
nonInteractive: true,
|
||||
});
|
||||
|
||||
if (scope === EnvironmentVariableScope.Project && !environment) {
|
||||
environment = await promptVariableEnvironmentAsync(false);
|
||||
}
|
||||
|
||||
const variables = await this.getVariablesForScopeAsync(graphqlClient, {
|
||||
scope,
|
||||
includingSensitive: includeSensitive,
|
||||
environment,
|
||||
projectId,
|
||||
});
|
||||
|
||||
if (format === 'short') {
|
||||
for (const variable of variables) {
|
||||
// TODO: Add Learn more link
|
||||
Log.log(
|
||||
`${chalk.bold(variable.name)}=${
|
||||
variable.value ??
|
||||
"***** (This is a secret env variable that can only be accessed on EAS builder and can't be read in any UI. Learn more.)"
|
||||
}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (scope === EnvironmentVariableScope.Shared) {
|
||||
Log.log(chalk.bold('Shared variables for this account:'));
|
||||
} else {
|
||||
Log.log(chalk.bold(`Variables for this project for environment ${environment}:`));
|
||||
}
|
||||
Log.log(
|
||||
variables.map(variable => formatVariable(variable)).join(`\n\n${chalk.dim('———')}\n\n`)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async getVariablesForScopeAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
scope,
|
||||
includingSensitive,
|
||||
environment,
|
||||
projectId,
|
||||
}: {
|
||||
scope: EnvironmentVariableScope;
|
||||
includingSensitive: boolean;
|
||||
environment?: EnvironmentVariableEnvironment;
|
||||
projectId: string;
|
||||
}
|
||||
): Promise<EnvironmentVariableFragment[]> {
|
||||
if (scope === EnvironmentVariableScope.Project && environment) {
|
||||
if (includingSensitive) {
|
||||
return await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
environment,
|
||||
});
|
||||
}
|
||||
return await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
environment,
|
||||
});
|
||||
}
|
||||
|
||||
return includingSensitive
|
||||
? await EnvironmentVariablesQuery.sharedWithSensitiveAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
})
|
||||
: await EnvironmentVariablesQuery.sharedAsync(graphqlClient, { appId: projectId });
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EASEnvironmentFlag, EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import { EnvironmentVariableFragment } from '../../graphql/generated';
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../log';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
|
||||
|
||||
export default class EnvironmentVariablePull extends EasCommand {
|
||||
static override description = 'pull env file';
|
||||
|
||||
static override hidden = true;
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
...EASEnvironmentFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
path: Flags.string({
|
||||
description: 'Path to the result `.env` file',
|
||||
default: '.env.local',
|
||||
}),
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
flags: { environment, path: targetPath, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(EnvironmentVariablePull);
|
||||
|
||||
if (!environment) {
|
||||
environment = await promptVariableEnvironmentAsync(nonInteractive);
|
||||
}
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariablePull, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
targetPath = targetPath ?? '.env.local';
|
||||
|
||||
const environmentVariables = await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
appId: projectId,
|
||||
environment,
|
||||
}
|
||||
);
|
||||
|
||||
if (!nonInteractive && (await fs.exists(targetPath))) {
|
||||
const result = await confirmAsync({
|
||||
message: `File ${targetPath} already exists. Do you want to overwrite it?`,
|
||||
});
|
||||
if (!result) {
|
||||
Log.log('Aborting...');
|
||||
throw new Error(`File ${targetPath} already exists.`);
|
||||
}
|
||||
}
|
||||
|
||||
const filePrefix = `# Environment: ${environment}\n\n`;
|
||||
|
||||
const envFileContent = environmentVariables
|
||||
.map((variable: EnvironmentVariableFragment) => {
|
||||
if (variable.value === null) {
|
||||
return `# ${variable.name}=***** (secret variables are not available for reading)`;
|
||||
}
|
||||
return `${variable.name}=${variable.value}`;
|
||||
})
|
||||
.join('\n');
|
||||
await fs.writeFile(targetPath, filePrefix + envFileContent);
|
||||
|
||||
const secretEnvVariables = environmentVariables.filter(
|
||||
(variable: EnvironmentVariableFragment) => variable.value === null
|
||||
);
|
||||
if (secretEnvVariables.length > 0) {
|
||||
Log.warn(
|
||||
`The eas env:pull command tried to pull environment variables with "secret" visibility. The variables with "secret" visibility are not available for reading, therefore thet were marked as "*****" in the generated .env file. Provide values for these manually in ${targetPath} if needed. Skipped variables: ${secretEnvVariables
|
||||
.map(v => v.name)
|
||||
.join('\n')}`
|
||||
);
|
||||
Log.warn();
|
||||
}
|
||||
|
||||
Log.log(
|
||||
`Pulled environment variables from ${environment.toLowerCase()} environment to ${targetPath}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import dotenv from 'dotenv';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EASEnvironmentFlag } from '../../commandUtils/flags';
|
||||
import {
|
||||
EnvironmentVariableEnvironment,
|
||||
EnvironmentVariableFragment,
|
||||
EnvironmentVariableScope,
|
||||
EnvironmentVariableVisibility,
|
||||
} from '../../graphql/generated';
|
||||
import {
|
||||
EnvironmentVariableMutation,
|
||||
EnvironmentVariablePushInput,
|
||||
} from '../../graphql/mutations/EnvironmentVariableMutation';
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../log';
|
||||
import { confirmAsync, promptAsync } from '../../prompts';
|
||||
|
||||
type PushFlags = {
|
||||
environment?: EnvironmentVariableEnvironment;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export default class EnvironmentVariablePush extends EasCommand {
|
||||
static override description = 'push env file';
|
||||
|
||||
static override hidden = true;
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
...EASEnvironmentFlag,
|
||||
path: Flags.string({
|
||||
description: 'Path to the input `.env` file',
|
||||
default: '.env.local',
|
||||
}),
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(EnvironmentVariablePush);
|
||||
|
||||
const { environment, path: envPath } = this.validateFlags(flags);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariablePush, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
|
||||
const updateVariables: Record<string, EnvironmentVariablePushInput> =
|
||||
await this.parseEnvFileAsync(envPath, environment);
|
||||
|
||||
const variableNames = Object.keys(updateVariables);
|
||||
|
||||
const existingVariables = await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
environment,
|
||||
filterNames: variableNames,
|
||||
});
|
||||
|
||||
const existingDifferentVariables: EnvironmentVariableFragment[] = [];
|
||||
// Remove variables that are the same as the ones in the environment
|
||||
existingVariables.forEach(variable => {
|
||||
const existingVariableUpdate = updateVariables[variable.name];
|
||||
if (existingVariableUpdate && existingVariableUpdate.value !== variable.value) {
|
||||
existingDifferentVariables.push(variable);
|
||||
} else {
|
||||
delete updateVariables[variable.name];
|
||||
}
|
||||
});
|
||||
|
||||
const existingDifferentSharedVariables = existingDifferentVariables.filter(
|
||||
variable => variable.scope === EnvironmentVariableScope.Shared
|
||||
);
|
||||
|
||||
if (existingDifferentSharedVariables.length > 0) {
|
||||
const existingDifferentSharedVariablesNames = existingDifferentSharedVariables.map(
|
||||
variable => variable.name
|
||||
);
|
||||
Log.error('Shared variables cannot be overwritten by eas env:push command.');
|
||||
Log.error('Remove them from the env file or unlink them from the project to continue:');
|
||||
existingDifferentSharedVariablesNames.forEach(name => {
|
||||
Log.error(`- ${name}`);
|
||||
});
|
||||
throw new Error('Shared variables cannot be overwritten by eas env:push command');
|
||||
}
|
||||
|
||||
if (existingDifferentVariables.length > 0) {
|
||||
Log.warn('Some variables already exist in the environment.');
|
||||
const variableNames = existingDifferentVariables.map(variable => variable.name);
|
||||
|
||||
const confirmationMessage =
|
||||
variableNames.length > 1
|
||||
? `The ${variableNames.join(
|
||||
', '
|
||||
)} environment variables already exist in ${environment} environment. Do you want to override them all?`
|
||||
: `The ${variableNames[0]} environment variable already exists in ${environment} environment. Do you want to override it?`;
|
||||
|
||||
const confirm = await confirmAsync({
|
||||
message: confirmationMessage,
|
||||
});
|
||||
|
||||
let variablesToOverwrite: string[] = [];
|
||||
|
||||
if (!confirm && existingDifferentVariables.length === 1) {
|
||||
throw new Error('No new variables to push.');
|
||||
}
|
||||
|
||||
if (confirm) {
|
||||
variablesToOverwrite = existingDifferentVariables.map(variable => variable.name);
|
||||
} else {
|
||||
const promptResult = await promptAsync({
|
||||
type: 'multiselect',
|
||||
name: 'variablesToOverwrite',
|
||||
message: 'Select variables to overwrite:',
|
||||
// @ts-expect-error property missing from `@types/prompts`
|
||||
optionsPerPage: 20,
|
||||
choices: existingDifferentVariables.map(variable => ({
|
||||
title: `${variable.name}: ${updateVariables[variable.name].value} (was ${
|
||||
variable.value ?? '(secret)'
|
||||
})`,
|
||||
value: variable.name,
|
||||
})),
|
||||
});
|
||||
variablesToOverwrite = promptResult.variablesToOverwrite;
|
||||
}
|
||||
|
||||
for (const existingVariable of existingVariables) {
|
||||
const name = existingVariable.name;
|
||||
if (variablesToOverwrite.includes(name)) {
|
||||
updateVariables[name]['overwrite'] = true;
|
||||
} else {
|
||||
delete updateVariables[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any of the sensitive variables already exist in the environment. Prompt the user to overwrite them.
|
||||
const existingSensitiveVariables = existingVariables.filter(
|
||||
variable => variable.value === null
|
||||
);
|
||||
|
||||
if (existingSensitiveVariables.length > 0) {
|
||||
const existingSensitiveVariablesNames = existingSensitiveVariables.map(
|
||||
variable => `- ${variable.name}`
|
||||
);
|
||||
const confirm = await confirmAsync({
|
||||
message: `You are about to overwrite sensitive variables.\n${existingSensitiveVariablesNames.join(
|
||||
'\n'
|
||||
)}\n Do you want to continue?`,
|
||||
});
|
||||
if (!confirm) {
|
||||
throw new Error('Aborting...');
|
||||
}
|
||||
}
|
||||
|
||||
const variablesToPush = Object.values(updateVariables);
|
||||
|
||||
if (variablesToPush.length === 0) {
|
||||
Log.log('No new variables to push.');
|
||||
return;
|
||||
}
|
||||
|
||||
await EnvironmentVariableMutation.createBulkEnvironmentVariablesForAppAsync(
|
||||
graphqlClient,
|
||||
variablesToPush,
|
||||
projectId
|
||||
);
|
||||
|
||||
Log.log(`Uploaded env file to ${environment} environment.`);
|
||||
}
|
||||
|
||||
private async parseEnvFileAsync(
|
||||
envPath: string,
|
||||
environment: string
|
||||
): Promise<Record<string, EnvironmentVariablePushInput>> {
|
||||
if (!(await fs.exists(envPath))) {
|
||||
throw new Error(`File ${envPath} does not exist.`);
|
||||
}
|
||||
const pushInput: Record<string, EnvironmentVariablePushInput> = {};
|
||||
|
||||
const variables: Record<string, string> = dotenv.parse(await fs.readFile(envPath, 'utf8'));
|
||||
|
||||
for (const [name, value] of Object.entries(variables)) {
|
||||
pushInput[name] = {
|
||||
name,
|
||||
value,
|
||||
environment,
|
||||
visibility: name.startsWith('EXPO_SENSITIVE')
|
||||
? EnvironmentVariableVisibility.Sensitive
|
||||
: EnvironmentVariableVisibility.Public,
|
||||
};
|
||||
}
|
||||
|
||||
return pushInput;
|
||||
}
|
||||
|
||||
private validateFlags(flags: PushFlags): Required<PushFlags> {
|
||||
if (!flags.environment) {
|
||||
throw new Error('Please provide an environment to push the env file to.');
|
||||
}
|
||||
|
||||
return { ...flags, environment: flags.environment };
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EASEnvironmentFlag, EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import { EnvironmentVariableScope } from '../../graphql/generated';
|
||||
import { EnvironmentVariableMutation } from '../../graphql/mutations/EnvironmentVariableMutation';
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
|
||||
|
||||
export default class EnvironmentVariableUnlink extends EasCommand {
|
||||
static override description = 'unlink a shared environment variable to the current project';
|
||||
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
name: Flags.string({
|
||||
description: 'Name of the variable',
|
||||
}),
|
||||
...EASEnvironmentFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
flags: { name, 'non-interactive': nonInteractive, environment },
|
||||
} = await this.parse(EnvironmentVariableUnlink);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableUnlink, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
if (!environment) {
|
||||
environment = await promptVariableEnvironmentAsync(nonInteractive);
|
||||
}
|
||||
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
const appVariables = await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
environment,
|
||||
});
|
||||
const linkedVariables = appVariables.filter(
|
||||
({ scope }) => scope === EnvironmentVariableScope.Shared
|
||||
);
|
||||
|
||||
if (linkedVariables.length === 0) {
|
||||
throw new Error(`There are no linked shared env variables for project ${projectDisplayName}`);
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
name = await selectAsync(
|
||||
'Select shared variable to unlink',
|
||||
linkedVariables.map(variable => ({
|
||||
title: variable.name,
|
||||
value: variable.name,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
const selectedVariable = linkedVariables.find(variable => variable.name === name);
|
||||
|
||||
if (!selectedVariable) {
|
||||
throw new Error(`Shared variable ${name} doesn't exist`);
|
||||
}
|
||||
|
||||
const unlinkedVariable = await EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync(
|
||||
graphqlClient,
|
||||
selectedVariable.id,
|
||||
projectId,
|
||||
environment
|
||||
);
|
||||
|
||||
if (!unlinkedVariable) {
|
||||
throw new Error(
|
||||
`Could not unlink variable with name ${selectedVariable.name} from project ${projectDisplayName}`
|
||||
);
|
||||
}
|
||||
|
||||
Log.withTick(
|
||||
`Unlinked variable ${chalk.bold(unlinkedVariable.name)} from the project ${chalk.bold(
|
||||
projectDisplayName
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import {
|
||||
EASEnvironmentFlag,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableScopeFlag,
|
||||
EASVariableVisibilityFlag,
|
||||
} from '../../commandUtils/flags';
|
||||
import { EnvironmentVariableScope, EnvironmentVariableVisibility } from '../../graphql/generated';
|
||||
import { EnvironmentVariableMutation } from '../../graphql/mutations/EnvironmentVariableMutation';
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getOwnerAccountForProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { promptVariableEnvironmentAsync, promptVariableValueAsync } from '../../utils/prompts';
|
||||
|
||||
type UpdateFlags = {
|
||||
name?: string;
|
||||
value?: string;
|
||||
scope?: EnvironmentVariableScope;
|
||||
environment?: string;
|
||||
visibility?: EnvironmentVariableVisibility;
|
||||
'non-interactive': boolean;
|
||||
};
|
||||
|
||||
export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
static override description =
|
||||
'update an environment variable on the current project or owner account';
|
||||
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
name: Flags.string({
|
||||
description: 'Name of the variable',
|
||||
}),
|
||||
value: Flags.string({
|
||||
description: 'Text value or the variable',
|
||||
}),
|
||||
...EASVariableVisibilityFlag,
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.Analytics,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(EnvironmentVariableUpdate);
|
||||
let {
|
||||
name,
|
||||
value,
|
||||
scope,
|
||||
'non-interactive': nonInteractive,
|
||||
environment,
|
||||
visibility,
|
||||
} = this.validateFlags(flags);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableUpdate, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
const [projectDisplayName, ownerAccount] = await Promise.all([
|
||||
getDisplayNameForProjectIdAsync(graphqlClient, projectId),
|
||||
getOwnerAccountForProjectIdAsync(graphqlClient, projectId),
|
||||
]);
|
||||
|
||||
if (scope === EnvironmentVariableScope.Project) {
|
||||
if (!environment) {
|
||||
environment = await promptVariableEnvironmentAsync(nonInteractive);
|
||||
}
|
||||
const existingVariables = await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
environment,
|
||||
});
|
||||
if (!name) {
|
||||
name = await selectAsync(
|
||||
'Select variable',
|
||||
existingVariables.map(variable => ({
|
||||
title: variable.name,
|
||||
value: variable.name,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
const existingVariable = existingVariables.find(variable => variable.name === name);
|
||||
if (!existingVariable) {
|
||||
throw new Error(
|
||||
`Variable with name ${name} does not exist on project ${projectDisplayName}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
value = await promptVariableValueAsync({
|
||||
nonInteractive,
|
||||
required: false,
|
||||
initial: existingVariable.value,
|
||||
});
|
||||
if (!value || value.length === 0) {
|
||||
value = '';
|
||||
}
|
||||
}
|
||||
|
||||
const variable = await EnvironmentVariableMutation.createForAppAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
name,
|
||||
value,
|
||||
environment,
|
||||
visibility,
|
||||
overwrite: true,
|
||||
},
|
||||
projectId
|
||||
);
|
||||
if (!variable) {
|
||||
throw new Error(
|
||||
`Could not update variable with name ${name} on project ${projectDisplayName}`
|
||||
);
|
||||
}
|
||||
|
||||
Log.withTick(
|
||||
`Updated variable ${chalk.bold(name)} on project ${chalk.bold(projectDisplayName)}.`
|
||||
);
|
||||
} else if (scope === EnvironmentVariableScope.Shared) {
|
||||
const sharedVariables = await EnvironmentVariablesQuery.sharedAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
});
|
||||
|
||||
if (!name) {
|
||||
name = await selectAsync(
|
||||
'Select variable',
|
||||
sharedVariables.map(variable => ({
|
||||
title: variable.name,
|
||||
value: variable.name,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
const existingVariable = sharedVariables.find(variable => variable.name === name);
|
||||
if (!existingVariable) {
|
||||
throw new Error(
|
||||
"Variable with this name doesn't exist on this account. Use a different name."
|
||||
);
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
value = await promptVariableValueAsync({
|
||||
nonInteractive,
|
||||
required: false,
|
||||
initial: existingVariable.value,
|
||||
});
|
||||
if (!value || value.length === 0) {
|
||||
value = '';
|
||||
}
|
||||
}
|
||||
|
||||
const variable = await EnvironmentVariableMutation.createSharedVariableAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
name,
|
||||
value,
|
||||
visibility,
|
||||
overwrite: true,
|
||||
},
|
||||
ownerAccount.id
|
||||
);
|
||||
|
||||
if (!variable) {
|
||||
throw new Error(
|
||||
`Could not update variable with name ${name} on account ${ownerAccount.name}`
|
||||
);
|
||||
}
|
||||
|
||||
Log.withTick(
|
||||
`Updated shared variable ${chalk.bold(name)} on account ${chalk.bold(ownerAccount.name)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
private validateFlags(flags: UpdateFlags): UpdateFlags {
|
||||
if (flags['non-interactive']) {
|
||||
if (!flags.name) {
|
||||
throw new Error(
|
||||
'Variable name is required in non-interactive mode. Run the command with --name flag.'
|
||||
);
|
||||
}
|
||||
|
||||
if (flags.scope === EnvironmentVariableScope.Project && !flags.environment) {
|
||||
throw new Error(
|
||||
'Environment is required when updating project-wide variable in non-interactive mode. Run the command with --environment flag.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,8 @@ export default class MetadataLint extends EasCommand {
|
||||
await loadConfigAsync({ projectDir, profile: submitProfile });
|
||||
|
||||
if (flags.json) {
|
||||
return printJsonOnlyOutput([]);
|
||||
printJsonOnlyOutput([]);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.log('✅ Store configuration is valid.');
|
||||
@@ -70,7 +71,8 @@ export default class MetadataLint extends EasCommand {
|
||||
}
|
||||
|
||||
if (flags.json) {
|
||||
return printJsonOnlyOutput(error.errors);
|
||||
printJsonOnlyOutput(error.errors);
|
||||
return;
|
||||
}
|
||||
|
||||
logMetadataValidationError(error);
|
||||
|
||||
@@ -110,6 +110,7 @@ describe(ProjectInit.name, () => {
|
||||
it('is no-op if already configured for id', async () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
name: 'testing-123',
|
||||
slug: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
@@ -121,6 +122,7 @@ describe(ProjectInit.name, () => {
|
||||
it('prompts to overwrite when different', async () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
name: 'testing-123',
|
||||
slug: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
@@ -135,6 +137,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -152,6 +155,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -186,6 +190,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -198,6 +203,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -223,6 +229,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -247,6 +254,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -273,6 +281,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -288,6 +297,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -302,6 +312,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -329,6 +340,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -355,6 +367,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -384,6 +397,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -398,6 +412,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -437,6 +452,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -464,6 +480,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '0129',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getProjectDashboardUrl } from '../../build/utils/url';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { saveProjectIdToAppConfigAsync } from '../../commandUtils/context/contextUtils/getProjectIdAsync';
|
||||
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import { AppPrivacy, Role } from '../../graphql/generated';
|
||||
import { AppMutation } from '../../graphql/mutations/AppMutation';
|
||||
import { AppQuery } from '../../graphql/queries/AppQuery';
|
||||
@@ -33,14 +34,10 @@ export default class ProjectInit extends EasCommand {
|
||||
description: 'ID of the EAS project to link',
|
||||
}),
|
||||
force: Flags.boolean({
|
||||
description: 'Whether to overwrite any existing project ID',
|
||||
dependsOn: ['id'],
|
||||
}),
|
||||
// this is the same as EASNonInteractiveFlag but with the dependsOn
|
||||
'non-interactive': Flags.boolean({
|
||||
description: 'Run the command in non-interactive mode.',
|
||||
dependsOn: ['id'],
|
||||
description:
|
||||
'Whether to create a new project/link an existing project without additional prompts or overwrite any existing project ID when running with --id flag',
|
||||
}),
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
@@ -216,10 +213,11 @@ export default class ProjectInit extends EasCommand {
|
||||
});
|
||||
}
|
||||
|
||||
private static async initializeWithInteractiveSelectionAsync(
|
||||
private static async initializeWithoutExplicitIDAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
actor: Actor,
|
||||
projectDir: string
|
||||
projectDir: string,
|
||||
{ force, nonInteractive }: InitializeMethodOptions
|
||||
): Promise<string> {
|
||||
const exp = getPrivateExpoConfig(projectDir);
|
||||
const existingProjectId = exp.extra?.eas?.projectId;
|
||||
@@ -245,6 +243,20 @@ export default class ProjectInit extends EasCommand {
|
||||
if (!accountName) {
|
||||
if (allAccounts.length === 1) {
|
||||
accountName = allAccounts[0].name;
|
||||
} else if (nonInteractive) {
|
||||
if (!force) {
|
||||
throw new Error(
|
||||
`There are multiple accounts that you have access to: ${allAccounts
|
||||
.map(a => a.name)
|
||||
.join(
|
||||
', '
|
||||
)}. Explicitly set the owner property in your app config or run this command with the --force flag to proceed with a default account: ${
|
||||
allAccounts[0].name
|
||||
}.`
|
||||
);
|
||||
}
|
||||
accountName = allAccounts[0].name;
|
||||
Log.log(`Using default account ${accountName} for non-interactive and force mode`);
|
||||
} else {
|
||||
const choices = ProjectInit.getAccountChoices(
|
||||
actor,
|
||||
@@ -274,13 +286,21 @@ export default class ProjectInit extends EasCommand {
|
||||
projectName
|
||||
);
|
||||
if (existingProjectIdOnServer) {
|
||||
const affirmedLink = await confirmAsync({
|
||||
message: `Existing project found: ${projectFullName} (ID: ${existingProjectIdOnServer}). Link this project?`,
|
||||
});
|
||||
if (!affirmedLink) {
|
||||
throw new Error(
|
||||
`Project ID configuration canceled. Re-run the command to select a different account/project.`
|
||||
);
|
||||
if (!force) {
|
||||
if (nonInteractive) {
|
||||
throw new Error(
|
||||
`Existing project found: ${projectFullName} (ID: ${existingProjectIdOnServer}). Use --force flag to continue with this project.`
|
||||
);
|
||||
}
|
||||
|
||||
const affirmedLink = await confirmAsync({
|
||||
message: `Existing project found: ${projectFullName} (ID: ${existingProjectIdOnServer}). Link this project?`,
|
||||
});
|
||||
if (!affirmedLink) {
|
||||
throw new Error(
|
||||
`Project ID configuration canceled. Re-run the command to select a different account/project.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await ProjectInit.saveProjectIdAndLogSuccessAsync(projectDir, existingProjectIdOnServer);
|
||||
@@ -293,11 +313,18 @@ export default class ProjectInit extends EasCommand {
|
||||
);
|
||||
}
|
||||
|
||||
const affirmedCreate = await confirmAsync({
|
||||
message: `Would you like to create a project for ${projectFullName}?`,
|
||||
});
|
||||
if (!affirmedCreate) {
|
||||
throw new Error(`Project ID configuration canceled for ${projectFullName}.`);
|
||||
if (!force) {
|
||||
if (nonInteractive) {
|
||||
throw new Error(
|
||||
`Project does not exist: ${projectFullName}. Use --force flag to create this project.`
|
||||
);
|
||||
}
|
||||
const affirmedCreate = await confirmAsync({
|
||||
message: `Would you like to create a project for ${projectFullName}?`,
|
||||
});
|
||||
if (!affirmedCreate) {
|
||||
throw new Error(`Project ID configuration canceled for ${projectFullName}.`);
|
||||
}
|
||||
}
|
||||
|
||||
const projectDashboardUrl = getProjectDashboardUrl(accountName, projectName);
|
||||
@@ -405,10 +432,14 @@ export default class ProjectInit extends EasCommand {
|
||||
});
|
||||
idForConsistency = idArgument;
|
||||
} else {
|
||||
idForConsistency = await ProjectInit.initializeWithInteractiveSelectionAsync(
|
||||
idForConsistency = await ProjectInit.initializeWithoutExplicitIDAsync(
|
||||
graphqlClient,
|
||||
actor,
|
||||
projectDir
|
||||
projectDir,
|
||||
{
|
||||
force,
|
||||
nonInteractive,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,496 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { AppVersionSource, EasJson } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import { getEASUpdateURL, getExpoWebsiteBaseUrl } from '../../api';
|
||||
import { runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
||||
import { reviewAndCommitChangesAsync } from '../../build/utils/repository';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { DynamicConfigContextFn } from '../../commandUtils/context/DynamicProjectConfigContextField';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { validateOrSetProjectIdAsync } from '../../commandUtils/context/contextUtils/getProjectIdAsync';
|
||||
import { CredentialsContextProjectInfo } from '../../credentials/context';
|
||||
import { SetUpBuildCredentialsCommandAction } from '../../credentials/manager/SetUpBuildCredentialsCommandAction';
|
||||
import {
|
||||
AppFragment,
|
||||
AppPlatform,
|
||||
OnboardingDeviceType,
|
||||
OnboardingEnvironment,
|
||||
} from '../../graphql/generated';
|
||||
import { UserPreferencesMutation } from '../../graphql/mutations/UserPreferencesMutation';
|
||||
import { AppQuery } from '../../graphql/queries/AppQuery';
|
||||
import Log, { learnMore, link } from '../../log';
|
||||
import {
|
||||
canAccessRepositoryUsingSshAsync,
|
||||
runGitCloneAsync,
|
||||
runGitPushAsync,
|
||||
} from '../../onboarding/git';
|
||||
import { installDependenciesAsync } from '../../onboarding/installDependencies';
|
||||
import { runCommandAsync } from '../../onboarding/runCommand';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { ExpoConfigOptions, getPrivateExpoConfig } from '../../project/expoConfig';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { Actor } from '../../user/User';
|
||||
import { easCliVersion } from '../../utils/easCli';
|
||||
import GitClient from '../../vcs/clients/git';
|
||||
|
||||
export default class Onboarding extends EasCommand {
|
||||
static override aliases = ['init:onboarding', 'onboarding'];
|
||||
|
||||
static override description =
|
||||
'continue onboarding process started on the https://expo.new website.';
|
||||
|
||||
static override flags = {};
|
||||
|
||||
static override args = [{ name: 'TARGET_PROJECT_DIRECTORY' }];
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.Analytics,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args: { TARGET_PROJECT_DIRECTORY: targetProjectDirInput },
|
||||
} = await this.parse(Onboarding);
|
||||
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
analytics,
|
||||
} = await this.getContextAsync(Onboarding, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
|
||||
if (actor.__typename === 'Robot') {
|
||||
throw new Error(
|
||||
'This command is not available for robot users. Make sure you are not using a robot token and try again.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!actor.preferences.onboarding) {
|
||||
throw new Error(
|
||||
'This command can only be run as part of the onboarding process started on the Expo website. Visit https://expo.new to start a new project.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!actor.preferences.onboarding.platform) {
|
||||
throw new Error(
|
||||
'This command can only be run as part of the onboarding process started on the Expo website. It seems like you started an onboarding process, but we are missing some information needed to be filled in before running the eas init:onboarding command (selected platform). Continue the onboarding process on the Expo website.'
|
||||
);
|
||||
}
|
||||
if (!actor.preferences.onboarding.environment) {
|
||||
throw new Error(
|
||||
'This command can only be run as part of the onboarding process started on the Expo website. It seems like you started an onboarding process, but we are missing some information needed to be filled in before running the eas init:onboarding command (selected environment). Continue the onboarding process on the Expo website.'
|
||||
);
|
||||
}
|
||||
if (!actor.preferences.onboarding.deviceType) {
|
||||
throw new Error(
|
||||
'This command can only be run as part of the onboarding process started on the Expo website. It seems like you started an onboarding process, but we are missing some information needed to be filled in before running the eas init:onboarding command (selected device type). Continue the onboarding process on the Expo website.'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
new Date(actor.preferences.onboarding.lastUsed) < new Date(Date.now() - 1000 * 60 * 60 * 24)
|
||||
) {
|
||||
Log.warn(
|
||||
'It seems like you started an onboarding process, but it has been a while since you last used it. If you want to start a new onboarding process, visit https://expo.new.'
|
||||
);
|
||||
Log.log();
|
||||
}
|
||||
|
||||
const platform =
|
||||
actor.preferences.onboarding.platform === AppPlatform.Android
|
||||
? Platform.ANDROID
|
||||
: Platform.IOS;
|
||||
|
||||
const app = await AppQuery.byIdAsync(graphqlClient, actor.preferences.onboarding.appId);
|
||||
|
||||
const githubUsername = app.githubRepository
|
||||
? app.githubRepository.metadata.githubRepoOwnerName
|
||||
: 'expo';
|
||||
const githubRepositoryName = app.githubRepository
|
||||
? app.githubRepository.metadata.githubRepoName
|
||||
: 'expo-template-default';
|
||||
|
||||
Log.log(`👋 Welcome to Expo, ${actor.username}!`);
|
||||
Log.log();
|
||||
Log.log('✨ We will continue your onboarding process in EAS CLI');
|
||||
Log.log();
|
||||
Log.log(
|
||||
`🚚 Let's start by cloning ${
|
||||
app.githubRepository
|
||||
? `your project (${githubUsername}/${githubRepositoryName})`
|
||||
: `default Expo template project (${githubUsername}/${githubRepositoryName})`
|
||||
} from GitHub and installing dependencies.`
|
||||
);
|
||||
Log.log();
|
||||
let initialTargetProjectDirectory: string;
|
||||
if (targetProjectDirInput) {
|
||||
initialTargetProjectDirectory = targetProjectDirInput;
|
||||
Log.log(`📂 Cloning the project to ${initialTargetProjectDirectory}`);
|
||||
} else {
|
||||
const { selectedTargetProjectDirectory } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'selectedTargetProjectDirectory',
|
||||
message: app.githubRepository
|
||||
? '📂 Where would you like to clone the project to?'
|
||||
: '📂 Where would you like to create your new project directory?',
|
||||
initial: app.githubRepository
|
||||
? path.join(process.cwd(), githubRepositoryName)
|
||||
: path.join(process.cwd(), `${actor.username}-first-project`),
|
||||
});
|
||||
initialTargetProjectDirectory = selectedTargetProjectDirectory;
|
||||
}
|
||||
Log.log();
|
||||
|
||||
const cloneMethod = (await canAccessRepositoryUsingSshAsync({
|
||||
githubUsername,
|
||||
githubRepositoryName,
|
||||
}))
|
||||
? 'ssh'
|
||||
: 'https';
|
||||
Log.log(chalk.dim(`We detected that ${cloneMethod} is your preffered git clone method`));
|
||||
Log.log();
|
||||
|
||||
const { targetProjectDir: finalTargetProjectDirectory } = await runGitCloneAsync({
|
||||
githubUsername,
|
||||
githubRepositoryName,
|
||||
targetProjectDir: initialTargetProjectDirectory,
|
||||
cloneMethod,
|
||||
});
|
||||
|
||||
const vcsClient = new GitClient(finalTargetProjectDirectory);
|
||||
if (!app.githubRepository) {
|
||||
await fs.remove(path.join(finalTargetProjectDirectory, '.git'));
|
||||
await runCommandAsync({
|
||||
cwd: finalTargetProjectDirectory,
|
||||
command: 'git',
|
||||
args: ['init'],
|
||||
});
|
||||
Log.log();
|
||||
await configureProjectFromBareDefaultExpoTemplateAsync({
|
||||
app,
|
||||
vcsClient,
|
||||
targetDir: finalTargetProjectDirectory,
|
||||
});
|
||||
}
|
||||
|
||||
await installDependenciesAsync({
|
||||
projectDir: finalTargetProjectDirectory,
|
||||
});
|
||||
const exp = await getPrivateExpoConfigWithProjectIdAsync({
|
||||
projectDir: finalTargetProjectDirectory,
|
||||
graphqlClient,
|
||||
actor,
|
||||
});
|
||||
const getDynamicProjectConfigFn = getDynamicPrivateProjectConfigGetter({
|
||||
projectDir: finalTargetProjectDirectory,
|
||||
graphqlClient,
|
||||
actor,
|
||||
});
|
||||
|
||||
if (!app.githubRepository) {
|
||||
await runCommandAsync({
|
||||
cwd: finalTargetProjectDirectory,
|
||||
command: 'npx',
|
||||
args: ['expo', 'install', 'expo-updates'],
|
||||
});
|
||||
Log.log();
|
||||
await runCommandAsync({
|
||||
cwd: finalTargetProjectDirectory,
|
||||
command: 'npx',
|
||||
args: ['expo', 'install', 'expo-insights'],
|
||||
});
|
||||
Log.log();
|
||||
await runCommandAsync({
|
||||
cwd: finalTargetProjectDirectory,
|
||||
command: 'npx',
|
||||
args: ['expo', 'install', 'expo-dev-client'],
|
||||
});
|
||||
Log.log();
|
||||
}
|
||||
await vcsClient.trackFileAsync('package-lock.json');
|
||||
|
||||
const shouldSetupCredentials =
|
||||
((platform === Platform.IOS &&
|
||||
actor.preferences.onboarding.deviceType === OnboardingDeviceType.Device) ||
|
||||
platform === Platform.ANDROID) &&
|
||||
actor.preferences.onboarding.environment === OnboardingEnvironment.DevBuild;
|
||||
if (shouldSetupCredentials) {
|
||||
Log.log('🔑 Now we need to set up build credentials for your project:');
|
||||
await new SetUpBuildCredentialsCommandAction(
|
||||
actor,
|
||||
graphqlClient,
|
||||
vcsClient,
|
||||
analytics,
|
||||
exp,
|
||||
getDynamicProjectConfigFn,
|
||||
platform,
|
||||
actor.preferences.onboarding.deviceType === OnboardingDeviceType.Simulator
|
||||
? 'development-simulator'
|
||||
: 'development',
|
||||
finalTargetProjectDirectory
|
||||
).runAsync();
|
||||
}
|
||||
|
||||
if (app.githubRepository && (await vcsClient.hasUncommittedChangesAsync())) {
|
||||
Log.log(
|
||||
'📦 We will now commit the changes made by the configuration process and push them to GitHub:'
|
||||
);
|
||||
Log.log();
|
||||
Log.log('🔍 Checking for changes in the repository...');
|
||||
await vcsClient.showChangedFilesAsync();
|
||||
await reviewAndCommitChangesAsync(
|
||||
vcsClient,
|
||||
`[eas-onboarding] Install dependencies${
|
||||
shouldSetupCredentials ? ' and set up build credentials' : ''
|
||||
}`,
|
||||
{ nonInteractive: false }
|
||||
);
|
||||
Log.log('📤 Pushing changes to GitHub...');
|
||||
await runGitPushAsync({
|
||||
targetProjectDir: finalTargetProjectDirectory,
|
||||
});
|
||||
} else if (!app.githubRepository) {
|
||||
await runCommandAsync({
|
||||
cwd: finalTargetProjectDirectory,
|
||||
command: 'git',
|
||||
args: ['add', '.'],
|
||||
});
|
||||
Log.log();
|
||||
await runCommandAsync({
|
||||
cwd: finalTargetProjectDirectory,
|
||||
command: 'git',
|
||||
args: ['commit', '-m', 'Initial commit'],
|
||||
});
|
||||
Log.log();
|
||||
}
|
||||
|
||||
Log.log();
|
||||
Log.log('🎉 We finished configuring your project.');
|
||||
Log.log();
|
||||
if (
|
||||
!!app.githubRepository ||
|
||||
actor.preferences.onboarding.environment === OnboardingEnvironment.ExpoGo
|
||||
) {
|
||||
Log.log('🚀 You can now go back to the website to continue:');
|
||||
const url = new URL(
|
||||
`/onboarding/develop/set-up-project-on-your-machine?project=${app.slug}&accountId=${app.ownerAccount.id}`,
|
||||
getExpoWebsiteBaseUrl()
|
||||
).toString();
|
||||
Log.log(`👉 ${link(url)}`);
|
||||
} else {
|
||||
Log.log('🚀 Now we are going to trigger your first build');
|
||||
Log.log();
|
||||
const { buildIds } = await runBuildAndSubmitAsync(
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
finalTargetProjectDirectory,
|
||||
{
|
||||
nonInteractive: true,
|
||||
requestedPlatform:
|
||||
platform === Platform.ANDROID ? RequestedPlatform.Android : RequestedPlatform.Ios,
|
||||
profile:
|
||||
actor.preferences.onboarding.deviceType === OnboardingDeviceType.Simulator
|
||||
? 'development-simulator'
|
||||
: 'development',
|
||||
wait: false,
|
||||
clearCache: false,
|
||||
json: false,
|
||||
autoSubmit: false,
|
||||
localBuildOptions: {},
|
||||
freezeCredentials: false,
|
||||
repack: true,
|
||||
},
|
||||
actor,
|
||||
getDynamicProjectConfigFn
|
||||
);
|
||||
const buildId = buildIds[0];
|
||||
Log.log();
|
||||
Log.log('🚀 You can now go back to the website to continue:');
|
||||
const url = new URL(
|
||||
`/onboarding/develop/set-up-project-on-your-machine?project=${app.slug}&accountId=${app.ownerAccount.id}&buildId=${buildId}`,
|
||||
getExpoWebsiteBaseUrl()
|
||||
).toString();
|
||||
Log.log(`👉 ${link(url)}`);
|
||||
}
|
||||
|
||||
const { __typename, ...previousPreferences } = actor.preferences.onboarding;
|
||||
await UserPreferencesMutation.markCliDoneInOnboardingUserPreferencesAsync(graphqlClient, {
|
||||
...previousPreferences,
|
||||
appId: app.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// we can't get this automated by using command context because when we run a command the project directory doesn't exist yet
|
||||
async function getPrivateExpoConfigWithProjectIdAsync({
|
||||
projectDir,
|
||||
graphqlClient,
|
||||
actor,
|
||||
options,
|
||||
}: {
|
||||
projectDir: string;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
actor: Actor;
|
||||
options?: ExpoConfigOptions;
|
||||
}): Promise<CredentialsContextProjectInfo> {
|
||||
const expBefore = getPrivateExpoConfig(projectDir, options);
|
||||
const projectId = await validateOrSetProjectIdAsync({
|
||||
exp: expBefore,
|
||||
graphqlClient,
|
||||
actor,
|
||||
options: {
|
||||
nonInteractive: false,
|
||||
},
|
||||
cwd: projectDir,
|
||||
});
|
||||
const exp = getPrivateExpoConfig(projectDir, options);
|
||||
return {
|
||||
exp,
|
||||
projectId,
|
||||
};
|
||||
}
|
||||
|
||||
// we can't get this automated by using command context because when we run a command the project directory doesn't exist yet
|
||||
function getDynamicPrivateProjectConfigGetter({
|
||||
projectDir,
|
||||
graphqlClient,
|
||||
actor,
|
||||
}: {
|
||||
projectDir: string;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
actor: Actor;
|
||||
}): DynamicConfigContextFn {
|
||||
return async (options?: ExpoConfigOptions) => {
|
||||
return {
|
||||
...(await getPrivateExpoConfigWithProjectIdAsync({
|
||||
projectDir,
|
||||
graphqlClient,
|
||||
actor,
|
||||
options,
|
||||
})),
|
||||
projectDir,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
async function configureProjectFromBareDefaultExpoTemplateAsync({
|
||||
app,
|
||||
vcsClient,
|
||||
targetDir,
|
||||
}: {
|
||||
app: AppFragment;
|
||||
vcsClient: GitClient;
|
||||
targetDir: string;
|
||||
}): Promise<void> {
|
||||
// Android package name requires each component to start with a lowercase letter.
|
||||
const isUsernameValidSegment = /^[^a-z]/.test(app.ownerAccount.name);
|
||||
const userPrefix = isUsernameValidSegment ? 'user' : '';
|
||||
const isSlugValidSegment = /^[^a-z]/.test(app.slug);
|
||||
const slugPrefix = isSlugValidSegment ? 'app' : '';
|
||||
|
||||
const bundleIdentifier = `com.${userPrefix}${stripInvalidCharactersForBundleIdentifier(
|
||||
app.ownerAccount.name
|
||||
)}.${slugPrefix}${stripInvalidCharactersForBundleIdentifier(app.slug)}`;
|
||||
const updateUrl = getEASUpdateURL(app.id);
|
||||
|
||||
const easBuildGitHubConfig = {
|
||||
android: {
|
||||
image: 'latest',
|
||||
},
|
||||
ios: {
|
||||
image: 'latest',
|
||||
},
|
||||
};
|
||||
|
||||
const easJson: EasJson = {
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
appVersionSource: AppVersionSource.REMOTE,
|
||||
},
|
||||
build: {
|
||||
development: {
|
||||
developmentClient: true,
|
||||
distribution: 'internal',
|
||||
...easBuildGitHubConfig,
|
||||
},
|
||||
'development-simulator': {
|
||||
extends: 'development',
|
||||
ios: {
|
||||
simulator: true,
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
channel: 'main',
|
||||
...easBuildGitHubConfig,
|
||||
},
|
||||
production: {
|
||||
channel: 'production',
|
||||
autoIncrement: true,
|
||||
...easBuildGitHubConfig,
|
||||
},
|
||||
},
|
||||
submit: {
|
||||
production: {},
|
||||
},
|
||||
};
|
||||
|
||||
const easJsonPath = path.join(targetDir, 'eas.json');
|
||||
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
|
||||
await vcsClient.trackFileAsync(easJsonPath);
|
||||
Log.log(
|
||||
`✅ Generated ${chalk.bold('eas.json')}. ${learnMore(
|
||||
'https://docs.expo.dev/build-reference/eas-json/'
|
||||
)}`
|
||||
);
|
||||
Log.log();
|
||||
|
||||
const baseExpoConfig = JSON.parse(await fs.readFile(path.join(targetDir, 'app.json'), 'utf8'))
|
||||
.expo as ExpoConfig;
|
||||
|
||||
const expoConfig: ExpoConfig = {
|
||||
...baseExpoConfig,
|
||||
name: app.name ?? app.slug,
|
||||
slug: app.slug,
|
||||
extra: {
|
||||
eas: {
|
||||
projectId: app.id,
|
||||
},
|
||||
},
|
||||
owner: app.ownerAccount.name,
|
||||
updates: {
|
||||
url: updateUrl,
|
||||
},
|
||||
runtimeVersion: {
|
||||
policy: 'appVersion',
|
||||
},
|
||||
ios: {
|
||||
...baseExpoConfig.ios,
|
||||
bundleIdentifier,
|
||||
},
|
||||
android: {
|
||||
...baseExpoConfig.android,
|
||||
package: bundleIdentifier,
|
||||
},
|
||||
};
|
||||
|
||||
const appJsonPath = path.join(targetDir, 'app.json');
|
||||
await fs.writeFile(appJsonPath, `${JSON.stringify({ expo: expoConfig }, null, 2)}\n`);
|
||||
await vcsClient.trackFileAsync(appJsonPath);
|
||||
Log.log(
|
||||
`✅ Generated ${chalk.bold('app.json')}. ${learnMore(
|
||||
'https://docs.expo.dev/versions/latest/config/app/'
|
||||
)}`
|
||||
);
|
||||
Log.log();
|
||||
}
|
||||
|
||||
function stripInvalidCharactersForBundleIdentifier(string: string): string {
|
||||
return string.replaceAll(/[^A-Za-z0-9]/g, '');
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
import { Platform, SubmissionConfig } from '@expo/eas-build-job';
|
||||
import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
||||
import { Flags } from '@oclif/core';
|
||||
import nullthrows from 'nullthrows';
|
||||
import { z } from 'zod';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import {
|
||||
AndroidSubmissionConfigInput,
|
||||
IosSubmissionConfigInput,
|
||||
SubmissionAndroidReleaseStatus,
|
||||
SubmissionAndroidTrack,
|
||||
} from '../../graphql/generated';
|
||||
import { AppStoreConnectApiKeyQuery } from '../../graphql/queries/AppStoreConnectApiKeyQuery';
|
||||
import { GoogleServiceAccountKeyQuery } from '../../graphql/queries/GoogleServiceAccountKeyQuery';
|
||||
import AndroidSubmitCommand from '../../submit/android/AndroidSubmitCommand';
|
||||
import { SubmissionContext, createSubmissionContextAsync } from '../../submit/context';
|
||||
import IosSubmitCommand from '../../submit/ios/IosSubmitCommand';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
|
||||
|
||||
/**
|
||||
* This command will be run on the EAS workers.
|
||||
* This command resolves credentials and other
|
||||
* configuration, that normally would be included in the
|
||||
* job and metadata objects, and prints them to stdout.
|
||||
*/
|
||||
export default class SubmitInternal extends EasCommand {
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
platform: Flags.enum<Platform>({
|
||||
options: [Platform.ANDROID, Platform.IOS],
|
||||
required: true,
|
||||
}),
|
||||
profile: Flags.string({
|
||||
description:
|
||||
'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
}),
|
||||
id: Flags.string({
|
||||
description: 'ID of the build to submit',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
...this.ContextOptions.Analytics,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(SubmitInternal);
|
||||
// This command is always run with implicit --non-interactive and --json options
|
||||
enableJsonOutput();
|
||||
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
privateProjectConfig: { exp, projectId, projectDir },
|
||||
analytics,
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(SubmitInternal, {
|
||||
nonInteractive: true,
|
||||
vcsClientOverride: new GitNoCommitClient(),
|
||||
});
|
||||
|
||||
const submissionProfile = await EasJsonUtils.getSubmitProfileAsync(
|
||||
EasJsonAccessor.fromProjectPath(projectDir),
|
||||
flags.platform,
|
||||
flags.profile
|
||||
);
|
||||
|
||||
const ctx = await createSubmissionContextAsync({
|
||||
platform: flags.platform,
|
||||
projectDir,
|
||||
profile: submissionProfile,
|
||||
archiveFlags: {
|
||||
id: flags.id,
|
||||
},
|
||||
nonInteractive: true,
|
||||
isVerboseFastlaneEnabled: false,
|
||||
actor,
|
||||
graphqlClient,
|
||||
analytics,
|
||||
exp,
|
||||
projectId,
|
||||
vcsClient,
|
||||
specifiedProfile: flags.profile,
|
||||
});
|
||||
|
||||
let config;
|
||||
|
||||
if (ctx.platform === Platform.IOS) {
|
||||
const command = new IosSubmitCommand(ctx as SubmissionContext<Platform.IOS>);
|
||||
const submitter = await command.runAsync();
|
||||
const mutationInput = await submitter.getSubmissionInputAsync();
|
||||
const iosConfig = mutationInput.submissionConfig;
|
||||
|
||||
const ascApiKeyJson = await getAppStoreConnectApiKeyJsonAsync({
|
||||
iosConfig,
|
||||
graphqlClient,
|
||||
});
|
||||
|
||||
const configInput: z.input<typeof SubmissionConfig.Ios.SchemaZ> = {
|
||||
ascAppIdentifier: iosConfig.ascAppIdentifier,
|
||||
isVerboseFastlaneEnabled: iosConfig.isVerboseFastlaneEnabled ?? undefined,
|
||||
...(ascApiKeyJson
|
||||
? { ascApiJsonKey: ascApiKeyJson }
|
||||
: {
|
||||
appleIdUsername: nullthrows(iosConfig.appleIdUsername),
|
||||
appleAppSpecificPassword: nullthrows(iosConfig.appleAppSpecificPassword),
|
||||
}),
|
||||
};
|
||||
|
||||
config = SubmissionConfig.Ios.SchemaZ.parse(configInput);
|
||||
} else if (ctx.platform === Platform.ANDROID) {
|
||||
const command = new AndroidSubmitCommand(ctx as SubmissionContext<Platform.ANDROID>);
|
||||
const submitter = await command.runAsync();
|
||||
const mutationInput = await submitter.getSubmissionInputAsync();
|
||||
const androidConfig = mutationInput.submissionConfig;
|
||||
|
||||
const changesNotSentForReview = androidConfig.changesNotSentForReview ?? undefined;
|
||||
const releaseStatus = androidConfig.releaseStatus
|
||||
? graphQlReleaseStatusToConfigReleaseStatus[androidConfig.releaseStatus]
|
||||
: undefined;
|
||||
const googleServiceAccountKeyJson = nullthrows(
|
||||
await getGoogleServiceAccountKeyJsonAsync({
|
||||
androidConfig,
|
||||
graphqlClient,
|
||||
})
|
||||
);
|
||||
const track = graphQlTrackToConfigTrack[androidConfig.track];
|
||||
|
||||
const configInput: z.input<typeof SubmissionConfig.Android.SchemaZ> = {
|
||||
changesNotSentForReview,
|
||||
googleServiceAccountKeyJson,
|
||||
track,
|
||||
...(releaseStatus === SubmissionConfig.Android.ReleaseStatus.IN_PROGRESS
|
||||
? {
|
||||
releaseStatus: SubmissionConfig.Android.ReleaseStatus.IN_PROGRESS,
|
||||
rollout: androidConfig.rollout ?? undefined,
|
||||
}
|
||||
: { releaseStatus }),
|
||||
};
|
||||
|
||||
config = SubmissionConfig.Android.SchemaZ.parse(configInput);
|
||||
} else {
|
||||
throw new Error(`Unsupported platform: ${ctx.platform}`);
|
||||
}
|
||||
|
||||
printJsonOnlyOutput({ config });
|
||||
}
|
||||
}
|
||||
|
||||
async function getGoogleServiceAccountKeyJsonAsync({
|
||||
androidConfig,
|
||||
graphqlClient,
|
||||
}: {
|
||||
androidConfig: AndroidSubmissionConfigInput;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
}): Promise<string | null> {
|
||||
if (androidConfig.googleServiceAccountKeyJson) {
|
||||
return androidConfig.googleServiceAccountKeyJson;
|
||||
} else if (androidConfig.googleServiceAccountKeyId) {
|
||||
const key = await GoogleServiceAccountKeyQuery.getByIdAsync(
|
||||
graphqlClient,
|
||||
androidConfig.googleServiceAccountKeyId
|
||||
);
|
||||
|
||||
return key.keyJson;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getAppStoreConnectApiKeyJsonAsync({
|
||||
iosConfig,
|
||||
graphqlClient,
|
||||
}: {
|
||||
iosConfig: IosSubmissionConfigInput;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
}): Promise<string | null> {
|
||||
if (iosConfig.ascApiKey) {
|
||||
return JSON.stringify({
|
||||
key_id: iosConfig.ascApiKey.keyIdentifier,
|
||||
issuer_id: iosConfig.ascApiKey.issuerIdentifier,
|
||||
key: iosConfig.ascApiKey.keyP8,
|
||||
});
|
||||
} else if (iosConfig.ascApiKeyId) {
|
||||
const key = await AppStoreConnectApiKeyQuery.getByIdAsync(graphqlClient, iosConfig.ascApiKeyId);
|
||||
|
||||
return JSON.stringify({
|
||||
key_id: key.keyIdentifier,
|
||||
issuer_id: key.issuerIdentifier,
|
||||
key: key.keyP8,
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const graphQlReleaseStatusToConfigReleaseStatus = {
|
||||
[SubmissionAndroidReleaseStatus.Draft]: SubmissionConfig.Android.ReleaseStatus.DRAFT,
|
||||
[SubmissionAndroidReleaseStatus.InProgress]: SubmissionConfig.Android.ReleaseStatus.IN_PROGRESS,
|
||||
[SubmissionAndroidReleaseStatus.Completed]: SubmissionConfig.Android.ReleaseStatus.COMPLETED,
|
||||
[SubmissionAndroidReleaseStatus.Halted]: SubmissionConfig.Android.ReleaseStatus.HALTED,
|
||||
};
|
||||
|
||||
const graphQlTrackToConfigTrack = {
|
||||
[SubmissionAndroidTrack.Production]: SubmissionConfig.Android.ReleaseTrack.PRODUCTION,
|
||||
[SubmissionAndroidTrack.Beta]: SubmissionConfig.Android.ReleaseTrack.BETA,
|
||||
[SubmissionAndroidTrack.Alpha]: SubmissionConfig.Android.ReleaseTrack.ALPHA,
|
||||
[SubmissionAndroidTrack.Internal]: SubmissionConfig.Android.ReleaseTrack.INTERNAL,
|
||||
};
|
||||
@@ -21,7 +21,7 @@ import { UpdateFragment } from '../../../graphql/generated';
|
||||
import { PublishMutation } from '../../../graphql/mutations/PublishMutation';
|
||||
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import { collectAssetsAsync, uploadAssetsAsync } from '../../../project/publish';
|
||||
import { getBranchNameFromChannelNameAsync } from '../../../update/getBranchNameFromChannelNameAsync';
|
||||
import { getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync } from '../../../update/getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync';
|
||||
import { resolveVcsClient } from '../../../vcs';
|
||||
|
||||
const projectRoot = '/test-project';
|
||||
@@ -47,7 +47,7 @@ jest.mock('@expo/config-plugins');
|
||||
jest.mock('../../../branch/queries');
|
||||
jest.mock('../../../commandUtils/context/contextUtils/getProjectIdAsync');
|
||||
jest.mock('../../../update/configure');
|
||||
jest.mock('../../../update/getBranchNameFromChannelNameAsync');
|
||||
jest.mock('../../../update/getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync');
|
||||
jest.mock('../../../graphql/mutations/PublishMutation');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../graphql/queries/UpdateQuery');
|
||||
@@ -70,20 +70,6 @@ describe(UpdatePublish.name, () => {
|
||||
jest.mocked(PublishMutation.publishUpdateGroupAsync).mockClear();
|
||||
});
|
||||
|
||||
// Deprecated and split to a new command: update:republish
|
||||
it('errors with --republish', async () => {
|
||||
await expect(new UpdatePublish(['--republish'], commandOptions).run()).rejects.toThrow(
|
||||
'--group and --republish flags are deprecated'
|
||||
);
|
||||
});
|
||||
|
||||
// Deprecated and split to a new command: update:republish
|
||||
it('errors with --group', async () => {
|
||||
await expect(new UpdatePublish(['--group=abc123'], commandOptions).run()).rejects.toThrow(
|
||||
'--group and --republish flags are deprecated'
|
||||
);
|
||||
});
|
||||
|
||||
it('errors with both --channel and --branch', async () => {
|
||||
const flags = ['--channel=channel123', '--branch=branch123'];
|
||||
|
||||
@@ -120,7 +106,9 @@ describe(UpdatePublish.name, () => {
|
||||
const { projectId } = mockTestProject();
|
||||
const { platforms, runtimeVersion } = mockTestExport();
|
||||
|
||||
jest.mocked(getBranchNameFromChannelNameAsync).mockResolvedValue('branchFromChannel');
|
||||
jest
|
||||
.mocked(getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync)
|
||||
.mockResolvedValue({ branchId: 'branch123', branchName: 'branchFromChannel' });
|
||||
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
|
||||
branchId: 'branch123',
|
||||
createdBranch: false,
|
||||
@@ -274,6 +262,7 @@ function mockTestProject({
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
|
||||
@@ -11,8 +11,9 @@ import { jester } from '../../../credentials/__tests__/fixtures-constants';
|
||||
import { UpdateFragment } from '../../../graphql/generated';
|
||||
import { PublishMutation } from '../../../graphql/mutations/PublishMutation';
|
||||
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import { BranchQuery } from '../../../graphql/queries/BranchQuery';
|
||||
import { UpdateQuery } from '../../../graphql/queries/UpdateQuery';
|
||||
import { getBranchNameFromChannelNameAsync } from '../../../update/getBranchNameFromChannelNameAsync';
|
||||
import { getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync } from '../../../update/getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync';
|
||||
import { selectUpdateGroupOnBranchAsync } from '../../../update/queries';
|
||||
import {
|
||||
getCodeSigningInfoAsync,
|
||||
@@ -44,7 +45,8 @@ jest.mock('../../../commandUtils/context/contextUtils/getProjectIdAsync');
|
||||
jest.mock('../../../graphql/mutations/PublishMutation');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../graphql/queries/UpdateQuery');
|
||||
jest.mock('../../../update/getBranchNameFromChannelNameAsync');
|
||||
jest.mock('../../../graphql/queries/BranchQuery');
|
||||
jest.mock('../../../update/getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync');
|
||||
jest.mock('../../../update/queries');
|
||||
jest.mock('../../../ora', () => ({
|
||||
ora: () => ({
|
||||
@@ -55,7 +57,10 @@ jest.mock('../../../utils/code-signing');
|
||||
jest.mock('../../../fetch');
|
||||
|
||||
describe(UpdateRepublish.name, () => {
|
||||
afterEach(() => vol.reset());
|
||||
afterEach(() => {
|
||||
vol.reset();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('errors when providing both --group and --branch', async () => {
|
||||
const flags = ['--group=1234', '--branch=main'];
|
||||
@@ -87,6 +92,16 @@ describe(UpdateRepublish.name, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('errors when providing both --destination-channel and --destination-branch', async () => {
|
||||
const flags = ['--group=1234', '--destination-channel=test', '--destination-branch=wat'];
|
||||
|
||||
mockTestProject();
|
||||
|
||||
await expect(new UpdateRepublish(flags, commandOptions).run()).rejects.toThrow(
|
||||
/--destination-branch=wat cannot also be provided when using --destination-channel/
|
||||
);
|
||||
});
|
||||
|
||||
it('errors when update group is empty', async () => {
|
||||
const flags = ['--group=1234'];
|
||||
|
||||
@@ -269,7 +284,9 @@ describe(UpdateRepublish.name, () => {
|
||||
|
||||
mockTestProject();
|
||||
// Mock resolving the channel to branch name, only valid for a single branch connected
|
||||
jest.mocked(getBranchNameFromChannelNameAsync).mockResolvedValue('branchFromChannel');
|
||||
jest
|
||||
.mocked(getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync)
|
||||
.mockResolvedValue({ branchId: updateStub.branch.id, branchName: 'branchFromChannel' });
|
||||
// Mock the prompt to ask the user which update to republish, from branch
|
||||
jest.mocked(selectUpdateGroupOnBranchAsync).mockResolvedValue([updateStub]);
|
||||
// Mock mutations to store the new update
|
||||
@@ -304,6 +321,94 @@ describe(UpdateRepublish.name, () => {
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('republishes to a different branch when --destination-branch is supplied', async () => {
|
||||
const flags = ['--branch=branch123', '--message=test-republish', '--destination-branch=other'];
|
||||
|
||||
mockTestProject();
|
||||
// Mock the prompt to ask the user which update to republish, from branch
|
||||
jest.mocked(selectUpdateGroupOnBranchAsync).mockResolvedValue([updateStub]);
|
||||
// Mock mutations to store the new update
|
||||
jest.mocked(PublishMutation.publishUpdateGroupAsync).mockResolvedValue([
|
||||
{
|
||||
...updateStub,
|
||||
branch: { id: 'other-id', name: 'other' },
|
||||
id: 'update-new',
|
||||
platform: 'ios',
|
||||
manifestPermalink: 'https://expo.dev/@test/test-project/manifest',
|
||||
},
|
||||
]);
|
||||
jest.mocked(BranchQuery.getBranchByNameAsync).mockResolvedValue({
|
||||
id: 'other-id',
|
||||
name: 'other',
|
||||
});
|
||||
|
||||
await new UpdateRepublish(flags, commandOptions).run();
|
||||
|
||||
expect(selectUpdateGroupOnBranchAsync).toHaveBeenCalledWith(
|
||||
expect.any(Object), // graphql client
|
||||
expect.objectContaining({ branchName: 'branch123' })
|
||||
);
|
||||
|
||||
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalledWith(
|
||||
expect.any(Object), // graphql client
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
branchId: 'other-id',
|
||||
runtimeVersion: updateStub.runtimeVersion,
|
||||
updateInfoGroup: expect.objectContaining({
|
||||
ios: expect.any(Object),
|
||||
}),
|
||||
gitCommitHash: updateStub.gitCommitHash,
|
||||
awaitingCodeSigningInfo: false,
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('republishes to a different branch when --destination-channel is supplied', async () => {
|
||||
const flags = ['--branch=branch123', '--message=test-republish', '--destination-channel=blah'];
|
||||
|
||||
mockTestProject();
|
||||
// Mock the prompt to ask the user which update to republish, from branch
|
||||
jest.mocked(selectUpdateGroupOnBranchAsync).mockResolvedValue([updateStub]);
|
||||
// Mock mutations to store the new update
|
||||
jest.mocked(PublishMutation.publishUpdateGroupAsync).mockResolvedValue([
|
||||
{
|
||||
...updateStub,
|
||||
branch: { id: 'other-id', name: 'other' },
|
||||
id: 'update-new',
|
||||
platform: 'ios',
|
||||
manifestPermalink: 'https://expo.dev/@test/test-project/manifest',
|
||||
},
|
||||
]);
|
||||
|
||||
jest
|
||||
.mocked(getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync)
|
||||
.mockResolvedValue({ branchId: 'blah-id', branchName: 'blah' });
|
||||
|
||||
await new UpdateRepublish(flags, commandOptions).run();
|
||||
|
||||
expect(selectUpdateGroupOnBranchAsync).toHaveBeenCalledWith(
|
||||
expect.any(Object), // graphql client
|
||||
expect.objectContaining({ branchName: 'branch123' })
|
||||
);
|
||||
|
||||
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalledWith(
|
||||
expect.any(Object), // graphql client
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
branchId: 'blah-id',
|
||||
runtimeVersion: updateStub.runtimeVersion,
|
||||
updateInfoGroup: expect.objectContaining({
|
||||
ios: expect.any(Object),
|
||||
}),
|
||||
gitCommitHash: updateStub.gitCommitHash,
|
||||
awaitingCodeSigningInfo: false,
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
/** Create a new in-memory project, based on src/commands/project/__tests__/init.test.ts */
|
||||
@@ -367,6 +472,7 @@ function mockTestProject({
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
|
||||
Loaded 100 of 318 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user