Compare commits

..
Author SHA1 Message Date
Quinlan Jung 0bcd94e83f [eas-cli] eas updates: warn users if they near plan limits 2023-04-19 16:55:35 -07:00
174 changed files with 1691 additions and 5532 deletions

No files matched your search

+5 -2
View File
@@ -1,5 +1,8 @@
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. --> <!-- Thanks for contributing to _EAS CLI_! Before you submit your pull request, please make sure to check the following boxes by putting an x in the [ ] (don't: [x ], [ x], do: [x]) -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->
# Checklist
- [ ] I've added an entry to [CHANGELOG.md](https://github.com/expo/eas-cli/blob/main/CHANGELOG.md#main) if necessary. You can comment this pull request with `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]` and CHANGELOG.md will be updated automatically.
# Why # Why
-48
View File
@@ -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: 18
- name: Checkout Pull Request
run: hub pr checkout ${{ github.event.issue.number }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install dependencies
working-directory: ./scripts
run: yarn install --frozen-lockfile --check-files
- name: Add changelog entry
working-directory: ./scripts
run: |
PARAMS=${PR_COMMENT_BODY#/changelog-entry }
if [[ ! "$PARAMS" =~ " " ]]; then
echo "Invalid /changelog-entry command"
exit 1
fi
CATEGORY=$( cut -d ' ' -f 1 <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
MESSAGE=$( cut -d ' ' -f 2- <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
yarn changelog-entry "$CATEGORY" "$MESSAGE"
env:
GITHUB_PR_AUTHOR: ${{ github.event.issue.user.login }}
GITHUB_PR_NUMBER: ${{ github.event.issue.number }}
PR_COMMENT_BODY: ${{ github.event.comment.body }}
- name: Commit and push
run: |
# use the last commit author name and email
git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)"
git config --global user.email "$(git --no-pager log --format=format:'%ae' -n 1)"
git add CHANGELOG.md
git commit -m "update CHANGELOG.md"
git push
+40 -29
View File
@@ -1,37 +1,48 @@
name: CHANGELOG.md PR check
on: on:
pull_request: issue_comment:
types: types: [created]
- opened
- synchronize name: Changelog
- labeled
- unlabeled
jobs: jobs:
pr-changelog-entry: pr-commented:
name: Check CHANGELOG.md updated
runs-on: ubuntu-latest 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: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Check if CHANGELOG.md was updated
id: changelog-updated
uses: tj-actions/changed-files@v35
with: with:
files: CHANGELOG.md token: ${{ secrets.EXPO_BOT_PAT }}
- name: Fail if CHANGELOG.md was not updated and the "no changelog" label is absent - uses: actions/setup-node@v2
if: steps.changelog-updated.outputs.any_changed == 'false' && !contains(github.event.pull_request.labels.*.name, 'no changelog')
uses: actions/github-script@v6
with: with:
script: | node-version: 18
core.setFailed('Please add a changelog entry!') - name: Checkout Pull Request
- uses: mshick/[email protected] run: hub pr checkout ${{ github.event.issue.number }}
if: always() env:
with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
message-id: changelog-entry-check - name: Install dependencies
refresh-message-position: true working-directory: ./scripts
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!' }} run: yarn install --frozen-lockfile --check-files
message-failure: | - name: Add changelog entry
❌ 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. working-directory: ./scripts
🤖 To use the bot, simply comment on this PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. run: |
⏩ 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. 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
+2 -6
View File
@@ -1,5 +1,4 @@
name: Run tests name: Run tests
on: on:
push: push:
branches: [main] branches: [main]
@@ -26,23 +25,20 @@ jobs:
- run: yarn typecheck - run: yarn typecheck
- run: yarn test - run: yarn test
if: ${{ !matrix.coverage }} if: ${{ !matrix.coverage }}
env:
NODE_OPTIONS: --max-old-space-size=8192
- run: yarn test --coverage - run: yarn test --coverage
if: ${{ matrix.coverage }} if: ${{ matrix.coverage }}
env: env:
# Oclif seems to cause OOM errors when testing with coverage enabled. # Oclif seems to cause OOM errors when testing with coverage enabled.
# Remove this whenever we solved those OOM errors. # Remove this whenever we solved those OOM errors.
NODE_OPTIONS: --max-old-space-size=8192 NODE_OPTIONS: --max-old-space-size=4096
- run: yarn lint --max-warnings=0 - run: yarn lint --max-warnings=0
- run: yarn install --frozen-lockfile --check-files - run: yarn install --frozen-lockfile --check-files
working-directory: ./scripts working-directory: ./scripts
- run: yarn lint-changelog
- uses: codecov/codecov-action@v1 - uses: codecov/codecov-action@v1
if: ${{ matrix.coverage }} if: ${{ matrix.coverage }}
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
- run: yarn lint-changelog
notify-slack: notify-slack:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: test needs: test
+1 -128
View File
@@ -8,137 +8,10 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features ### 🎉 New features
- Show build profile when selecting a build for eas build:run. ([#1901](https://github.com/expo/eas-cli/pull/1901) by [@keith-kurak](https://github.com/keith-kurak))
- Adds -m alias to --message in update/republish and removed README comment. ([#1905](https://github.com/expo/eas-cli/pull/1905) by [@pusongqi](https://github.com/pusongqi))
### 🐛 Bug fixes ### 🐛 Bug fixes
### 🧹 Chores ### 🧹 Chores
- Better runtimeVersion output. ([#1894](https://github.com/expo/eas-cli/pull/1894) by [@quinlanj](https://github.com/quinlanj))
- Print better error message when uploading project archive tarball fails. ([#1897](https://github.com/expo/eas-cli/pull/1897) by [@szdziedzic](https://github.com/szdziedzic))
- Set useClassicUpdates to false on `eas update` and `eas update:configure`. ([#1914](https://github.com/expo/eas-cli/pull/1914) by [@quinlanj](https://github.com/quinlanj))
## [3.14.0](https://github.com/expo/eas-cli/releases/tag/v3.14.0) - 2023-06-20
### 🎉 New features
- Added support for SSO users. ([#1875](https://github.com/expo/eas-cli/pull/1875) by [@lzkb](https://github.com/lzkb))
- Added new bundle identifier capabilities and entitlements from WWDC23. ([#1870](https://github.com/expo/eas-cli/pull/1870) by [@EvanBacon](https://github.com/EvanBacon))
- Selecting default keystore via CLI. ([#1889](https://github.com/expo/eas-cli/pull/1889) by [@khamilowicz](https://github.com/khamilowicz))
### 🧹 Chores
- Change sso flag display. ([#1890](https://github.com/expo/eas-cli/pull/1890) by [@lzkb](https://github.com/lzkb))
- Build:configure -- add channels to eas.json if using eas updates. ([#1887](https://github.com/expo/eas-cli/pull/1887) by [@quinlanj](https://github.com/quinlanj))
- Eas update: Error gracefully if no git repo. ([#1884](https://github.com/expo/eas-cli/pull/1884) by [@quinlanj](https://github.com/quinlanj))
- Error gracefully if expo pkg not installed. ([#1883](https://github.com/expo/eas-cli/pull/1883) by [@quinlanj](https://github.com/quinlanj))
- Update `eas build:configure` command to show the link of `eas.json` when generated. ([#1878](https://github.com/expo/eas-cli/pull/1878) by [@amandeepmittal](https://github.com/amandeepmittal))
- Create app.json or add the "expo" key if either are missing, before modifying or reading the file. ([#1881](https://github.com/expo/eas-cli/pull/1881) by [@brentvatne](https://github.com/brentvatne))
- Include the original stack in re-thrown errors thrown from EAS CLI commands. ([#1882](https://github.com/expo/eas-cli/pull/1882) by [@brentvatne](https://github.com/brentvatne))
- Make `update:configure` less verbose. ([#1888](https://github.com/expo/eas-cli/pull/1888) by [@quinlanj](https://github.com/quinlanj))
- Improve validation for values from app config. ([#1893](https://github.com/expo/eas-cli/pull/1893) by [@wkozyra95](https://github.com/wkozyra95))
- Improve `expo-updates` validation for builds with `channel` property set. ([#1885](https://github.com/expo/eas-cli/pull/1885) by [@szdziedzic](https://github.com/szdziedzic))
## [3.13.3](https://github.com/expo/eas-cli/releases/tag/v3.13.3) - 2023-06-05
### 🐛 Bug fixes
- Show original GraphQL error message in case of an unexpected error. ([#1862](https://github.com/expo/eas-cli/pull/1862) by [@dsokal](https://github.com/dsokal))
- Fix updates synchronization of native files to include strings.xml for bare projects. ([#1865](https://github.com/expo/eas-cli/pull/1865) by [@wschurman](https://github.com/wschurman))
## [3.13.2](https://github.com/expo/eas-cli/releases/tag/v3.13.2) - 2023-05-26
### 🧹 Chores
- Refactor getExpoConfig to remove dangerous default. ([#1857](https://github.com/expo/eas-cli/pull/1857) by [@wschurman](https://github.com/wschurman))
- Add support for pending-cancel build status. ([#1855](https://github.com/expo/eas-cli/pull/1855) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
## [3.13.1](https://github.com/expo/eas-cli/releases/tag/v3.13.1) - 2023-05-24
### 🧹 Chores
- Short format for selecting devices prompt. ([#1840](https://github.com/expo/eas-cli/pull/1840) by [@khamilowicz](https://github.com/khamilowicz))
- Improve typescript types for user display. ([#1851](https://github.com/expo/eas-cli/pull/1851) by [@wschurman](https://github.com/wschurman))
- Add error for `large` resource class not available on the free plan to server-side defined errors. ([#1848](https://github.com/expo/eas-cli/pull/1848) by [@szdziedzic](https://github.com/szdziedzic))
## [3.13.0](https://github.com/expo/eas-cli/releases/tag/v3.13.0) - 2023-05-17
### 🎉 New features
- Add clear cache flag to eas update. ([#1839](https://github.com/expo/eas-cli/pull/1839) by [@quinlanj](https://github.com/quinlanj))
- Print EAS Update assets that timed out during upload or processing. ([#1849](https://github.com/expo/eas-cli/pull/1849) by [@wschurman](https://github.com/wschurman))
### 🐛 Bug fixes
- Fix building iOS projects with framework targets. ([#1835](https://github.com/expo/eas-cli/pull/1835) by [@dsokal](https://github.com/dsokal))
### 🧹 Chores
- Ignore dirty workingdir when building from GitHub. ([#1842](https://github.com/expo/eas-cli/pull/1842) by [@wkozyra95](https://github.com/wkozyra95))
- Remove App Specific Password prompt. ([#1843](https://github.com/expo/eas-cli/pull/1843) by [@quinlanj](https://github.com/quinlanj))
- Validate `CFBundleShortVersionString`. ([#1846](https://github.com/expo/eas-cli/pull/1846) by [@khamilowicz](https://github.com/khamilowicz))
## [3.12.1](https://github.com/expo/eas-cli/releases/tag/v3.12.1) - 2023-05-15
### 🐛 Bug fixes
- Don't submit expired builds. ([#1837](https://github.com/expo/eas-cli/pull/1837) by [@dsokal](https://github.com/dsokal))
### 🧹 Chores
- Change red apple icon to green apple icon on successful build. ([#1828](https://github.com/expo/eas-cli/pull/1828) by [@dchhetri](https://github.com/dchhetri))
## [3.12.0](https://github.com/expo/eas-cli/releases/tag/v3.12.0) - 2023-05-08
### 🎉 New features
- Add `build:version:get` command. ([#1815](https://github.com/expo/eas-cli/pull/1815) by [@wkozyra95](https://github.com/wkozyra95))
### 🧹 Chores
- Upgrade dependencies. ([#1824](https://github.com/expo/eas-cli/pull/1824) by [@dsokal](https://github.com/dsokal))
## [3.11.0](https://github.com/expo/eas-cli/releases/tag/v3.11.0) - 2023-05-05
### 🎉 New features
- Add account list to `eas whoami`/`eas account:view`. ([#1814](https://github.com/expo/eas-cli/pull/1814) by [@wschurman](https://github.com/wschurman))
- Add `large` resource class for iOS as allowed value in `eas.json`. ([#1817](https://github.com/expo/eas-cli/pull/1817) by [@szdziedzic](https://github.com/szdziedzic))
### 🧹 Chores
- Print request ID on unforeseen GraphQL error for easier tracking and follow up. ([#1813](https://github.com/expo/eas-cli/pull/1813) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
## [3.10.2](https://github.com/expo/eas-cli/releases/tag/v3.10.2) - 2023-04-25
### 🧹 Chores
- Add info about Xcode 14.3 image to `eas.schema.json`. ([#1808](https://github.com/expo/eas-cli/pull/1808) by [@szdziedzic](https://github.com/szdziedzic))
- Allow users to select an app to run using the `build:run` command if multiple apps are found in the tarball. ([#1807](https://github.com/expo/eas-cli/pull/1807) by [@szdziedzic](https://github.com/szdziedzic))
- Don't resolve `default` resource class for iOS in CLI. ([#1734](https://github.com/expo/eas-cli/pull/1734) by [@szdziedzic](https://github.com/szdziedzic))
## [3.10.1](https://github.com/expo/eas-cli/releases/tag/v3.10.1) - 2023-04-25
### 🧹 Chores
- Add `cache.paths` field and deprecate `cache.customPaths`. ([#1794](https://github.com/expo/eas-cli/pull/1794) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
## [3.10.0](https://github.com/expo/eas-cli/releases/tag/v3.10.0) - 2023-04-21
### 🎉 New features
- Added eas device:rename command. ([#1787](https://github.com/expo/eas-cli/pull/1787) by [@keith-kurak](https://github.com/keith-kurak))
### 🐛 Bug fixes
- Fix `device:delete` not disabling on Apple. ([#1803](https://github.com/expo/eas-cli/pull/1803) by [@keith-kurak](https://github.com/keith-kurak))
- Fix message truncation for updates. ([#1801](https://github.com/expo/eas-cli/pull/1801) by [@wschurman](https://github.com/wschurman))
### 🧹 Chores
- Throw an error if somebody tries to start iOS build with the `large` resource class selected using the deprecated `--resource-class` flag. ([#1795](https://github.com/expo/eas-cli/pull/1795) by [@szdziedzic](https://github.com/szdziedzic))
## [3.9.3](https://github.com/expo/eas-cli/releases/tag/v3.9.3) - 2023-04-18 ## [3.9.3](https://github.com/expo/eas-cli/releases/tag/v3.9.3) - 2023-04-18
### 🐛 Bug fixes ### 🐛 Bug fixes
@@ -216,7 +89,7 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features ### 🎉 New features
- Support variant detection for package\*UniversalApk Gradle Commands. ([#1708](https://github.com/expo/eas-cli/pull/1708) by [@frw](https://github.com/frw)) - Support variant detection for package*UniversalApk Gradle Commands. ([#1708](https://github.com/expo/eas-cli/pull/1708) by [@frw](https://github.com/frw))
## [3.6.1](https://github.com/expo/eas-cli/releases/tag/v3.6.1) - 2023-02-20 ## [3.6.1](https://github.com/expo/eas-cli/releases/tag/v3.6.1) - 2023-02-20
+6 -6
View File
@@ -1,10 +1,10 @@
* @szdziedzic @khamilowicz * @dsokal @wkozyra95
packages/eas-cli/src/build @szdziedzic @khamilowicz packages/eas-cli/src/build @dsokal @wkozyra95
packages/eas-cli/src/commands/build @szdziedzic @khamilowicz packages/eas-cli/src/commands/build @dsokal @wkozyra95
packages/eas-cli/src/submit @szdziedzic @khamilowicz packages/eas-cli/src/submit @dsokal
packages/eas-cli/src/commands/submit.ts @szdziedzic @khamilowicz packages/eas-cli/src/commands/submit.ts @dsokal
packages/eas-cli/src/update @EvanBacon @byCedric @wschurman packages/eas-cli/src/update @EvanBacon @byCedric @wschurman
packages/eas-cli/src/commands/update @EvanBacon @byCedric @wschurman packages/eas-cli/src/commands/update @EvanBacon @byCedric @wschurman
@@ -13,4 +13,4 @@ packages/eas-cli/schema @bycedric
packages/eas-cli/src/metadata @bycedric packages/eas-cli/src/metadata @bycedric
packages/eas-cli/src/commands/metadata @bycedric packages/eas-cli/src/commands/metadata @bycedric
packages/eas-json @szdziedzic @khamilowicz packages/eas-json @dsokal @wkozyra95
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"npmClient": "yarn", "npmClient": "yarn",
"useWorkspaces": true, "useWorkspaces": true,
"version": "3.14.0", "version": "3.9.3",
"$schema": "node_modules/lerna/schemas/lerna-schema.json" "$schema": "node_modules/lerna/schemas/lerna-schema.json"
} }
+130 -178
View File
@@ -55,69 +55,66 @@ eas --help COMMAND
# Commands # Commands
<!-- commands --> <!-- commands -->
* [`eas account:login`](#eas-accountlogin)
- [`eas account:login`](#eas-accountlogin) * [`eas account:logout`](#eas-accountlogout)
- [`eas account:logout`](#eas-accountlogout) * [`eas account:view`](#eas-accountview)
- [`eas account:view`](#eas-accountview) * [`eas analytics [STATUS]`](#eas-analytics-status)
- [`eas analytics [STATUS]`](#eas-analytics-status) * [`eas autocomplete [SHELL]`](#eas-autocomplete-shell)
- [`eas autocomplete [SHELL]`](#eas-autocomplete-shell) * [`eas branch:create [NAME]`](#eas-branchcreate-name)
- [`eas branch:create [NAME]`](#eas-branchcreate-name) * [`eas branch:delete [NAME]`](#eas-branchdelete-name)
- [`eas branch:delete [NAME]`](#eas-branchdelete-name) * [`eas branch:list`](#eas-branchlist)
- [`eas branch:list`](#eas-branchlist) * [`eas branch:rename`](#eas-branchrename)
- [`eas branch:rename`](#eas-branchrename) * [`eas branch:view [NAME]`](#eas-branchview-name)
- [`eas branch:view [NAME]`](#eas-branchview-name) * [`eas build`](#eas-build)
- [`eas build`](#eas-build) * [`eas build:cancel [BUILD_ID]`](#eas-buildcancel-build_id)
- [`eas build:cancel [BUILD_ID]`](#eas-buildcancel-build_id) * [`eas build:configure`](#eas-buildconfigure)
- [`eas build:configure`](#eas-buildconfigure) * [`eas build:inspect`](#eas-buildinspect)
- [`eas build:inspect`](#eas-buildinspect) * [`eas build:list`](#eas-buildlist)
- [`eas build:list`](#eas-buildlist) * [`eas build:resign`](#eas-buildresign)
- [`eas build:resign`](#eas-buildresign) * [`eas build:run`](#eas-buildrun)
- [`eas build:run`](#eas-buildrun) * [`eas build:submit`](#eas-buildsubmit)
- [`eas build:submit`](#eas-buildsubmit) * [`eas build:version:set`](#eas-buildversionset)
- [`eas build:version:get`](#eas-buildversionget) * [`eas build:version:sync`](#eas-buildversionsync)
- [`eas build:version:set`](#eas-buildversionset) * [`eas build:view [BUILD_ID]`](#eas-buildview-build_id)
- [`eas build:version:sync`](#eas-buildversionsync) * [`eas channel:create [NAME]`](#eas-channelcreate-name)
- [`eas build:view [BUILD_ID]`](#eas-buildview-build_id) * [`eas channel:edit [NAME]`](#eas-channeledit-name)
- [`eas channel:create [NAME]`](#eas-channelcreate-name) * [`eas channel:list`](#eas-channellist)
- [`eas channel:edit [NAME]`](#eas-channeledit-name) * [`eas channel:rollout [CHANNEL]`](#eas-channelrollout-channel)
- [`eas channel:list`](#eas-channellist) * [`eas channel:view [NAME]`](#eas-channelview-name)
- [`eas channel:rollout [CHANNEL]`](#eas-channelrollout-channel) * [`eas config`](#eas-config)
- [`eas channel:view [NAME]`](#eas-channelview-name) * [`eas credentials`](#eas-credentials)
- [`eas config`](#eas-config) * [`eas device:create`](#eas-devicecreate)
- [`eas credentials`](#eas-credentials) * [`eas device:delete`](#eas-devicedelete)
- [`eas device:create`](#eas-devicecreate) * [`eas device:list`](#eas-devicelist)
- [`eas device:delete`](#eas-devicedelete) * [`eas device:view [UDID]`](#eas-deviceview-udid)
- [`eas device:list`](#eas-devicelist) * [`eas diagnostics`](#eas-diagnostics)
- [`eas device:rename`](#eas-devicerename) * [`eas help [COMMAND]`](#eas-help-command)
- [`eas device:view [UDID]`](#eas-deviceview-udid) * [`eas init`](#eas-init)
- [`eas diagnostics`](#eas-diagnostics) * [`eas login`](#eas-login)
- [`eas help [COMMAND]`](#eas-help-command) * [`eas logout`](#eas-logout)
- [`eas init`](#eas-init) * [`eas metadata:lint`](#eas-metadatalint)
- [`eas login`](#eas-login) * [`eas metadata:pull`](#eas-metadatapull)
- [`eas logout`](#eas-logout) * [`eas metadata:push`](#eas-metadatapush)
- [`eas metadata:lint`](#eas-metadatalint) * [`eas open`](#eas-open)
- [`eas metadata:pull`](#eas-metadatapull) * [`eas project:info`](#eas-projectinfo)
- [`eas metadata:push`](#eas-metadatapush) * [`eas project:init`](#eas-projectinit)
- [`eas open`](#eas-open) * [`eas secret:create`](#eas-secretcreate)
- [`eas project:info`](#eas-projectinfo) * [`eas secret:delete`](#eas-secretdelete)
- [`eas project:init`](#eas-projectinit) * [`eas secret:list`](#eas-secretlist)
- [`eas secret:create`](#eas-secretcreate) * [`eas secret:push`](#eas-secretpush)
- [`eas secret:delete`](#eas-secretdelete) * [`eas submit`](#eas-submit)
- [`eas secret:list`](#eas-secretlist) * [`eas update`](#eas-update)
- [`eas secret:push`](#eas-secretpush) * [`eas update:configure`](#eas-updateconfigure)
- [`eas submit`](#eas-submit) * [`eas update:delete GROUPID`](#eas-updatedelete-groupid)
- [`eas update`](#eas-update) * [`eas update:list`](#eas-updatelist)
- [`eas update:configure`](#eas-updateconfigure) * [`eas update:republish`](#eas-updaterepublish)
- [`eas update:delete GROUPID`](#eas-updatedelete-groupid) * [`eas update:view GROUPID`](#eas-updateview-groupid)
- [`eas update:list`](#eas-updatelist) * [`eas webhook:create`](#eas-webhookcreate)
- [`eas update:republish`](#eas-updaterepublish) * [`eas webhook:delete [ID]`](#eas-webhookdelete-id)
- [`eas update:view GROUPID`](#eas-updateview-groupid) * [`eas webhook:list`](#eas-webhooklist)
- [`eas webhook:create`](#eas-webhookcreate) * [`eas webhook:update`](#eas-webhookupdate)
- [`eas webhook:delete [ID]`](#eas-webhookdelete-id) * [`eas webhook:view ID`](#eas-webhookview-id)
- [`eas webhook:list`](#eas-webhooklist) * [`eas whoami`](#eas-whoami)
- [`eas webhook:update`](#eas-webhookupdate)
- [`eas webhook:view ID`](#eas-webhookview-id)
- [`eas whoami`](#eas-whoami)
## `eas account:login` ## `eas account:login`
@@ -134,7 +131,7 @@ ALIASES
$ eas login $ eas login
``` ```
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/account/login.ts)_ _See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout` ## `eas account:logout`
@@ -151,7 +148,7 @@ ALIASES
$ eas logout $ eas logout
``` ```
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/account/logout.ts)_ _See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view` ## `eas account:view`
@@ -168,7 +165,7 @@ ALIASES
$ eas whoami $ eas whoami
``` ```
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/account/view.ts)_ _See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]` ## `eas analytics [STATUS]`
@@ -182,7 +179,7 @@ DESCRIPTION
display or change analytics settings display or change analytics settings
``` ```
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/analytics.ts)_ _See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]` ## `eas autocomplete [SHELL]`
@@ -232,7 +229,7 @@ DESCRIPTION
create a branch create a branch
``` ```
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/branch/create.ts)_ _See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]` ## `eas branch:delete [NAME]`
@@ -253,7 +250,7 @@ DESCRIPTION
delete a branch delete a branch
``` ```
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/branch/delete.ts)_ _See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list` ## `eas branch:list`
@@ -273,7 +270,7 @@ DESCRIPTION
list all branches list all branches
``` ```
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/branch/list.ts)_ _See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename` ## `eas branch:rename`
@@ -293,7 +290,7 @@ DESCRIPTION
rename a branch rename a branch
``` ```
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/branch/rename.ts)_ _See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]` ## `eas branch:view [NAME]`
@@ -316,7 +313,7 @@ DESCRIPTION
view a branch view a branch
``` ```
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/branch/view.ts)_ _See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build` ## `eas build`
@@ -346,7 +343,7 @@ DESCRIPTION
start a build start a build
``` ```
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/index.ts)_ _See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]` ## `eas build:cancel [BUILD_ID]`
@@ -363,7 +360,7 @@ DESCRIPTION
cancel a build cancel a build
``` ```
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/cancel.ts)_ _See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure` ## `eas build:configure`
@@ -380,7 +377,7 @@ DESCRIPTION
configure the project to support EAS Build configure the project to support EAS Build
``` ```
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/configure.ts)_ _See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:inspect` ## `eas build:inspect`
@@ -415,7 +412,7 @@ DESCRIPTION
inspect the state of the project at specific build stages, useful for troubleshooting inspect the state of the project at specific build stages, useful for troubleshooting
``` ```
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/inspect.ts)_ _See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list` ## `eas build:list`
@@ -423,11 +420,10 @@ list all builds for your project
``` ```
USAGE USAGE
$ eas build:list [--platform all|android|ios] [--status $ eas build:list [--platform all|android|ios] [--status new|in-queue|in-progress|errored|finished|canceled]
new|in-queue|in-progress|pending-cancel|errored|finished|canceled] [--distribution store|internal|simulator] [--distribution store|internal|simulator] [--channel <value>] [--appVersion <value>] [--appBuildVersion <value>]
[--channel <value>] [--appVersion <value>] [--appBuildVersion <value>] [--sdkVersion <value>] [--runtimeVersion [--sdkVersion <value>] [--runtimeVersion <value>] [--appIdentifier <value>] [--buildProfile <value>]
<value>] [--appIdentifier <value>] [--buildProfile <value>] [--gitCommitHash <value>] [--offset <value>] [--limit [--gitCommitHash <value>] [--offset <value>] [--limit <value>] [--json --non-interactive]
<value>] [--json --non-interactive]
FLAGS FLAGS
--appBuildVersion=<value> --appBuildVersion=<value>
@@ -437,23 +433,23 @@ FLAGS
--channel=<value> --channel=<value>
--distribution=(store|internal|simulator) --distribution=(store|internal|simulator)
--gitCommitHash=<value> --gitCommitHash=<value>
--json Enable JSON output, non-JSON messages --json Enable JSON output, non-JSON messages will be printed
will be printed to stderr. to stderr.
--limit=<value> The number of items to fetch each query. --limit=<value> The number of items to fetch each query. Defaults to 10
Defaults to 10 and is capped at 50. and is capped at 50.
--non-interactive Run the command in non-interactive mode. --non-interactive Run the command in non-interactive mode.
--offset=<value> Start queries from specified index. Use --offset=<value> Start queries from specified index. Use for paginating
for paginating results. Defaults to 0. results. Defaults to 0.
--platform=(all|android|ios) --platform=(all|android|ios)
--runtimeVersion=<value> --runtimeVersion=<value>
--sdkVersion=<value> --sdkVersion=<value>
--status=(new|in-queue|in-progress|pending-cancel|errored|finished|canceled) --status=(new|in-queue|in-progress|errored|finished|canceled)
DESCRIPTION DESCRIPTION
list all builds for your project list all builds for your project
``` ```
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/list.ts)_ _See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:resign` ## `eas build:resign`
@@ -479,7 +475,7 @@ DESCRIPTION
re-sign a build archive re-sign a build archive
``` ```
_See code: [src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/resign.ts)_ _See code: [src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/resign.ts)_
## `eas build:run` ## `eas build:run`
@@ -503,7 +499,7 @@ DESCRIPTION
run simulator/emulator builds from eas-cli run simulator/emulator builds from eas-cli
``` ```
_See code: [src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/run.ts)_ _See code: [src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/run.ts)_
## `eas build:submit` ## `eas build:submit`
@@ -533,30 +529,9 @@ ALIASES
$ eas build:submit $ eas build:submit
``` ```
## `eas build:version:get`
get the latest version from EAS servers
```
USAGE
$ eas build:version:get [-p android|ios|all] [-e <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.
-p, --platform=(android|ios|all)
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
DESCRIPTION
get the latest version from EAS servers
```
_See code: [src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/version/get.ts)_
## `eas build:version:set` ## `eas build:version:set`
update version of an app Update version of an app.
``` ```
USAGE USAGE
@@ -568,14 +543,14 @@ FLAGS
-p, --platform=(android|ios) -p, --platform=(android|ios)
DESCRIPTION DESCRIPTION
update version of an app Update version of an app.
``` ```
_See code: [src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/version/set.ts)_ _See code: [src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/version/set.ts)_
## `eas build:version:sync` ## `eas build:version:sync`
update a version in native code with a value stored on EAS servers Update a version in native code with a value stored on EAS servers
``` ```
USAGE USAGE
@@ -587,10 +562,10 @@ FLAGS
-p, --platform=(android|ios|all) -p, --platform=(android|ios|all)
DESCRIPTION DESCRIPTION
update a version in native code with a value stored on EAS servers Update a version in native code with a value stored on EAS servers
``` ```
_See code: [src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/version/sync.ts)_ _See code: [src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]` ## `eas build:view [BUILD_ID]`
@@ -607,7 +582,7 @@ DESCRIPTION
view a build for your project view a build for your project
``` ```
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/build/view.ts)_ _See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]` ## `eas channel:create [NAME]`
@@ -628,7 +603,7 @@ DESCRIPTION
create a channel create a channel
``` ```
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/channel/create.ts)_ _See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:edit [NAME]` ## `eas channel:edit [NAME]`
@@ -650,7 +625,7 @@ DESCRIPTION
point a channel at a new branch point a channel at a new branch
``` ```
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/channel/edit.ts)_ _See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list` ## `eas channel:list`
@@ -670,7 +645,7 @@ DESCRIPTION
list all channels list all channels
``` ```
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/channel/list.ts)_ _See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:rollout [CHANNEL]` ## `eas channel:rollout [CHANNEL]`
@@ -694,7 +669,7 @@ DESCRIPTION
Roll a new branch out on a channel incrementally. Roll a new branch out on a channel incrementally.
``` ```
_See code: [src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/channel/rollout.ts)_ _See code: [src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/channel/rollout.ts)_
## `eas channel:view [NAME]` ## `eas channel:view [NAME]`
@@ -717,7 +692,7 @@ DESCRIPTION
view a channel view a channel
``` ```
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/channel/view.ts)_ _See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config` ## `eas config`
@@ -738,7 +713,7 @@ DESCRIPTION
display project configuration (app.json + eas.json) display project configuration (app.json + eas.json)
``` ```
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/config.ts)_ _See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/config.ts)_
## `eas credentials` ## `eas credentials`
@@ -755,7 +730,7 @@ DESCRIPTION
manage credentials manage credentials
``` ```
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/credentials.ts)_ _See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/credentials.ts)_
## `eas device:create` ## `eas device:create`
@@ -769,7 +744,7 @@ DESCRIPTION
register new Apple Devices to use for internal distribution register new Apple Devices to use for internal distribution
``` ```
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/device/create.ts)_ _See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete` ## `eas device:delete`
@@ -789,7 +764,7 @@ DESCRIPTION
remove a registered device from your account remove a registered device from your account
``` ```
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/device/delete.ts)_ _See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list` ## `eas device:list`
@@ -810,28 +785,7 @@ DESCRIPTION
list all registered devices for your account list all registered devices for your account
``` ```
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/device/list.ts)_ _See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:rename`
rename a registered device
```
USAGE
$ eas device:rename [--apple-team-id <value>] [--udid <value>] [--name <value>] [--json --non-interactive]
FLAGS
--apple-team-id=<value> The Apple team ID on which to find the device
--json Enable JSON output, non-JSON messages will be printed to stderr.
--name=<value> The new name for the device
--non-interactive Run the command in non-interactive mode.
--udid=<value> The Apple device ID to rename
DESCRIPTION
rename a registered device
```
_See code: [src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/device/rename.ts)_
## `eas device:view [UDID]` ## `eas device:view [UDID]`
@@ -845,7 +799,7 @@ DESCRIPTION
view a device for your project view a device for your project
``` ```
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/device/view.ts)_ _See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics` ## `eas diagnostics`
@@ -859,7 +813,7 @@ DESCRIPTION
display environment info display environment info
``` ```
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/diagnostics.ts)_ _See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas help [COMMAND]` ## `eas help [COMMAND]`
@@ -947,7 +901,7 @@ DESCRIPTION
validate the local store configuration validate the local store configuration
``` ```
_See code: [src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/metadata/lint.ts)_ _See code: [src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/metadata/lint.ts)_
## `eas metadata:pull` ## `eas metadata:pull`
@@ -964,7 +918,7 @@ DESCRIPTION
generate the local store configuration from the app stores generate the local store configuration from the app stores
``` ```
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/metadata/pull.ts)_ _See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push` ## `eas metadata:push`
@@ -981,7 +935,7 @@ DESCRIPTION
sync the local store configuration to the app stores sync the local store configuration to the app stores
``` ```
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/metadata/push.ts)_ _See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas open` ## `eas open`
@@ -995,7 +949,7 @@ DESCRIPTION
open the project page in a web browser open the project page in a web browser
``` ```
_See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/open.ts)_ _See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/open.ts)_
## `eas project:info` ## `eas project:info`
@@ -1009,7 +963,7 @@ DESCRIPTION
information about the current project information about the current project
``` ```
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/project/info.ts)_ _See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init` ## `eas project:init`
@@ -1031,7 +985,7 @@ ALIASES
$ eas init $ eas init
``` ```
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/project/init.ts)_ _See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/project/init.ts)_
## `eas secret:create` ## `eas secret:create`
@@ -1054,7 +1008,7 @@ DESCRIPTION
create an environment secret on the current project or owner account create an environment secret on the current project or owner account
``` ```
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/secret/create.ts)_ _See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/secret/create.ts)_
## `eas secret:delete` ## `eas secret:delete`
@@ -1072,7 +1026,7 @@ DESCRIPTION
delete an environment secret by ID delete an environment secret by ID
``` ```
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/secret/delete.ts)_ _See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/secret/delete.ts)_
## `eas secret:list` ## `eas secret:list`
@@ -1086,7 +1040,7 @@ DESCRIPTION
list environment secrets available for your current app list environment secrets available for your current app
``` ```
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/secret/list.ts)_ _See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/secret/list.ts)_
## `eas secret:push` ## `eas secret:push`
@@ -1106,7 +1060,7 @@ DESCRIPTION
read environment secrets from env file and store on the server read environment secrets from env file and store on the server
``` ```
_See code: [src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/secret/push.ts)_ _See code: [src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/secret/push.ts)_
## `eas submit` ## `eas submit`
@@ -1136,7 +1090,7 @@ ALIASES
$ eas build:submit $ eas build:submit
``` ```
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/submit.ts)_ _See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/submit.ts)_
## `eas update` ## `eas update`
@@ -1145,15 +1099,14 @@ publish an update group
``` ```
USAGE USAGE
$ eas update [--branch <value>] [--channel <value>] [--message <value>] [--republish | --input-dir <value> $ eas update [--branch <value>] [--channel <value>] [--message <value>] [--republish | --input-dir <value>
| --skip-bundler] [--group <value> | | ] [--clear-cache] [-p android|ios|all] [--auto] [--private-key-path <value>] | --skip-bundler] [--group <value> | | ] [-p android|ios|all] [--auto] [--private-key-path <value>] [--json
[--json --non-interactive] --non-interactive]
FLAGS FLAGS
-p, --platform=(android|ios|all) [default: all] -p, --platform=(android|ios|all) [default: all]
--auto Use the current git branch and commit message for the EAS branch and update message --auto Use the current git branch and commit message for the EAS branch and update message
--branch=<value> Branch to publish the update group on --branch=<value> Branch to publish the update group on
--channel=<value> Channel that the published update should affect --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) --group=<value> Update group to republish (deprecated, see republish command)
--input-dir=<value> [default: dist] Location of the bundle --input-dir=<value> [default: dist] Location of the bundle
--json Enable JSON output, non-JSON messages will be printed to stderr. --json Enable JSON output, non-JSON messages will be printed to stderr.
@@ -1169,7 +1122,7 @@ DESCRIPTION
publish an update group publish an update group
``` ```
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/update/index.ts)_ _See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure` ## `eas update:configure`
@@ -1187,7 +1140,7 @@ DESCRIPTION
configure the project to support EAS Update configure the project to support EAS Update
``` ```
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/update/configure.ts)_ _See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID` ## `eas update:delete GROUPID`
@@ -1208,7 +1161,7 @@ DESCRIPTION
delete all the updates in an update group delete all the updates in an update group
``` ```
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/update/delete.ts)_ _See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:list` ## `eas update:list`
@@ -1230,7 +1183,7 @@ DESCRIPTION
view the recent updates view the recent updates
``` ```
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/update/list.ts)_ _See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:republish` ## `eas update:republish`
@@ -1254,7 +1207,7 @@ DESCRIPTION
roll back to an existing update roll back to an existing update
``` ```
_See code: [src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/update/republish.ts)_ _See code: [src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/update/republish.ts)_
## `eas update:view GROUPID` ## `eas update:view GROUPID`
@@ -1274,7 +1227,7 @@ DESCRIPTION
update group details update group details
``` ```
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/update/view.ts)_ _See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/update/view.ts)_
## `eas webhook:create` ## `eas webhook:create`
@@ -1295,7 +1248,7 @@ DESCRIPTION
create a webhook create a webhook
``` ```
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/webhook/create.ts)_ _See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]` ## `eas webhook:delete [ID]`
@@ -1315,7 +1268,7 @@ DESCRIPTION
delete a webhook delete a webhook
``` ```
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/webhook/delete.ts)_ _See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list` ## `eas webhook:list`
@@ -1333,7 +1286,7 @@ DESCRIPTION
list webhooks list webhooks
``` ```
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/webhook/list.ts)_ _See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update` ## `eas webhook:update`
@@ -1355,7 +1308,7 @@ DESCRIPTION
update a webhook update a webhook
``` ```
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/webhook/update.ts)_ _See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID` ## `eas webhook:view ID`
@@ -1372,7 +1325,7 @@ DESCRIPTION
view a webhook view a webhook
``` ```
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v3.14.0/packages/eas-cli/src/commands/webhook/view.ts)_ _See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v3.9.3/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami` ## `eas whoami`
@@ -1388,5 +1341,4 @@ DESCRIPTION
ALIASES ALIASES
$ eas whoami $ eas whoami
``` ```
<!-- commandsstop --> <!-- commandsstop -->
+206 -984
View File
File diff suppressed because it is too large. Load diff
-1
View File
@@ -2,5 +2,4 @@ module.exports = async () => {
process.env.TZ = 'UTC'; process.env.TZ = 'UTC';
process.env.EAS_NO_VCS = '1'; process.env.EAS_NO_VCS = '1';
process.env.EAS_PROJECT_ROOT = '/app'; process.env.EAS_PROJECT_ROOT = '/app';
process.env.FORCE_COLOR = '1';
}; };
+8 -9
View File
@@ -1,20 +1,20 @@
{ {
"name": "eas-cli", "name": "eas-cli",
"description": "EAS command line tool", "description": "EAS command line tool",
"version": "3.14.0", "version": "3.9.3",
"author": "Expo <[email protected]>", "author": "Expo <[email protected]>",
"bin": { "bin": {
"eas": "./bin/run" "eas": "./bin/run"
}, },
"bugs": "https://github.com/expo/eas-cli/issues", "bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": { "dependencies": {
"@expo/apple-utils": "1.3.1", "@expo/apple-utils": "1.2.0",
"@expo/code-signing-certificates": "0.0.5", "@expo/code-signing-certificates": "0.0.5",
"@expo/config": "8.1.2", "@expo/config": "7.0.3",
"@expo/config-plugins": "7.2.3", "@expo/config-plugins": "5.0.4",
"@expo/config-types": "49.0.0", "@expo/config-types": "47.0.0",
"@expo/eas-build-job": "1.0.13", "@expo/eas-build-job": "1.0.7",
"@expo/eas-json": "3.13.0", "@expo/eas-json": "3.9.2",
"@expo/json-file": "8.2.37", "@expo/json-file": "8.2.37",
"@expo/multipart-body-parser": "1.1.0", "@expo/multipart-body-parser": "1.1.0",
"@expo/osascript": "2.0.33", "@expo/osascript": "2.0.33",
@@ -27,7 +27,7 @@
"@expo/results": "1.0.0", "@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1", "@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.0", "@expo/spawn-async": "1.7.0",
"@expo/steps": "1.0.23", "@expo/steps": "1.0.8",
"@expo/timeago.js": "1.0.0", "@expo/timeago.js": "1.0.0",
"@oclif/core": "1.23.2", "@oclif/core": "1.23.2",
"@oclif/plugin-autocomplete": "1.3.10", "@oclif/plugin-autocomplete": "1.3.10",
@@ -47,7 +47,6 @@
"fast-glob": "3.2.12", "fast-glob": "3.2.12",
"figures": "3.2.0", "figures": "3.2.0",
"form-data": "4.0.0", "form-data": "4.0.0",
"freeport-async": "2.0.0",
"fs-extra": "10.1.0", "fs-extra": "10.1.0",
"getenv": "1.0.0", "getenv": "1.0.0",
"gradle-to-js": "2.0.1", "gradle-to-js": "2.0.1",
@@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BuildVersionGetView reading version when appVersionSource is set to local 1`] = `"This project is not configured for using remote version source. Add {"cli": { "appVersionSource": "remote" }} in eas.json or re-run this command without "--non-interactive" flag."`;
@@ -1,161 +0,0 @@
import { AppVersionSource, EasJson } from '@expo/eas-json';
import chalk from 'chalk';
import BuildVersionGetView from '../../commands/build/version/get';
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
import Log from '../../log';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { getMockEasJson, mockCommandContext, mockProjectId, mockTestCommand } from './utils';
jest.mock('../../project/applicationIdentifier');
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,
},
};
}
describe(BuildVersionGetView, () => {
afterEach(() => {
jest.clearAllMocks();
});
test('reading version for platform android', async () => {
const ctx = mockCommandContext(BuildVersionGetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '100',
storeVersion: '1.0.0',
}));
const cmd = mockTestCommand(BuildVersionGetView, ['--platform=android'], ctx);
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()),
});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
const cmd = mockTestCommand(BuildVersionGetView, ['--platform=android'], ctx);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
expect(Log.log).toHaveBeenCalledWith(`No remote versions are configured for this project.`);
expect(enableJsonOutput).not.toHaveBeenCalled();
expect(printJsonOnlyOutput).not.toHaveBeenCalled();
});
test('reading version with --json flag for platform android', async () => {
const ctx = mockCommandContext(BuildVersionGetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '100',
storeVersion: '1.0.0',
}));
const cmd = mockTestCommand(
BuildVersionGetView,
['--non-interactive', '--json', '--platform=android'],
ctx
);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
expect(enableJsonOutput).toHaveBeenCalled();
expect(printJsonOnlyOutput).toHaveBeenCalledWith({
versionCode: '100',
});
});
test('reading version with --json flag for platform ios', async () => {
const ctx = mockCommandContext(BuildVersionGetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '100',
storeVersion: '1.0.0',
}));
const cmd = mockTestCommand(
BuildVersionGetView,
['--non-interactive', '--json', '--platform=ios'],
ctx
);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'IOS',
'eas.test.com'
);
expect(enableJsonOutput).toHaveBeenCalled();
expect(printJsonOnlyOutput).toHaveBeenCalledWith({
buildNumber: '100',
});
});
test('reading version with --json flag when no remote version is set', async () => {
const ctx = mockCommandContext(BuildVersionGetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
const cmd = mockTestCommand(
BuildVersionGetView,
['--non-interactive', '--json', '--platform=android'],
ctx
);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
expect(enableJsonOutput).toHaveBeenCalled();
expect(printJsonOnlyOutput).toHaveBeenCalledWith({});
});
test('reading version when appVersionSource is set to local ', 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.toThrowErrorMatchingSnapshot();
});
});
@@ -1,140 +0,0 @@
import { AppVersionSource, EasJson } from '@expo/eas-json';
import fs from 'fs-extra';
import path from 'path';
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 * as prompts from '../../prompts';
import {
getMockAppFragment,
getMockEasJson,
mockCommandContext,
mockProjectId,
mockTestCommand,
} from './utils';
jest.mock('../../project/applicationIdentifier');
jest.mock('../../graphql/queries/AppVersionQuery');
jest.mock('../../graphql/queries/AppQuery');
jest.mock('../../graphql/mutations/AppVersionMutation');
jest.mock('fs');
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();
});
test('setting version for platform android', async () => {
const ctx = mockCommandContext(BuildVersionSetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
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()),
});
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '100',
storeVersion: '1.0.0',
}));
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
await cmd.run();
expect(Log.log).toHaveBeenCalledWith(expect.stringMatching('configured with versionCode 100'));
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
});
test('printing info that there is no remote version configured', async () => {
const ctx = mockCommandContext(BuildVersionSetView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => null);
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
const cmd = mockTestCommand(BuildVersionSetView, ['--platform=android'], ctx);
await cmd.run();
expect(Log.log).toHaveBeenCalledWith(
expect.stringMatching('does not have any versionCode configured')
);
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'ANDROID',
'eas.test.com'
);
});
test('setting version aborts when appVersionSource is set to local and users 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.confirmAsync).mockImplementation(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, {});
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);
await cmd.run();
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
});
});
@@ -1,204 +0,0 @@
import { Workflow } from '@expo/eas-build-job';
import { AppVersionSource, EasJson } from '@expo/eas-json';
import fs from 'fs-extra';
import path from 'path';
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../build/android/version';
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../build/ios/version';
import BuildVersionSyncView from '../../commands/build/version/sync';
import { Target } from '../../credentials/ios/types';
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
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 { resolveWorkflowAsync } from '../../project/workflow';
import * as prompts from '../../prompts';
import {
getMockAppFragment,
getMockEasJson,
mockCommandContext,
mockProjectId,
mockTestCommand,
} from './utils';
jest.mock('../../build/android/version');
jest.mock('../../build/ios/version');
jest.mock('../../project/applicationIdentifier');
jest.mock('../../graphql/queries/AppVersionQuery');
jest.mock('../../graphql/queries/AppQuery');
jest.mock('../../graphql/mutations/AppVersionMutation');
jest.mock('../../project/workflow');
jest.mock('../../project/android/gradleUtils');
jest.mock('../../project/ios/target');
jest.mock('../../project/ios/scheme');
jest.mock('fs');
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();
});
test('syncing version for managed project on platform android', async () => {
const ctx = mockCommandContext(BuildVersionSyncView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '1000',
storeVersion: '0.0.1',
}));
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
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()),
});
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '1000',
storeVersion: '0.0.1',
}));
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
jest.mocked(resolveWorkflowAsync).mockImplementation(async () => Workflow.GENERIC);
jest.mocked(getAppBuildGradleAsync).mockImplementation(async () => ({
android: {},
}));
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).toHaveBeenCalled();
expect(updateAndroidNativeVersionsAsync).toHaveBeenCalledWith({
projectDir: ctx.projectDir,
versionCode: 1000,
});
});
test('syncing version for managed project on platform ios', async () => {
const ctx = mockCommandContext(BuildVersionSyncView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '1000',
storeVersion: '0.0.1',
}));
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
jest.mocked(resolveWorkflowAsync).mockImplementation(async () => Workflow.MANAGED);
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=ios'], ctx);
const syncIosAsync = jest.spyOn(cmd, 'syncIosAsync' as any);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'IOS',
'eas.test.com'
);
expect(syncIosAsync).not.toHaveBeenCalled();
});
test('syncing version for bare project on platform ios', async () => {
const ctx = mockCommandContext(BuildVersionSyncView, {
easJson: withRemoteVersionSource(getMockEasJson()),
});
const fakeTarget: Target = {
targetName: 'testapp',
bundleIdentifier: 'eas.test.com',
entitlements: {},
};
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest.mocked(AppVersionQuery.latestVersionAsync).mockImplementation(async () => ({
buildVersion: '1000',
storeVersion: '0.0.1',
}));
jest.mocked(prompts.promptAsync).mockImplementation(async () => ({
version: '1000',
}));
jest.mocked(resolveWorkflowAsync).mockImplementation(async () => Workflow.GENERIC);
jest.mocked(resolveTargetsAsync).mockImplementation(async () => [fakeTarget]);
const cmd = mockTestCommand(BuildVersionSyncView, ['--platform=ios'], ctx);
const syncIosAsync = jest.spyOn(cmd, 'syncIosAsync' as any);
await cmd.run();
expect(AppVersionQuery.latestVersionAsync).toHaveBeenCalledWith(
ctx.loggedIn.graphqlClient,
mockProjectId,
'IOS',
'eas.test.com'
);
expect(syncIosAsync).toHaveBeenCalled();
expect(updateIosNativeVersionsAsync).toHaveBeenCalledWith({
buildNumber: '1000',
projectDir: ctx.projectDir,
targets: [fakeTarget],
});
});
test('syncing version aborts when appVersionSource is set to local and users 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.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, {});
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);
await cmd.run();
const easJsonAfterCmd = await fs.readJson(path.join(ctx.projectDir, 'eas.json'));
expect(easJsonAfterCmd.cli.appVersionSource).toBe('remote');
});
});
@@ -1,149 +0,0 @@
import { ExpoConfig } from '@expo/config-types';
import { EasJson } from '@expo/eas-json';
import { Command, Config } from '@oclif/core';
import { vol } from 'memfs';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { ContextInput, ContextOutput } from '../../commandUtils/EasCommand';
import { DynamicConfigContextFn } from '../../commandUtils/context/DynamicProjectConfigContextField';
import { AppFragment, Role } from '../../graphql/generated';
export function getMockEasJson(): EasJson {
return {
cli: {},
build: {
production: {},
},
};
}
export const mockProjectId = 'fake-project-id';
export function getMockAppFragment(): AppFragment {
return {
id: mockProjectId,
slug: 'testapp',
fullName: '@testuser/testpp',
ownerAccount: {
id: 'test-account-id',
name: 'testuser',
users: [
{
role: Role.Owner,
actor: {
id: 'test-user-id',
},
},
],
},
};
}
export function getMockExpoConfig(): ExpoConfig {
return {
name: 'testapp',
slug: 'tetsapp',
extra: {
projectId: mockProjectId,
},
};
}
export interface LocalProjectContext {
projectDir: string;
exp?: ExpoConfig;
appId?: string;
}
export function mockCommandContext<
C extends {
[name: string]: any;
} = object
>(
commandClass: { contextDefinition: ContextInput<C> },
overrides: {
easJson?: EasJson;
exp?: ExpoConfig;
projectId?: string;
getDynamicPrivateProjectConfigAsync?: DynamicConfigContextFn;
getDynamicPublicProjectConfigAsync?: DynamicConfigContextFn;
}
): ContextOutput<C> {
const projectDir = path.join('/test', uuidv4());
vol.reset();
vol.fromJSON({ 'eas.json': JSON.stringify(overrides.easJson ?? getMockEasJson()) }, projectDir);
const contextDefinition = commandClass.contextDefinition;
const result: any = {};
for (const [contextKey] of Object.entries(contextDefinition)) {
if (contextKey === 'loggedIn') {
result.loggedIn = {};
}
if (contextKey === 'projectDir') {
result.projectDir = projectDir;
}
if (contextKey === 'maybeLoggedIn') {
result.maybeLoggedIn = {};
}
if (contextKey === 'sessionManager') {
result.sessionManager = {};
}
if (contextKey === 'projectConfig') {
result.projectConfig = {
exp: overrides.exp ?? getMockExpoConfig(),
projectId: overrides.projectId ?? mockProjectId,
projectDir,
};
}
if (contextKey === 'getDynamicPrivateProjectConfigAsync') {
result.getDynamicPrivateProjectConfigAsync = () => ({
exp: overrides.exp ?? getMockExpoConfig(),
projectId: overrides.projectId ?? mockProjectId,
projectDir,
});
}
if (contextKey === 'getDynamicPublicProjectConfigAsync') {
result.getDynamicPublicProjectConfigAsync = () => ({
exp: overrides.exp ?? getMockExpoConfig(),
projectId: overrides.projectId ?? mockProjectId,
projectDir,
});
}
}
return result;
}
export function mockTestCommand<T extends Command>(
commandConstructor: new (argv: string[], config: Config) => T,
argv: string[],
ctx: any
): T {
const cmd = new commandConstructor(argv, new Config({ root: __dirname }));
(cmd as any).getContextAsync = jest.fn(() => ctx);
return cmd;
}
export class NoErrorThrownError extends Error {}
export const getErrorAsync = async <TError = any>(
call: () => unknown
): Promise<TError | NoErrorThrownError> => {
try {
await call();
throw new NoErrorThrownError();
} catch (error: unknown) {
return error as TError;
}
};
export const getError = <TError = any>(call: () => unknown): TError | NoErrorThrownError => {
try {
call();
throw new NoErrorThrownError();
} catch (error: unknown) {
return error as TError;
}
};
-12
View File
@@ -2,7 +2,6 @@ import { JSONValue } from '@expo/json-file';
import { ApiV2Error } from './ApiV2Error'; import { ApiV2Error } from './ApiV2Error';
import fetch, { RequestError, RequestInit } from './fetch'; import fetch, { RequestError, RequestInit } from './fetch';
import { getFreePortAsync } from './utils/port';
interface RequestOptions { interface RequestOptions {
body: JSONValue; body: JSONValue;
@@ -105,14 +104,3 @@ export function getEASUpdateURL(projectId: string): string {
return new URL(projectId, `https://u.expo.dev`).href; return new URL(projectId, `https://u.expo.dev`).href;
} }
} }
export async function getSsoLocalServerPortAsync(): Promise<number> {
let startPort: number;
if (process.env.SSO_LOCAL_SERVER_PORT) {
startPort = Number(process.env.SSO_LOCAL_SERVER_PORT);
} else {
startPort = 19300;
}
const port = await getFreePortAsync(startPort);
return port;
}
@@ -1,9 +1,7 @@
import { Platform } from '@expo/eas-build-job'; import { Platform } from '@expo/eas-build-job';
import { CombinedError } from '@urql/core'; import { CombinedError } from '@urql/core';
import { GraphQLError } from 'graphql/error'; import { GraphQLError } from 'graphql/error';
import { v4 as uuidv4 } from 'uuid';
import { getError } from '../../__tests__/commands/utils';
import { EasCommandError } from '../../commandUtils/errors'; import { EasCommandError } from '../../commandUtils/errors';
import Build from '../../commands/build'; import Build from '../../commands/build';
import Log, { link } from '../../log'; import Log, { link } from '../../log';
@@ -13,7 +11,6 @@ import {
EasBuildFreeTierDisabledAndroidError, EasBuildFreeTierDisabledAndroidError,
EasBuildFreeTierDisabledError, EasBuildFreeTierDisabledError,
EasBuildFreeTierDisabledIOSError, EasBuildFreeTierDisabledIOSError,
EasBuildResourceClassNotAvailableInFreeTierError,
EasBuildTooManyPendingBuildsError, EasBuildTooManyPendingBuildsError,
RequestValidationError, RequestValidationError,
TurtleDeprecatedJobFormatError, TurtleDeprecatedJobFormatError,
@@ -45,11 +42,9 @@ const EXPECTED_MAX_BUILD_COUNT_MESSAGE_IOS = `You have already reached the maxim
const EXPECTED_GENERIC_MESSAGE = const EXPECTED_GENERIC_MESSAGE =
'Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, report the issue.'; 'Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, report the issue.';
const mockRequestId = uuidv4();
const getGraphQLError = (message: string, errorCode: string): GraphQLError => { const getGraphQLError = (message: string, errorCode: string): GraphQLError => {
return new GraphQLError(message, null, null, null, null, null, { return new GraphQLError(message, null, null, null, null, null, {
errorCode, errorCode,
requestId: mockRequestId,
}); });
}; };
@@ -119,135 +114,64 @@ describe(Build.name, () => {
}); });
describe('error is server-side defined error', () => { describe('error is server-side defined error', () => {
describe('throws correct EasCommandError subclass', () => {
describe('for TURTLE_DEPRECATED_JOB_FORMAT', () => { describe('for TURTLE_DEPRECATED_JOB_FORMAT', () => {
describe('with Android', () => { it('does it with Android', async () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'TURTLE_DEPRECATED_JOB_FORMAT'); const graphQLError = getGraphQLError('Error 1', 'TURTLE_DEPRECATED_JOB_FORMAT');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<TurtleDeprecatedJobFormatError>( try {
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, TurtleDeprecatedJobFormatError);
} }
);
assertReThrownError(
handleBuildRequestErrorThrownError,
TurtleDeprecatedJobFormatError,
'Error 1'
);
}); });
it('throws correct EasBuildResourceClassNotAvailableInFreeTierError', async () => { it('does it with iOS', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError(
'Error 1',
'EAS_BUILD_RESOURCE_CLASS_NOT_AVAILABLE_IN_FREE_TIER'
);
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError =
getError<EasBuildResourceClassNotAvailableInFreeTierError>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildResourceClassNotAvailableInFreeTierError,
'Error 1'
);
});
});
describe('with iOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'TURTLE_DEPRECATED_JOB_FORMAT'); const graphQLError = getGraphQLError('Error 1', 'TURTLE_DEPRECATED_JOB_FORMAT');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<TurtleDeprecatedJobFormatError>( try {
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, TurtleDeprecatedJobFormatError);
} }
);
assertReThrownError(
handleBuildRequestErrorThrownError,
TurtleDeprecatedJobFormatError,
'Error 1'
);
});
it('throws correct EasBuildResourceClassNotAvailableInFreeTierError', async () => {
const platform = Platform.IOS;
const graphQLError = getGraphQLError(
'Error 1',
'EAS_BUILD_RESOURCE_CLASS_NOT_AVAILABLE_IN_FREE_TIER'
);
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError =
getError<EasBuildResourceClassNotAvailableInFreeTierError>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildResourceClassNotAvailableInFreeTierError,
'Error 1'
);
});
}); });
}); });
describe('for EAS_BUILD_FREE_TIER_DISABLED', () => { describe('for EAS_BUILD_FREE_TIER_DISABLED', () => {
describe('with Android', () => { it('does it with Android', async () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildFreeTierDisabledError>( try {
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, EasBuildFreeTierDisabledError);
} }
); });
assertReThrownError( it('does it with iOS', async () => {
handleBuildRequestErrorThrownError,
EasBuildFreeTierDisabledError,
'Error 1'
);
});
});
describe('with iOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildFreeTierDisabledError>( try {
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, EasBuildFreeTierDisabledError);
} }
); });
});
assertReThrownError( it('does it for EAS_BUILD_FREE_TIER_DISABLED_IOS', async () => {
handleBuildRequestErrorThrownError,
EasBuildFreeTierDisabledError,
'Error 1'
);
});
});
});
describe('for EAS_BUILD_FREE_TIER_DISABLED_IOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED_IOS'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED_IOS');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
@@ -256,247 +180,181 @@ describe(Build.name, () => {
try { try {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) { } catch (caughtError) {
assertReThrownError(caughtError as Error, EasBuildFreeTierDisabledIOSError, 'Error 1'); assertReThrownError(caughtError as Error, EasBuildFreeTierDisabledIOSError);
} }
}); });
});
describe('for EAS_BUILD_FREE_TIER_DISABLED_ANDROID', () => { it('does it for EAS_BUILD_FREE_TIER_DISABLED_ANDROID', async () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED_ANDROID'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_FREE_TIER_DISABLED_ANDROID');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildFreeTierDisabledAndroidError>( try {
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, EasBuildFreeTierDisabledAndroidError);
} }
);
assertReThrownError(
handleBuildRequestErrorThrownError,
EasBuildFreeTierDisabledAndroidError,
'Error 1'
);
});
}); });
describe('for VALIDATION_ERROR', () => { describe('for VALIDATION_ERROR', () => {
describe('with Android', () => { it('does it with Android', async () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'VALIDATION_ERROR'); const graphQLError = getGraphQLError('Error 1', 'VALIDATION_ERROR');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<RequestValidationError>(() => { try {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, RequestValidationError);
}
}); });
assertReThrownError( it('does it with iOS', async () => {
handleBuildRequestErrorThrownError,
RequestValidationError,
'Error 1'
);
});
});
describe('with iOS', () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'VALIDATION_ERROR'); const graphQLError = getGraphQLError('Error 1', 'VALIDATION_ERROR');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<RequestValidationError>(() => { try {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
}); } catch (caughtError) {
assertReThrownError(caughtError as Error, RequestValidationError);
assertReThrownError( }
handleBuildRequestErrorThrownError,
RequestValidationError,
'Error 1'
);
}); });
}); });
}); });
}); });
describe('error is other expected graphQL-related error', () => { describe('error is other expected graphQL-related error', () => {
describe('throws correct EasCommandError subclass', () => {
describe('for EAS_BUILD_DOWN_FOR_MAINTENANCE', () => { describe('for EAS_BUILD_DOWN_FOR_MAINTENANCE', () => {
describe('with Android', () => { it('does it with Android', async () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_DOWN_FOR_MAINTENANCE'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_DOWN_FOR_MAINTENANCE');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildDownForMaintenanceError>( try {
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} } catch (caughtError) {
);
assertReThrownError( assertReThrownError(
handleBuildRequestErrorThrownError, caughtError as Error,
EasBuildDownForMaintenanceError, EasBuildDownForMaintenanceError,
EXPECTED_EAS_BUILD_DOWN_MESSAGE EXPECTED_EAS_BUILD_DOWN_MESSAGE
); );
}
}); });
});
describe('with iOS', () => { it('does it with iOS', async () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_DOWN_FOR_MAINTENANCE'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_DOWN_FOR_MAINTENANCE');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildDownForMaintenanceError>( try {
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} } catch (caughtError) {
);
assertReThrownError( assertReThrownError(
handleBuildRequestErrorThrownError, caughtError as Error,
EasBuildDownForMaintenanceError, EasBuildDownForMaintenanceError,
EXPECTED_EAS_BUILD_DOWN_MESSAGE EXPECTED_EAS_BUILD_DOWN_MESSAGE
); );
}); }
}); });
}); });
describe('for EAS_BUILD_TOO_MANY_PENDING_BUILDS', () => { describe('for EAS_BUILD_TOO_MANY_PENDING_BUILDS', () => {
describe('with Android', () => { it('does it with Android', async () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_TOO_MANY_PENDING_BUILDS'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_TOO_MANY_PENDING_BUILDS');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildTooManyPendingBuildsError>( try {
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} } catch (caughtError) {
);
assertReThrownError( assertReThrownError(
handleBuildRequestErrorThrownError, caughtError as Error,
EasBuildTooManyPendingBuildsError, EasBuildTooManyPendingBuildsError,
EXPECTED_MAX_BUILD_COUNT_MESSAGE_ANDROID EXPECTED_MAX_BUILD_COUNT_MESSAGE_ANDROID
); );
}
}); });
});
describe('with iOS', () => { it('does it with iOS', async () => {
it('throws correct EasCommandError subclass', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_TOO_MANY_PENDING_BUILDS'); const graphQLError = getGraphQLError('Error 1', 'EAS_BUILD_TOO_MANY_PENDING_BUILDS');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const handleBuildRequestErrorThrownError = getError<EasBuildTooManyPendingBuildsError>( try {
() => {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} } catch (caughtError) {
);
assertReThrownError( assertReThrownError(
handleBuildRequestErrorThrownError, caughtError as Error,
EasBuildTooManyPendingBuildsError, EasBuildTooManyPendingBuildsError,
EXPECTED_MAX_BUILD_COUNT_MESSAGE_IOS EXPECTED_MAX_BUILD_COUNT_MESSAGE_IOS
); );
}
}); });
}); });
}); });
}); });
describe('error is unexpected graphQL-related error', () => { describe('error is unexpected graphQL-related error', () => {
describe('with Android', () => { describe('throws base Error class with custom message', () => {
it('throws base Error class with custom message', async () => { it('does it with Android', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR'); const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const expectedMessage = `${EXPECTED_GENERIC_MESSAGE}\nRequest ID: ${mockRequestId}\nError message: Error 1`;
const handleBuildRequestErrorThrownError = getError<Error>(() => { try {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
assertReThrownError(caughtError as Error, Error, EXPECTED_GENERIC_MESSAGE);
}
}); });
assertReThrownError(handleBuildRequestErrorThrownError, Error, expectedMessage); it('does it with iOS', async () => {
});
describe('without request ID', () => {
it('throws base Error class with custom message without request ID line', async () => {
const platform = Platform.ANDROID;
const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR');
delete graphQLError.extensions.requestId;
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const expectedMessage = `${EXPECTED_GENERIC_MESSAGE}\nError message: Error 1`;
const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(handleBuildRequestErrorThrownError, Error, expectedMessage);
});
});
});
describe('with iOS', () => {
it('throws base Error class with custom message', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR'); const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR');
const graphQLErrors = [graphQLError]; const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors }); const error = new CombinedError({ graphQLErrors });
const expectedMessage = `${EXPECTED_GENERIC_MESSAGE}\nRequest ID: ${mockRequestId}\nError message: Error 1`;
const handleBuildRequestErrorThrownError = getError<Error>(() => { try {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
}); } catch (caughtError) {
assertReThrownError(caughtError as Error, Error, EXPECTED_GENERIC_MESSAGE);
assertReThrownError(handleBuildRequestErrorThrownError, Error, expectedMessage); }
});
describe('without request ID', () => {
it('throws base Error class with custom message without request ID line', async () => {
const platform = Platform.IOS;
const graphQLError = getGraphQLError('Error 1', 'UNKNOWN_GRAPHQL_ERROR');
delete graphQLError.extensions.requestId;
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
const expectedMessage = `${EXPECTED_GENERIC_MESSAGE}\nError message: Error 1`;
const handleBuildRequestErrorThrownError = getError<Error>(() => {
handleBuildRequestError(error, platform);
});
assertReThrownError(handleBuildRequestErrorThrownError, Error, expectedMessage);
});
}); });
}); });
}); });
describe('error is unexpected non-graphQL-related error', () => { describe('error is unexpected non-graphQL-related error', () => {
describe('with Android', () => { describe('throws base Error class with propagated message', () => {
it('throws base Error class with propagated message', async () => { it('does it with Android', async () => {
const platform = Platform.ANDROID; const platform = Platform.ANDROID;
const error = new Error('Non-graphQL-related error'); const error = new Error('Non-graphQL-related error');
const handleBuildRequestErrorThrownError = getError<Error>(() => { try {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
} catch (caughtError) {
expect(caughtError).toStrictEqual(error);
}
}); });
expect(handleBuildRequestErrorThrownError).toStrictEqual(error); it('does it with iOS', async () => {
});
});
describe('with iOS', () => {
it('throws base Error class with propagated message', async () => {
const platform = Platform.IOS; const platform = Platform.IOS;
const error = new Error('Non-graphQL-related error'); const error = new Error('Non-graphQL-related error');
const handleBuildRequestErrorThrownError = getError<Error>(() => { try {
handleBuildRequestError(error, platform); handleBuildRequestError(error, platform);
}); } catch (caughtError) {
expect(caughtError).toStrictEqual(error);
expect(handleBuildRequestErrorThrownError).toStrictEqual(error); }
}); });
}); });
}); });
@@ -24,7 +24,7 @@ interface JobData {
const cacheDefaults = { const cacheDefaults = {
disabled: false, disabled: false,
paths: [], customPaths: [],
}; };
export async function prepareJobAsync( export async function prepareJobAsync(
+4 -33
View File
@@ -42,8 +42,6 @@ import {
EasBuildFreeTierDisabledAndroidError, EasBuildFreeTierDisabledAndroidError,
EasBuildFreeTierDisabledError, EasBuildFreeTierDisabledError,
EasBuildFreeTierDisabledIOSError, EasBuildFreeTierDisabledIOSError,
EasBuildProjectArchiveUploadError,
EasBuildResourceClassNotAvailableInFreeTierError,
EasBuildTooManyPendingBuildsError, EasBuildTooManyPendingBuildsError,
RequestValidationError, RequestValidationError,
TurtleDeprecatedJobFormatError, TurtleDeprecatedJobFormatError,
@@ -82,14 +80,9 @@ interface Builder<TPlatform extends Platform, Credentials, TJob extends Job> {
export type BuildRequestSender = () => Promise<BuildFragment | undefined>; export type BuildRequestSender = () => Promise<BuildFragment | undefined>;
function resolveBuildParamsInput<T extends Platform>( function resolveBuildParamsInput<T extends Platform>(ctx: BuildContext<T>): BuildParamsInput {
ctx: BuildContext<T>,
metadata: Metadata
): BuildParamsInput {
return { return {
resourceClass: ctx.resourceClass, resourceClass: ctx.resourceClass,
sdkVersion: metadata.sdkVersion,
reactNativeVersion: metadata.reactNativeVersion,
}; };
} }
@@ -149,7 +142,7 @@ export async function prepareBuildRequestForPlatformAsync<
assert(projectArchive); assert(projectArchive);
const metadata = await collectMetadataAsync(ctx); const metadata = await collectMetadataAsync(ctx);
const buildParams = resolveBuildParamsInput(ctx, metadata); const buildParams = resolveBuildParamsInput(ctx);
const job = await builder.prepareJobAsync(ctx, { const job = await builder.prepareJobAsync(ctx, {
projectArchive, projectArchive,
credentials: credentialsResult?.credentials, credentials: credentialsResult?.credentials,
@@ -183,8 +176,6 @@ const SERVER_SIDE_DEFINED_ERRORS: Record<string, typeof EasCommandError> = {
EAS_BUILD_FREE_TIER_DISABLED: EasBuildFreeTierDisabledError, EAS_BUILD_FREE_TIER_DISABLED: EasBuildFreeTierDisabledError,
EAS_BUILD_FREE_TIER_DISABLED_IOS: EasBuildFreeTierDisabledIOSError, EAS_BUILD_FREE_TIER_DISABLED_IOS: EasBuildFreeTierDisabledIOSError,
EAS_BUILD_FREE_TIER_DISABLED_ANDROID: EasBuildFreeTierDisabledAndroidError, EAS_BUILD_FREE_TIER_DISABLED_ANDROID: EasBuildFreeTierDisabledAndroidError,
EAS_BUILD_RESOURCE_CLASS_NOT_AVAILABLE_IN_FREE_TIER:
EasBuildResourceClassNotAvailableInFreeTierError,
VALIDATION_ERROR: RequestValidationError, VALIDATION_ERROR: RequestValidationError,
}; };
@@ -206,15 +197,8 @@ export function handleBuildRequestError(error: any, platform: Platform): never {
`You have already reached the maximum number of pending ${requestedPlatformDisplayNames[platform]} builds for your account. Try again later.` `You have already reached the maximum number of pending ${requestedPlatformDisplayNames[platform]} builds for your account. Try again later.`
); );
} else if (error?.graphQLErrors) { } else if (error?.graphQLErrors) {
const graphQLError = error.graphQLErrors[0];
const requestIdLine = graphQLError?.extensions?.requestId
? `\nRequest ID: ${graphQLError.extensions.requestId}`
: '';
const originalErrorMessage = graphQLError?.message
? `\nError message: ${graphQLError.message}`
: '';
throw new Error( throw new Error(
`Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, report the issue.${requestIdLine}${originalErrorMessage}` 'Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, report the issue.'
); );
} }
throw error; throw error;
@@ -270,12 +254,6 @@ async function uploadProjectAsync<TPlatform extends Platform>(
properties: ctx.analyticsEventProperties, properties: ctx.analyticsEventProperties,
} }
); );
} catch (err: any) {
let errMessage = 'Failed to upload the project tarball to EAS Build';
if (err.message) {
errMessage += `\n\nReason: ${err.message}`;
}
throw new EasBuildProjectArchiveUploadError(errMessage);
} finally { } finally {
if (projectTarballPath) { if (projectTarballPath) {
await fs.remove(projectTarballPath); await fs.remove(projectTarballPath);
@@ -505,7 +483,6 @@ async function handleSingleBuildProgressAsync(
} }
break; break;
} }
case BuildStatus.PendingCancel:
case BuildStatus.Canceled: case BuildStatus.Canceled:
spinner.fail('Build canceled'); spinner.fail('Build canceled');
return { refetch: false }; return { refetch: false };
@@ -536,7 +513,6 @@ const statusToDisplayName: Record<BuildStatus, string> = {
[BuildStatus.New]: 'waiting to enter the queue (concurrency limit reached)', [BuildStatus.New]: 'waiting to enter the queue (concurrency limit reached)',
[BuildStatus.InQueue]: 'in queue', [BuildStatus.InQueue]: 'in queue',
[BuildStatus.InProgress]: 'in progress', [BuildStatus.InProgress]: 'in progress',
[BuildStatus.PendingCancel]: 'canceled',
[BuildStatus.Canceled]: 'canceled', [BuildStatus.Canceled]: 'canceled',
[BuildStatus.Finished]: 'finished', [BuildStatus.Finished]: 'finished',
[BuildStatus.Errored]: 'failed', [BuildStatus.Errored]: 'failed',
@@ -555,12 +531,7 @@ async function handleMultipleBuildsProgressAsync(
builds.filter(build => build.status === BuildStatus.Finished).length === buildCount; builds.filter(build => build.status === BuildStatus.Finished).length === buildCount;
const allSettled = const allSettled =
builds.filter(build => builds.filter(build =>
[ [BuildStatus.Finished, BuildStatus.Errored, BuildStatus.Canceled].includes(build.status)
BuildStatus.Finished,
BuildStatus.Errored,
BuildStatus.Canceled,
BuildStatus.PendingCancel,
].includes(build.status)
).length === buildCount; ).length === buildCount;
if (allSettled) { if (allSettled) {
+24 -14
View File
@@ -1,9 +1,9 @@
import { Platform, Workflow } from '@expo/eas-build-job'; import { Platform, Workflow } from '@expo/eas-build-job';
import { EasJson, EasJsonAccessor } from '@expo/eas-json'; import { EasJson, EasJsonAccessor, ResourceClass } from '@expo/eas-json';
import chalk from 'chalk'; import chalk from 'chalk';
import fs from 'fs-extra'; import fs from 'fs-extra';
import Log, { learnMore } from '../log'; import Log from '../log';
import { resolveWorkflowAsync } from '../project/workflow'; import { resolveWorkflowAsync } from '../project/workflow';
import { easCliVersion } from '../utils/easCli'; import { easCliVersion } from '../utils/easCli';
import { getVcsClient } from '../vcs'; import { getVcsClient } from '../vcs';
@@ -14,10 +14,6 @@ interface ConfigureParams {
nonInteractive: boolean; nonInteractive: boolean;
} }
export async function easJsonExistsAsync(projectDir: string): Promise<boolean> {
return await fs.pathExists(EasJsonAccessor.formatEasJsonPath(projectDir));
}
/** /**
* Creates eas.json if it does not exist. * Creates eas.json if it does not exist.
* *
@@ -28,7 +24,7 @@ export async function easJsonExistsAsync(projectDir: string): Promise<boolean> {
export async function ensureProjectConfiguredAsync( export async function ensureProjectConfiguredAsync(
configureParams: ConfigureParams configureParams: ConfigureParams
): Promise<boolean> { ): Promise<boolean> {
if (await easJsonExistsAsync(configureParams.projectDir)) { if (await fs.pathExists(EasJsonAccessor.formatEasJsonPath(configureParams.projectDir))) {
return false; return false;
} }
@@ -57,11 +53,21 @@ const EAS_JSON_MANAGED_DEFAULT: EasJson = {
development: { development: {
developmentClient: true, developmentClient: true,
distribution: 'internal', distribution: 'internal',
ios: {
resourceClass: ResourceClass.M_MEDIUM,
},
}, },
preview: { preview: {
distribution: 'internal', distribution: 'internal',
ios: {
resourceClass: ResourceClass.M_MEDIUM,
},
},
production: {
ios: {
resourceClass: ResourceClass.M_MEDIUM,
},
}, },
production: {},
}, },
submit: { submit: {
production: {}, production: {},
@@ -80,12 +86,20 @@ const EAS_JSON_BARE_DEFAULT: EasJson = {
}, },
ios: { ios: {
buildConfiguration: 'Debug', buildConfiguration: 'Debug',
resourceClass: ResourceClass.M_MEDIUM,
}, },
}, },
preview: { preview: {
distribution: 'internal', distribution: 'internal',
ios: {
resourceClass: ResourceClass.M_MEDIUM,
},
},
production: {
ios: {
resourceClass: ResourceClass.M_MEDIUM,
},
}, },
production: {},
}, },
submit: { submit: {
production: {}, production: {},
@@ -106,9 +120,5 @@ async function createEasJsonAsync(projectDir: string): Promise<void> {
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`); await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
await getVcsClient().trackFileAsync(easJsonPath); await getVcsClient().trackFileAsync(easJsonPath);
Log.withTick( Log.withTick(`Generated ${chalk.bold('eas.json')}`);
`Generated ${chalk.bold('eas.json')}. ${learnMore(
'https://docs.expo.dev/build-reference/eas-json/'
)}`
);
} }
-2
View File
@@ -8,7 +8,6 @@ import { CredentialsContext } from '../credentials/context';
import { Target } from '../credentials/ios/types'; import { Target } from '../credentials/ios/types';
import { BuildResourceClass } from '../graphql/generated'; import { BuildResourceClass } from '../graphql/generated';
import { GradleBuildContext } from '../project/android/gradle'; import { GradleBuildContext } from '../project/android/gradle';
import { CustomBuildConfigMetadata } from '../project/customBuildConfig';
import { XcodeBuildContext } from '../project/ios/scheme'; import { XcodeBuildContext } from '../project/ios/scheme';
import { Actor } from '../user/User'; import { Actor } from '../user/User';
import { LocalBuildOptions } from './local'; import { LocalBuildOptions } from './local';
@@ -52,7 +51,6 @@ export interface BuildContext<T extends Platform> {
graphqlClient: ExpoGraphqlClient; graphqlClient: ExpoGraphqlClient;
analytics: Analytics; analytics: Analytics;
workflow: Workflow; workflow: Workflow;
customBuildConfigMetadata?: CustomBuildConfigMetadata;
android: T extends Platform.ANDROID ? AndroidBuildContext : undefined; android: T extends Platform.ANDROID ? AndroidBuildContext : undefined;
ios: T extends Platform.IOS ? IosBuildContext : undefined; ios: T extends Platform.IOS ? IosBuildContext : undefined;
} }
+5 -7
View File
@@ -9,7 +9,6 @@ import { Analytics, AnalyticsEventProperties, BuildEvent } from '../analytics/An
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField'; import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient'; import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { CredentialsContext } from '../credentials/context'; import { CredentialsContext } from '../credentials/context';
import { CustomBuildConfigMetadata } from '../project/customBuildConfig';
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils'; import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
import { resolveWorkflowAsync } from '../project/workflow'; import { resolveWorkflowAsync } from '../project/workflow';
import { Actor } from '../user/User'; import { Actor } from '../user/User';
@@ -34,8 +33,7 @@ export async function createBuildContextAsync<T extends Platform>({
actor, actor,
graphqlClient, graphqlClient,
analytics, analytics,
getDynamicPrivateProjectConfigAsync, getDynamicProjectConfigAsync,
customBuildConfigMetadata,
}: { }: {
buildProfileName: string; buildProfileName: string;
buildProfile: BuildProfile<T>; buildProfile: BuildProfile<T>;
@@ -51,10 +49,9 @@ export async function createBuildContextAsync<T extends Platform>({
actor: Actor; actor: Actor;
graphqlClient: ExpoGraphqlClient; graphqlClient: ExpoGraphqlClient;
analytics: Analytics; analytics: Analytics;
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn; getDynamicProjectConfigAsync: DynamicConfigContextFn;
customBuildConfigMetadata?: CustomBuildConfigMetadata;
}): Promise<BuildContext<T>> { }): Promise<BuildContext<T>> {
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env }); const { exp, projectId } = await getDynamicProjectConfigAsync({ env: buildProfile.env });
const projectName = exp.slug; const projectName = exp.slug;
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId); const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
const workflow = await resolveWorkflowAsync(projectDir, platform); const workflow = await resolveWorkflowAsync(projectDir, platform);
@@ -93,6 +90,8 @@ export async function createBuildContextAsync<T extends Platform>({
const resourceClass = await resolveBuildResourceClassAsync( const resourceClass = await resolveBuildResourceClassAsync(
buildProfile, buildProfile,
platform, platform,
projectDir,
exp,
resourceClassFlag resourceClassFlag
); );
@@ -119,7 +118,6 @@ export async function createBuildContextAsync<T extends Platform>({
workflow, workflow,
message, message,
runFromCI, runFromCI,
customBuildConfigMetadata,
}; };
if (platform === Platform.ANDROID) { if (platform === Platform.ANDROID) {
const common = commonContext as CommonContext<Platform.ANDROID>; const common = commonContext as CommonContext<Platform.ANDROID>;
-4
View File
@@ -8,12 +8,8 @@ export class EasBuildFreeTierDisabledIOSError extends EasCommandError {}
export class EasBuildFreeTierDisabledAndroidError extends EasCommandError {} export class EasBuildFreeTierDisabledAndroidError extends EasCommandError {}
export class EasBuildResourceClassNotAvailableInFreeTierError extends EasCommandError {}
export class RequestValidationError extends EasCommandError {} export class RequestValidationError extends EasCommandError {}
export class EasBuildDownForMaintenanceError extends EasCommandError {} export class EasBuildDownForMaintenanceError extends EasCommandError {}
export class EasBuildTooManyPendingBuildsError extends EasCommandError {} export class EasBuildTooManyPendingBuildsError extends EasCommandError {}
export class EasBuildProjectArchiveUploadError extends EasCommandError {}
@@ -77,7 +77,7 @@ describe(bumpVersionAsync, () => {
); );
await bumpVersionAsync({ await bumpVersionAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER, bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/app', projectDir: '/repo',
exp: fakeExp, exp: fakeExp,
targets: [ targets: [
{ {
@@ -88,9 +88,9 @@ describe(bumpVersionAsync, () => {
], ],
}); });
const appJSON = await fs.readJSON('/app/app.json'); const appJSON = await fs.readJSON('/repo/app.json');
const infoPlist = (await readPlistAsync( const infoPlist = (await readPlistAsync(
'/app/ios/myproject/Info.plist' '/repo/ios/myproject/Info.plist'
)) as IOSConfig.InfoPlist; )) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.0'); expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('2'); expect(fakeExp.ios?.buildNumber).toBe('2');
@@ -113,7 +113,7 @@ describe(bumpVersionAsync, () => {
); );
await bumpVersionAsync({ await bumpVersionAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER, bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/app', projectDir: '/repo',
exp: fakeExp, exp: fakeExp,
targets: [ targets: [
{ {
@@ -124,9 +124,9 @@ describe(bumpVersionAsync, () => {
], ],
}); });
const appJSON = await fs.readJSON('/app/app.json'); const appJSON = await fs.readJSON('/repo/app.json');
const infoPlist = (await readPlistAsync( const infoPlist = (await readPlistAsync(
'/app/ios/myproject/Info2.plist' '/repo/ios/myproject/Info2.plist'
)) as IOSConfig.InfoPlist; )) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.0'); expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('2'); expect(fakeExp.ios?.buildNumber).toBe('2');
@@ -148,7 +148,7 @@ describe(bumpVersionAsync, () => {
); );
await bumpVersionAsync({ await bumpVersionAsync({
bumpStrategy: BumpStrategy.APP_VERSION, bumpStrategy: BumpStrategy.APP_VERSION,
projectDir: '/app', projectDir: '/repo',
exp: fakeExp, exp: fakeExp,
targets: [ targets: [
{ {
@@ -159,9 +159,9 @@ describe(bumpVersionAsync, () => {
], ],
}); });
const appJSON = await fs.readJSON('/app/app.json'); const appJSON = await fs.readJSON('/repo/app.json');
const infoPlist = (await readPlistAsync( const infoPlist = (await readPlistAsync(
'/app/ios/myproject/Info.plist' '/repo/ios/myproject/Info.plist'
)) as IOSConfig.InfoPlist; )) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.1'); expect(fakeExp.version).toBe('1.0.1');
expect(fakeExp.ios?.buildNumber).toBe('1'); expect(fakeExp.ios?.buildNumber).toBe('1');
@@ -183,7 +183,7 @@ describe(bumpVersionAsync, () => {
); );
await bumpVersionAsync({ await bumpVersionAsync({
bumpStrategy: BumpStrategy.NOOP, bumpStrategy: BumpStrategy.NOOP,
projectDir: '/app', projectDir: '/repo',
exp: fakeExp, exp: fakeExp,
targets: [ targets: [
{ {
@@ -194,9 +194,9 @@ describe(bumpVersionAsync, () => {
], ],
}); });
const appJSON = await fs.readJSON('/app/app.json'); const appJSON = await fs.readJSON('/repo/app.json');
const infoPlist = (await readPlistAsync( const infoPlist = (await readPlistAsync(
'/app/ios/myproject/Info.plist' '/repo/ios/myproject/Info.plist'
)) as IOSConfig.InfoPlist; )) as IOSConfig.InfoPlist;
expect(fakeExp.version).toBe('1.0.0'); expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('1'); expect(fakeExp.ios?.buildNumber).toBe('1');
@@ -214,11 +214,11 @@ describe(bumpVersionInAppJsonAsync, () => {
await bumpVersionInAppJsonAsync({ await bumpVersionInAppJsonAsync({
bumpStrategy: BumpStrategy.BUILD_NUMBER, bumpStrategy: BumpStrategy.BUILD_NUMBER,
projectDir: '/app', projectDir: '/repo',
exp: fakeExp, exp: fakeExp,
}); });
const appJSON = await fs.readJSON('/app/app.json'); const appJSON = await fs.readJSON('/repo/app.json');
expect(fakeExp.version).toBe('1.0.0'); expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('2'); expect(fakeExp.ios?.buildNumber).toBe('2');
expect(appJSON.expo.version).toBe('1.0.0'); expect(appJSON.expo.version).toBe('1.0.0');
@@ -230,11 +230,11 @@ describe(bumpVersionInAppJsonAsync, () => {
await bumpVersionInAppJsonAsync({ await bumpVersionInAppJsonAsync({
bumpStrategy: BumpStrategy.APP_VERSION, bumpStrategy: BumpStrategy.APP_VERSION,
projectDir: '/app', projectDir: '/repo',
exp: fakeExp, exp: fakeExp,
}); });
const appJSON = await fs.readJSON('/app/app.json'); const appJSON = await fs.readJSON('/repo/app.json');
expect(fakeExp.version).toBe('1.0.1'); expect(fakeExp.version).toBe('1.0.1');
expect(fakeExp.ios?.buildNumber).toBe('1'); expect(fakeExp.ios?.buildNumber).toBe('1');
expect(appJSON.expo.version).toBe('1.0.1'); expect(appJSON.expo.version).toBe('1.0.1');
@@ -246,11 +246,11 @@ describe(bumpVersionInAppJsonAsync, () => {
await bumpVersionInAppJsonAsync({ await bumpVersionInAppJsonAsync({
bumpStrategy: BumpStrategy.NOOP, bumpStrategy: BumpStrategy.NOOP,
projectDir: '/app', projectDir: '/repo',
exp: fakeExp, exp: fakeExp,
}); });
const appJSON = await fs.readJSON('/app/app.json'); const appJSON = await fs.readJSON('/repo/app.json');
expect(fakeExp.version).toBe('1.0.0'); expect(fakeExp.version).toBe('1.0.0');
expect(fakeExp.ios?.buildNumber).toBe('1'); expect(fakeExp.ios?.buildNumber).toBe('1');
expect(appJSON.expo.version).toBe('1.0.0'); expect(appJSON.expo.version).toBe('1.0.0');
@@ -262,7 +262,7 @@ describe(readBuildNumberAsync, () => {
describe('bare project', () => { describe('bare project', () => {
it('reads the build number from native code', async () => { it('reads the build number from native code', async () => {
const exp = initBareWorkflowProject(); const exp = initBareWorkflowProject();
const buildNumber = await readBuildNumberAsync('/app', exp, {}); const buildNumber = await readBuildNumberAsync('/repo', exp, {});
expect(buildNumber).toBe('1'); expect(buildNumber).toBe('1');
}); });
}); });
@@ -270,7 +270,7 @@ describe(readBuildNumberAsync, () => {
describe('managed project', () => { describe('managed project', () => {
it('reads the build number from expo config', async () => { it('reads the build number from expo config', async () => {
const exp = initManagedProject(); const exp = initManagedProject();
const buildNumber = await readBuildNumberAsync('/app', exp, {}); const buildNumber = await readBuildNumberAsync('/repo', exp, {});
expect(buildNumber).toBe('1'); expect(buildNumber).toBe('1');
}); });
}); });
@@ -280,38 +280,24 @@ describe(readShortVersionAsync, () => {
describe('bare project', () => { describe('bare project', () => {
it('reads the short version from native code', async () => { it('reads the short version from native code', async () => {
const exp = initBareWorkflowProject(); const exp = initBareWorkflowProject();
const appVersion = await readShortVersionAsync('/app', exp, {}); const appVersion = await readShortVersionAsync('/repo', exp, {});
expect(appVersion).toBe('1.0.0'); expect(appVersion).toBe('1.0.0');
}); });
it('evaluates interpolated build number', async () => { it('evaluates interpolated build number', async () => {
const exp = initBareWorkflowProject({ const exp = initBareWorkflowProject({
appVersion: '$(CURRENT_PROJECT_VERSION)', appVersion: '$(CURRENT_PROJECT_VERSION)',
}); });
const buildNumber = await readShortVersionAsync('/app', exp, { const buildNumber = await readShortVersionAsync('/repo', exp, {
CURRENT_PROJECT_VERSION: '1.0.0', CURRENT_PROJECT_VERSION: '1.0.0',
}); });
expect(buildNumber).toBe('1.0.0'); expect(buildNumber).toBe('1.0.0');
}); });
it('fails when build number is invalid', async () => {
const exp = initBareWorkflowProject({
appVersion: '$(CURRENT_PROJECT_VERSION)',
});
await expect(
readShortVersionAsync('/app', exp, {
CURRENT_PROJECT_VERSION: '0.0.7.1.028',
})
).rejects.toThrowError(
'CFBundleShortVersionString (version field in app.json/app.config.js) must be a period-separated list of three non-negative integers. Current value: 0.0.7.1.028'
);
});
}); });
describe('managed project', () => { describe('managed project', () => {
it('reads the version from app config', async () => { it('reads the version from app config', async () => {
const exp = initBareWorkflowProject(); const exp = initBareWorkflowProject();
const appVersion = await readShortVersionAsync('/app', exp, {}); const appVersion = await readShortVersionAsync('/repo', exp, {});
expect(appVersion).toBe('1.0.0'); expect(appVersion).toBe('1.0.0');
}); });
}); });
@@ -323,75 +309,66 @@ describe(getInfoPlistPath, () => {
{ {
'./ios/testapp/Info.plist': '', './ios/testapp/Info.plist': '',
}, },
'/app' '/repo'
); );
const plistPath = getInfoPlistPath('/app', {}); const plistPath = getInfoPlistPath('/repo', {});
expect(plistPath).toBe('/app/ios/testapp/Info.plist'); expect(plistPath).toBe('/repo/ios/testapp/Info.plist');
}); });
it('returns INFOPLIST_FILE if specified', () => { it('returns INFOPLIST_FILE if specified', () => {
vol.fromJSON( vol.fromJSON(
{ {
'./ios/testapp/Info.plist': '', './ios/testapp/Info.plist': '',
}, },
'/app' '/repo'
); );
const plistPath = getInfoPlistPath('/app', { INFOPLIST_FILE: './qwert/NotInfo.plist' }); const plistPath = getInfoPlistPath('/repo', { INFOPLIST_FILE: './qwert/NotInfo.plist' });
expect(plistPath).toBe('/app/ios/qwert/NotInfo.plist'); expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist');
}); });
it('evaluates SRCROOT in Info.plist', () => { it('evaluates SRCROOT in Info.plist', () => {
vol.fromJSON( vol.fromJSON(
{ {
'./ios/testapp/Info.plist': '', './ios/testapp/Info.plist': '',
}, },
'/app' '/repo'
); );
const plistPath = getInfoPlistPath('/app', { const plistPath = getInfoPlistPath('/repo', {
INFOPLIST_FILE: '$(SRCROOT)/qwert/NotInfo.plist', INFOPLIST_FILE: '$(SRCROOT)/qwert/NotInfo.plist',
}); });
expect(plistPath).toBe('/app/ios/qwert/NotInfo.plist'); expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist');
}); });
it('evaluates BuildSettings in Info.plist', () => { it('evaluates BuildSettings in Info.plist', () => {
vol.fromJSON( vol.fromJSON(
{ {
'./ios/testapp/Info.plist': '', './ios/testapp/Info.plist': '',
}, },
'/app' '/repo'
); );
const plistPath = getInfoPlistPath('/app', { const plistPath = getInfoPlistPath('/repo', {
INFOPLIST_FILE: '$(SRCROOT)/qwert/$(TARGET_NAME).plist', INFOPLIST_FILE: '$(SRCROOT)/qwert/$(TARGET_NAME).plist',
TARGET_NAME: 'NotInfo', TARGET_NAME: 'NotInfo',
}); });
expect(plistPath).toBe('/app/ios/qwert/NotInfo.plist'); expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist');
}); });
}); });
function initBareWorkflowProject({ function initBareWorkflowProject({
appVersion = '1.0.0', appVersion = '1.0.0',
buildNumber = '1', buildNumber = '1',
expoConfig = {},
infoPlistName = 'Info.plist', infoPlistName = 'Info.plist',
}: { }: { appVersion?: string; buildNumber?: string; infoPlistName?: string } = {}): ExpoConfig {
appVersion?: string;
buildNumber?: string;
infoPlistName?: string;
expoConfig?: { version?: string; ios?: object };
} = {}): ExpoConfig {
const fakeExp: ExpoConfig = { const fakeExp: ExpoConfig = {
name: 'myproject', name: 'myproject',
slug: 'myproject', slug: 'myproject',
version: '1.0.0', version: '1.0.0',
ios: { ios: {
buildNumber: '1', buildNumber: '1',
...expoConfig.ios,
}, },
...expoConfig,
}; };
vol.fromJSON( vol.fromJSON(
{ {
'./app.json': JSON.stringify({ './app.json': JSON.stringify({
expo: fakeExp, expo: fakeExp,
}), }),
'./ios/myproject.xcodeproj/project.pbxproj': '',
[`./ios/myproject/${infoPlistName}`]: `<?xml version="1.0" encoding="UTF-8"?> [`./ios/myproject/${infoPlistName}`]: `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
@@ -403,7 +380,7 @@ function initBareWorkflowProject({
</dict> </dict>
</plist>`, </plist>`,
}, },
'/app' '/repo'
); );
return fakeExp; return fakeExp;
@@ -425,7 +402,7 @@ function initManagedProject(): ExpoConfig {
expo: fakeExp, expo: fakeExp,
}), }),
}, },
'/app' '/repo'
); );
return fakeExp; return fakeExp;
+1 -1
View File
@@ -27,7 +27,7 @@ interface JobData {
const cacheDefaults = { const cacheDefaults = {
disabled: false, disabled: false,
paths: [], customPaths: [],
}; };
export async function prepareJobAsync( export async function prepareJobAsync(
+3 -17
View File
@@ -22,8 +22,6 @@ import { readPlistAsync, writePlistAsync } from '../../utils/plist';
import { updateAppJsonConfigAsync } from '../utils/appJson'; import { updateAppJsonConfigAsync } from '../utils/appJson';
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version'; import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
const SHORT_VERSION_REGEX = /^\d+.\d+.\d+$/;
export enum BumpStrategy { export enum BumpStrategy {
APP_VERSION, APP_VERSION,
BUILD_NUMBER, BUILD_NUMBER,
@@ -99,14 +97,6 @@ export async function bumpVersionInAppJsonAsync({
} }
} }
function validateShortVersion(shortVersion: string | undefined): 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}`
);
}
}
export async function readShortVersionAsync( export async function readShortVersionAsync(
projectDir: string, projectDir: string,
exp: ExpoConfig, exp: ExpoConfig,
@@ -115,15 +105,11 @@ export async function readShortVersionAsync(
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS); const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
if (workflow === Workflow.GENERIC) { if (workflow === Workflow.GENERIC) {
const infoPlist = await readInfoPlistAsync(projectDir, buildSettings); const infoPlist = await readInfoPlistAsync(projectDir, buildSettings);
return (
const shortVersion =
infoPlist.CFBundleShortVersionString && infoPlist.CFBundleShortVersionString &&
evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings); evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings)
);
validateShortVersion(shortVersion);
return shortVersion;
} else { } else {
validateShortVersion(exp.version);
return exp.version; return exp.version;
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@ import semver from 'semver';
import { ora } from '../ora'; import { ora } from '../ora';
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin'; const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
const PLUGIN_PACKAGE_VERSION = '1.0.24'; const PLUGIN_PACKAGE_VERSION = '0.0.128';
export enum LocalBuildMode { export enum LocalBuildMode {
/** /**
+1 -6
View File
@@ -19,7 +19,6 @@ import { getVcsClient } from '../vcs';
import { maybeResolveVersionsAsync as maybeResolveAndroidVersionsAsync } from './android/version'; import { maybeResolveVersionsAsync as maybeResolveAndroidVersionsAsync } from './android/version';
import { BuildContext } from './context'; import { BuildContext } from './context';
import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios/version'; import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios/version';
import { LocalBuildMode } from './local';
export async function collectMetadataAsync<T extends Platform>( export async function collectMetadataAsync<T extends Platform>(
ctx: BuildContext<T> ctx: BuildContext<T>
@@ -48,10 +47,7 @@ export async function collectMetadataAsync<T extends Platform>(
gitCommitMessage: truncateGitCommitMessage( gitCommitMessage: truncateGitCommitMessage(
(await vcsClient.getLastCommitMessageAsync()) ?? undefined (await vcsClient.getLastCommitMessageAsync()) ?? undefined
), ),
isGitWorkingTreeDirty: isGitWorkingTreeDirty: await vcsClient.hasUncommittedChangesAsync(),
ctx.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL
? false
: await vcsClient.hasUncommittedChangesAsync(),
username: getUsername(ctx.exp, ctx.user), username: getUsername(ctx.exp, ctx.user),
message: ctx.message, message: ctx.message,
...(ctx.platform === Platform.IOS && { ...(ctx.platform === Platform.IOS && {
@@ -62,7 +58,6 @@ export async function collectMetadataAsync<T extends Platform>(
runWithNoWaitFlag: ctx.noWait, runWithNoWaitFlag: ctx.noWait,
runFromCI: ctx.runFromCI, runFromCI: ctx.runFromCI,
buildMode: ctx.buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD, buildMode: ctx.buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
customWorkflowName: ctx.customBuildConfigMetadata?.workflowName,
}; };
return sanitizeMetadata(metadata); return sanitizeMetadata(metadata);
} }
-1
View File
@@ -132,7 +132,6 @@ function createBuildToPartialChoiceMaker(
name: build.platform === AppPlatform.Ios ? 'Build number' : 'Version code', name: build.platform === AppPlatform.Ios ? 'Build number' : 'Version code',
value: build.appBuildVersion ? chalk.bold(build.appBuildVersion) : null, value: build.appBuildVersion ? chalk.bold(build.appBuildVersion) : null,
}, },
{ name: 'Profile', value: build.buildProfile ? chalk.bold(build.buildProfile) : null },
]; ];
const filteredDescriptionArray: string[] = descriptionItems const filteredDescriptionArray: string[] = descriptionItems
+10 -75
View File
@@ -31,18 +31,10 @@ import {
appPlatformEmojis, appPlatformEmojis,
toPlatforms, toPlatforms,
} from '../platform'; } from '../platform';
import { import { validateCustomBuildConfigAsync } from '../project/customBuildConfig';
CustomBuildConfigMetadata,
validateCustomBuildConfigAsync,
} from '../project/customBuildConfig';
import { checkExpoSdkIsSupportedAsync } from '../project/expoSdk'; import { checkExpoSdkIsSupportedAsync } from '../project/expoSdk';
import { validateMetroConfigForManagedWorkflowAsync } from '../project/metroConfig'; import { validateMetroConfigForManagedWorkflowAsync } from '../project/metroConfig';
import { import { validateAppVersionRuntimePolicySupportAsync } from '../project/projectUtils';
installExpoUpdatesAsync,
isExpoUpdatesInstalledAsDevDependency,
isExpoUpdatesInstalledOrAvailable,
validateAppVersionRuntimePolicySupportAsync,
} from '../project/projectUtils';
import { import {
validateAppConfigForRemoteVersionSource, validateAppConfigForRemoteVersionSource,
validateBuildProfileVersionSettings, validateBuildProfileVersionSettings,
@@ -98,7 +90,7 @@ export async function runBuildAndSubmitAsync(
projectDir: string, projectDir: string,
flags: BuildFlags, flags: BuildFlags,
actor: Actor, actor: Actor,
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn getDynamicProjectConfigAsync: DynamicConfigContextFn
): Promise<void> { ): Promise<void> {
await getVcsClient().ensureRepoExistsAsync(); await getVcsClient().ensureRepoExistsAsync();
await ensureRepoIsCleanAsync(flags.nonInteractive); await ensureRepoIsCleanAsync(flags.nonInteractive);
@@ -125,14 +117,9 @@ export async function runBuildAndSubmitAsync(
buildProfiles, buildProfiles,
}); });
const customBuildConfigMetadataByPlatform: { [p in AppPlatform]?: CustomBuildConfigMetadata } =
{};
for (const buildProfile of buildProfiles) { for (const buildProfile of buildProfiles) {
validateBuildProfileVersionSettings(buildProfile, easJsonCliConfig); validateBuildProfileVersionSettings(buildProfile, easJsonCliConfig);
const maybeMetadata = await validateCustomBuildConfigAsync(projectDir, buildProfile.profile); await validateCustomBuildConfigAsync(projectDir, buildProfile.profile);
if (maybeMetadata) {
customBuildConfigMetadataByPlatform[toAppPlatform(buildProfile.platform)] = maybeMetadata;
}
} }
const startedBuilds: { const startedBuilds: {
@@ -142,7 +129,6 @@ export async function runBuildAndSubmitAsync(
const buildCtxByPlatform: { [p in AppPlatform]?: BuildContext<Platform> } = {}; const buildCtxByPlatform: { [p in AppPlatform]?: BuildContext<Platform> } = {};
for (const buildProfile of buildProfiles) { for (const buildProfile of buildProfiles) {
const platform = toAppPlatform(buildProfile.platform);
const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({ const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({
projectDir, projectDir,
flags, flags,
@@ -152,13 +138,12 @@ export async function runBuildAndSubmitAsync(
actor, actor,
graphqlClient, graphqlClient,
analytics, analytics,
getDynamicPrivateProjectConfigAsync, getDynamicProjectConfigAsync,
customBuildConfigMetadata: customBuildConfigMetadataByPlatform[platform],
}); });
if (maybeBuild) { if (maybeBuild) {
startedBuilds.push({ build: maybeBuild, buildProfile }); startedBuilds.push({ build: maybeBuild, buildProfile });
} }
buildCtxByPlatform[platform] = buildCtx; buildCtxByPlatform[toAppPlatform(buildProfile.platform)] = buildCtx;
} }
if (flags.localBuildOptions.localBuildMode) { if (flags.localBuildOptions.localBuildMode) {
@@ -223,9 +208,7 @@ export async function runBuildAndSubmitAsync(
} }
const haveAllBuildsFailedOrCanceled = builds.every( const haveAllBuildsFailedOrCanceled = builds.every(
build => build => build?.status && [BuildStatus.Errored, BuildStatus.Canceled].includes(build?.status)
build?.status &&
[BuildStatus.Errored, BuildStatus.Canceled, BuildStatus.PendingCancel].includes(build?.status)
); );
await maybeDownloadAndRunSimulatorBuildsAsync(builds, flags); await maybeDownloadAndRunSimulatorBuildsAsync(builds, flags);
@@ -262,8 +245,7 @@ async function prepareAndStartBuildAsync({
actor, actor,
graphqlClient, graphqlClient,
analytics, analytics,
getDynamicPrivateProjectConfigAsync, getDynamicProjectConfigAsync,
customBuildConfigMetadata,
}: { }: {
projectDir: string; projectDir: string;
flags: BuildFlags; flags: BuildFlags;
@@ -273,8 +255,7 @@ async function prepareAndStartBuildAsync({
actor: Actor; actor: Actor;
graphqlClient: ExpoGraphqlClient; graphqlClient: ExpoGraphqlClient;
analytics: Analytics; analytics: Analytics;
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn; getDynamicProjectConfigAsync: DynamicConfigContextFn;
customBuildConfigMetadata?: CustomBuildConfigMetadata;
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> { }): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
const buildCtx = await createBuildContextAsync({ const buildCtx = await createBuildContextAsync({
buildProfileName: buildProfile.profileName, buildProfileName: buildProfile.profileName,
@@ -291,8 +272,7 @@ async function prepareAndStartBuildAsync({
actor, actor,
graphqlClient, graphqlClient,
analytics, analytics,
getDynamicPrivateProjectConfigAsync, getDynamicProjectConfigAsync,
customBuildConfigMetadata,
}); });
if (moreBuilds) { if (moreBuilds) {
@@ -311,14 +291,6 @@ async function prepareAndStartBuildAsync({
buildCtx.projectId, buildCtx.projectId,
flags.nonInteractive flags.nonInteractive
); );
if (buildProfile.profile.channel) {
await validateExpoUpdatesInstalledAsProjectDependencyAsync({
projectDir,
sdkVersion: buildCtx.exp.sdkVersion,
nonInteractive: flags.nonInteractive,
buildProfile,
});
}
await validateAppVersionRuntimePolicySupportAsync(buildCtx.projectDir, buildCtx.exp); await validateAppVersionRuntimePolicySupportAsync(buildCtx.projectDir, buildCtx.exp);
if (easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE) { if (easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE) {
@@ -438,40 +410,3 @@ async function maybeDownloadAndRunSimulatorBuildsAsync(
} }
} }
} }
async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
projectDir,
buildProfile,
nonInteractive,
sdkVersion,
}: {
projectDir: string;
buildProfile: ProfileData<'build'>;
nonInteractive: boolean;
sdkVersion?: string;
}): Promise<void> {
if (isExpoUpdatesInstalledOrAvailable(projectDir, sdkVersion)) {
return;
}
if (isExpoUpdatesInstalledAsDevDependency(projectDir)) {
Log.warn(
`The build profile "${buildProfile.profileName}" uses the channel "${buildProfile.profile.channel}", but you've added "expo-updates" as a dev dependency. To make channels work for your builds, move "expo-updates" from dev dependencies to the main dependencies in your project.`
);
} else if (nonInteractive) {
Log.warn(
`The build profile "${buildProfile.profileName}" has specified the channel "${buildProfile.profile.channel}", but the "expo-updates" package hasn't been installed. To use channels for your builds, install the "expo-updates" package by running "npx expo install expo-updates".`
);
} else {
Log.warn(
`The build profile "${buildProfile.profileName}" specifies the channel "${buildProfile.profile.channel}", but the "expo-updates" package is missing. To use channels in your builds, install the "expo-updates" package.`
);
const installExpoUpdates = await confirmAsync({
message: `Would you like to install the "expo-updates" package?`,
});
if (installExpoUpdates) {
await installExpoUpdatesAsync(projectDir, { silent: false });
Log.withTick('Installed expo-updates');
}
}
}
-1
View File
@@ -2,7 +2,6 @@ export enum BuildStatus {
NEW = 'new', NEW = 'new',
IN_QUEUE = 'in-queue', IN_QUEUE = 'in-queue',
IN_PROGRESS = 'in-progress', IN_PROGRESS = 'in-progress',
PENDING_CANCEL = 'pending-cancel',
ERRORED = 'errored', ERRORED = 'errored',
FINISHED = 'finished', FINISHED = 'finished',
CANCELED = 'canceled', CANCELED = 'canceled',
@@ -27,7 +27,6 @@ export function formatGraphQLBuild(build: BuildFragment): string {
return chalk.blue('in queue'); return chalk.blue('in queue');
case GraphQLBuildStatus.InProgress: case GraphQLBuildStatus.InProgress:
return chalk.blue('in progress'); return chalk.blue('in progress');
case GraphQLBuildStatus.PendingCancel:
case GraphQLBuildStatus.Canceled: case GraphQLBuildStatus.Canceled:
return chalk.gray('canceled'); return chalk.gray('canceled');
case GraphQLBuildStatus.Finished: case GraphQLBuildStatus.Finished:
@@ -91,7 +90,6 @@ export function formatGraphQLBuild(build: BuildFragment): string {
case GraphQLBuildStatus.InQueue: case GraphQLBuildStatus.InQueue:
case GraphQLBuildStatus.InProgress: case GraphQLBuildStatus.InProgress:
return '<in progress>'; return '<in progress>';
case GraphQLBuildStatus.PendingCancel:
case GraphQLBuildStatus.Canceled: case GraphQLBuildStatus.Canceled:
case GraphQLBuildStatus.Errored: case GraphQLBuildStatus.Errored:
return null; return null;
@@ -80,7 +80,7 @@ function printBuildResult(build: BuildFragment): void {
} }
return; return;
} }
if ([BuildStatus.Canceled, BuildStatus.PendingCancel].includes(build.status)) { if (build.status === BuildStatus.Canceled) {
Log.addNewLineIfNone(); Log.addNewLineIfNone();
Log.error( Log.error(
`${appPlatformEmojis[build.platform]} ${ `${appPlatformEmojis[build.platform]} ${
@@ -1,9 +1,12 @@
import { ExpoConfig } from '@expo/config-types';
import { Platform } from '@expo/eas-build-job'; import { Platform } from '@expo/eas-build-job';
import { BuildProfile, ResourceClass } from '@expo/eas-json'; import { BuildProfile, ResourceClass } from '@expo/eas-json';
import chalk from 'chalk'; import chalk from 'chalk';
import semver from 'semver';
import { BuildResourceClass } from '../../graphql/generated'; import { BuildResourceClass } from '../../graphql/generated';
import Log, { link } from '../../log'; import Log from '../../log';
import { getReactNativeVersionAsync } from '../metadata';
type AndroidResourceClass = Exclude< type AndroidResourceClass = Exclude<
ResourceClass, ResourceClass,
@@ -39,20 +42,22 @@ const androidResourceClassToBuildResourceClassMapping: Record<
export async function resolveBuildResourceClassAsync<T extends Platform>( export async function resolveBuildResourceClassAsync<T extends Platform>(
profile: BuildProfile<T>, profile: BuildProfile<T>,
platform: Platform, platform: Platform,
projectDir: string,
exp: ExpoConfig,
resourceClassFlag?: ResourceClass resourceClassFlag?: ResourceClass
): Promise<BuildResourceClass> { ): Promise<BuildResourceClass> {
const profileResourceClass = profile.resourceClass; const profileResourceClass = profile.resourceClass;
if (profileResourceClass && resourceClassFlag && resourceClassFlag !== profileResourceClass) { if (profileResourceClass && resourceClassFlag && resourceClassFlag !== profileResourceClass) {
Log.warn( Log.warn(
`Build profile specifies the "${profileResourceClass}" resource class but you passed "${resourceClassFlag}" to --resource-class.\nUsing "${resourceClassFlag}" as the override.` `Build profile specifies the "${profileResourceClass}" resource class but you passed "${resourceClassFlag}" to --resource-class.\nUsing the "${resourceClassFlag}" as the override.`
); );
} }
const selectedResourceClass = resourceClassFlag ?? profileResourceClass; const selectedResourceClass = resourceClassFlag ?? profileResourceClass;
return platform === Platform.IOS return platform === Platform.IOS
? resolveIosResourceClass(resourceClassFlag, profileResourceClass) ? await resolveIosResourceClassAsync(exp, projectDir, resourceClassFlag ?? profileResourceClass)
: resolveAndroidResourceClass(selectedResourceClass); : resolveAndroidResourceClass(selectedResourceClass);
} }
@@ -74,19 +79,13 @@ function resolveAndroidResourceClass(selectedResourceClass?: ResourceClass): Bui
return androidResourceClassToBuildResourceClassMapping[resourceClass as AndroidResourceClass]; return androidResourceClassToBuildResourceClassMapping[resourceClass as AndroidResourceClass];
} }
function resolveIosResourceClass( async function resolveIosResourceClassAsync(
resourceClassFlag?: ResourceClass, exp: ExpoConfig,
profileResourceClass?: ResourceClass projectDir: string,
): BuildResourceClass { selectedResourceClass?: ResourceClass
const resourceClass = resourceClassFlag ?? profileResourceClass ?? ResourceClass.DEFAULT; ): Promise<BuildResourceClass> {
const resourceClass =
if (resourceClassFlag === ResourceClass.LARGE) { selectedResourceClass ?? (await resolveIosDefaultRequestedResourceClassAsync(exp, projectDir));
throw new Error(
`Experimental "large" resource class for Intel iOS workers is no longer available. Remove the specified resource class to use the default, or learn more about all available resource classes: ${link(
'https://docs.expo.dev/build-reference/eas-json/'
)}`
);
}
if ([ResourceClass.M1_EXPERIMENTAL, ResourceClass.M1_MEDIUM].includes(resourceClass)) { if ([ResourceClass.M1_EXPERIMENTAL, ResourceClass.M1_MEDIUM].includes(resourceClass)) {
Log.warn( Log.warn(
@@ -96,13 +95,29 @@ function resolveIosResourceClass(
); );
} }
if ([ResourceClass.M_LARGE, ResourceClass.M1_LARGE].includes(resourceClass)) { if (resourceClass === ResourceClass.M1_LARGE) {
Log.warn( Log.warn(
`Resource class ${chalk.bold(resourceClass)} is deprecated. Use ${chalk.bold( `Resource class ${chalk.bold('m1-large')} is deprecated. Use ${chalk.bold(
'large' 'm-large'
)} instead.` )} instead.`
); );
} }
return iosResourceClassToBuildResourceClassMapping[resourceClass]; return iosResourceClassToBuildResourceClassMapping[resourceClass];
} }
async function resolveIosDefaultRequestedResourceClassAsync(
exp: ExpoConfig,
projectDir: string
): Promise<ResourceClass> {
const { sdkVersion } = exp;
const reactNativeVersion = await getReactNativeVersionAsync(projectDir);
if (
(sdkVersion && semver.satisfies(sdkVersion, '>=48')) ||
(reactNativeVersion && semver.satisfies(reactNativeVersion, '>=0.71.0'))
) {
return ResourceClass.M_MEDIUM;
} else {
return ResourceClass.DEFAULT;
}
}
+10 -20
View File
@@ -11,19 +11,16 @@ import Log from '../log';
import SessionManager from '../user/SessionManager'; import SessionManager from '../user/SessionManager';
import AnalyticsContextField from './context/AnalyticsContextField'; import AnalyticsContextField from './context/AnalyticsContextField';
import ContextField from './context/ContextField'; import ContextField from './context/ContextField';
import { import { DynamicProjectConfigContextField } from './context/DynamicProjectConfigContextField';
DynamicPrivateProjectConfigContextField,
DynamicPublicProjectConfigContextField,
} from './context/DynamicProjectConfigContextField';
import LoggedInContextField from './context/LoggedInContextField'; import LoggedInContextField from './context/LoggedInContextField';
import MaybeLoggedInContextField from './context/MaybeLoggedInContextField'; import MaybeLoggedInContextField from './context/MaybeLoggedInContextField';
import { OptionalPrivateProjectConfigContextField } from './context/OptionalPrivateProjectConfigContextField'; import { OptionalProjectConfigContextField } from './context/OptionalProjectConfigContextField';
import { PrivateProjectConfigContextField } from './context/PrivateProjectConfigContextField'; import ProjectConfigContextField from './context/ProjectConfigContextField';
import ProjectDirContextField from './context/ProjectDirContextField'; import ProjectDirContextField from './context/ProjectDirContextField';
import SessionManagementContextField from './context/SessionManagementContextField'; import SessionManagementContextField from './context/SessionManagementContextField';
import { EasCommandError } from './errors'; import { EasCommandError } from './errors';
export type ContextInput< type ContextInput<
T extends { T extends {
[name: string]: any; [name: string]: any;
} = object } = object
@@ -31,7 +28,7 @@ export type ContextInput<
[P in keyof T]: ContextField<T[P]>; [P in keyof T]: ContextField<T[P]>;
}; };
export type ContextOutput< type ContextOutput<
T extends { T extends {
[name: string]: any; [name: string]: any;
} = object } = object
@@ -68,7 +65,7 @@ export default abstract class EasCommand extends Command {
* run within a project directory, null otherwise. * run within a project directory, null otherwise.
*/ */
OptionalProjectConfig: { OptionalProjectConfig: {
privateProjectConfig: new OptionalPrivateProjectConfigContextField(), projectConfig: new OptionalProjectConfigContextField(),
}, },
/** /**
* Require this command to be run in a project directory. Return the project directory in the context. * Require this command to be run in a project directory. Return the project directory in the context.
@@ -81,9 +78,7 @@ export default abstract class EasCommand extends Command {
*/ */
DynamicProjectConfig: { DynamicProjectConfig: {
// eslint-disable-next-line async-protect/async-suffix // eslint-disable-next-line async-protect/async-suffix
getDynamicPublicProjectConfigAsync: new DynamicPublicProjectConfigContextField(), getDynamicProjectConfigAsync: new DynamicProjectConfigContextField(),
// eslint-disable-next-line async-protect/async-suffix
getDynamicPrivateProjectConfigAsync: new DynamicPrivateProjectConfigContextField(),
}, },
/** /**
* Require the project to be identified and registered on server. Returns the project config in the context. * Require the project to be identified and registered on server. Returns the project config in the context.
@@ -92,7 +87,7 @@ export default abstract class EasCommand extends Command {
*/ */
ProjectConfig: { ProjectConfig: {
loggedIn: new LoggedInContextField(), loggedIn: new LoggedInContextField(),
privateProjectConfig: new PrivateProjectConfigContextField(), projectConfig: new ProjectConfigContextField(),
}, },
/** /**
* Analytics manager. Returns the analytics manager in the context for use by the command. * Analytics manager. Returns the analytics manager in the context for use by the command.
@@ -193,18 +188,13 @@ export default abstract class EasCommand extends Command {
if (err instanceof EasCommandError) { if (err instanceof EasCommandError) {
Log.error(err.message); Log.error(err.message);
} else if (err instanceof CombinedError && err?.graphQLErrors) { } else if (err instanceof CombinedError && err?.graphQLErrors) {
let cleanMessage = err.message.replace('[GraphQL] ', ''); const cleanMessage = err.message.replace('[GraphQL] ', '');
cleanMessage += err?.graphQLErrors?.[0]?.extensions?.requestId
? `\nRequest ID: ${err.graphQLErrors[0].extensions.requestId}`
: '';
Log.error(cleanMessage); Log.error(cleanMessage);
baseMessage = BASE_GRAPHQL_ERROR_MESSAGE; baseMessage = BASE_GRAPHQL_ERROR_MESSAGE;
} else { } else {
Log.error(err.message); Log.error(err.message);
} }
Log.debug(err); Log.debug(err);
const sanitizedError = new Error(baseMessage); throw new Error(baseMessage);
sanitizedError.stack = err.stack;
throw sanitizedError;
} }
} }
@@ -1,6 +1,4 @@
import { CombinedError } from '@urql/core'; import { CombinedError } from '@urql/core';
import { GraphQLError } from 'graphql/error';
import { v4 as uuidv4 } from 'uuid';
import { AnalyticsWithOrchestration, createAnalyticsAsync } from '../../analytics/AnalyticsManager'; import { AnalyticsWithOrchestration, createAnalyticsAsync } from '../../analytics/AnalyticsManager';
import Log from '../../log'; import Log from '../../log';
@@ -19,7 +17,6 @@ jest.mock('../../analytics/AnalyticsManager', () => {
jest.mock('../../log'); jest.mock('../../log');
let originalProcessArgv: string[]; let originalProcessArgv: string[];
const mockRequestId = uuidv4();
beforeAll(() => { beforeAll(() => {
originalProcessArgv = process.argv; originalProcessArgv = process.argv;
@@ -140,38 +137,6 @@ describe(EasCommand.name, () => {
expect(logDebugSpy).toBeCalledWith(error); expect(logDebugSpy).toBeCalledWith(error);
}); });
it('logs the cleaned message with request ID if present', async () => {
const TestEasCommand = createTestEasCommand();
const logErrorSpy = jest.spyOn(Log, 'error');
const logDebugSpy = jest.spyOn(Log, 'debug');
const runAsyncMock = jest.spyOn(TestEasCommand.prototype as any, 'runAsync');
const graphQLError = new GraphQLError(
'Unexpected GraphQL error message',
null,
null,
null,
null,
null,
{
errorCode: 'UNKNOWN_GRAPHQL_ERROR',
requestId: mockRequestId,
}
);
const graphQLErrors = [graphQLError];
const error = new CombinedError({ graphQLErrors });
runAsyncMock.mockImplementation(() => {
throw error;
});
try {
await TestEasCommand.run();
} catch {}
expect(logErrorSpy).toBeCalledWith(
`Unexpected GraphQL error message\nRequest ID: ${mockRequestId}`
);
expect(logDebugSpy).toBeCalledWith(error);
});
it('re-throws the error with default base message', async () => { it('re-throws the error with default base message', async () => {
const TestEasCommand = createTestEasCommand(); const TestEasCommand = createTestEasCommand();
const runAsyncMock = jest.spyOn(TestEasCommand.prototype as any, 'runAsync'); const runAsyncMock = jest.spyOn(TestEasCommand.prototype as any, 'runAsync');
@@ -1,55 +1,29 @@
import { ExpoConfig } from '@expo/config-types'; import { ExpoConfig } from '@expo/config-types';
import { import { ExpoConfigOptions, getExpoConfig } from '../../project/expoConfig';
ExpoConfigOptions,
getPrivateExpoConfig,
getPublicExpoConfig,
} from '../../project/expoConfig';
import ContextField, { ContextOptions } from './ContextField'; import ContextField, { ContextOptions } from './ContextField';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync'; import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync'; import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
export type DynamicConfigContextFn = (options?: ExpoConfigOptions) => Promise<{ export type DynamicConfigContextFn = (options: ExpoConfigOptions) => Promise<{
projectId: string; projectId: string;
exp: ExpoConfig; exp: ExpoConfig;
projectDir: string; projectDir: string;
}>; }>;
export class DynamicPublicProjectConfigContextField extends ContextField<DynamicConfigContextFn> { export class DynamicProjectConfigContextField extends ContextField<DynamicConfigContextFn> {
async getValueAsync({ async getValueAsync({
nonInteractive, nonInteractive,
sessionManager, sessionManager,
}: ContextOptions): Promise<DynamicConfigContextFn> { }: ContextOptions): Promise<DynamicConfigContextFn> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync(); const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
return async (options?: ExpoConfigOptions) => { return async (options?: ExpoConfigOptions) => {
const expBefore = getPublicExpoConfig(projectDir, options); const expBefore = getExpoConfig(projectDir, options);
const projectId = await getProjectIdAsync(sessionManager, expBefore, { const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive, nonInteractive,
env: options?.env, env: options?.env,
}); });
const exp = getPublicExpoConfig(projectDir, options); const exp = getExpoConfig(projectDir, options);
return {
exp,
projectDir,
projectId,
};
};
}
}
export class DynamicPrivateProjectConfigContextField extends ContextField<DynamicConfigContextFn> {
async getValueAsync({
nonInteractive,
sessionManager,
}: ContextOptions): Promise<DynamicConfigContextFn> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
return async (options?: ExpoConfigOptions) => {
const expBefore = getPrivateExpoConfig(projectDir, options);
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
env: options?.env,
});
const exp = getPrivateExpoConfig(projectDir, options);
return { return {
exp, exp,
projectDir, projectDir,
@@ -1,12 +1,12 @@
import { ExpoConfig } from '@expo/config-types'; import { ExpoConfig } from '@expo/config-types';
import { InvalidEasJsonError } from '@expo/eas-json/build/errors'; import { InvalidEasJsonError } from '@expo/eas-json/build/errors';
import { getPrivateExpoConfig } from '../../project/expoConfig'; import { getExpoConfig } from '../../project/expoConfig';
import ContextField, { ContextOptions } from './ContextField'; import ContextField, { ContextOptions } from './ContextField';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync'; import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync'; import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
export class OptionalPrivateProjectConfigContextField extends ContextField< export class OptionalProjectConfigContextField extends ContextField<
| { | {
projectId: string; projectId: string;
exp: ExpoConfig; exp: ExpoConfig;
@@ -35,11 +35,11 @@ export class OptionalPrivateProjectConfigContextField extends ContextField<
return undefined; return undefined;
} }
const expBefore = getPrivateExpoConfig(projectDir); const expBefore = getExpoConfig(projectDir);
const projectId = await getProjectIdAsync(sessionManager, expBefore, { const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive, nonInteractive,
}); });
const exp = getPrivateExpoConfig(projectDir); const exp = getExpoConfig(projectDir);
return { return {
exp, exp,
projectDir, projectDir,
@@ -1,11 +1,11 @@
import { ExpoConfig } from '@expo/config-types'; import { ExpoConfig } from '@expo/config-types';
import { getPrivateExpoConfig } from '../../project/expoConfig'; import { getExpoConfig } from '../../project/expoConfig';
import ContextField, { ContextOptions } from './ContextField'; import ContextField, { ContextOptions } from './ContextField';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync'; import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync'; import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
export class PrivateProjectConfigContextField extends ContextField<{ export default class ProjectConfigContextField extends ContextField<{
projectId: string; projectId: string;
exp: ExpoConfig; exp: ExpoConfig;
projectDir: string; projectDir: string;
@@ -16,11 +16,11 @@ export class PrivateProjectConfigContextField extends ContextField<{
projectDir: string; projectDir: string;
}> { }> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync(); const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
const expBefore = getPrivateExpoConfig(projectDir); const expBefore = getExpoConfig(projectDir);
const projectId = await getProjectIdAsync(sessionManager, expBefore, { const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive, nonInteractive,
}); });
const exp = getPrivateExpoConfig(projectDir); const exp = getExpoConfig(projectDir);
return { return {
projectId, projectId,
@@ -1,4 +1,4 @@
import { getProjectConfigDescription } from '@expo/config'; import { getProjectConfigDescription, modifyConfigAsync } from '@expo/config';
import { ExpoConfig } from '@expo/config-types'; import { ExpoConfig } from '@expo/config-types';
import { Env } from '@expo/eas-build-job'; import { Env } from '@expo/eas-build-job';
import chalk from 'chalk'; import chalk from 'chalk';
@@ -6,7 +6,7 @@ import chalk from 'chalk';
import { AppQuery } from '../../../graphql/queries/AppQuery'; import { AppQuery } from '../../../graphql/queries/AppQuery';
import Log, { learnMore } from '../../../log'; import Log, { learnMore } from '../../../log';
import { ora } from '../../../ora'; import { ora } from '../../../ora';
import { createOrModifyExpoConfigAsync, getPrivateExpoConfig } from '../../../project/expoConfig'; import { getExpoConfig } from '../../../project/expoConfig';
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync'; import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
import { toAppPrivacy } from '../../../project/projectUtils'; import { toAppPrivacy } from '../../../project/projectUtils';
import SessionManager from '../../../user/SessionManager'; import SessionManager from '../../../user/SessionManager';
@@ -24,8 +24,8 @@ export async function saveProjectIdToAppConfigAsync(
projectId: string, projectId: string,
options: { env?: Env } = {} options: { env?: Env } = {}
): Promise<void> { ): Promise<void> {
const exp = getPrivateExpoConfig(projectDir, options); const exp = getExpoConfig(projectDir, options);
const result = await createOrModifyExpoConfigAsync( const result = await modifyConfigAsync(
projectDir, projectDir,
{ {
extra: { ...exp.extra, eas: { ...exp.extra?.eas, projectId } }, extra: { ...exp.extra, eas: { ...exp.extra?.eas, projectId } },
@@ -160,12 +160,12 @@ export async function getProjectIdAsync(
switch (user.__typename) { switch (user.__typename) {
case 'User': case 'User':
return user.username; return user.username;
case 'SSOUser':
return user.username;
case 'Robot': case 'Robot':
throw new Error( throw new Error(
'The "owner" manifest property is required when using robot users. See: https://docs.expo.dev/versions/latest/config/app/#owner' 'The "owner" manifest property is required when using robot users. See: https://docs.expo.dev/versions/latest/config/app/#owner'
); );
case 'SSOUser':
throw new Error('SSO users are not supported yet.');
} }
}; };
+1 -17
View File
@@ -1,5 +1,3 @@
import { Flags } from '@oclif/core';
import EasCommand from '../../commandUtils/EasCommand'; import EasCommand from '../../commandUtils/EasCommand';
import Log from '../../log'; import Log from '../../log';
@@ -7,27 +5,13 @@ export default class AccountLogin extends EasCommand {
static override description = 'log in with your Expo account'; static override description = 'log in with your Expo account';
static override aliases = ['login']; static override aliases = ['login'];
static override flags = {
// can pass either --sso or -s
sso: Flags.boolean({
description: 'Login with SSO',
char: 's',
default: false,
hidden: true,
}),
};
static override contextDefinition = { static override contextDefinition = {
...this.ContextOptions.SessionManagment, ...this.ContextOptions.SessionManagment,
}; };
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
const {
flags: { sso },
} = await this.parse(AccountLogin);
const { sessionManager } = await this.getContextAsync(AccountLogin, { nonInteractive: false }); const { sessionManager } = await this.getContextAsync(AccountLogin, { nonInteractive: false });
await sessionManager.showLoginPromptAsync({ sso }); await sessionManager.showLoginPromptAsync();
Log.log('Logged in'); Log.log('Logged in');
} }
} }
+1 -42
View File
@@ -1,10 +1,8 @@
import chalk from 'chalk'; import chalk from 'chalk';
import nullthrows from 'nullthrows';
import EasCommand from '../../commandUtils/EasCommand'; import EasCommand from '../../commandUtils/EasCommand';
import { Role } from '../../graphql/generated';
import Log from '../../log'; import Log from '../../log';
import { Actor, getActorDisplayName } from '../../user/User'; import { getActorDisplayName } from '../../user/User';
export default class AccountView extends EasCommand { export default class AccountView extends EasCommand {
static override description = 'show the username you are logged in as'; static override description = 'show the username you are logged in as';
@@ -20,48 +18,9 @@ export default class AccountView extends EasCommand {
} = await this.getContextAsync(AccountView, { nonInteractive: true }); } = await this.getContextAsync(AccountView, { nonInteractive: true });
if (actor) { if (actor) {
Log.log(chalk.green(getActorDisplayName(actor))); Log.log(chalk.green(getActorDisplayName(actor)));
// personal account is included, only show if more accounts that personal account
// but do show personal account in list if there are more
const accountExcludingPersonalAccount = actor.accounts.filter(
account => !('username' in actor) || account.name !== actor.username
);
if (accountExcludingPersonalAccount.length > 0) {
Log.newLine();
Log.log('Accounts:');
actor.accounts.forEach(account => {
const roleOnAccount = AccountView.getRoleOnAccount(actor, account);
Log.log(`• ${account.name} (Role: ${AccountView.getLabelForRole(roleOnAccount)})`);
});
}
} else { } else {
Log.warn('Not logged in'); Log.warn('Not logged in');
process.exit(1); process.exit(1);
} }
} }
private static getRoleOnAccount(actor: Actor, account: Actor['accounts'][0]): Role {
if ('username' in actor && account.name === actor.username) {
return Role.Owner;
}
return nullthrows(account.users.find(user => user.actor.id === actor.id)?.role);
}
private static getLabelForRole(role: Role): string {
switch (role) {
case Role.Owner:
return 'Owner';
case Role.Admin:
return 'Admin';
case Role.Developer:
return 'Developer';
case Role.ViewOnly:
return 'Viewer';
case Role.Custom:
case Role.HasAdmin:
case Role.NotAdmin:
return 'Custom';
}
}
} }
@@ -35,7 +35,7 @@ export default class BranchCreate extends EasCommand {
flags: { json: jsonFlag, 'non-interactive': nonInteractive }, flags: { json: jsonFlag, 'non-interactive': nonInteractive },
} = await this.parse(BranchCreate); } = await this.parse(BranchCreate);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchCreate, { } = await this.getContextAsync(BranchCreate, {
nonInteractive, nonInteractive,
@@ -108,7 +108,7 @@ export default class BranchDelete extends EasCommand {
} }
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchDelete, { nonInteractive }); } = await this.getContextAsync(BranchDelete, { nonInteractive });
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId); const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
+1 -1
View File
@@ -20,7 +20,7 @@ export default class BranchList extends EasCommand {
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
const { flags } = await this.parse(BranchList); const { flags } = await this.parse(BranchList);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchList, { } = await this.getContextAsync(BranchList, {
nonInteractive: flags['non-interactive'], nonInteractive: flags['non-interactive'],
@@ -72,7 +72,7 @@ export default class BranchRename extends EasCommand {
flags: { json: jsonFlag, from: currentName, to: newName, 'non-interactive': nonInteractive }, flags: { json: jsonFlag, from: currentName, to: newName, 'non-interactive': nonInteractive },
} = await this.parse(BranchRename); } = await this.parse(BranchRename);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchRename, { } = await this.getContextAsync(BranchRename, {
nonInteractive, nonInteractive,
+1 -1
View File
@@ -38,7 +38,7 @@ export default class BranchView extends EasCommand {
} = await this.parse(BranchView); } = await this.parse(BranchView);
const { 'non-interactive': nonInteractive } = flags; const { 'non-interactive': nonInteractive } = flags;
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchView, { } = await this.getContextAsync(BranchView, {
nonInteractive, nonInteractive,
@@ -147,7 +147,7 @@ export default class BuildCancel extends EasCommand {
flags: { 'non-interactive': nonInteractive }, flags: { 'non-interactive': nonInteractive },
} = await this.parse(BuildCancel); } = await this.parse(BuildCancel);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildCancel, { } = await this.getContextAsync(BuildCancel, {
nonInteractive, nonInteractive,
@@ -174,7 +174,7 @@ export default class BuildCancel extends EasCommand {
const spinner = ora().start('Canceling the build…'); const spinner = ora().start('Canceling the build…');
try { try {
const { status } = await cancelBuildAsync(graphqlClient, buildId); const { status } = await cancelBuildAsync(graphqlClient, buildId);
if ([BuildStatus.Canceled, BuildStatus.PendingCancel].includes(status)) { if (status === BuildStatus.Canceled) {
spinner.succeed('Build canceled'); spinner.succeed('Build canceled');
} else { } else {
spinner.text = 'Build is already completed'; spinner.text = 'Build is already completed';
@@ -7,11 +7,10 @@ import { ensureProjectConfiguredAsync } from '../../build/configure';
import EasCommand from '../../commandUtils/EasCommand'; import EasCommand from '../../commandUtils/EasCommand';
import Log, { learnMore } from '../../log'; import Log, { learnMore } from '../../log';
import { RequestedPlatform } from '../../platform'; import { RequestedPlatform } from '../../platform';
import { isExpoUpdatesInstalled, isUsingEASUpdate } from '../../project/projectUtils'; import { isExpoUpdatesInstalled } from '../../project/projectUtils';
import { resolveWorkflowAsync } from '../../project/workflow'; import { resolveWorkflowAsync } from '../../project/workflow';
import { promptAsync } from '../../prompts'; import { promptAsync } from '../../prompts';
import { syncUpdatesConfigurationAsync as syncAndroidUpdatesConfigurationAsync } from '../../update/android/UpdatesModule'; import { syncUpdatesConfigurationAsync as syncAndroidUpdatesConfigurationAsync } from '../../update/android/UpdatesModule';
import { ensureEASUpdateIsConfiguredInEasJsonAsync } from '../../update/configure';
import { syncUpdatesConfigurationAsync as syncIosUpdatesConfigurationAsync } from '../../update/ios/UpdatesModule'; import { syncUpdatesConfigurationAsync as syncIosUpdatesConfigurationAsync } from '../../update/ios/UpdatesModule';
import { getVcsClient } from '../../vcs'; import { getVcsClient } from '../../vcs';
@@ -34,7 +33,7 @@ export default class BuildConfigure extends EasCommand {
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
const { flags } = await this.parse(BuildConfigure); const { flags } = await this.parse(BuildConfigure);
const { const {
privateProjectConfig: { exp, projectId, projectDir }, projectConfig: { exp, projectId, projectDir },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildConfigure, { } = await this.getContextAsync(BuildConfigure, {
nonInteractive: false, nonInteractive: false,
@@ -58,13 +57,10 @@ export default class BuildConfigure extends EasCommand {
// ensure eas.json exists // ensure eas.json exists
Log.newLine(); Log.newLine();
const didCreateEasJson = await ensureProjectConfiguredAsync({ await ensureProjectConfiguredAsync({
projectDir, projectDir,
nonInteractive: false, nonInteractive: false,
}); });
if (didCreateEasJson && isUsingEASUpdate(exp, projectId)) {
await ensureEASUpdateIsConfiguredInEasJsonAsync(projectDir);
}
// configure expo-updates // configure expo-updates
if (expoUpdatesIsInstalled) { if (expoUpdatesIsInstalled) {
+2 -2
View File
@@ -118,7 +118,7 @@ export default class Build extends EasCommand {
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
getDynamicPrivateProjectConfigAsync, getDynamicProjectConfigAsync,
projectDir, projectDir,
analytics, analytics,
} = await this.getContextAsync(Build, { } = await this.getContextAsync(Build, {
@@ -144,7 +144,7 @@ export default class Build extends EasCommand {
projectDir, projectDir,
flagsWithPlatform, flagsWithPlatform,
actor, actor,
getDynamicPrivateProjectConfigAsync getDynamicProjectConfigAsync
); );
} }
@@ -73,7 +73,7 @@ export default class BuildInspect extends EasCommand {
const { flags } = await this.parse(BuildInspect); const { flags } = await this.parse(BuildInspect);
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
getDynamicPrivateProjectConfigAsync, getDynamicProjectConfigAsync,
projectDir, projectDir,
analytics, analytics,
} = await this.getContextAsync(BuildInspect, { } = await this.getContextAsync(BuildInspect, {
@@ -120,7 +120,7 @@ export default class BuildInspect extends EasCommand {
}, },
}, },
actor, actor,
getDynamicPrivateProjectConfigAsync getDynamicProjectConfigAsync
); );
if (!flags.verbose) { if (!flags.verbose) {
Log.log(chalk.green('Build successful')); Log.log(chalk.green('Build successful'));
@@ -46,7 +46,7 @@ export default class BuildInternal extends EasCommand {
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
getDynamicPrivateProjectConfigAsync, getDynamicProjectConfigAsync,
projectDir, projectDir,
analytics, analytics,
} = await this.getContextAsync(BuildInternal, { } = await this.getContextAsync(BuildInternal, {
@@ -74,7 +74,7 @@ export default class BuildInternal extends EasCommand {
}, },
}, },
actor, actor,
getDynamicPrivateProjectConfigAsync getDynamicProjectConfigAsync
); );
} }
} }
+1 -4
View File
@@ -27,7 +27,6 @@ export default class BuildList extends EasCommand {
BuildStatus.NEW, BuildStatus.NEW,
BuildStatus.IN_QUEUE, BuildStatus.IN_QUEUE,
BuildStatus.IN_PROGRESS, BuildStatus.IN_PROGRESS,
BuildStatus.PENDING_CANCEL,
BuildStatus.ERRORED, BuildStatus.ERRORED,
BuildStatus.FINISHED, BuildStatus.FINISHED,
BuildStatus.CANCELED, BuildStatus.CANCELED,
@@ -69,7 +68,7 @@ export default class BuildList extends EasCommand {
'non-interactive': nonInteractive, 'non-interactive': nonInteractive,
} = flags; } = flags;
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildList, { } = await this.getContextAsync(BuildList, {
nonInteractive, nonInteractive,
@@ -124,8 +123,6 @@ const toGraphQLBuildStatus = (buildStatus?: BuildStatus): GraphQLBuildStatus | u
return GraphQLBuildStatus.InQueue; return GraphQLBuildStatus.InQueue;
} else if (buildStatus === BuildStatus.IN_PROGRESS) { } else if (buildStatus === BuildStatus.IN_PROGRESS) {
return GraphQLBuildStatus.InProgress; return GraphQLBuildStatus.InProgress;
} else if (buildStatus === BuildStatus.PENDING_CANCEL) {
return GraphQLBuildStatus.PendingCancel;
} else if (buildStatus === BuildStatus.ERRORED) { } else if (buildStatus === BuildStatus.ERRORED) {
return GraphQLBuildStatus.Errored; return GraphQLBuildStatus.Errored;
} else if (buildStatus === BuildStatus.FINISHED) { } else if (buildStatus === BuildStatus.FINISHED) {
@@ -101,7 +101,7 @@ export default class BuildResign extends EasCommand {
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
getDynamicPrivateProjectConfigAsync, getDynamicProjectConfigAsync,
projectDir, projectDir,
analytics, analytics,
} = await this.getContextAsync(BuildResign, { } = await this.getContextAsync(BuildResign, {
@@ -130,7 +130,7 @@ export default class BuildResign extends EasCommand {
platform, platform,
flags.profile ?? 'production' flags.profile ?? 'production'
); );
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env }); const { exp, projectId } = await getDynamicProjectConfigAsync({ env: buildProfile.env });
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId); const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
const build = await this.ensureBuildSelectedAsync( const build = await this.ensureBuildSelectedAsync(
{ graphqlClient, projectId, platform, nonInteractive, limit, offset }, { graphqlClient, projectId, platform, nonInteractive, limit, offset },
+3 -4
View File
@@ -81,7 +81,7 @@ export default class Run extends EasCommand {
const queryOptions = getPaginatedQueryOptions(flags); const queryOptions = getPaginatedQueryOptions(flags);
const { const {
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
privateProjectConfig: { projectId }, projectConfig: { projectId },
} = await this.getContextAsync(Run, { } = await this.getContextAsync(Run, {
nonInteractive: false, nonInteractive: false,
}); });
@@ -210,9 +210,8 @@ async function maybeGetBuildAsync(
}, },
paginatedQueryOptions, paginatedQueryOptions,
selectPromptDisabledFunction: build => !isRunnableOnSimulatorOrEmulator(build), selectPromptDisabledFunction: build => !isRunnableOnSimulatorOrEmulator(build),
selectPromptWarningMessage: `Artifacts for this build have expired and are no longer available, or this is not ${ selectPromptWarningMessage:
flags.selectedPlatform === AppPlatform.Ios ? 'a simulator' : 'an emulator' 'Artifacts for this build have expired and are no longer available, or this is not a simulator/emulator build.',
} build.`,
}); });
return validateChosenBuild(build, flags.selectedPlatform); return validateChosenBuild(build, flags.selectedPlatform);
} else if (flags.runArchiveFlags.latest) { } else if (flags.runArchiveFlags.latest) {
@@ -1,120 +0,0 @@
import { Platform } from '@expo/eas-build-job';
import { EasJsonAccessor } from '@expo/eas-json';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import EasCommand from '../../../commandUtils/EasCommand';
import { EasNonInteractiveAndJsonFlags } from '../../../commandUtils/flags';
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
import { toAppPlatform } from '../../../graphql/types/AppPlatform';
import Log from '../../../log';
import { selectRequestedPlatformAsync, toPlatforms } from '../../../platform';
import { getApplicationIdentifierAsync } from '../../../project/applicationIdentifier';
import {
ensureVersionSourceIsRemoteAsync,
validateAppConfigForRemoteVersionSource,
} from '../../../project/remoteVersionSource';
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
import { getProfilesAsync } from '../../../utils/profiles';
export default class BuildVersionGetView extends EasCommand {
public static override description = 'get the latest version from EAS servers';
public static override flags = {
platform: Flags.enum({
char: 'p',
options: ['android', 'ios', 'all'],
}),
profile: Flags.string({
char: 'e',
description:
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
helpValue: 'PROFILE_NAME',
}),
...EasNonInteractiveAndJsonFlags,
};
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
...this.ContextOptions.DynamicProjectConfig,
...this.ContextOptions.ProjectDir,
};
public async runAsync(): Promise<void> {
const { flags } = await this.parse(BuildVersionGetView);
if (flags.json) {
enableJsonOutput();
}
const {
loggedIn: { graphqlClient },
getDynamicPrivateProjectConfigAsync,
projectDir,
} = await this.getContextAsync(BuildVersionGetView, {
nonInteractive: true,
});
if (!flags.platform && flags['non-interactive']) {
throw new Error('"--platform" flag is required in non-interactive mode.');
}
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
await ensureVersionSourceIsRemoteAsync(easJsonAccessor, {
nonInteractive: flags['non-interactive'],
});
const platforms = toPlatforms(requestedPlatform);
const buildProfiles = await getProfilesAsync({
type: 'build',
easJsonAccessor,
platforms,
profileName: flags.profile ?? undefined,
});
const results: { [key in Platform]?: string } = {};
for (const { profile, platform } of buildProfiles) {
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
env: profile.env,
});
validateAppConfigForRemoteVersionSource(exp, platform);
const applicationIdentifier = await getApplicationIdentifierAsync({
graphqlClient,
projectDir,
projectId,
exp,
buildProfile: profile,
platform,
});
const remoteVersions = await AppVersionQuery.latestVersionAsync(
graphqlClient,
projectId,
toAppPlatform(platform),
applicationIdentifier
);
if (remoteVersions?.buildVersion) {
results[platform] = remoteVersions?.buildVersion;
}
}
if (flags.json) {
const jsonResults: { versionCode?: string; buildNumber?: string } = {};
if (results.android) {
jsonResults.versionCode = results.android;
}
if (results.ios) {
jsonResults.buildNumber = results.ios;
}
printJsonOnlyOutput(jsonResults);
} else {
if (Object.keys(results).length === 0) {
Log.log('No remote versions are configured for this project.');
return;
}
if (results.android) {
Log.log(`Android versionCode - ${chalk.bold(results.android)}`);
}
if (results.ios) {
Log.log(`iOS buildNumber - ${chalk.bold(results.ios)}`);
}
}
}
}
@@ -22,7 +22,7 @@ import {
import { promptAsync } from '../../../prompts'; import { promptAsync } from '../../../prompts';
export default class BuildVersionSetView extends EasCommand { export default class BuildVersionSetView extends EasCommand {
static override description = 'update version of an app'; static override description = 'Update version of an app.';
static override flags = { static override flags = {
platform: Flags.enum({ platform: Flags.enum({
@@ -47,7 +47,7 @@ export default class BuildVersionSetView extends EasCommand {
const { flags } = await this.parse(BuildVersionSetView); const { flags } = await this.parse(BuildVersionSetView);
const { const {
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
getDynamicPrivateProjectConfigAsync, getDynamicProjectConfigAsync,
projectDir, projectDir,
} = await this.getContextAsync(BuildVersionSetView, { } = await this.getContextAsync(BuildVersionSetView, {
nonInteractive: false, nonInteractive: false,
@@ -55,14 +55,14 @@ export default class BuildVersionSetView extends EasCommand {
const platform = await selectPlatformAsync(flags.platform); const platform = await selectPlatformAsync(flags.platform);
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir); const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
await ensureVersionSourceIsRemoteAsync(easJsonAccessor, { nonInteractive: false }); await ensureVersionSourceIsRemoteAsync(easJsonAccessor);
const profile = await EasJsonUtils.getBuildProfileAsync( const profile = await EasJsonUtils.getBuildProfileAsync(
easJsonAccessor, easJsonAccessor,
platform, platform,
flags.profile ?? undefined flags.profile ?? undefined
); );
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: profile.env }); const { exp, projectId } = await getDynamicProjectConfigAsync({ env: profile.env });
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId); const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
validateAppConfigForRemoteVersionSource(exp, platform); validateAppConfigForRemoteVersionSource(exp, platform);
@@ -39,7 +39,7 @@ interface SyncContext<T extends Platform> {
export default class BuildVersionSyncView extends EasCommand { export default class BuildVersionSyncView extends EasCommand {
public static override description = public static override description =
'update a version in native code with a value stored on EAS servers'; 'Update a version in native code with a value stored on EAS servers';
public static override flags = { public static override flags = {
platform: Flags.enum({ platform: Flags.enum({
@@ -64,7 +64,7 @@ export default class BuildVersionSyncView extends EasCommand {
const { flags } = await this.parse(BuildVersionSyncView); const { flags } = await this.parse(BuildVersionSyncView);
const { const {
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
getDynamicPrivateProjectConfigAsync, getDynamicProjectConfigAsync,
projectDir, projectDir,
} = await this.getContextAsync(BuildVersionSyncView, { } = await this.getContextAsync(BuildVersionSyncView, {
nonInteractive: true, nonInteractive: true,
@@ -72,7 +72,7 @@ export default class BuildVersionSyncView extends EasCommand {
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform); const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir); const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
await ensureVersionSourceIsRemoteAsync(easJsonAccessor, { nonInteractive: false }); await ensureVersionSourceIsRemoteAsync(easJsonAccessor);
const platforms = toPlatforms(requestedPlatform); const platforms = toPlatforms(requestedPlatform);
const buildProfiles = await getProfilesAsync({ const buildProfiles = await getProfilesAsync({
@@ -82,7 +82,7 @@ export default class BuildVersionSyncView extends EasCommand {
profileName: flags.profile ?? undefined, profileName: flags.profile ?? undefined,
}); });
for (const profileInfo of buildProfiles) { for (const profileInfo of buildProfiles) {
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ const { exp, projectId } = await getDynamicProjectConfigAsync({
env: profileInfo.profile.env, env: profileInfo.profile.env,
}); });
+1 -1
View File
@@ -28,7 +28,7 @@ export default class BuildView extends EasCommand {
flags, flags,
} = await this.parse(BuildView); } = await this.parse(BuildView);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildView, { } = await this.getContextAsync(BuildView, {
nonInteractive: true, nonInteractive: true,
@@ -38,7 +38,7 @@ export default class ChannelCreate extends EasCommand {
flags: { json: jsonFlag, 'non-interactive': nonInteractive }, flags: { json: jsonFlag, 'non-interactive': nonInteractive },
} = await this.parse(ChannelCreate); } = await this.parse(ChannelCreate);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelCreate, { } = await this.getContextAsync(ChannelCreate, {
nonInteractive, nonInteractive,
@@ -41,7 +41,7 @@ export default class ChannelDelete extends EasCommand {
flags: { json: jsonFlag, 'non-interactive': nonInteractive }, flags: { json: jsonFlag, 'non-interactive': nonInteractive },
} = await this.parse(ChannelDelete); } = await this.parse(ChannelDelete);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelDelete, { } = await this.getContextAsync(ChannelDelete, {
nonInteractive, nonInteractive,
@@ -75,7 +75,7 @@ export default class ChannelEdit extends EasCommand {
flags: { branch: branchFlag, json, 'non-interactive': nonInteractive }, flags: { branch: branchFlag, json, 'non-interactive': nonInteractive },
} = await this.parse(ChannelEdit); } = await this.parse(ChannelEdit);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelEdit, { } = await this.getContextAsync(ChannelEdit, {
nonInteractive, nonInteractive,
@@ -27,7 +27,7 @@ export default class ChannelList extends EasCommand {
const paginatedQueryOptions = getPaginatedQueryOptions(flags); const paginatedQueryOptions = getPaginatedQueryOptions(flags);
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags; const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelList, { } = await this.getContextAsync(ChannelList, {
nonInteractive, nonInteractive,
@@ -336,7 +336,7 @@ export default class ChannelRollout extends EasCommand {
}, },
} = await this.parse(ChannelRollout); } = await this.parse(ChannelRollout);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelRollout, { } = await this.getContextAsync(ChannelRollout, {
nonInteractive, nonInteractive,
@@ -38,7 +38,7 @@ export default class ChannelView extends EasCommand {
const paginatedQueryOptions = getPaginatedQueryOptions(flags); const paginatedQueryOptions = getPaginatedQueryOptions(flags);
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags; const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelView, { } = await this.getContextAsync(ChannelView, {
nonInteractive, nonInteractive,
+3 -2
View File
@@ -41,7 +41,7 @@ export default class Config extends EasCommand {
enableJsonOutput(); enableJsonOutput();
} }
const { platform: maybePlatform, profile: maybeProfile } = flags; const { platform: maybePlatform, profile: maybeProfile } = flags;
const { getDynamicPublicProjectConfigAsync, projectDir } = await this.getContextAsync(Config, { const { getDynamicProjectConfigAsync, projectDir } = await this.getContextAsync(Config, {
nonInteractive: false, nonInteractive: false,
}); });
@@ -82,8 +82,9 @@ export default class Config extends EasCommand {
Log.log(JSON.stringify(profile, null, 2)); Log.log(JSON.stringify(profile, null, 2));
} }
} else { } else {
const { exp: appConfig } = await getDynamicPublicProjectConfigAsync({ const { exp: appConfig } = await getDynamicProjectConfigAsync({
env: profile.env, env: profile.env,
isPublicConfig: true,
}); });
if (flags.json) { if (flags.json) {
+4 -4
View File
@@ -21,8 +21,8 @@ export default class Credentials extends EasCommand {
const { flags } = await this.parse(Credentials); const { flags } = await this.parse(Credentials);
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
privateProjectConfig, projectConfig,
getDynamicPrivateProjectConfigAsync, getDynamicProjectConfigAsync,
analytics, analytics,
} = await this.getContextAsync(Credentials, { } = await this.getContextAsync(Credentials, {
nonInteractive: false, nonInteractive: false,
@@ -32,8 +32,8 @@ export default class Credentials extends EasCommand {
actor, actor,
graphqlClient, graphqlClient,
analytics, analytics,
privateProjectConfig ?? null, projectConfig ?? null,
getDynamicPrivateProjectConfigAsync, getDynamicProjectConfigAsync,
flags.platform flags.platform
).runAsync(); ).runAsync();
} }
@@ -15,7 +15,7 @@ export default class DeviceCreate extends EasCommand {
// this command is interactive by design // this command is interactive by design
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
privateProjectConfig, projectConfig,
} = await this.getContextAsync(DeviceCreate, { } = await this.getContextAsync(DeviceCreate, {
nonInteractive: false, nonInteractive: false,
}); });
@@ -24,7 +24,7 @@ export default class DeviceCreate extends EasCommand {
appStore: new AppStoreApi(), appStore: new AppStoreApi(),
user: actor, user: actor,
graphqlClient, graphqlClient,
projectId: privateProjectConfig?.projectId, projectId: projectConfig?.projectId,
}); });
const manager = new DeviceManager(ctx); const manager = new DeviceManager(ctx);
await manager.createAsync(); await manager.createAsync();
@@ -43,7 +43,7 @@ export default class DeviceDelete extends EasCommand {
const paginatedQueryOptions = getPaginatedQueryOptions(flags); const paginatedQueryOptions = getPaginatedQueryOptions(flags);
let { 'apple-team-id': appleTeamIdentifier, udid } = flags; let { 'apple-team-id': appleTeamIdentifier, udid } = flags;
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(DeviceDelete, { } = await this.getContextAsync(DeviceDelete, {
nonInteractive: paginatedQueryOptions.nonInteractive, nonInteractive: paginatedQueryOptions.nonInteractive,
@@ -112,17 +112,11 @@ export default class DeviceDelete extends EasCommand {
const removeAppleSpinner = ora('Disabling device on Apple').start(); const removeAppleSpinner = ora('Disabling device on Apple').start();
try { try {
const appleValidatedDevices = await Device.getAsync(context); const appleValidatedDevices = await Device.getAsync(context);
const appleValidatedDevice = appleValidatedDevices.find( const appleValidatedDevice = appleValidatedDevices.find(d => d.id === device.id);
d => d.attributes.udid === device.identifier
);
if (appleValidatedDevice) { if (appleValidatedDevice) {
await appleValidatedDevice.updateAsync({ status: DeviceStatus.DISABLED }); await appleValidatedDevice.updateAsync({ status: DeviceStatus.DISABLED });
removeAppleSpinner.succeed('Disabled device on Apple');
} else {
removeAppleSpinner.warn(
'Device not found on Apple Developer Portal. Expo-registered devices will not appear there until they are chosen for an internal distribution build.'
);
} }
removeAppleSpinner.succeed('Disabled device on Apple');
} catch (err) { } catch (err) {
removeAppleSpinner.fail(); removeAppleSpinner.fail();
throw err; throw err;
+1 -1
View File
@@ -29,7 +29,7 @@ export default class BuildList extends EasCommand {
const { flags } = await this.parse(BuildList); const { flags } = await this.parse(BuildList);
const paginatedQueryOptions = getPaginatedQueryOptions(flags); const paginatedQueryOptions = getPaginatedQueryOptions(flags);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildList, { } = await this.getContextAsync(BuildList, {
nonInteractive: paginatedQueryOptions.nonInteractive, nonInteractive: paginatedQueryOptions.nonInteractive,
@@ -1,168 +0,0 @@
import { Device } from '@expo/apple-utils';
import { Flags } from '@oclif/core';
import assert from 'assert';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { PaginatedQueryOptions, getPaginatedQueryOptions } from '../../commandUtils/pagination';
import { AppleDeviceMutation } from '../../credentials/ios/api/graphql/mutations/AppleDeviceMutation';
import {
AppleDeviceQuery,
AppleDeviceQueryResult,
} from '../../credentials/ios/api/graphql/queries/AppleDeviceQuery';
import { authenticateAsync, getRequestContext } from '../../credentials/ios/appstore/authenticate';
import {
selectAppleDeviceOnAppleTeamAsync,
selectAppleTeamOnAccountAsync,
} from '../../devices/queries';
import formatDevice from '../../devices/utils/formatDevice';
import { AppleDevice, AppleTeamFragment, Maybe } from '../../graphql/generated';
import Log from '../../log';
import { ora } from '../../ora';
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
export default class DeviceRename extends EasCommand {
static override description = 'rename a registered device';
static override flags = {
'apple-team-id': Flags.string({ description: 'The Apple team ID on which to find the device' }),
udid: Flags.string({ description: 'The Apple device ID to rename' }),
name: Flags.string({ description: 'The new name for the device' }),
...EasNonInteractiveAndJsonFlags,
};
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(DeviceRename);
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
let { 'apple-team-id': appleTeamIdentifier, udid, name } = flags;
const {
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(DeviceRename, {
nonInteractive: paginatedQueryOptions.nonInteractive,
});
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
let appleTeamName;
if (paginatedQueryOptions.json) {
enableJsonOutput();
}
let appleTeam: AppleTeamFragment;
if (!appleTeamIdentifier) {
appleTeam = await selectAppleTeamOnAccountAsync(graphqlClient, {
accountName: account.name,
selectionPromptTitle: `What Apple team would you like to list devices for?`,
paginatedQueryOptions,
});
appleTeamIdentifier = appleTeam.appleTeamIdentifier;
appleTeamName = appleTeam.appleTeamName;
}
assert(appleTeamIdentifier, 'No team identifier is specified');
const chosenDevice = udid
? await AppleDeviceQuery.getByDeviceIdentifierAsync(graphqlClient, account.name, udid)
: await selectAppleDeviceOnAppleTeamAsync(graphqlClient, {
accountName: account.name,
appleTeamIdentifier,
selectionPromptTitle: `Which device would you like to rename?`,
paginatedQueryOptions,
});
const newDeviceName = name ? name : await this.promptForNewDeviceNameAsync(chosenDevice.name);
this.logChosenDevice(chosenDevice, appleTeamName, appleTeamIdentifier, paginatedQueryOptions);
await this.renameDeviceOnExpoAsync(graphqlClient, chosenDevice, newDeviceName!);
await this.renameDeviceOnAppleAsync(chosenDevice, appleTeamIdentifier, newDeviceName!);
}
async promptForNewDeviceNameAsync(
initial: Maybe<string> | undefined
): Promise<string | undefined> {
const { name } = await promptAsync({
type: 'text',
name: 'name',
message: 'New device name:',
initial: initial ?? undefined,
});
return name;
}
async renameDeviceOnExpoAsync(
graphqlClient: ExpoGraphqlClient,
chosenDevice: AppleDevice | AppleDeviceQueryResult,
newDeviceName: string
): Promise<void> {
const removalSpinner = ora(`Renaming Apple device on Expo`).start();
try {
await AppleDeviceMutation.updateAppleDeviceAsync(graphqlClient, chosenDevice.id, {
name: newDeviceName,
});
removalSpinner.succeed('Renamed Apple device on Expo');
} catch (err) {
removalSpinner.fail();
throw err;
}
}
async renameDeviceOnAppleAsync(
device: AppleDevice | AppleDeviceQueryResult,
appleTeamIdentifier: string,
newDeviceName: string
): Promise<void> {
const ctx = await authenticateAsync({ teamId: appleTeamIdentifier });
const context = getRequestContext(ctx);
Log.addNewLineIfNone();
const removeAppleSpinner = ora('Renaming device on Apple').start();
try {
const appleValidatedDevices = await Device.getAsync(context);
const appleValidatedDevice = appleValidatedDevices.find(
d => d.attributes.udid === device.identifier
);
if (appleValidatedDevice) {
await appleValidatedDevice.updateAsync({ name: newDeviceName });
removeAppleSpinner.succeed('Renamed device on Apple');
} else {
removeAppleSpinner.warn(
'Device not found on Apple Developer Portal. Expo-registered devices will not appear there until they are chosen for an internal distribution build.'
);
}
} catch (err) {
removeAppleSpinner.fail();
throw err;
}
}
logChosenDevice(
device: AppleDevice | AppleDeviceQueryResult,
appleTeamName: Maybe<string> | undefined,
appleTeamIdentifier: string,
{ json }: PaginatedQueryOptions
): void {
if (json) {
printJsonOnlyOutput(device);
} else {
Log.addNewLineIfNone();
Log.log(
formatDevice(device, {
appleTeamName,
appleTeamIdentifier: appleTeamIdentifier!,
})
);
Log.newLine();
}
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ If you are not sure what is the UDID of the device you are looking for, run:
throw new Error('Device UDID is missing'); throw new Error('Device UDID is missing');
} }
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(DeviceView, { } = await this.getContextAsync(DeviceView, {
nonInteractive: true, nonInteractive: true,
@@ -35,7 +35,7 @@ export default class MetadataPull extends EasCommand {
const { flags } = await this.parse(MetadataPull); const { flags } = await this.parse(MetadataPull);
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
privateProjectConfig: { exp, projectId, projectDir }, projectConfig: { exp, projectId, projectDir },
analytics, analytics,
} = await this.getContextAsync(MetadataPull, { } = await this.getContextAsync(MetadataPull, {
nonInteractive: false, nonInteractive: false,
@@ -33,7 +33,7 @@ export default class MetadataPush extends EasCommand {
const { flags } = await this.parse(MetadataPush); const { flags } = await this.parse(MetadataPush);
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
privateProjectConfig: { exp, projectId, projectDir }, projectConfig: { exp, projectId, projectDir },
analytics, analytics,
} = await this.getContextAsync(MetadataPush, { } = await this.getContextAsync(MetadataPush, {
nonInteractive: false, nonInteractive: false,
+1 -1
View File
@@ -16,7 +16,7 @@ export default class Open extends EasCommand {
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
// this command is interactive by nature (only really run by humans in a terminal) // this command is interactive by nature (only really run by humans in a terminal)
const { const {
privateProjectConfig: { projectId, exp }, projectConfig: { projectId, exp },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(Open, { } = await this.getContextAsync(Open, {
nonInteractive: false, nonInteractive: false,
@@ -1,4 +1,4 @@
import { AppJSONConfig, PackageJSONConfig, getConfig } from '@expo/config'; import { AppJSONConfig, PackageJSONConfig, getConfig, modifyConfigAsync } from '@expo/config';
import chalk from 'chalk'; import chalk from 'chalk';
import { vol } from 'memfs'; import { vol } from 'memfs';
import { instance, mock } from 'ts-mockito'; import { instance, mock } from 'ts-mockito';
@@ -12,17 +12,12 @@ import FeatureGating from '../../../commandUtils/gating/FeatureGating';
import { jester, jester2 } from '../../../credentials/__tests__/fixtures-constants'; import { jester, jester2 } from '../../../credentials/__tests__/fixtures-constants';
import { AppMutation } from '../../../graphql/mutations/AppMutation'; import { AppMutation } from '../../../graphql/mutations/AppMutation';
import { AppQuery } from '../../../graphql/queries/AppQuery'; import { AppQuery } from '../../../graphql/queries/AppQuery';
import { createOrModifyExpoConfigAsync } from '../../../project/expoConfig';
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync'; import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
import { confirmAsync, promptAsync } from '../../../prompts'; import { confirmAsync, promptAsync } from '../../../prompts';
import ProjectInit from '../init'; import ProjectInit from '../init';
jest.mock('fs'); jest.mock('fs');
jest.mock('@expo/config'); jest.mock('@expo/config');
jest.mock('../../../project/expoConfig', () => ({
...jest.requireActual('../../../project/expoConfig'),
createOrModifyExpoConfigAsync: jest.fn(),
}));
jest.mock('../../../prompts'); jest.mock('../../../prompts');
jest.mock('../../../user/actions'); jest.mock('../../../user/actions');
jest.mock('../../../graphql/mutations/AppMutation'); jest.mock('../../../graphql/mutations/AppMutation');
@@ -148,16 +143,13 @@ describe(ProjectInit.name, () => {
}); });
describe('force', () => { describe('force', () => {
beforeEach(() => { it('does not prompt to overwrite when different', async () => {
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({ jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
id: '1234', id: '1234',
slug: 'testing-123', slug: 'testing-123',
fullName: '@jester/testing-123', fullName: '@jester/testing-123',
ownerAccount: jester.accounts[0], ownerAccount: jester.accounts[0],
}); });
});
it('does not prompt to overwrite when different', async () => {
await new ProjectInit(['--id', '12345', '--force'], { await new ProjectInit(['--id', '12345', '--force'], {
root: '/test-project', root: '/test-project',
} as any).run(); } as any).run();
@@ -191,7 +183,7 @@ describe(ProjectInit.name, () => {
}); });
await new ProjectInit(['--id', '1234'], commandOptions).run(); await new ProjectInit(['--id', '1234'], commandOptions).run();
expect(createOrModifyExpoConfigAsync).not.toHaveBeenCalled(); expect(modifyConfigAsync).not.toHaveBeenCalled();
}); });
it('prompts to configure if not consistent', async () => { it('prompts to configure if not consistent', async () => {
@@ -203,20 +195,14 @@ describe(ProjectInit.name, () => {
}); });
jest.mocked(confirmAsync).mockResolvedValue(true); jest.mocked(confirmAsync).mockResolvedValue(true);
jest jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any });
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await new ProjectInit(['--id', '1234'], commandOptions).run(); await new ProjectInit(['--id', '1234'], commandOptions).run();
expect(confirmAsync).toHaveBeenCalled(); expect(confirmAsync).toHaveBeenCalled();
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledTimes(2); expect(modifyConfigAsync).toHaveBeenCalledTimes(2);
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', { expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { owner: 'jester2' });
owner: 'jester2', expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { slug: 'testing-124' });
});
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
slug: 'testing-124',
});
}); });
it('overrides if force flag is present and it is not consistent', async () => { it('overrides if force flag is present and it is not consistent', async () => {
@@ -227,20 +213,14 @@ describe(ProjectInit.name, () => {
ownerAccount: jester2.accounts[0], ownerAccount: jester2.accounts[0],
}); });
jest jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any });
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await new ProjectInit(['--id', '1234', '--force'], commandOptions).run(); await new ProjectInit(['--id', '1234', '--force'], commandOptions).run();
expect(confirmAsync).not.toHaveBeenCalled(); expect(confirmAsync).not.toHaveBeenCalled();
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledTimes(2); expect(modifyConfigAsync).toHaveBeenCalledTimes(2);
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', { expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { owner: 'jester2' });
owner: 'jester2', expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { slug: 'testing-124' });
});
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
slug: 'testing-124',
});
}); });
it('throws if non-interactive is present and it is not consistent', async () => { it('throws if non-interactive is present and it is not consistent', async () => {
@@ -251,9 +231,7 @@ describe(ProjectInit.name, () => {
ownerAccount: jester2.accounts[0], ownerAccount: jester2.accounts[0],
}); });
jest jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any });
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await expect( await expect(
new ProjectInit(['--id', '1234', '--non-interactive'], commandOptions).run() new ProjectInit(['--id', '1234', '--non-interactive'], commandOptions).run()
@@ -262,7 +240,7 @@ describe(ProjectInit.name, () => {
); );
expect(confirmAsync).not.toHaveBeenCalled(); expect(confirmAsync).not.toHaveBeenCalled();
expect(createOrModifyExpoConfigAsync).not.toHaveBeenCalled(); expect(modifyConfigAsync).not.toHaveBeenCalled();
}); });
}); });
}); });
@@ -293,7 +271,7 @@ describe(ProjectInit.name, () => {
}); });
await new ProjectInit(['--id', '1234'], commandOptions).run(); await new ProjectInit(['--id', '1234'], commandOptions).run();
expect(createOrModifyExpoConfigAsync).not.toHaveBeenCalled(); expect(modifyConfigAsync).not.toHaveBeenCalled();
}); });
it('prompts to configure if not consistent', async () => { it('prompts to configure if not consistent', async () => {
@@ -307,20 +285,14 @@ describe(ProjectInit.name, () => {
}); });
jest.mocked(confirmAsync).mockResolvedValue(true); jest.mocked(confirmAsync).mockResolvedValue(true);
jest jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any });
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await new ProjectInit(['--id', '1234'], commandOptions).run(); await new ProjectInit(['--id', '1234'], commandOptions).run();
expect(confirmAsync).toHaveBeenCalled(); expect(confirmAsync).toHaveBeenCalled();
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledTimes(2); expect(modifyConfigAsync).toHaveBeenCalledTimes(2);
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', { expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { owner: 'jester2' });
owner: 'jester2', expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { slug: 'testing-124' });
});
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
slug: 'testing-124',
});
}); });
it('overrides if force flag is present and it is not consistent', async () => { it('overrides if force flag is present and it is not consistent', async () => {
@@ -333,20 +305,14 @@ describe(ProjectInit.name, () => {
ownerAccount: jester2.accounts[0], ownerAccount: jester2.accounts[0],
}); });
jest jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any });
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await new ProjectInit(['--id', '1234', '--force'], commandOptions).run(); await new ProjectInit(['--id', '1234', '--force'], commandOptions).run();
expect(confirmAsync).not.toHaveBeenCalled(); expect(confirmAsync).not.toHaveBeenCalled();
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledTimes(2); expect(modifyConfigAsync).toHaveBeenCalledTimes(2);
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', { expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { owner: 'jester2' });
owner: 'jester2', expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { slug: 'testing-124' });
});
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
slug: 'testing-124',
});
}); });
it('throws if non-interactive is present and it is not consistent', async () => { it('throws if non-interactive is present and it is not consistent', async () => {
@@ -359,9 +325,7 @@ describe(ProjectInit.name, () => {
ownerAccount: jester2.accounts[0], ownerAccount: jester2.accounts[0],
}); });
jest jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any });
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await expect( await expect(
new ProjectInit(['--id', '1234', '--non-interactive'], commandOptions).run() new ProjectInit(['--id', '1234', '--non-interactive'], commandOptions).run()
@@ -370,7 +334,7 @@ describe(ProjectInit.name, () => {
); );
expect(confirmAsync).not.toHaveBeenCalled(); expect(confirmAsync).not.toHaveBeenCalled();
expect(createOrModifyExpoConfigAsync).not.toHaveBeenCalled(); expect(modifyConfigAsync).not.toHaveBeenCalled();
}); });
}); });
}); });
@@ -403,20 +367,14 @@ describe(ProjectInit.name, () => {
}); });
jest.mocked(confirmAsync).mockResolvedValue(true); jest.mocked(confirmAsync).mockResolvedValue(true);
jest jest.mocked(modifyConfigAsync).mockResolvedValue({ type: 'success', config: {} as any });
.mocked(createOrModifyExpoConfigAsync)
.mockResolvedValue({ type: 'success', config: {} as any });
await new ProjectInit([], commandOptions).run(); await new ProjectInit([], commandOptions).run();
expect(confirmAsync).toHaveBeenCalled(); expect(confirmAsync).toHaveBeenCalled();
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledTimes(2); expect(modifyConfigAsync).toHaveBeenCalledTimes(2);
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', { expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { owner: 'jester2' });
owner: 'jester2', expect(modifyConfigAsync).toHaveBeenCalledWith('/test-project', { slug: 'testing-124' });
});
expect(createOrModifyExpoConfigAsync).toHaveBeenCalledWith('/test-project', {
slug: 'testing-124',
});
}); });
}); });
@@ -43,7 +43,7 @@ export default class ProjectInfo extends EasCommand {
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(ProjectInfo, { } = await this.getContextAsync(ProjectInfo, {
nonInteractive: true, nonInteractive: true,
@@ -1,4 +1,4 @@
import { getProjectConfigDescription } from '@expo/config'; import { getProjectConfigDescription, modifyConfigAsync } from '@expo/config';
import { ExpoConfig } from '@expo/config-types'; import { ExpoConfig } from '@expo/config-types';
import { Flags } from '@oclif/core'; import { Flags } from '@oclif/core';
import chalk from 'chalk'; import chalk from 'chalk';
@@ -13,7 +13,7 @@ import { AppMutation } from '../../graphql/mutations/AppMutation';
import { AppQuery } from '../../graphql/queries/AppQuery'; import { AppQuery } from '../../graphql/queries/AppQuery';
import Log, { link } from '../../log'; import Log, { link } from '../../log';
import { ora } from '../../ora'; import { ora } from '../../ora';
import { createOrModifyExpoConfigAsync, getPrivateExpoConfig } from '../../project/expoConfig'; import { getExpoConfig } from '../../project/expoConfig';
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync'; import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
import { toAppPrivacy } from '../../project/projectUtils'; import { toAppPrivacy } from '../../project/projectUtils';
import { confirmAsync, promptAsync } from '../../prompts'; import { confirmAsync, promptAsync } from '../../prompts';
@@ -60,7 +60,7 @@ export default class ProjectInit extends EasCommand {
projectDir: string, projectDir: string,
modifications: Partial<ExpoConfig> modifications: Partial<ExpoConfig>
): Promise<void> { ): Promise<void> {
const result = await createOrModifyExpoConfigAsync(projectDir, modifications); const result = await modifyConfigAsync(projectDir, modifications);
switch (result.type) { switch (result.type) {
case 'success': case 'success':
break; break;
@@ -98,7 +98,7 @@ export default class ProjectInit extends EasCommand {
projectDir: string, projectDir: string,
{ force, nonInteractive }: InitializeMethodOptions { force, nonInteractive }: InitializeMethodOptions
): Promise<void> { ): Promise<void> {
const exp = getPrivateExpoConfig(projectDir); const exp = getExpoConfig(projectDir);
const appForProjectId = await AppQuery.byIdAsync(graphqlClient, projectId); const appForProjectId = await AppQuery.byIdAsync(graphqlClient, projectId);
const correctOwner = appForProjectId.ownerAccount.name; const correctOwner = appForProjectId.ownerAccount.name;
const correctSlug = appForProjectId.slug; const correctSlug = appForProjectId.slug;
@@ -153,7 +153,7 @@ export default class ProjectInit extends EasCommand {
projectDir: string, projectDir: string,
{ force, nonInteractive }: InitializeMethodOptions { force, nonInteractive }: InitializeMethodOptions
): Promise<void> { ): Promise<void> {
const exp = getPrivateExpoConfig(projectDir); const exp = getExpoConfig(projectDir);
const existingProjectId = exp.extra?.eas?.projectId; const existingProjectId = exp.extra?.eas?.projectId;
if (projectId === existingProjectId) { if (projectId === existingProjectId) {
@@ -209,7 +209,7 @@ export default class ProjectInit extends EasCommand {
actor: Actor, actor: Actor,
projectDir: string projectDir: string
): Promise<string> { ): Promise<string> {
const exp = getPrivateExpoConfig(projectDir); const exp = getExpoConfig(projectDir);
const existingProjectId = exp.extra?.eas?.projectId; const existingProjectId = exp.extra?.eas?.projectId;
if (existingProjectId) { if (existingProjectId) {
@@ -66,7 +66,7 @@ export default class EnvironmentSecretCreate extends EasCommand {
}, },
} = await this.parse(EnvironmentSecretCreate); } = await this.parse(EnvironmentSecretCreate);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvironmentSecretCreate, { } = await this.getContextAsync(EnvironmentSecretCreate, {
nonInteractive, nonInteractive,
@@ -31,7 +31,7 @@ export default class EnvironmentSecretDelete extends EasCommand {
flags: { id, 'non-interactive': nonInteractive }, flags: { id, 'non-interactive': nonInteractive },
} = await this.parse(EnvironmentSecretDelete); } = await this.parse(EnvironmentSecretDelete);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvironmentSecretDelete, { } = await this.getContextAsync(EnvironmentSecretDelete, {
nonInteractive, nonInteractive,
+1 -1
View File
@@ -19,7 +19,7 @@ export default class EnvironmentSecretList extends EasCommand {
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvironmentSecretList, { } = await this.getContextAsync(EnvironmentSecretList, {
nonInteractive: true, nonInteractive: true,
+1 -1
View File
@@ -51,7 +51,7 @@ export default class EnvironmentSecretPush extends EasCommand {
flags: { scope, force, 'env-file': maybeEnvFilePath, 'non-interactive': nonInteractive }, flags: { scope, force, 'env-file': maybeEnvFilePath, 'non-interactive': nonInteractive },
} = await this.parse(EnvironmentSecretPush); } = await this.parse(EnvironmentSecretPush);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvironmentSecretPush, { } = await this.getContextAsync(EnvironmentSecretPush, {
nonInteractive, nonInteractive,
+1 -1
View File
@@ -100,7 +100,7 @@ export default class Submit extends EasCommand {
const { flags: rawFlags } = await this.parse(Submit); const { flags: rawFlags } = await this.parse(Submit);
const { const {
loggedIn: { actor, graphqlClient }, loggedIn: { actor, graphqlClient },
privateProjectConfig: { exp, projectId, projectDir }, projectConfig: { exp, projectId, projectDir },
analytics, analytics,
} = await this.getContextAsync(Submit, { } = await this.getContextAsync(Submit, {
nonInteractive: false, nonInteractive: false,
@@ -1,4 +1,11 @@
import { AppJSONConfig, ExpoConfig, PackageJSONConfig, Platform, getConfig } from '@expo/config'; import {
AppJSONConfig,
ExpoConfig,
GetConfigOptions,
PackageJSONConfig,
Platform,
getConfig,
} from '@expo/config';
import { Updates } from '@expo/config-plugins'; import { Updates } from '@expo/config-plugins';
import { vol } from 'memfs'; import { vol } from 'memfs';
import nullthrows from 'nullthrows'; import nullthrows from 'nullthrows';
@@ -7,10 +14,7 @@ import { instance, mock } from 'ts-mockito';
import UpdatePublish from '..'; import UpdatePublish from '..';
import { ensureBranchExistsAsync } from '../../../branch/queries'; import { ensureBranchExistsAsync } from '../../../branch/queries';
import { import { DynamicProjectConfigContextField } from '../../../commandUtils/context/DynamicProjectConfigContextField';
DynamicPrivateProjectConfigContextField,
DynamicPublicProjectConfigContextField,
} from '../../../commandUtils/context/DynamicProjectConfigContextField';
import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField'; import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient'; import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides'; import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides';
@@ -233,19 +237,13 @@ function mockTestProject({
jest.mocked(getConfig).mockReturnValue(mockManifest as any); jest.mocked(getConfig).mockReturnValue(mockManifest as any);
jest jest
.spyOn(DynamicPrivateProjectConfigContextField.prototype, 'getValueAsync') .spyOn(DynamicProjectConfigContextField.prototype, 'getValueAsync')
.mockResolvedValue(async () => { .mockResolvedValue(async (options?: GetConfigOptions) => {
const exp = { ...mockManifest.exp }; let exp = { ...mockManifest.exp };
return {
exp, // Fake a limited public config
projectDir: projectRoot, if (options?.isPublicConfig) {
projectId: configuredProjectId, exp = {
};
});
jest
.spyOn(DynamicPublicProjectConfigContextField.prototype, 'getValueAsync')
.mockResolvedValue(async () => {
const exp = {
name: mockManifest.exp.name, name: mockManifest.exp.name,
version: mockManifest.exp.version, version: mockManifest.exp.version,
slug: mockManifest.exp.slug, slug: mockManifest.exp.slug,
@@ -253,6 +251,8 @@ function mockTestProject({
owner: mockManifest.exp.owner, owner: mockManifest.exp.owner,
extra: mockManifest.exp.extra, extra: mockManifest.exp.extra,
}; };
}
return { return {
exp, exp,
projectDir: projectRoot, projectDir: projectRoot,
@@ -3,7 +3,7 @@ import { vol } from 'memfs';
import { instance, mock } from 'ts-mockito'; import { instance, mock } from 'ts-mockito';
import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField'; import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField';
import { PrivateProjectConfigContextField } from '../../../commandUtils/context/PrivateProjectConfigContextField'; import ProjectConfigContextField from '../../../commandUtils/context/ProjectConfigContextField';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient'; import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides'; import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides';
import FeatureGating from '../../../commandUtils/gating/FeatureGating'; import FeatureGating from '../../../commandUtils/gating/FeatureGating';
@@ -324,7 +324,7 @@ function mockTestProject({
const graphqlClient = instance(mock<ExpoGraphqlClient>({})); const graphqlClient = instance(mock<ExpoGraphqlClient>({}));
jest.mocked(getConfig).mockReturnValue(mockManifest as any); jest.mocked(getConfig).mockReturnValue(mockManifest as any);
jest.spyOn(PrivateProjectConfigContextField.prototype, 'getValueAsync').mockResolvedValue({ jest.spyOn(ProjectConfigContextField.prototype, 'getValueAsync').mockResolvedValue({
exp: mockManifest.exp, exp: mockManifest.exp,
projectDir: projectRoot, projectDir: projectRoot,
projectId: configuredProjectId, projectId: configuredProjectId,
@@ -1,4 +1,10 @@
import { AppJSONConfig, ExpoConfig, PackageJSONConfig, getConfig } from '@expo/config'; import {
AppJSONConfig,
ExpoConfig,
GetConfigOptions,
PackageJSONConfig,
getConfig,
} from '@expo/config';
import { Updates } from '@expo/config-plugins'; import { Updates } from '@expo/config-plugins';
import { vol } from 'memfs'; import { vol } from 'memfs';
import nullthrows from 'nullthrows'; import nullthrows from 'nullthrows';
@@ -6,10 +12,7 @@ import path from 'path';
import { instance, mock } from 'ts-mockito'; import { instance, mock } from 'ts-mockito';
import { ensureBranchExistsAsync } from '../../../branch/queries'; import { ensureBranchExistsAsync } from '../../../branch/queries';
import { import { DynamicProjectConfigContextField } from '../../../commandUtils/context/DynamicProjectConfigContextField';
DynamicPrivateProjectConfigContextField,
DynamicPublicProjectConfigContextField,
} from '../../../commandUtils/context/DynamicProjectConfigContextField';
import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField'; import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient'; import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides'; import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides';
@@ -214,19 +217,13 @@ function mockTestProject({
jest.mocked(getConfig).mockReturnValue(mockManifest as any); jest.mocked(getConfig).mockReturnValue(mockManifest as any);
jest jest
.spyOn(DynamicPrivateProjectConfigContextField.prototype, 'getValueAsync') .spyOn(DynamicProjectConfigContextField.prototype, 'getValueAsync')
.mockResolvedValue(async () => { .mockResolvedValue(async (options?: GetConfigOptions) => {
const exp = { ...mockManifest.exp }; let exp = { ...mockManifest.exp };
return {
exp, // Fake a limited public config
projectDir: projectRoot, if (options?.isPublicConfig) {
projectId: configuredProjectId, exp = {
};
});
jest
.spyOn(DynamicPublicProjectConfigContextField.prototype, 'getValueAsync')
.mockResolvedValue(async () => {
const exp = {
name: mockManifest.exp.name, name: mockManifest.exp.name,
version: mockManifest.exp.version, version: mockManifest.exp.version,
slug: mockManifest.exp.slug, slug: mockManifest.exp.slug,
@@ -234,6 +231,8 @@ function mockTestProject({
owner: mockManifest.exp.owner, owner: mockManifest.exp.owner,
extra: mockManifest.exp.extra, extra: mockManifest.exp.extra,
}; };
}
return { return {
exp, exp,
projectDir: projectRoot, projectDir: projectRoot,
@@ -1,16 +1,14 @@
import { Flags } from '@oclif/core'; import { Flags } from '@oclif/core';
import chalk from 'chalk'; import chalk from 'chalk';
import { easJsonExistsAsync } from '../../build/configure';
import EasCommand from '../../commandUtils/EasCommand'; import EasCommand from '../../commandUtils/EasCommand';
import { EASNonInteractiveFlag } from '../../commandUtils/flags'; import { EASNonInteractiveFlag } from '../../commandUtils/flags';
import Log, { learnMore } from '../../log'; import Log from '../../log';
import { RequestedPlatform } from '../../platform'; import { RequestedPlatform } from '../../platform';
import { import {
ensureEASUpdateIsConfiguredAsync, ensureEASUpdateIsConfiguredAsync,
ensureEASUpdateIsConfiguredInEasJsonAsync, ensureEASUpdateIsConfiguredInEasJsonAsync,
} from '../../update/configure'; } from '../../update/configure';
import { getVcsClient } from '../../vcs';
export default class UpdateConfigure extends EasCommand { export default class UpdateConfigure extends EasCommand {
static override description = 'configure the project to support EAS Update'; static override description = 'configure the project to support EAS Update';
@@ -34,7 +32,7 @@ export default class UpdateConfigure extends EasCommand {
const { flags } = await this.parse(UpdateConfigure); const { flags } = await this.parse(UpdateConfigure);
const platform = flags.platform as RequestedPlatform; const platform = flags.platform as RequestedPlatform;
const { const {
privateProjectConfig: { projectId, exp, projectDir }, projectConfig: { projectId, exp, projectDir },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdateConfigure, { } = await this.getContextAsync(UpdateConfigure, {
nonInteractive: flags['non-interactive'], nonInteractive: flags['non-interactive'],
@@ -44,8 +42,6 @@ export default class UpdateConfigure extends EasCommand {
'💡 The following process will configure your project to run EAS Update. These changes only apply to your local project files and you can safely revert them at any time.' '💡 The following process will configure your project to run EAS Update. These changes only apply to your local project files and you can safely revert them at any time.'
); );
await getVcsClient().ensureRepoExistsAsync();
await ensureEASUpdateIsConfiguredAsync(graphqlClient, { await ensureEASUpdateIsConfiguredAsync(graphqlClient, {
exp, exp,
projectId, projectId,
@@ -58,14 +54,21 @@ export default class UpdateConfigure extends EasCommand {
Log.addNewLineIfNone(); Log.addNewLineIfNone();
Log.log(`🎉 Your app is configured with EAS Update!`); Log.log(`🎉 Your app is configured with EAS Update!`);
Log.newLine(); Log.newLine();
const easJsonExists = await easJsonExistsAsync(projectDir); Log.log(`${chalk.bold('Next steps')}:`);
if (!easJsonExists) { Log.newLine();
Log.log(`- Run ${chalk.bold('eas build:configure')} to complete your installation`); Log.log('Update a production build:');
} Log.log(`1. Create a new build. Example: ${chalk.bold('eas build --profile production')}.`);
Log.log('2. Make changes in your project.');
Log.log(`3. Publish an update. Example: ${chalk.bold('eas update --channel production')}.`);
Log.log('4. Force close and reopen the app at least twice to view the update.');
Log.newLine();
Log.log('Preview an update:');
Log.log( Log.log(
`- ${learnMore('https://docs.expo.dev/eas-update/introduction/', { `1. Publish an update to a branch. Example: ${chalk.bold('eas update --branch new-feature')}.`
learnMoreMessage: 'Learn more about other capabilities of EAS Update', );
})}` Log.log(
'2. In Expo Go or a development build, navigate to Projects > [project name] > Branch > Open.'
); );
} }
} }
+15 -65
View File
@@ -23,7 +23,6 @@ import { RequestedPlatform } from '../../platform';
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils'; import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
import { import {
ExpoCLIExportPlatformFlag, ExpoCLIExportPlatformFlag,
RawAsset,
buildBundlesAsync, buildBundlesAsync,
buildUnsortedUpdateInfoGroupAsync, buildUnsortedUpdateInfoGroupAsync,
collectAssetsAsync, collectAssetsAsync,
@@ -40,13 +39,13 @@ import {
} from '../../project/publish'; } from '../../project/publish';
import { ensureEASUpdateIsConfiguredAsync } from '../../update/configure'; import { ensureEASUpdateIsConfiguredAsync } from '../../update/configure';
import { getUpdateGroupJsonInfo } from '../../update/utils'; import { getUpdateGroupJsonInfo } from '../../update/utils';
import { maybeWarnAboutEasPlanOverageAsync } from '../../utils/billing';
import { import {
checkManifestBodyAgainstUpdateInfoGroup, checkManifestBodyAgainstUpdateInfoGroup,
getCodeSigningInfoAsync, getCodeSigningInfoAsync,
getManifestBodyAsync, getManifestBodyAsync,
signBody, signBody,
} from '../../utils/code-signing'; } from '../../utils/code-signing';
import areSetsEqual from '../../utils/expodash/areSetsEqual';
import uniqBy from '../../utils/expodash/uniqBy'; import uniqBy from '../../utils/expodash/uniqBy';
import formatFields from '../../utils/formatFields'; import formatFields from '../../utils/formatFields';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json'; import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
@@ -61,7 +60,6 @@ type RawUpdateFlags = {
platform: string; platform: string;
'input-dir': string; 'input-dir': string;
'skip-bundler': boolean; 'skip-bundler': boolean;
'clear-cache': boolean;
'private-key-path'?: string; 'private-key-path'?: string;
'non-interactive': boolean; 'non-interactive': boolean;
json: boolean; json: boolean;
@@ -79,7 +77,6 @@ type UpdateFlags = {
updateMessage?: string; updateMessage?: string;
inputDir: string; inputDir: string;
skipBundler: boolean; skipBundler: boolean;
clearCache: boolean;
privateKeyPath?: string; privateKeyPath?: string;
json: boolean; json: boolean;
nonInteractive: boolean; nonInteractive: boolean;
@@ -98,7 +95,6 @@ export default class UpdatePublish extends EasCommand {
required: false, required: false,
}), }),
message: Flags.string({ message: Flags.string({
char: 'm',
description: 'A short message describing the update', description: 'A short message describing the update',
required: false, required: false,
}), }),
@@ -119,10 +115,6 @@ export default class UpdatePublish extends EasCommand {
description: `Skip running Expo CLI to bundle the app before publishing`, description: `Skip running Expo CLI to bundle the app before publishing`,
default: false, default: false,
}), }),
'clear-cache': Flags.boolean({
description: `Clear the bundler cache before publishing`,
default: false,
}),
platform: Flags.enum({ platform: Flags.enum({
char: 'p', char: 'p',
options: [ options: [
@@ -160,7 +152,6 @@ export default class UpdatePublish extends EasCommand {
updateMessage: updateMessageArg, updateMessage: updateMessageArg,
inputDir, inputDir,
skipBundler, skipBundler,
clearCache,
privateKeyPath, privateKeyPath,
json: jsonFlag, json: jsonFlag,
nonInteractive, nonInteractive,
@@ -168,8 +159,7 @@ export default class UpdatePublish extends EasCommand {
} = this.sanitizeFlags(rawFlags); } = this.sanitizeFlags(rawFlags);
const { const {
getDynamicPublicProjectConfigAsync, getDynamicProjectConfigAsync,
getDynamicPrivateProjectConfigAsync,
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdatePublish, { } = await this.getContextAsync(UpdatePublish, {
nonInteractive, nonInteractive,
@@ -183,8 +173,11 @@ export default class UpdatePublish extends EasCommand {
exp: expPossiblyWithoutEasUpdateConfigured, exp: expPossiblyWithoutEasUpdateConfigured,
projectId, projectId,
projectDir, projectDir,
} = await getDynamicPublicProjectConfigAsync(); } = await getDynamicProjectConfigAsync({
isPublicConfig: true,
});
await maybeWarnAboutEasPlanOverageAsync(graphqlClient, projectId);
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]); await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]);
await ensureEASUpdateIsConfiguredAsync(graphqlClient, { await ensureEASUpdateIsConfiguredAsync(graphqlClient, {
@@ -194,8 +187,10 @@ export default class UpdatePublish extends EasCommand {
projectId, projectId,
}); });
const { exp } = await getDynamicPublicProjectConfigAsync(); const { exp } = await getDynamicProjectConfigAsync({ isPublicConfig: true });
const { exp: expPrivate } = await getDynamicPrivateProjectConfigAsync(); const { exp: expPrivate } = await getDynamicProjectConfigAsync({
isPublicConfig: false,
});
const codeSigningInfo = await getCodeSigningInfoAsync(expPrivate, privateKeyPath); const codeSigningInfo = await getCodeSigningInfoAsync(expPrivate, privateKeyPath);
const branchName = await getBranchNameForCommandAsync({ const branchName = await getBranchNameForCommandAsync({
@@ -219,7 +214,7 @@ export default class UpdatePublish extends EasCommand {
if (!skipBundler) { if (!skipBundler) {
const bundleSpinner = ora().start('Exporting...'); const bundleSpinner = ora().start('Exporting...');
try { try {
await buildBundlesAsync({ projectDir, inputDir, exp, platformFlag, clearCache }); await buildBundlesAsync({ projectDir, inputDir, exp, platformFlag });
bundleSpinner.succeed('Exported bundle(s)'); bundleSpinner.succeed('Exported bundle(s)');
} catch (e) { } catch (e) {
bundleSpinner.fail('Export failed'); bundleSpinner.fail('Export failed');
@@ -241,58 +236,14 @@ export default class UpdatePublish extends EasCommand {
const assets = filterExportedPlatformsByFlag(collectedAssets, platformFlag); const assets = filterExportedPlatformsByFlag(collectedAssets, platformFlag);
realizedPlatforms = Object.keys(assets) as PublishPlatform[]; realizedPlatforms = Object.keys(assets) as PublishPlatform[];
// Timeout mechanism: const uploadResults = await uploadAssetsAsync(
// - Start with 60 second timeout. 60 seconds is chosen because the cloud function that processes
// uploaded assets has a timeout of 60 seconds.
// - Each time one or more assets reports as ready, reset the timeout to 60 seconds.
// - Start upload. Internally, uploadAssetsAsync uploads them all and then checks for successful
// processing every (5 + n) seconds with a linear backoff of n + 1 second.
// - At the same time as upload is started, start timeout checker which checks every 1 second to see
// if timeout has been reached. When timeout expires, send a cancellation signal to currently running
// upload function call to instruct it to stop uploading or checking for successful processing.
let lastUploadedStorageKeys = new Set<string>();
let lastAssetUploadResults: {
asset: RawAsset & { storageKey: string };
finished: boolean;
}[] = [];
let timeAtWhichToTimeout = Date.now() + 60 * 1000; // sixty seconds from now
const cancelationToken = { isCanceledOrFinished: false };
const uploadResults = await Promise.race([
uploadAssetsAsync(
graphqlClient, graphqlClient,
assets, assets,
projectId, projectId,
cancelationToken, (totalAssets, missingAssets) => {
assetUploadResults => { assetSpinner.text = `Uploading (${totalAssets - missingAssets}/${totalAssets})`;
const currentUploadedStorageKeys = new Set( }
assetUploadResults.filter(r => r.finished).map(r => r.asset.storageKey)
); );
if (!areSetsEqual(currentUploadedStorageKeys, lastUploadedStorageKeys)) {
timeAtWhichToTimeout = Date.now() + 60 * 1000; // reset timeout to sixty seconds from now
lastUploadedStorageKeys = currentUploadedStorageKeys;
lastAssetUploadResults = assetUploadResults;
}
const totalAssets = assetUploadResults.length;
const missingAssetCount = assetUploadResults.filter(a => !a.finished).length;
assetSpinner.text = `Uploading (${totalAssets - missingAssetCount}/${totalAssets})`;
}
),
(async () => {
while (Date.now() < timeAtWhichToTimeout) {
if (cancelationToken.isCanceledOrFinished) {
break;
}
await new Promise(res => setTimeout(res, 1000)); // wait 1 second
}
cancelationToken.isCanceledOrFinished = true;
const timedOutAssets = lastAssetUploadResults
.filter(r => !r.finished)
.map(r => `\n- ${r.asset.originalPath ?? r.asset.path}`);
throw new Error(`Asset processing timed out for assets: ${timedOutAssets}`);
})(),
]);
uploadedAssetCount = uploadResults.uniqueUploadedAssetCount; uploadedAssetCount = uploadResults.uniqueUploadedAssetCount;
assetLimitPerUpdateGroup = uploadResults.assetLimitPerUpdateGroup; assetLimitPerUpdateGroup = uploadResults.assetLimitPerUpdateGroup;
@@ -539,7 +490,6 @@ export default class UpdatePublish extends EasCommand {
updateMessage, updateMessage,
inputDir: flags['input-dir'], inputDir: flags['input-dir'],
skipBundler: flags['skip-bundler'], skipBundler: flags['skip-bundler'],
clearCache: flags['clear-cache'],
platform: flags.platform as RequestedPlatform, platform: flags.platform as RequestedPlatform,
privateKeyPath: flags['private-key-path'], privateKeyPath: flags['private-key-path'],
nonInteractive, nonInteractive,
+1 -1
View File
@@ -41,7 +41,7 @@ export default class UpdateList extends EasCommand {
const { flags } = await this.parse(UpdateList); const { flags } = await this.parse(UpdateList);
const { branch: branchFlag, all, json: jsonFlag, 'non-interactive': nonInteractive } = flags; const { branch: branchFlag, all, json: jsonFlag, 'non-interactive': nonInteractive } = flags;
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdateList, { } = await this.getContextAsync(UpdateList, {
nonInteractive, nonInteractive,
@@ -72,7 +72,6 @@ export default class UpdateRepublish extends EasCommand {
exclusive: ['branch', 'channel'], exclusive: ['branch', 'channel'],
}), }),
message: Flags.string({ message: Flags.string({
char: 'm',
description: 'Short message describing the republished update', description: 'Short message describing the republished update',
required: false, required: false,
}), }),
@@ -95,7 +94,7 @@ export default class UpdateRepublish extends EasCommand {
const flags = this.sanitizeFlags(rawFlags); const flags = this.sanitizeFlags(rawFlags);
const { const {
privateProjectConfig: { exp, projectId }, projectConfig: { exp, projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdateRepublish, { } = await this.getContextAsync(UpdateRepublish, {
nonInteractive: flags.nonInteractive, nonInteractive: flags.nonInteractive,
@@ -122,8 +122,7 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
} = this.sanitizeFlags(rawFlags); } = this.sanitizeFlags(rawFlags);
const { const {
getDynamicPublicProjectConfigAsync, getDynamicProjectConfigAsync,
getDynamicPrivateProjectConfigAsync,
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdateRollBackToEmbedded, { } = await this.getContextAsync(UpdateRollBackToEmbedded, {
nonInteractive, nonInteractive,
@@ -137,7 +136,9 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
exp: expPossiblyWithoutEasUpdateConfigured, exp: expPossiblyWithoutEasUpdateConfigured,
projectId, projectId,
projectDir, projectDir,
} = await getDynamicPublicProjectConfigAsync(); } = await getDynamicProjectConfigAsync({
isPublicConfig: true,
});
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]); await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]);
@@ -148,8 +149,10 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
projectId, projectId,
}); });
const { exp } = await getDynamicPublicProjectConfigAsync(); const { exp } = await getDynamicProjectConfigAsync({ isPublicConfig: true });
const { exp: expPrivate } = await getDynamicPrivateProjectConfigAsync(); const { exp: expPrivate } = await getDynamicProjectConfigAsync({
isPublicConfig: false,
});
const codeSigningInfo = await getCodeSigningInfoAsync(expPrivate, privateKeyPath); const codeSigningInfo = await getCodeSigningInfoAsync(expPrivate, privateKeyPath);
const branchName = await getBranchNameForCommandAsync({ const branchName = await getBranchNameForCommandAsync({
@@ -33,7 +33,7 @@ export default class WebhookCreate extends EasCommand {
async runAsync(): Promise<void> { async runAsync(): Promise<void> {
const { flags } = await this.parse(WebhookCreate); const { flags } = await this.parse(WebhookCreate);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(WebhookCreate, { } = await this.getContextAsync(WebhookCreate, {
nonInteractive: flags['non-interactive'], nonInteractive: flags['non-interactive'],
@@ -39,7 +39,7 @@ export default class WebhookDelete extends EasCommand {
flags: { 'non-interactive': nonInteractive }, flags: { 'non-interactive': nonInteractive },
} = await this.parse(WebhookDelete); } = await this.parse(WebhookDelete);
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(WebhookDelete, { } = await this.getContextAsync(WebhookDelete, {
nonInteractive, nonInteractive,
@@ -36,7 +36,7 @@ export default class WebhookList extends EasCommand {
} }
const { const {
privateProjectConfig: { projectId }, projectConfig: { projectId },
loggedIn: { graphqlClient }, loggedIn: { graphqlClient },
} = await this.getContextAsync(WebhookList, { } = await this.getContextAsync(WebhookList, {
nonInteractive: true, nonInteractive: true,
@@ -151,7 +151,6 @@ export function getNewAndroidApiMock(): { [key in keyof typeof AndroidGraphqlCli
updateAndroidAppCredentialsAsync: jest.fn(), updateAndroidAppCredentialsAsync: jest.fn(),
updateAndroidAppBuildCredentialsAsync: jest.fn(), updateAndroidAppBuildCredentialsAsync: jest.fn(),
createAndroidAppBuildCredentialsAsync: jest.fn(), createAndroidAppBuildCredentialsAsync: jest.fn(),
setDefaultAndroidAppBuildCredentialsAsync: jest.fn(),
getDefaultAndroidAppBuildCredentialsAsync: jest.fn(), getDefaultAndroidAppBuildCredentialsAsync: jest.fn(),
getAndroidAppBuildCredentialsByNameAsync: jest.fn(), getAndroidAppBuildCredentialsByNameAsync: jest.fn(),
createOrUpdateAndroidAppBuildCredentialsByNameAsync: jest.fn(), createOrUpdateAndroidAppBuildCredentialsByNameAsync: jest.fn(),
@@ -146,16 +146,6 @@ export async function updateAndroidAppBuildCredentialsAsync(
); );
} }
export async function setDefaultAndroidAppBuildCredentialsAsync(
graphqlClient: ExpoGraphqlClient,
buildCredentials: AndroidAppBuildCredentialsFragment
): Promise<AndroidAppBuildCredentialsFragment> {
return await AndroidAppBuildCredentialsMutation.setDefaultAndroidAppBuildCredentialsAsync(
graphqlClient,
buildCredentials.id
);
}
export async function createAndroidAppBuildCredentialsAsync( export async function createAndroidAppBuildCredentialsAsync(
graphqlClient: ExpoGraphqlClient, graphqlClient: ExpoGraphqlClient,
appLookupParams: AppLookupParams, appLookupParams: AppLookupParams,
@@ -174,6 +164,14 @@ export async function createAndroidAppBuildCredentialsAsync(
graphqlClient, graphqlClient,
appLookupParams appLookupParams
); );
const buildCredentialsList = androidAppCredentials.androidAppBuildCredentialsList;
const existingDefaultBuildCredentials =
buildCredentialsList.find(buildCredentials => buildCredentials.isDefault) ?? null;
if (existingDefaultBuildCredentials && isDefault) {
throw new Error(
'Cannot create new default Android Build Credentials. A set of default credentials exists already.'
);
}
return await AndroidAppBuildCredentialsMutation.createAndroidAppBuildCredentialsAsync( return await AndroidAppBuildCredentialsMutation.createAndroidAppBuildCredentialsAsync(
graphqlClient, graphqlClient,
@@ -5,7 +5,6 @@ import gql from 'graphql-tag';
import { ExpoGraphqlClient } from '../../../../../commandUtils/context/contextUtils/createGraphqlClient'; import { ExpoGraphqlClient } from '../../../../../commandUtils/context/contextUtils/createGraphqlClient';
import { withErrorHandlingAsync } from '../../../../../graphql/client'; import { withErrorHandlingAsync } from '../../../../../graphql/client';
import { import {
AndroidAppBuildCredentials,
AndroidAppBuildCredentialsFragment, AndroidAppBuildCredentialsFragment,
AndroidAppBuildCredentialsInput, AndroidAppBuildCredentialsInput,
CreateAndroidAppBuildCredentialsMutation, CreateAndroidAppBuildCredentialsMutation,
@@ -56,37 +55,6 @@ export const AndroidAppBuildCredentialsMutation = {
); );
return data.androidAppBuildCredentials.createAndroidAppBuildCredentials; return data.androidAppBuildCredentials.createAndroidAppBuildCredentials;
}, },
async setDefaultAndroidAppBuildCredentialsAsync(
graphqlClient: ExpoGraphqlClient,
androidAppBuildCredentialsId: string
): Promise<AndroidAppBuildCredentialsFragment> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<AndroidAppBuildCredentials>(
gql`
mutation AndroidAppBuildCredentialsMutation(
$androidAppBuildCredentialsId: ID!
$isDefault: Boolean!
) {
androidAppBuildCredentials {
setDefault(id: $androidAppBuildCredentialsId, isDefault: $isDefault) {
id
...AndroidAppBuildCredentialsFragment
}
}
}
${print(AndroidAppBuildCredentialsFragmentNode)}
`,
{
androidAppBuildCredentialsId,
isDefault: true,
}
)
.toPromise()
);
assert(data, `GraphQL: 'setDefault' not defined in server response ${JSON.stringify(data)}}`);
return data;
},
async setKeystoreAsync( async setKeystoreAsync(
graphqlClient: ExpoGraphqlClient, graphqlClient: ExpoGraphqlClient,
androidAppBuildCredentialsId: string, androidAppBuildCredentialsId: string,
+14 -2
View File
@@ -6,7 +6,7 @@ import chalk from 'chalk';
import { Analytics } from '../analytics/AnalyticsManager'; import { Analytics } from '../analytics/AnalyticsManager';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient'; import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import Log from '../log'; import Log from '../log';
import { getPrivateExpoConfig } from '../project/expoConfig'; import { getExpoConfig } from '../project/expoConfig';
import { confirmAsync } from '../prompts'; import { confirmAsync } from '../prompts';
import { Actor } from '../user/User'; import { Actor } from '../user/User';
import * as AndroidGraphqlClient from './android/api/GraphqlClient'; import * as AndroidGraphqlClient from './android/api/GraphqlClient';
@@ -56,6 +56,18 @@ export class CredentialsContext {
this.projectInfo = options.projectInfo; this.projectInfo = options.projectInfo;
} }
static getExpoConfigInProject(
projectDir: string,
{ env }: { env?: Env } = {}
): ExpoConfig | null {
try {
return getExpoConfig(projectDir, { env });
} catch {
// ignore error, context might be created outside of expo project
return null;
}
}
get hasProjectContext(): boolean { get hasProjectContext(): boolean {
return !!this.projectInfo; return !!this.projectInfo;
} }
@@ -75,7 +87,7 @@ export class CredentialsContext {
return; return;
} }
// trigger getConfig error // trigger getConfig error
getPrivateExpoConfig(this.options.projectDir); getExpoConfig(this.options.projectDir);
} }
async bestEffortAppStoreAuthenticateAsync(): Promise<void> { async bestEffortAppStoreAuthenticateAsync(): Promise<void> {
@@ -13,7 +13,6 @@ export async function chooseDevicesAsync(
const { devices } = await promptAsync({ const { devices } = await promptAsync({
type: 'multiselect', type: 'multiselect',
name: 'devices', name: 'devices',
selectionFormat: '<num> devices selected',
message: 'Select devices for the ad hoc build:', message: 'Select devices for the ad hoc build:',
hint: '- Space to select. Return to submit', hint: '- Space to select. Return to submit',
choices: allDevices.map(device => ({ choices: allDevices.map(device => ({
Loaded 100 of 174 files, more files were not shown because too many files have changed in this diff. Show more