mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d1bf434ec |
No files matched your search
@@ -3,7 +3,7 @@ commentConfiguration:
|
|||||||
epilogue: 'Generated by [CodeMention](https://github.com/expo/expo/actions/workflows/codemention.yaml)'
|
epilogue: 'Generated by [CodeMention](https://github.com/expo/expo/actions/workflows/codemention.yaml)'
|
||||||
rules:
|
rules:
|
||||||
- patterns: ['**/*']
|
- patterns: ['**/*']
|
||||||
mentions: ['khamilowicz', 'sjchmiela']
|
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela', 'radoslawkrzemien']
|
||||||
- patterns: ['packages/eas-cli/src/update/**']
|
- patterns: ['packages/eas-cli/src/update/**']
|
||||||
mentions: ['wschurman', 'quinlanj']
|
mentions: ['wschurman', 'quinlanj']
|
||||||
- patterns: ['packages/eas-cli/src/commands/update/**']
|
- patterns: ['packages/eas-cli/src/commands/update/**']
|
||||||
@@ -18,7 +18,3 @@ rules:
|
|||||||
mentions: ['bycedric']
|
mentions: ['bycedric']
|
||||||
- patterns: ['packages/eas-cli/src/commands/metadata/**']
|
- patterns: ['packages/eas-cli/src/commands/metadata/**']
|
||||||
mentions: ['bycedric']
|
mentions: ['bycedric']
|
||||||
- patterns: ['packages/eas-cli/src/worker/**']
|
|
||||||
mentions: ['kitten', 'kadikraman']
|
|
||||||
- patterns: ['packages/eas-cli/src/commands/deploy/**']
|
|
||||||
mentions: ['kitten', 'kadikraman']
|
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Check if CHANGELOG.md was updated
|
- name: Check if CHANGELOG.md was updated
|
||||||
id: changelog-updated
|
id: changelog-updated
|
||||||
uses: tj-actions/changed-files@b1ba699b304f2083b602164e06a89b868c84f076
|
uses: tj-actions/changed-files@v41
|
||||||
with:
|
with:
|
||||||
files: CHANGELOG.md
|
files: CHANGELOG.md
|
||||||
- name: Fail if CHANGELOG.md was not updated and the "no changelog" label is absent
|
- name: Fail if CHANGELOG.md was not updated and the "no changelog" label is absent
|
||||||
|
|||||||
@@ -226,3 +226,16 @@ jobs:
|
|||||||
SLACK_USERNAME: 'EAS CLI CI'
|
SLACK_USERNAME: 'EAS CLI CI'
|
||||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||||
SLACK_FOOTER: ''
|
SLACK_FOOTER: ''
|
||||||
|
open-pr-in-universe:
|
||||||
|
name: Open a PR with `@expo/eas-json` bump in universe
|
||||||
|
needs: publish-to-npm
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Open a PR with `@expo/eas-json` version bump in universe
|
||||||
|
uses: benc-uk/workflow-dispatch@v1
|
||||||
|
with:
|
||||||
|
repo: expo/universe
|
||||||
|
ref: main
|
||||||
|
workflow: update-eas-json.yml
|
||||||
|
token: ${{ secrets.EXPO_BOT_PAT }}
|
||||||
@@ -28,9 +28,6 @@ jobs:
|
|||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
- run: yarn install --frozen-lockfile --check-files
|
- run: yarn install --frozen-lockfile --check-files
|
||||||
- run: yarn typecheck
|
- run: yarn typecheck
|
||||||
- run: |
|
|
||||||
git config --global user.email "[email protected]"
|
|
||||||
git config --global user.name "Test"
|
|
||||||
- run: yarn test
|
- run: yarn test
|
||||||
if: ${{ !matrix.coverage }}
|
if: ${{ !matrix.coverage }}
|
||||||
env:
|
env:
|
||||||
@@ -52,7 +49,7 @@ jobs:
|
|||||||
|
|
||||||
notify-slack:
|
notify-slack:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [test]
|
needs: test
|
||||||
name: Notify Slack
|
name: Notify Slack
|
||||||
if: ${{ github.ref == 'refs/heads/main' && always() }}
|
if: ${{ github.ref == 'refs/heads/main' && always() }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
destination_branch: 'main'
|
destination_branch: 'main'
|
||||||
pr_title: 'Upgrade eas-build dependencies'
|
pr_title: 'Upgrade eas-build dependencies'
|
||||||
pr_body: ':arrow_up: Upgrades [`eas-build`](https://github.com/expo/eas-build) dependencies.'
|
pr_body: ':arrow_up: Upgrades [`eas-build`](https://github.com/expo/eas-build) dependencies.'
|
||||||
pr_reviewer: 'radoslawkrzemien,sjchmiela,khamilowicz'
|
pr_reviewer: 'szdziedzic,radoslawkrzemien,sjchmiela,khamilowicz'
|
||||||
github_token: ${{ secrets.EXPO_BOT_PAT }}
|
github_token: ${{ secrets.EXPO_BOT_PAT }}
|
||||||
|
|
||||||
- uses: actions/github-script@v3
|
- uses: actions/github-script@v3
|
||||||
|
|||||||
+3
-234
@@ -8,244 +8,13 @@ This is the log of notable changes to EAS CLI and related packages.
|
|||||||
|
|
||||||
### 🎉 New features
|
### 🎉 New features
|
||||||
|
|
||||||
-- Add `--verbose-logs` flag for `build` command ([#3000](https://github.com/expo/eas-cli/pull/3000) by [@khamilowicz](https://github.com/khamilowicz))
|
|
||||||
|
|
||||||
- Add `eas-build-cache-provider` package. ([#3002](https://github.com/expo/eas-cli/pull/3002) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Add missing sourcemap normalization to EAS Hosting worker deployment uploads. ([#3004](https://github.com/expo/eas-cli/pull/3004) by [@kitten](https://github.com/kitten))
|
|
||||||
|
|
||||||
### 🧹 Chores
|
|
||||||
|
|
||||||
## [16.3.3](https://github.com/expo/eas-cli/releases/tag/v16.3.3) - 2025-04-24
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Add `sdk-52` alias to the list or supported android images. ([#2989](https://github.com/expo/eas-cli/pull/2989) by [@kadikraman](https://github.com/kadikraman))
|
|
||||||
- Detect when expo metro config is used. ([#2996](https://github.com/expo/eas-cli/pull/2996) by [@kadikraman](https://github.com/kadikraman))
|
|
||||||
|
|
||||||
### 🧹 Chores
|
|
||||||
|
|
||||||
- upload assetmap.json when publishing update. ([#2994](https://github.com/expo/eas-cli/pull/2994) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- Add error messages for CDN-level upload errors. ([#2998](https://github.com/expo/eas-cli/pull/2998) by [@kitten](https://github.com/kitten))
|
|
||||||
|
|
||||||
## [16.3.2](https://github.com/expo/eas-cli/releases/tag/v16.3.2) - 2025-04-17
|
|
||||||
|
|
||||||
### 🧹 Chores
|
|
||||||
|
|
||||||
- Update upload command to display build info. ([#2990](https://github.com/expo/eas-cli/pull/2990) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
||||||
- change launch asset file extension. ([#2991](https://github.com/expo/eas-cli/pull/2991) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
|
|
||||||
## [16.3.1](https://github.com/expo/eas-cli/releases/tag/v16.3.1) - 2025-04-11
|
|
||||||
|
|
||||||
### 🧹 Chores
|
|
||||||
|
|
||||||
- Add `--no-dev-client` flag for `build:download` command. ([#2985](https://github.com/expo/eas-cli/pull/2985) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
||||||
|
|
||||||
## [16.3.0](https://github.com/expo/eas-cli/releases/tag/v16.3.0) - 2025-04-09
|
|
||||||
|
|
||||||
### 🎉 New features
|
|
||||||
|
|
||||||
- Add `eas upload` command. ([#2932](https://github.com/expo/eas-cli/pull/2932), [#2981](https://github.com/expo/eas-cli/pull/2981), [#2983](https://github.com/expo/eas-cli/pull/2983) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
||||||
- Add `eas build:download` command. ([#2982](https://github.com/expo/eas-cli/pull/2982) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
||||||
|
|
||||||
## [16.2.2](https://github.com/expo/eas-cli/releases/tag/v16.2.2) - 2025-04-08
|
|
||||||
|
|
||||||
## [16.2.1](https://github.com/expo/eas-cli/releases/tag/v16.2.1) - 2025-04-04
|
|
||||||
|
|
||||||
### 🧹 Chores
|
|
||||||
|
|
||||||
- Bump `@expo/apple-utils` to use async JWT API. ([#2973](https://github.com/expo/eas-cli/pull/2973) by [@EvanBacon](https://github.com/EvanBacon))
|
|
||||||
|
|
||||||
## [16.2.0](https://github.com/expo/eas-cli/releases/tag/v16.2.0) - 2025-04-03
|
|
||||||
|
|
||||||
### 🎉 New features
|
|
||||||
|
|
||||||
- Add environment flag to `eas fingerprint:compare`. ([#2954](https://github.com/expo/eas-cli/pull/2954) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- Add build-profile flag to `eas fingerprint:generate`. ([#2966](https://github.com/expo/eas-cli/pull/2966) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
|
|
||||||
### 🧹 Chores
|
|
||||||
|
|
||||||
- Remove hidden flag from `eas fingerprint:generate`. ([#2965](https://github.com/expo/eas-cli/pull/2965) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- Refactor `eas update` command to improve code readability. ([#2976](https://github.com/expo/eas-cli/pull/2976) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- `eas update`: add warning if no build exists with fingerprint. ([#2977](https://github.com/expo/eas-cli/pull/2977) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
|
|
||||||
## [16.1.0](https://github.com/expo/eas-cli/releases/tag/v16.1.0) - 2025-03-26
|
|
||||||
|
|
||||||
### 🎉 New features
|
|
||||||
|
|
||||||
- Add environment flag to `eas fingerprint:generate`. ([#2951](https://github.com/expo/eas-cli/pull/2951) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- Add `corepack` field to `eas.json`. ([#2964](https://github.com/expo/eas-cli/pull/2964) by [@szdziedzic](https://github.com/szdziedzic))
|
|
||||||
|
|
||||||
## [16.0.1](https://github.com/expo/eas-cli/releases/tag/v16.0.1) - 2025-03-20
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Make `eas update:configure` re-apply configuration from app.json /app.config.js when run multiple times. ([#2957](https://github.com/expo/eas-cli/pull/2957) by [@brentvatne](https://github.com/brentvatne))
|
|
||||||
|
|
||||||
## [16.0.0](https://github.com/expo/eas-cli/releases/tag/v16.0.0) - 2025-03-19
|
|
||||||
|
|
||||||
### 🛠 Breaking changes
|
|
||||||
|
|
||||||
- Add support for `.easignore` when `requireCommit` is set to `true`. ([#2942](https://github.com/expo/eas-cli/pull/2942) by [@sjchmiela](https://github.com/sjchmiela))
|
|
||||||
- Up to 15.0.0, if `requireCommit` was `true`, `.easignore` was silently ignored.
|
|
||||||
- Versions 15.0.0-15.0.13 started using `.easignore` to skip files from being bundled into a tarball when `requireCommit` was `true`. This was an unexpected change in behavior.
|
|
||||||
- To clear this up, versions 15.0.13-15.0.15 were erroring if `.easignore` was present when `requireCommit` was `true`.
|
|
||||||
- `[email protected]` formalizes the 15.0.0-15.0.13 behavior by adhering to `.easignore` even when `requireCommit` is set to `true`.
|
|
||||||
- If you know what you're doing and you want to suppress a warning printed, you can do so by setting `EAS_SUPPRESS_REQUIRE_COMMIT_EASIGNORE_WARNING` environment variable to `true`.
|
|
||||||
|
|
||||||
### 🎉 New features
|
|
||||||
|
|
||||||
- Add requestId to ApiV2Error. ([#2941](https://github.com/expo/eas-cli/pull/2941) by [@wschurman](https://github.com/wschurman))
|
|
||||||
- Release `eas fingerprint:generate` in hidden mode ([#2937](https://github.com/expo/eas-cli/pull/2937) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Use correct logic to determine whether artifacts have expired in `eas build:run` command. ([#2931](https://github.com/expo/eas-cli/pull/2931) by [@szdziedzic](https://github.com/szdziedzic))
|
|
||||||
|
|
||||||
## [15.0.15](https://github.com/expo/eas-cli/releases/tag/v15.0.15) - 2025-03-12
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Pass through the updates version to `setUpdatesConfigAsync`, which expects it in order to determine which field values to use. ([#2934](https://github.com/expo/eas-cli/pull/2934) by [@brentvatne](https://github.com/brentvatne)).
|
|
||||||
|
|
||||||
## [15.0.14](https://github.com/expo/eas-cli/releases/tag/v15.0.14) - 2025-03-06
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- In `EAS_NO_VCS=1`, use Git for repository root when `EAS_PROJECT_ROOT` is not set. ([#2901](https://github.com/expo/eas-cli/pull/2901) by [@sjchmiela](https://github.com/sjchmiela))
|
|
||||||
|
|
||||||
### 🧹 Chores
|
|
||||||
|
|
||||||
- Suggest using `eas build:dev` for matching configurations. ([#2929](https://github.com/expo/eas-cli/pull/2929) by [@szdziedzic](https://github.com/szdziedzic))
|
|
||||||
|
|
||||||
## [15.0.13](https://github.com/expo/eas-cli/releases/tag/v15.0.13) - 2025-03-04
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Fixed `.git` being always unexpectedly removed if you had `requireCommit: true` and `.easignore` present. ([#2925](https://github.com/expo/eas-cli/pull/2925) by [@sjchmiela](https://github.com/sjchmiela))
|
|
||||||
|
|
||||||
### 🧹 Chores
|
|
||||||
|
|
||||||
- Fix `eas fingerprint:compare` URL generation and pretty prints. ([#2909](https://github.com/expo/eas-cli/pull/2909) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- fix formatFields to handle empty array. ([#2914](https://github.com/expo/eas-cli/pull/2914) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- fix git diff header in `fingerprint:compare`. ([#2915](https://github.com/expo/eas-cli/pull/2915) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- amend fingerprint compare url. ([#2923](https://github.com/expo/eas-cli/pull/2923) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- improve error message for `applicationIdSuffix`. ([#2924](https://github.com/expo/eas-cli/pull/2924) by [@kadikraman](https://github.com/kadikraman))
|
|
||||||
|
|
||||||
## [15.0.12](https://github.com/expo/eas-cli/releases/tag/v15.0.12) - 2025-02-22
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Fix APNS key creation. ([#2916](https://github.com/expo/eas-cli/pull/2916) by [@EvanBacon](https://github.com/EvanBacon))
|
|
||||||
|
|
||||||
## [15.0.11](https://github.com/expo/eas-cli/releases/tag/v15.0.11) - 2025-02-21
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Fix internal TestFlight group creation. ([#2906](https://github.com/expo/eas-cli/pull/2906) by [@EvanBacon](https://github.com/EvanBacon))
|
|
||||||
|
|
||||||
### 🧹 Chores
|
|
||||||
|
|
||||||
- Narrow amount of data queried for basic update channel operations. ([#2901](https://github.com/expo/eas-cli/pull/2901) by [@wschurman](https://github.com/wschurman))
|
|
||||||
- Fix `eas fingerprint:compare` description. ([#2908](https://github.com/expo/eas-cli/pull/2908) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- Skip auto-creation of TestFlight group when there are already exisitng TestFlight groups and allow to opt out of the behavior by setting `EAS_NO_AUTO_TESTFLIGHT_SETUP` env var. ([#2856](https://github.com/expo/eas-cli/pull/2856) by [@szdziedzic](https://github.com/szdziedzic))
|
|
||||||
|
|
||||||
## [15.0.10](https://github.com/expo/eas-cli/releases/tag/v15.0.10) - 2025-02-11
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Fix files deleted in working directory not being removed from the project archive when `requireCommit` is false. ([#2900](https://github.com/expo/eas-cli/pull/2900) by [@sjchmiela](https://github.com/sjchmiela))
|
|
||||||
|
|
||||||
## [15.0.9](https://github.com/expo/eas-cli/releases/tag/v15.0.9) - 2025-02-09
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Fix files not being ignored when creating a tarball on Windows in Git repository in no `requireCommit` mode. ([#2894](https://github.com/expo/eas-cli/pull/2894) by [@sjchmiela](https://github.com/sjchmiela))
|
|
||||||
|
|
||||||
## [15.0.8](https://github.com/expo/eas-cli/releases/tag/v15.0.8) - 2025-02-09
|
|
||||||
|
|
||||||
## [15.0.7](https://github.com/expo/eas-cli/releases/tag/v15.0.7) - 2025-02-09
|
|
||||||
|
|
||||||
## [15.0.6](https://github.com/expo/eas-cli/releases/tag/v15.0.6) - 2025-02-07
|
|
||||||
|
|
||||||
## [15.0.5](https://github.com/expo/eas-cli/releases/tag/v15.0.5) - 2025-02-06
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Do not copy files over onto a cloned Git repository when packing the project archive if `requireCommit` is true. ([#2885](https://github.com/expo/eas-cli/pull/2885) by [@sjchmiela](https://github.com/sjchmiela))
|
|
||||||
- Fix `EISDIR` error when archiving project with submodules ignored. ([#2884](https://github.com/expo/eas-cli/pull/2884) by [@sjchmiela](https://github.com/sjchmiela))
|
|
||||||
|
|
||||||
## [15.0.4](https://github.com/expo/eas-cli/releases/tag/v15.0.4) - 2025-02-05
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Fixed `GitClient` not respecting `.easignore` file. ([#2873](https://github.com/expo/eas-cli/pull/2873) by [@sjchmiela](https://github.com/sjchmiela))
|
|
||||||
- Fix symlink support in `makeShallowCopyAsync`. ([#2874](https://github.com/expo/eas-cli/pull/2874) by [@sjchmiela](https://github.com/sjchmiela))
|
|
||||||
- Allow excluding `.git` directory from project archive by adding it to `.easignore`. ([#2879](https://github.com/expo/eas-cli/pull/2879) by [@sjchmiela](https://github.com/sjchmiela))
|
|
||||||
|
|
||||||
### 🧹 Chores
|
|
||||||
|
|
||||||
- Popup website in fingerprint:compare. ([#2859](https://github.com/expo/eas-cli/pull/2859) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- Fix fingerprint:compare URL. ([#2861](https://github.com/expo/eas-cli/pull/2861) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- Make less gql calls in fingerprint:compare. ([#2860](https://github.com/expo/eas-cli/pull/2860) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- No longer require owner field for SDK >= 53 or canary. ([#2835](https://github.com/expo/eas-cli/pull/2835) by [@wschurman](https://github.com/wschurman))
|
|
||||||
- Add --open flag to fingerprint:compare. ([#2872](https://github.com/expo/eas-cli/pull/2872) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
|
|
||||||
## [15.0.3](https://github.com/expo/eas-cli/releases/tag/v15.0.3) - 2025-02-04
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Fixed EAS server environment variables does not pass to `npx expo-updates runtimeversion:resolve` call. ([#2867](https://github.com/expo/eas-cli/pull/2867) by [@kudo](https://github.com/kudo))
|
|
||||||
|
|
||||||
## [15.0.2](https://github.com/expo/eas-cli/releases/tag/v15.0.2) - 2025-02-04
|
|
||||||
|
|
||||||
## [15.0.1](https://github.com/expo/eas-cli/releases/tag/v15.0.1) - 2025-02-04
|
|
||||||
|
|
||||||
## [15.0.0](https://github.com/expo/eas-cli/releases/tag/v15.0.0) - 2025-02-04
|
|
||||||
|
|
||||||
### 🛠 Breaking changes
|
|
||||||
|
|
||||||
- Use Git to archive projects containing a Git repository. (Previously, Git would only be used if `requireCommit` flag in `eas.json` was set to `true`.) ([#2841](https://github.com/expo/eas-cli/pull/2841) by [@sjchmiela](https://github.com/sjchmiela))
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Print warning for `NoVcsClient` only once. ([#2863](https://github.com/expo/eas-cli/pull/2863) by [@szdziedzic](https://github.com/szdziedzic))
|
|
||||||
|
|
||||||
### 🧹 Chores
|
|
||||||
|
|
||||||
- Add update support for fingerprint:compare. ([#2850](https://github.com/expo/eas-cli/pull/2850) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- Add update group id support for fingerprint:compare. ([#2851](https://github.com/expo/eas-cli/pull/2851) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
- Add better interactive support for fingerprint:compare. ([#2854](https://github.com/expo/eas-cli/pull/2854) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
|
|
||||||
## [14.7.1](https://github.com/expo/eas-cli/releases/tag/v14.7.1) - 2025-01-31
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Account for `ios.config.usesNonExemptEncryption` in non-exempt encryption status prompt. ([#2852](https://github.com/expo/eas-cli/pull/2852) by [@EvanBacon](https://github.com/EvanBacon))
|
|
||||||
|
|
||||||
## [14.7.0](https://github.com/expo/eas-cli/releases/tag/v14.7.0) - 2025-01-30
|
|
||||||
|
|
||||||
### 🎉 New features
|
|
||||||
|
|
||||||
- Add `--submit` and `-s` as aliases for `--auto-submit` flag. ([#2846](https://github.com/expo/eas-cli/pull/2846) by [@szdziedzic](https://github.com/szdziedzic))
|
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
|
||||||
|
|
||||||
- Skip non-exempt check in non-interactive mode. ([#2849](https://github.com/expo/eas-cli/pull/2849) by [@EvanBacon](https://github.com/EvanBacon))
|
|
||||||
|
|
||||||
## [14.6.0](https://github.com/expo/eas-cli/releases/tag/v14.6.0) - 2025-01-30
|
|
||||||
|
|
||||||
### 🎉 New features
|
|
||||||
|
|
||||||
- Prompt to set non-exempt encryption status for the iOS app to support faster store submissions. ([#2843](https://github.com/expo/eas-cli/pull/2843) by [@EvanBacon](https://github.com/EvanBacon))
|
|
||||||
- Automatically create internal TestFlight group in EAS Submit command. ([#2839](https://github.com/expo/eas-cli/pull/2839) by [@evanbacon](https://github.com/evanbacon))
|
|
||||||
- Sanitize and generate names for EAS Submit to prevent failures due to invalid characters or taken names. ([#2842](https://github.com/expo/eas-cli/pull/2842) by [@evanbacon](https://github.com/evanbacon))
|
- Sanitize and generate names for EAS Submit to prevent failures due to invalid characters or taken names. ([#2842](https://github.com/expo/eas-cli/pull/2842) by [@evanbacon](https://github.com/evanbacon))
|
||||||
- Release `eas fingerprint:compare`. ([#2821](https://github.com/expo/eas-cli/pull/2821) by [@quinlanj](https://github.com/quinlanj))
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
### 🧹 Chores
|
### 🧹 Chores
|
||||||
|
|
||||||
|
- Fix tests for VisionOS type.
|
||||||
- Make new autoIncremented builds start at nr 1 by default ([#2828](https://github.com/expo/eas-cli/pull/2828) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
- Make new autoIncremented builds start at nr 1 by default ([#2828](https://github.com/expo/eas-cli/pull/2828) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||||
|
|
||||||
## [14.5.0](https://github.com/expo/eas-cli/releases/tag/v14.5.0) - 2025-01-22
|
## [14.5.0](https://github.com/expo/eas-cli/releases/tag/v14.5.0) - 2025-01-22
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"version": "16.3.3",
|
"version": "14.5.0",
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||||
}
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# eas-build-cache-provider
|
|
||||||
|
|
||||||
A build cache provider plugin for the Expo CLI
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "eas-build-cache-provider",
|
|
||||||
"description": "A build cache provider plugin for the Expo CLI",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"author": "Expo <[email protected]>",
|
|
||||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
|
||||||
"dependencies": {
|
|
||||||
"@expo/config": "10.0.6",
|
|
||||||
"@babel/code-frame": "7.23.5",
|
|
||||||
"@expo/spawn-async": "^1.7.2",
|
|
||||||
"chalk": "4.1.2",
|
|
||||||
"figures": "3.2.0",
|
|
||||||
"fs-extra": "11.2.0",
|
|
||||||
"getenv": "1.0.0",
|
|
||||||
"log-symbols": "4.1.0",
|
|
||||||
"semver": "7.5.2",
|
|
||||||
"terminal-link": "2.1.1",
|
|
||||||
"tslib": "2.4.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tsconfig/node18": "18.2.4",
|
|
||||||
"@types/babel__code-frame": "7.0.3",
|
|
||||||
"@types/fs-extra": "11.0.4",
|
|
||||||
"@types/getenv": "^1.0.0",
|
|
||||||
"memfs": "3.4.13",
|
|
||||||
"rimraf": "3.0.2",
|
|
||||||
"typescript": "5.3.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0.0"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/expo/eas-cli",
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "build/index.js",
|
|
||||||
"types": "build/index.d.ts",
|
|
||||||
"repository": "expo/eas-cli",
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc --project tsconfig.build.json",
|
|
||||||
"build-allow-unused": "tsc --project tsconfig.allowUnused.json",
|
|
||||||
"watch": "yarn build --watch --preserveWatchOutput",
|
|
||||||
"watch-allow-unused": "yarn build-allow-unused --watch --preserveWatchOutput",
|
|
||||||
"typecheck": "tsc",
|
|
||||||
"prepack": "yarn rebuild",
|
|
||||||
"rebuild": "rimraf build && yarn build",
|
|
||||||
"test": "jest",
|
|
||||||
"clean": "rimraf build node_modules yarn-error.log"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"/build"
|
|
||||||
],
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"volta": {
|
|
||||||
"node": "20.11.0",
|
|
||||||
"yarn": "1.22.21"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: ['universe/shared/typescript-analysis'],
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['*.ts', '*.d.ts'],
|
|
||||||
parserOptions: {
|
|
||||||
project: './packages/eas-build-cache-provider/tsconfig.json',
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/explicit-function-return-type': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
allowExpressions: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { getPackageJson } from '@expo/config';
|
|
||||||
import { SpawnResult } from '@expo/spawn-async';
|
|
||||||
|
|
||||||
// import from '@expo/config';
|
|
||||||
type RunOptions = any;
|
|
||||||
|
|
||||||
export function isSpawnResultError(obj: any): obj is Error & SpawnResult {
|
|
||||||
return (
|
|
||||||
obj &&
|
|
||||||
'message' in obj &&
|
|
||||||
obj.status !== undefined &&
|
|
||||||
obj.stdout !== undefined &&
|
|
||||||
obj.stderr !== undefined
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isDevClientBuild({
|
|
||||||
runOptions,
|
|
||||||
projectRoot,
|
|
||||||
}: {
|
|
||||||
runOptions: RunOptions;
|
|
||||||
projectRoot: string;
|
|
||||||
}): boolean {
|
|
||||||
if (!hasDirectDevClientDependency(projectRoot)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('variant' in runOptions && runOptions.variant !== undefined) {
|
|
||||||
return runOptions.variant === 'debug';
|
|
||||||
}
|
|
||||||
if ('configuration' in runOptions && runOptions.configuration !== undefined) {
|
|
||||||
return runOptions.configuration === 'Debug';
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasDirectDevClientDependency(projectRoot: string): boolean {
|
|
||||||
const { dependencies = {}, devDependencies = {} } = getPackageJson(projectRoot);
|
|
||||||
return !!dependencies['expo-dev-client'] || !!devDependencies['expo-dev-client'];
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
import { Platform } from '@expo/config';
|
|
||||||
import spawnAsync from '@expo/spawn-async';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import fs from 'fs-extra';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
import { isDevClientBuild, isSpawnResultError } from './helpers';
|
|
||||||
import Log from './log';
|
|
||||||
|
|
||||||
// import from '@expo/config';
|
|
||||||
type CalculateFingerprintHashProps = any;
|
|
||||||
type RemoteBuildCachePlugin = any;
|
|
||||||
type RunOptions = any;
|
|
||||||
|
|
||||||
async function resolveRemoteBuildCacheAsync({
|
|
||||||
projectRoot,
|
|
||||||
platform,
|
|
||||||
fingerprintHash,
|
|
||||||
runOptions,
|
|
||||||
}: {
|
|
||||||
projectRoot: string;
|
|
||||||
platform: Platform;
|
|
||||||
fingerprintHash: string;
|
|
||||||
runOptions: RunOptions;
|
|
||||||
}): Promise<string | null> {
|
|
||||||
const easJsonPath = path.join(projectRoot, 'eas.json');
|
|
||||||
if (!(await fs.exists(easJsonPath))) {
|
|
||||||
Log.debug('eas.json not found, skip checking for remote builds');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.log(
|
|
||||||
chalk`{whiteBright \u203A} {bold Searching builds with matching fingerprint on EAS servers}`
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
const results = await spawnAsync(
|
|
||||||
'npx',
|
|
||||||
[
|
|
||||||
'eas-cli',
|
|
||||||
'build:download',
|
|
||||||
`--platform=${platform}`,
|
|
||||||
`--fingerprint=${fingerprintHash}`,
|
|
||||||
'--non-interactive',
|
|
||||||
isDevClientBuild({ runOptions, projectRoot }) ? '--dev-client' : '--no-dev-client',
|
|
||||||
'--json',
|
|
||||||
],
|
|
||||||
{
|
|
||||||
cwd: projectRoot,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Log.log(chalk`{whiteBright \u203A} {bold Successfully downloaded cached build}`);
|
|
||||||
// {
|
|
||||||
// "path": "/var/folders/03/lppcpcnn61q3mz5ckzmzd8w80000gn/T/eas-cli-nodejs/eas-build-run-cache/c0f9ba9c-0cf1-4c5c-8566-b28b7971050f_22f1bbfa-1c09-4b67-9e4a-721906546b58.app"
|
|
||||||
// }
|
|
||||||
const json = JSON.parse(results.stdout.trim());
|
|
||||||
return json?.path;
|
|
||||||
} catch (error) {
|
|
||||||
Log.debug('eas-cli error:', error);
|
|
||||||
// @TODO(2025-04-11): remove this in a future release
|
|
||||||
if (isSpawnResultError(error) && error.stderr.includes('command build:download not found')) {
|
|
||||||
Log.warn(
|
|
||||||
`To take advantage of remote build cache, upgrade your eas-cli installation to latest.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function uploadEASRemoteBuildCacheAsync({
|
|
||||||
projectRoot,
|
|
||||||
platform,
|
|
||||||
fingerprintHash,
|
|
||||||
buildPath,
|
|
||||||
}: {
|
|
||||||
projectRoot: string;
|
|
||||||
runOptions: RunOptions;
|
|
||||||
platform: Platform;
|
|
||||||
fingerprintHash: string;
|
|
||||||
buildPath?: string;
|
|
||||||
}): Promise<string | null> {
|
|
||||||
const easJsonPath = path.join(projectRoot, 'eas.json');
|
|
||||||
if (!(await fs.exists(easJsonPath))) {
|
|
||||||
Log.debug('eas.json not found, skip uploading builds');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Log.log(chalk`{whiteBright \u203A} {bold Uploading build to remote cache}`);
|
|
||||||
const results = await spawnAsync(
|
|
||||||
'npx',
|
|
||||||
[
|
|
||||||
'eas-cli',
|
|
||||||
'upload',
|
|
||||||
`--platform=${platform}`,
|
|
||||||
`--fingerprint=${fingerprintHash}`,
|
|
||||||
buildPath ? `--build-path=${buildPath}` : '',
|
|
||||||
'--non-interactive',
|
|
||||||
'--json',
|
|
||||||
],
|
|
||||||
{
|
|
||||||
cwd: projectRoot,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// {
|
|
||||||
// "url": "/var/folders/03/lppcpcnn61q3mz5ckzmzd8w80000gn/T/eas-cli-nodejs/eas-build-run-cache/c0f9ba9c-0cf1-4c5c-8566-b28b7971050f_22f1bbfa-1c09-4b67-9e4a-721906546b58.app"
|
|
||||||
// }
|
|
||||||
const json = JSON.parse(results.stdout.trim());
|
|
||||||
Log.log(chalk`{whiteBright \u203A} {bold Build successfully uploaded: ${json?.url}}`);
|
|
||||||
return json?.url;
|
|
||||||
} catch (error) {
|
|
||||||
Log.debug('eas-cli error:', error);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function calculateEASFingerprintHashAsync({
|
|
||||||
projectRoot,
|
|
||||||
platform,
|
|
||||||
}: CalculateFingerprintHashProps): Promise<string | null> {
|
|
||||||
// prefer using `eas fingerprint:generate` because it automatically upload sources
|
|
||||||
try {
|
|
||||||
const results = await spawnAsync(
|
|
||||||
'npx',
|
|
||||||
['eas-cli', 'fingerprint:generate', `--platform=${platform}`, '--json', '--non-interactive'],
|
|
||||||
{
|
|
||||||
cwd: projectRoot,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// {
|
|
||||||
// "hash": "203f960b965e154b77dc31c6c42e5582e8d77196"
|
|
||||||
// }
|
|
||||||
const json = JSON.parse(results.stdout.trim());
|
|
||||||
return json?.hash;
|
|
||||||
} catch (error) {
|
|
||||||
Log.debug('eas-cli error:', error);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EASRemoteBuildCacheProvider: RemoteBuildCachePlugin = {
|
|
||||||
resolveRemoteBuildCache: resolveRemoteBuildCacheAsync,
|
|
||||||
uploadRemoteBuildCache: uploadEASRemoteBuildCacheAsync,
|
|
||||||
calculateFingerprintHash: calculateEASFingerprintHashAsync,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EASRemoteBuildCacheProvider;
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
import chalk from 'chalk';
|
|
||||||
import figures from 'figures';
|
|
||||||
import { boolish } from 'getenv';
|
|
||||||
import logSymbols from 'log-symbols';
|
|
||||||
import terminalLink from 'terminal-link';
|
|
||||||
|
|
||||||
type Color = (...text: string[]) => string;
|
|
||||||
|
|
||||||
export default class Log {
|
|
||||||
public static readonly isDebug = boolish('EXPO_DEBUG', false);
|
|
||||||
|
|
||||||
public static log(...args: any[]): void {
|
|
||||||
Log.consoleLog(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static newLine(): void {
|
|
||||||
Log.consoleLog();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static addNewLineIfNone(): void {
|
|
||||||
if (!Log.isLastLineNewLine) {
|
|
||||||
Log.newLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static error(...args: any[]): void {
|
|
||||||
Log.consoleLog(...Log.withTextColor(args, chalk.red));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static warn(...args: any[]): void {
|
|
||||||
Log.consoleLog(...Log.withTextColor(args, chalk.yellow));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static debug(...args: any[]): void {
|
|
||||||
if (Log.isDebug) {
|
|
||||||
Log.consoleLog(...args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static gray(...args: any[]): void {
|
|
||||||
Log.consoleLog(...Log.withTextColor(args, chalk.gray));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static warnDeprecatedFlag(flag: string, message: string): void {
|
|
||||||
Log.warn(`› ${chalk.bold('--' + flag)} flag is deprecated. ${message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static fail(message: string): void {
|
|
||||||
Log.log(`${chalk.red(logSymbols.error)} ${message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static succeed(message: string): void {
|
|
||||||
Log.log(`${chalk.green(logSymbols.success)} ${message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static withTick(...args: any[]): void {
|
|
||||||
Log.consoleLog(chalk.green(figures.tick), ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static withInfo(...args: any[]): void {
|
|
||||||
Log.consoleLog(chalk.green(figures.info), ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static consoleLog(...args: any[]): void {
|
|
||||||
Log.updateIsLastLineNewLine(args);
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static withTextColor(args: any[], chalkColor: Color): string[] {
|
|
||||||
return args.map(arg => chalkColor(arg));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static isLastLineNewLine = false;
|
|
||||||
private static updateIsLastLineNewLine(args: any[]): void {
|
|
||||||
if (args.length === 0) {
|
|
||||||
Log.isLastLineNewLine = true;
|
|
||||||
} else {
|
|
||||||
const lastArg = args[args.length - 1];
|
|
||||||
if (typeof lastArg === 'string' && (lastArg === '' || lastArg.match(/[\r\n]$/))) {
|
|
||||||
Log.isLastLineNewLine = true;
|
|
||||||
} else {
|
|
||||||
Log.isLastLineNewLine = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints a link for given URL, using text if provided, otherwise text is just the URL.
|
|
||||||
* Format links as dim (unless disabled) and with an underline.
|
|
||||||
*
|
|
||||||
* @example https://expo.dev
|
|
||||||
*/
|
|
||||||
export function link(
|
|
||||||
url: string,
|
|
||||||
{ text = url, fallback, dim = true }: { text?: string; dim?: boolean; fallback?: string } = {}
|
|
||||||
): string {
|
|
||||||
// Links can be disabled via env variables https://github.com/jamestalmage/supports-hyperlinks/blob/master/index.js
|
|
||||||
const output = terminalLink(text, url, {
|
|
||||||
fallback: () =>
|
|
||||||
fallback ?? (text === url ? chalk.underline(url) : `${text}: ${chalk.underline(url)}`),
|
|
||||||
});
|
|
||||||
return dim ? chalk.dim(output) : output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a consistent "Learn more" link experience.
|
|
||||||
* Format links as dim (unless disabled) with an underline.
|
|
||||||
*
|
|
||||||
* @example Learn more: https://expo.dev
|
|
||||||
*/
|
|
||||||
export function learnMore(
|
|
||||||
url: string,
|
|
||||||
{
|
|
||||||
learnMoreMessage: maybeLearnMoreMessage,
|
|
||||||
dim = true,
|
|
||||||
}: { learnMoreMessage?: string; dim?: boolean } = {}
|
|
||||||
): string {
|
|
||||||
return link(url, { text: maybeLearnMoreMessage ?? 'Learn more', dim });
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig",
|
|
||||||
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@tsconfig/node18/tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"declaration": true,
|
|
||||||
"importHelpers": true,
|
|
||||||
"module": "commonjs",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noImplicitOverride": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"outDir": "build",
|
|
||||||
"rootDir": "src",
|
|
||||||
"typeRoots": ["../../node_modules/@types", "../../ts-declarations", "./node_modules/@types"]
|
|
||||||
},
|
|
||||||
"include": ["src/**/*"]
|
|
||||||
}
|
|
||||||
+80
-201
@@ -69,8 +69,6 @@ eas --help COMMAND
|
|||||||
* [`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:delete [BUILD_ID]`](#eas-builddelete-build_id)
|
* [`eas build:delete [BUILD_ID]`](#eas-builddelete-build_id)
|
||||||
* [`eas build:dev`](#eas-builddev)
|
|
||||||
* [`eas build:download`](#eas-builddownload)
|
|
||||||
* [`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)
|
||||||
@@ -107,8 +105,6 @@ eas --help COMMAND
|
|||||||
* [`eas env:pull [ENVIRONMENT]`](#eas-envpull-environment)
|
* [`eas env:pull [ENVIRONMENT]`](#eas-envpull-environment)
|
||||||
* [`eas env:push [ENVIRONMENT]`](#eas-envpush-environment)
|
* [`eas env:push [ENVIRONMENT]`](#eas-envpush-environment)
|
||||||
* [`eas env:update [ENVIRONMENT]`](#eas-envupdate-environment)
|
* [`eas env:update [ENVIRONMENT]`](#eas-envupdate-environment)
|
||||||
* [`eas fingerprint:compare [HASH1] [HASH2]`](#eas-fingerprintcompare-hash1-hash2)
|
|
||||||
* [`eas fingerprint:generate`](#eas-fingerprintgenerate)
|
|
||||||
* [`eas help [COMMAND]`](#eas-help-command)
|
* [`eas help [COMMAND]`](#eas-help-command)
|
||||||
* [`eas init`](#eas-init)
|
* [`eas init`](#eas-init)
|
||||||
* [`eas init:onboarding [TARGET_PROJECT_DIRECTORY]`](#eas-initonboarding-target_project_directory)
|
* [`eas init:onboarding [TARGET_PROJECT_DIRECTORY]`](#eas-initonboarding-target_project_directory)
|
||||||
@@ -162,7 +158,7 @@ ALIASES
|
|||||||
$ eas login
|
$ eas login
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/account/login.ts)_
|
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||||
|
|
||||||
## `eas account:logout`
|
## `eas account:logout`
|
||||||
|
|
||||||
@@ -179,7 +175,7 @@ ALIASES
|
|||||||
$ eas logout
|
$ eas logout
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/account/logout.ts)_
|
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||||
|
|
||||||
## `eas account:view`
|
## `eas account:view`
|
||||||
|
|
||||||
@@ -196,7 +192,7 @@ ALIASES
|
|||||||
$ eas whoami
|
$ eas whoami
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/account/view.ts)_
|
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||||
|
|
||||||
## `eas analytics [STATUS]`
|
## `eas analytics [STATUS]`
|
||||||
|
|
||||||
@@ -210,7 +206,7 @@ DESCRIPTION
|
|||||||
display or change analytics settings
|
display or change analytics settings
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/analytics.ts)_
|
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||||
|
|
||||||
## `eas autocomplete [SHELL]`
|
## `eas autocomplete [SHELL]`
|
||||||
|
|
||||||
@@ -262,7 +258,7 @@ DESCRIPTION
|
|||||||
create a branch
|
create a branch
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/branch/create.ts)_
|
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||||
|
|
||||||
## `eas branch:delete [NAME]`
|
## `eas branch:delete [NAME]`
|
||||||
|
|
||||||
@@ -283,7 +279,7 @@ DESCRIPTION
|
|||||||
delete a branch
|
delete a branch
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/branch/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||||
|
|
||||||
## `eas branch:list`
|
## `eas branch:list`
|
||||||
|
|
||||||
@@ -303,7 +299,7 @@ DESCRIPTION
|
|||||||
list all branches
|
list all branches
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/branch/list.ts)_
|
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||||
|
|
||||||
## `eas branch:rename`
|
## `eas branch:rename`
|
||||||
|
|
||||||
@@ -323,7 +319,7 @@ DESCRIPTION
|
|||||||
rename a branch
|
rename a branch
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/branch/rename.ts)_
|
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||||
|
|
||||||
## `eas branch:view [NAME]`
|
## `eas branch:view [NAME]`
|
||||||
|
|
||||||
@@ -346,7 +342,7 @@ DESCRIPTION
|
|||||||
view a branch
|
view a branch
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/branch/view.ts)_
|
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||||
|
|
||||||
## `eas build`
|
## `eas build`
|
||||||
|
|
||||||
@@ -354,16 +350,16 @@ start a build
|
|||||||
|
|
||||||
```
|
```
|
||||||
USAGE
|
USAGE
|
||||||
$ eas build [-p android|ios|all] [-e <value>] [--local] [--output <value>] [--wait] [--clear-cache] [-s |
|
$ eas build [-p android|ios|all] [-e <value>] [--local] [--output <value>] [--wait] [--clear-cache]
|
||||||
--auto-submit-with-profile <value>] [-m <value>] [--build-logger-level trace|debug|info|warn|error|fatal]
|
[--auto-submit | --auto-submit-with-profile <value>] [-m <value>] [--build-logger-level
|
||||||
[--freeze-credentials] [--json --non-interactive]
|
trace|debug|info|warn|error|fatal] [--freeze-credentials] [--json --non-interactive]
|
||||||
|
|
||||||
FLAGS
|
FLAGS
|
||||||
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to
|
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to
|
||||||
"production" if defined in eas.json.
|
"production" if defined in eas.json.
|
||||||
-m, --message=<value> A short message describing the build
|
-m, --message=<value> A short message describing the build
|
||||||
-p, --platform=(android|ios|all)
|
-p, --platform=(android|ios|all)
|
||||||
-s, --auto-submit Submit on build complete using the submit profile with the
|
--auto-submit Submit on build complete using the submit profile with the
|
||||||
same name as the build profile
|
same name as the build profile
|
||||||
--auto-submit-with-profile=PROFILE_NAME Submit on build complete using the submit profile with
|
--auto-submit-with-profile=PROFILE_NAME Submit on build complete using the submit profile with
|
||||||
provided name
|
provided name
|
||||||
@@ -383,7 +379,7 @@ DESCRIPTION
|
|||||||
start a build
|
start a build
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/index.ts)_
|
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||||
|
|
||||||
## `eas build:cancel [BUILD_ID]`
|
## `eas build:cancel [BUILD_ID]`
|
||||||
|
|
||||||
@@ -402,7 +398,7 @@ DESCRIPTION
|
|||||||
cancel a build
|
cancel a build
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/cancel.ts)_
|
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||||
|
|
||||||
## `eas build:configure`
|
## `eas build:configure`
|
||||||
|
|
||||||
@@ -419,7 +415,7 @@ DESCRIPTION
|
|||||||
configure the project to support EAS Build
|
configure the project to support EAS Build
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/configure.ts)_
|
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||||
|
|
||||||
## `eas build:delete [BUILD_ID]`
|
## `eas build:delete [BUILD_ID]`
|
||||||
|
|
||||||
@@ -438,48 +434,7 @@ DESCRIPTION
|
|||||||
delete a build
|
delete a build
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/build/delete.ts)_
|
||||||
|
|
||||||
## `eas build:dev`
|
|
||||||
|
|
||||||
run dev client simulator/emulator build with matching fingerprint or create a new one
|
|
||||||
|
|
||||||
```
|
|
||||||
USAGE
|
|
||||||
$ eas build:dev [-p ios|android] [-e <value>]
|
|
||||||
|
|
||||||
FLAGS
|
|
||||||
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. It must be a profile allowing to create
|
|
||||||
emulator/simulator internal distribution dev client builds. The "development-simulator"
|
|
||||||
build profile will be selected by default.
|
|
||||||
-p, --platform=(ios|android)
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
run dev client simulator/emulator build with matching fingerprint or create a new one
|
|
||||||
```
|
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/dev.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/dev.ts)_
|
|
||||||
|
|
||||||
## `eas build:download`
|
|
||||||
|
|
||||||
download simulator/emulator builds for a given fingerprint hash
|
|
||||||
|
|
||||||
```
|
|
||||||
USAGE
|
|
||||||
$ eas build:download --fingerprint <value> [-p ios|android] [--dev-client] [--json --non-interactive]
|
|
||||||
|
|
||||||
FLAGS
|
|
||||||
-p, --platform=(ios|android)
|
|
||||||
--[no-]dev-client Filter only dev-client builds.
|
|
||||||
--fingerprint=<value> (required) Fingerprint hash of the build to download
|
|
||||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
|
||||||
--non-interactive Run the command in non-interactive mode.
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
download simulator/emulator builds for a given fingerprint hash
|
|
||||||
```
|
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/download.ts)_
|
|
||||||
|
|
||||||
## `eas build:inspect`
|
## `eas build:inspect`
|
||||||
|
|
||||||
@@ -514,7 +469,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: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/inspect.ts)_
|
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||||
|
|
||||||
## `eas build:list`
|
## `eas build:list`
|
||||||
|
|
||||||
@@ -566,7 +521,7 @@ DESCRIPTION
|
|||||||
list all builds for your project
|
list all builds for your project
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/list.ts)_
|
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||||
|
|
||||||
## `eas build:resign`
|
## `eas build:resign`
|
||||||
|
|
||||||
@@ -595,7 +550,7 @@ DESCRIPTION
|
|||||||
re-sign a build archive
|
re-sign a build archive
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/resign.ts)_
|
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/build/resign.ts)_
|
||||||
|
|
||||||
## `eas build:run`
|
## `eas build:run`
|
||||||
|
|
||||||
@@ -621,7 +576,7 @@ DESCRIPTION
|
|||||||
run simulator/emulator builds from eas-cli
|
run simulator/emulator builds from eas-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/run.ts)_
|
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/build/run.ts)_
|
||||||
|
|
||||||
## `eas build:submit`
|
## `eas build:submit`
|
||||||
|
|
||||||
@@ -671,7 +626,7 @@ DESCRIPTION
|
|||||||
get the latest version from EAS servers
|
get the latest version from EAS servers
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/version/get.ts)_
|
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||||
|
|
||||||
## `eas build:version:set`
|
## `eas build:version:set`
|
||||||
|
|
||||||
@@ -690,7 +645,7 @@ DESCRIPTION
|
|||||||
update version of an app
|
update version of an app
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/version/set.ts)_
|
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||||
|
|
||||||
## `eas build:version:sync`
|
## `eas build:version:sync`
|
||||||
|
|
||||||
@@ -709,7 +664,7 @@ 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: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/version/sync.ts)_
|
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||||
|
|
||||||
## `eas build:view [BUILD_ID]`
|
## `eas build:view [BUILD_ID]`
|
||||||
|
|
||||||
@@ -726,7 +681,7 @@ DESCRIPTION
|
|||||||
view a build for your project
|
view a build for your project
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/build/view.ts)_
|
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||||
|
|
||||||
## `eas channel:create [NAME]`
|
## `eas channel:create [NAME]`
|
||||||
|
|
||||||
@@ -747,7 +702,7 @@ DESCRIPTION
|
|||||||
create a channel
|
create a channel
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/channel/create.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||||
|
|
||||||
## `eas channel:edit [NAME]`
|
## `eas channel:edit [NAME]`
|
||||||
|
|
||||||
@@ -769,7 +724,7 @@ DESCRIPTION
|
|||||||
point a channel at a new branch
|
point a channel at a new branch
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/channel/edit.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||||
|
|
||||||
## `eas channel:list`
|
## `eas channel:list`
|
||||||
|
|
||||||
@@ -789,7 +744,7 @@ DESCRIPTION
|
|||||||
list all channels
|
list all channels
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/channel/list.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||||
|
|
||||||
## `eas channel:pause [NAME]`
|
## `eas channel:pause [NAME]`
|
||||||
|
|
||||||
@@ -811,7 +766,7 @@ DESCRIPTION
|
|||||||
pause a channel to stop it from sending updates
|
pause a channel to stop it from sending updates
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/channel/pause.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/channel/pause.ts)_
|
||||||
|
|
||||||
## `eas channel:resume [NAME]`
|
## `eas channel:resume [NAME]`
|
||||||
|
|
||||||
@@ -833,7 +788,7 @@ DESCRIPTION
|
|||||||
resume a channel to start sending updates
|
resume a channel to start sending updates
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/channel/resume.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/channel/resume.ts)_
|
||||||
|
|
||||||
## `eas channel:rollout [CHANNEL]`
|
## `eas channel:rollout [CHANNEL]`
|
||||||
|
|
||||||
@@ -866,7 +821,7 @@ DESCRIPTION
|
|||||||
Roll a new branch out on a channel incrementally.
|
Roll a new branch out on a channel incrementally.
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/channel/rollout.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||||
|
|
||||||
## `eas channel:view [NAME]`
|
## `eas channel:view [NAME]`
|
||||||
|
|
||||||
@@ -889,7 +844,7 @@ DESCRIPTION
|
|||||||
view a channel
|
view a channel
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/channel/view.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||||
|
|
||||||
## `eas config`
|
## `eas config`
|
||||||
|
|
||||||
@@ -910,7 +865,7 @@ DESCRIPTION
|
|||||||
display project configuration (app.json + eas.json)
|
display project configuration (app.json + eas.json)
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/config.ts)_
|
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/config.ts)_
|
||||||
|
|
||||||
## `eas credentials`
|
## `eas credentials`
|
||||||
|
|
||||||
@@ -927,7 +882,7 @@ DESCRIPTION
|
|||||||
manage credentials
|
manage credentials
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/credentials/index.ts)_
|
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||||
|
|
||||||
## `eas credentials:configure-build`
|
## `eas credentials:configure-build`
|
||||||
|
|
||||||
@@ -945,11 +900,11 @@ DESCRIPTION
|
|||||||
Set up credentials for building your project.
|
Set up credentials for building your project.
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||||
|
|
||||||
## `eas deploy [2m[options][22m`
|
## `eas deploy [2m[options][22m`
|
||||||
|
|
||||||
deploy your Expo Router web build and API Routes
|
Deploy your Expo Router web build and API Routes.
|
||||||
|
|
||||||
```
|
```
|
||||||
USAGE
|
USAGE
|
||||||
@@ -967,13 +922,13 @@ FLAGS
|
|||||||
--prod Create a new production deployment.
|
--prod Create a new production deployment.
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
deploy your Expo Router web build and API Routes
|
Deploy your Expo Router web build and API Routes.
|
||||||
|
|
||||||
ALIASES
|
ALIASES
|
||||||
$ eas worker:deploy
|
$ eas worker:deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/deploy/index.ts)_
|
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/deploy/index.ts)_
|
||||||
|
|
||||||
## `eas deploy:alias`
|
## `eas deploy:alias`
|
||||||
|
|
||||||
@@ -998,7 +953,7 @@ ALIASES
|
|||||||
$ eas deploy:promote
|
$ eas deploy:promote
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/deploy/alias.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/deploy/alias.ts)_
|
_See code: [packages/eas-cli/src/commands/deploy/alias.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/deploy/alias.ts)_
|
||||||
|
|
||||||
## `eas deploy:promote`
|
## `eas deploy:promote`
|
||||||
|
|
||||||
@@ -1035,7 +990,7 @@ DESCRIPTION
|
|||||||
register new Apple Devices to use for internal distribution
|
register new Apple Devices to use for internal distribution
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/device/create.ts)_
|
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||||
|
|
||||||
## `eas device:delete`
|
## `eas device:delete`
|
||||||
|
|
||||||
@@ -1055,7 +1010,7 @@ DESCRIPTION
|
|||||||
remove a registered device from your account
|
remove a registered device from your account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/device/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||||
|
|
||||||
## `eas device:list`
|
## `eas device:list`
|
||||||
|
|
||||||
@@ -1076,7 +1031,7 @@ DESCRIPTION
|
|||||||
list all registered devices for your account
|
list all registered devices for your account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/device/list.ts)_
|
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||||
|
|
||||||
## `eas device:rename`
|
## `eas device:rename`
|
||||||
|
|
||||||
@@ -1097,7 +1052,7 @@ DESCRIPTION
|
|||||||
rename a registered device
|
rename a registered device
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/device/rename.ts)_
|
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/device/rename.ts)_
|
||||||
|
|
||||||
## `eas device:view [UDID]`
|
## `eas device:view [UDID]`
|
||||||
|
|
||||||
@@ -1111,7 +1066,7 @@ DESCRIPTION
|
|||||||
view a device for your project
|
view a device for your project
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/device/view.ts)_
|
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||||
|
|
||||||
## `eas diagnostics`
|
## `eas diagnostics`
|
||||||
|
|
||||||
@@ -1125,7 +1080,7 @@ DESCRIPTION
|
|||||||
display environment info
|
display environment info
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/diagnostics.ts)_
|
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||||
|
|
||||||
## `eas env:create [ENVIRONMENT]`
|
## `eas env:create [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1154,7 +1109,7 @@ DESCRIPTION
|
|||||||
create an environment variable for the current project or account
|
create an environment variable for the current project or account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/env/create.ts)_
|
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/create.ts)_
|
||||||
|
|
||||||
## `eas env:delete [ENVIRONMENT]`
|
## `eas env:delete [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1178,7 +1133,7 @@ DESCRIPTION
|
|||||||
delete an environment variable for the current project or account
|
delete an environment variable for the current project or account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/env/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/delete.ts)_
|
||||||
|
|
||||||
## `eas env:exec ENVIRONMENT BASH_COMMAND`
|
## `eas env:exec ENVIRONMENT BASH_COMMAND`
|
||||||
|
|
||||||
@@ -1199,7 +1154,7 @@ DESCRIPTION
|
|||||||
execute a command with environment variables from the selected environment
|
execute a command with environment variables from the selected environment
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/env/exec.ts)_
|
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/exec.ts)_
|
||||||
|
|
||||||
## `eas env:get [ENVIRONMENT]`
|
## `eas env:get [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1224,7 +1179,7 @@ DESCRIPTION
|
|||||||
view an environment variable for the current project or account
|
view an environment variable for the current project or account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/env/get.ts)_
|
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/get.ts)_
|
||||||
|
|
||||||
## `eas env:list [ENVIRONMENT]`
|
## `eas env:list [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1249,7 +1204,7 @@ DESCRIPTION
|
|||||||
list environment variables for the current project or account
|
list environment variables for the current project or account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/env/list.ts)_
|
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/list.ts)_
|
||||||
|
|
||||||
## `eas env:pull [ENVIRONMENT]`
|
## `eas env:pull [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1272,7 +1227,7 @@ DESCRIPTION
|
|||||||
pull environment variables for the selected environment to .env file
|
pull environment variables for the selected environment to .env file
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/env/pull.ts)_
|
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/pull.ts)_
|
||||||
|
|
||||||
## `eas env:push [ENVIRONMENT]`
|
## `eas env:push [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1293,7 +1248,7 @@ DESCRIPTION
|
|||||||
push environment variables from .env file to the selected environment
|
push environment variables from .env file to the selected environment
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/env/push.ts)_
|
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/push.ts)_
|
||||||
|
|
||||||
## `eas env:update [ENVIRONMENT]`
|
## `eas env:update [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1324,83 +1279,7 @@ DESCRIPTION
|
|||||||
update an environment variable on the current project or account
|
update an environment variable on the current project or account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/env/update.ts)_
|
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/env/update.ts)_
|
||||||
|
|
||||||
## `eas fingerprint:compare [HASH1] [HASH2]`
|
|
||||||
|
|
||||||
compare fingerprints of the current project, builds, and updates
|
|
||||||
|
|
||||||
```
|
|
||||||
USAGE
|
|
||||||
$ eas fingerprint:compare [HASH1] [HASH2] [--build-id <value>] [--update-id <value>] [--open] [--environment
|
|
||||||
development|preview|production] [--json --non-interactive]
|
|
||||||
|
|
||||||
ARGUMENTS
|
|
||||||
HASH1 If provided alone, HASH1 is compared against the current project's fingerprint.
|
|
||||||
HASH2 If two hashes are provided, HASH1 is compared against HASH2.
|
|
||||||
|
|
||||||
FLAGS
|
|
||||||
--build-id=<value>... Compare the fingerprint with the build with the specified ID
|
|
||||||
--environment=(development|preview|production) If generating a fingerprint from the local directory, use the
|
|
||||||
specified environment.
|
|
||||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
|
||||||
--non-interactive Run the command in non-interactive mode.
|
|
||||||
--open Open the fingerprint comparison in the browser
|
|
||||||
--update-id=<value>... Compare the fingerprint with the update with the specified ID
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
compare fingerprints of the current project, builds, and updates
|
|
||||||
|
|
||||||
EXAMPLES
|
|
||||||
$ eas fingerprint:compare # Compare fingerprints in interactive mode
|
|
||||||
|
|
||||||
$ eas fingerprint:compare <FINGERPRINT-HASH> # Compare fingerprint against local directory
|
|
||||||
|
|
||||||
$ eas fingerprint:compare <FINGERPRINT-HASH-1> <FINGERPRINT-HASH-2> # Compare provided fingerprints
|
|
||||||
|
|
||||||
$ eas fingerprint:compare --build-id <BUILD-ID> # Compare fingerprint from build against local directory
|
|
||||||
|
|
||||||
$ eas fingerprint:compare --build-id <BUILD-ID> --environment production # Compare fingerprint from build against local directory with the "production" environment
|
|
||||||
|
|
||||||
$ eas fingerprint:compare --build-id <BUILD-ID-1> --build-id <BUILD-ID-2> # Compare fingerprint from a build against another build
|
|
||||||
|
|
||||||
$ eas fingerprint:compare --build-id <BUILD-ID> --update-id <UPDATE-ID> # Compare fingerprint from build against fingerprint from update
|
|
||||||
|
|
||||||
$ eas fingerprint:compare <FINGERPRINT-HASH> --update-id <UPDATE-ID> # Compare fingerprint from update against provided fingerprint
|
|
||||||
```
|
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/fingerprint/compare.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/fingerprint/compare.ts)_
|
|
||||||
|
|
||||||
## `eas fingerprint:generate`
|
|
||||||
|
|
||||||
generate fingerprints from the current project
|
|
||||||
|
|
||||||
```
|
|
||||||
USAGE
|
|
||||||
$ eas fingerprint:generate [-p android|ios] [--environment development|preview|production | -e <value>] [--json
|
|
||||||
--non-interactive]
|
|
||||||
|
|
||||||
FLAGS
|
|
||||||
-e, --build-profile=<value> Name of the build profile from eas.json.
|
|
||||||
-p, --platform=(android|ios)
|
|
||||||
--environment=(development|preview|production) Environment variable's environment
|
|
||||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
|
||||||
--non-interactive Run the command in non-interactive mode.
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
generate fingerprints from the current project
|
|
||||||
|
|
||||||
EXAMPLES
|
|
||||||
$ eas fingerprint:generate # Generate fingerprint in interactive mode
|
|
||||||
|
|
||||||
$ eas fingerprint:generate --build-profile preview # Generate a fingerprint using the "preview" build profile
|
|
||||||
|
|
||||||
$ eas fingerprint:generate --environment preview # Generate a fingerprint using the "preview" environment
|
|
||||||
|
|
||||||
$ eas fingerprint:generate --json --non-interactive --platform android # Output fingerprint json to stdout
|
|
||||||
```
|
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/fingerprint/generate.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/fingerprint/generate.ts)_
|
|
||||||
|
|
||||||
## `eas help [COMMAND]`
|
## `eas help [COMMAND]`
|
||||||
|
|
||||||
@@ -1508,7 +1387,7 @@ DESCRIPTION
|
|||||||
validate the local store configuration
|
validate the local store configuration
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/metadata/lint.ts)_
|
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||||
|
|
||||||
## `eas metadata:pull`
|
## `eas metadata:pull`
|
||||||
|
|
||||||
@@ -1525,7 +1404,7 @@ DESCRIPTION
|
|||||||
generate the local store configuration from the app stores
|
generate the local store configuration from the app stores
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/metadata/pull.ts)_
|
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||||
|
|
||||||
## `eas metadata:push`
|
## `eas metadata:push`
|
||||||
|
|
||||||
@@ -1542,7 +1421,7 @@ DESCRIPTION
|
|||||||
sync the local store configuration to the app stores
|
sync the local store configuration to the app stores
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/metadata/push.ts)_
|
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||||
|
|
||||||
## `eas onboarding [TARGET_PROJECT_DIRECTORY]`
|
## `eas onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||||
|
|
||||||
@@ -1572,7 +1451,7 @@ DESCRIPTION
|
|||||||
open the project page in a web browser
|
open the project page in a web browser
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/open.ts)_
|
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/open.ts)_
|
||||||
|
|
||||||
## `eas project:info`
|
## `eas project:info`
|
||||||
|
|
||||||
@@ -1586,7 +1465,7 @@ DESCRIPTION
|
|||||||
information about the current project
|
information about the current project
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/project/info.ts)_
|
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||||
|
|
||||||
## `eas project:init`
|
## `eas project:init`
|
||||||
|
|
||||||
@@ -1609,7 +1488,7 @@ ALIASES
|
|||||||
$ eas init
|
$ eas init
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/project/init.ts)_
|
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||||
|
|
||||||
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||||
|
|
||||||
@@ -1627,7 +1506,7 @@ ALIASES
|
|||||||
$ eas onboarding
|
$ eas onboarding
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/project/onboarding.ts)_
|
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||||
|
|
||||||
## `eas submit`
|
## `eas submit`
|
||||||
|
|
||||||
@@ -1658,7 +1537,7 @@ ALIASES
|
|||||||
$ eas build:submit
|
$ eas build:submit
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/submit.ts)_
|
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/submit.ts)_
|
||||||
|
|
||||||
## `eas update`
|
## `eas update`
|
||||||
|
|
||||||
@@ -1700,7 +1579,7 @@ DESCRIPTION
|
|||||||
publish an update group
|
publish an update group
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/update/index.ts)_
|
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||||
|
|
||||||
## `eas update:configure`
|
## `eas update:configure`
|
||||||
|
|
||||||
@@ -1720,7 +1599,7 @@ DESCRIPTION
|
|||||||
configure the project to support EAS Update
|
configure the project to support EAS Update
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/update/configure.ts)_
|
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||||
|
|
||||||
## `eas update:delete GROUPID`
|
## `eas update:delete GROUPID`
|
||||||
|
|
||||||
@@ -1741,7 +1620,7 @@ DESCRIPTION
|
|||||||
delete all the updates in an update group
|
delete all the updates in an update group
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/update/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||||
|
|
||||||
## `eas update:edit [GROUPID]`
|
## `eas update:edit [GROUPID]`
|
||||||
|
|
||||||
@@ -1765,7 +1644,7 @@ DESCRIPTION
|
|||||||
edit all the updates in an update group
|
edit all the updates in an update group
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/update/edit.ts)_
|
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/update/edit.ts)_
|
||||||
|
|
||||||
## `eas update:list`
|
## `eas update:list`
|
||||||
|
|
||||||
@@ -1787,7 +1666,7 @@ DESCRIPTION
|
|||||||
view the recent updates
|
view the recent updates
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/update/list.ts)_
|
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||||
|
|
||||||
## `eas update:republish`
|
## `eas update:republish`
|
||||||
|
|
||||||
@@ -1819,7 +1698,7 @@ DESCRIPTION
|
|||||||
roll back to an existing update
|
roll back to an existing update
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/update/republish.ts)_
|
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/update/republish.ts)_
|
||||||
|
|
||||||
## `eas update:roll-back-to-embedded`
|
## `eas update:roll-back-to-embedded`
|
||||||
|
|
||||||
@@ -1847,7 +1726,7 @@ DESCRIPTION
|
|||||||
roll back to the embedded update
|
roll back to the embedded update
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||||
|
|
||||||
## `eas update:rollback`
|
## `eas update:rollback`
|
||||||
|
|
||||||
@@ -1867,7 +1746,7 @@ DESCRIPTION
|
|||||||
roll back to an embedded update or an existing update
|
roll back to an embedded update or an existing update
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/update/rollback.ts)_
|
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||||
|
|
||||||
## `eas update:view GROUPID`
|
## `eas update:view GROUPID`
|
||||||
|
|
||||||
@@ -1887,7 +1766,7 @@ DESCRIPTION
|
|||||||
update group details
|
update group details
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/update/view.ts)_
|
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||||
|
|
||||||
## `eas webhook:create`
|
## `eas webhook:create`
|
||||||
|
|
||||||
@@ -1908,7 +1787,7 @@ DESCRIPTION
|
|||||||
create a webhook
|
create a webhook
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/webhook/create.ts)_
|
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||||
|
|
||||||
## `eas webhook:delete [ID]`
|
## `eas webhook:delete [ID]`
|
||||||
|
|
||||||
@@ -1928,7 +1807,7 @@ DESCRIPTION
|
|||||||
delete a webhook
|
delete a webhook
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/webhook/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||||
|
|
||||||
## `eas webhook:list`
|
## `eas webhook:list`
|
||||||
|
|
||||||
@@ -1946,7 +1825,7 @@ DESCRIPTION
|
|||||||
list webhooks
|
list webhooks
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/webhook/list.ts)_
|
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||||
|
|
||||||
## `eas webhook:update`
|
## `eas webhook:update`
|
||||||
|
|
||||||
@@ -1968,7 +1847,7 @@ DESCRIPTION
|
|||||||
update a webhook
|
update a webhook
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/webhook/update.ts)_
|
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||||
|
|
||||||
## `eas webhook:view ID`
|
## `eas webhook:view ID`
|
||||||
|
|
||||||
@@ -1985,7 +1864,7 @@ DESCRIPTION
|
|||||||
view a webhook
|
view a webhook
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/webhook/view.ts)_
|
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||||
|
|
||||||
## `eas whoami`
|
## `eas whoami`
|
||||||
|
|
||||||
@@ -2027,7 +1906,7 @@ ALIASES
|
|||||||
|
|
||||||
## `eas deploy [2m[options][22m`
|
## `eas deploy [2m[options][22m`
|
||||||
|
|
||||||
deploy your Expo Router web build and API Routes
|
Deploy your Expo Router web build and API Routes.
|
||||||
|
|
||||||
```
|
```
|
||||||
USAGE
|
USAGE
|
||||||
@@ -2045,7 +1924,7 @@ FLAGS
|
|||||||
--prod Create a new production deployment.
|
--prod Create a new production deployment.
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
deploy your Expo Router web build and API Routes
|
Deploy your Expo Router web build and API Routes.
|
||||||
|
|
||||||
ALIASES
|
ALIASES
|
||||||
$ eas worker:deploy
|
$ eas worker:deploy
|
||||||
@@ -2069,7 +1948,7 @@ DESCRIPTION
|
|||||||
create a new workflow configuration YAML file
|
create a new workflow configuration YAML file
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/workflow/create.ts)_
|
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/workflow/create.ts)_
|
||||||
|
|
||||||
## `eas workflow:run [FILE]`
|
## `eas workflow:run [FILE]`
|
||||||
|
|
||||||
@@ -2089,7 +1968,7 @@ DESCRIPTION
|
|||||||
Run an EAS workflow
|
Run an EAS workflow
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/workflow/run.ts)_
|
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/workflow/run.ts)_
|
||||||
|
|
||||||
## `eas workflow:validate PATH`
|
## `eas workflow:validate PATH`
|
||||||
|
|
||||||
@@ -2109,5 +1988,5 @@ DESCRIPTION
|
|||||||
validate a workflow configuration yaml file
|
validate a workflow configuration yaml file
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v16.3.3/packages/eas-cli/src/commands/workflow/validate.ts)_
|
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v14.5.0/packages/eas-cli/src/commands/workflow/validate.ts)_
|
||||||
<!-- commandsstop -->
|
<!-- commandsstop -->
|
||||||
@@ -12,6 +12,3 @@ if (process.env.TMPDIR) {
|
|||||||
(fs.realpath as any).native = jest.fn();
|
(fs.realpath as any).native = jest.fn();
|
||||||
|
|
||||||
module.exports = fs;
|
module.exports = fs;
|
||||||
|
|
||||||
// Ensure requiring node:fs returns the mock too. This is needed for expo/config and expo/json-file.
|
|
||||||
jest.mock('node:fs', () => fs);
|
|
||||||
Generated
+36
-2292
File diff suppressed because it is too large.
Load diff
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "eas-cli",
|
"name": "eas-cli",
|
||||||
"description": "EAS command line tool",
|
"description": "EAS command line tool",
|
||||||
"version": "16.3.3",
|
"version": "14.5.0",
|
||||||
"author": "Expo <[email protected]>",
|
"author": "Expo <[email protected]>",
|
||||||
"bin": {
|
"bin": {
|
||||||
"eas": "./bin/run"
|
"eas": "./bin/run"
|
||||||
},
|
},
|
||||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/apple-utils": "2.1.12",
|
"@expo/apple-utils": "2.1.5",
|
||||||
"@expo/code-signing-certificates": "0.0.5",
|
"@expo/code-signing-certificates": "0.0.5",
|
||||||
"@expo/config": "10.0.6",
|
"@expo/config": "10.0.6",
|
||||||
"@expo/config-plugins": "9.0.12",
|
"@expo/config-plugins": "9.0.12",
|
||||||
"@expo/eas-build-job": "1.0.173",
|
"@expo/eas-build-job": "1.0.165",
|
||||||
"@expo/eas-json": "16.3.3",
|
"@expo/eas-json": "14.5.0",
|
||||||
"@expo/env": "^1.0.0",
|
"@expo/env": "^1.0.0",
|
||||||
"@expo/json-file": "8.3.3",
|
"@expo/json-file": "8.3.3",
|
||||||
"@expo/logger": "1.0.117",
|
"@expo/logger": "1.0.117",
|
||||||
@@ -28,7 +28,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.2",
|
"@expo/spawn-async": "1.7.2",
|
||||||
"@expo/steps": "1.0.173",
|
"@expo/steps": "1.0.166",
|
||||||
"@expo/timeago.js": "1.0.0",
|
"@expo/timeago.js": "1.0.0",
|
||||||
"@oclif/core": "^1.26.2",
|
"@oclif/core": "^1.26.2",
|
||||||
"@oclif/plugin-autocomplete": "^2.3.10",
|
"@oclif/plugin-autocomplete": "^2.3.10",
|
||||||
@@ -68,7 +68,6 @@
|
|||||||
"nanoid": "3.3.8",
|
"nanoid": "3.3.8",
|
||||||
"node-fetch": "2.6.7",
|
"node-fetch": "2.6.7",
|
||||||
"node-forge": "1.3.1",
|
"node-forge": "1.3.1",
|
||||||
"node-stream-zip": "1.15.0",
|
|
||||||
"nullthrows": "1.1.1",
|
"nullthrows": "1.1.1",
|
||||||
"ora": "5.1.0",
|
"ora": "5.1.0",
|
||||||
"pkg-dir": "4.2.0",
|
"pkg-dir": "4.2.0",
|
||||||
@@ -79,7 +78,6 @@
|
|||||||
"qrcode-terminal": "0.12.0",
|
"qrcode-terminal": "0.12.0",
|
||||||
"resolve-from": "5.0.0",
|
"resolve-from": "5.0.0",
|
||||||
"semver": "7.5.4",
|
"semver": "7.5.4",
|
||||||
"set-interval-async": "3.0.3",
|
|
||||||
"slash": "3.0.0",
|
"slash": "3.0.0",
|
||||||
"tar": "6.2.1",
|
"tar": "6.2.1",
|
||||||
"tar-stream": "3.1.7",
|
"tar-stream": "3.1.7",
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ export class ApiV2Error extends Error {
|
|||||||
readonly expoApiV2ErrorDetails?: JSONValue;
|
readonly expoApiV2ErrorDetails?: JSONValue;
|
||||||
readonly expoApiV2ErrorServerStack?: string;
|
readonly expoApiV2ErrorServerStack?: string;
|
||||||
readonly expoApiV2ErrorMetadata?: object;
|
readonly expoApiV2ErrorMetadata?: object;
|
||||||
readonly expoApiV2ErrorRequestId?: string;
|
|
||||||
|
|
||||||
constructor(response: {
|
constructor(response: {
|
||||||
message: string;
|
message: string;
|
||||||
@@ -14,13 +13,11 @@ export class ApiV2Error extends Error {
|
|||||||
stack?: string;
|
stack?: string;
|
||||||
details?: JSONValue;
|
details?: JSONValue;
|
||||||
metadata?: object;
|
metadata?: object;
|
||||||
requestId?: string;
|
|
||||||
}) {
|
}) {
|
||||||
super(response.message);
|
super(response.message);
|
||||||
this.expoApiV2ErrorCode = response.code;
|
this.expoApiV2ErrorCode = response.code;
|
||||||
this.expoApiV2ErrorDetails = response.details;
|
this.expoApiV2ErrorDetails = response.details;
|
||||||
this.expoApiV2ErrorServerStack = response.stack;
|
this.expoApiV2ErrorServerStack = response.stack;
|
||||||
this.expoApiV2ErrorMetadata = response.metadata;
|
this.expoApiV2ErrorMetadata = response.metadata;
|
||||||
this.expoApiV2ErrorRequestId = response.requestId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,6 @@ describe(ApiV2Client, () => {
|
|||||||
stack: 'line 1: hello',
|
stack: 'line 1: hello',
|
||||||
details: { who: 'world' },
|
details: { who: 'world' },
|
||||||
metadata: { an: 'object' },
|
metadata: { an: 'object' },
|
||||||
requestId: 'request-id',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -38,7 +37,6 @@ describe(ApiV2Client, () => {
|
|||||||
expect(error.expoApiV2ErrorDetails).toEqual({ who: 'world' });
|
expect(error.expoApiV2ErrorDetails).toEqual({ who: 'world' });
|
||||||
expect(error.expoApiV2ErrorMetadata).toEqual({ an: 'object' });
|
expect(error.expoApiV2ErrorMetadata).toEqual({ an: 'object' });
|
||||||
expect(error.expoApiV2ErrorServerStack).toEqual('line 1: hello');
|
expect(error.expoApiV2ErrorServerStack).toEqual('line 1: hello');
|
||||||
expect(error.expoApiV2ErrorRequestId).toEqual('request-id');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not convert non-APIv2 error to ApiV2Error', async () => {
|
it('does not convert non-APIv2 error to ApiV2Error', async () => {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ describe(ensureProjectConfiguredAsync, () => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeTruthy();
|
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeTruthy();
|
||||||
const vcsClientMock = jest.mocked(new GitClient({ requireCommit: false }));
|
const vcsClientMock = jest.mocked(new GitClient());
|
||||||
vcsClientMock.showChangedFilesAsync.mockImplementation(async () => {});
|
vcsClientMock.showChangedFilesAsync.mockImplementation(async () => {});
|
||||||
vcsClientMock.isCommitRequiredAsync.mockImplementation(async () => false);
|
vcsClientMock.isCommitRequiredAsync.mockImplementation(async () => false);
|
||||||
vcsClientMock.trackFileAsync.mockImplementation(async () => {});
|
vcsClientMock.trackFileAsync.mockImplementation(async () => {});
|
||||||
@@ -60,7 +60,7 @@ describe(ensureProjectConfiguredAsync, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeFalsy();
|
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeFalsy();
|
||||||
const vcsClientMock = jest.mocked(new GitClient({ requireCommit: false }));
|
const vcsClientMock = jest.mocked(new GitClient());
|
||||||
vcsClientMock.showChangedFilesAsync.mockImplementation(async () => {});
|
vcsClientMock.showChangedFilesAsync.mockImplementation(async () => {});
|
||||||
vcsClientMock.isCommitRequiredAsync.mockImplementation(async () => false);
|
vcsClientMock.isCommitRequiredAsync.mockImplementation(async () => false);
|
||||||
vcsClientMock.trackFileAsync.mockImplementation(async () => {});
|
vcsClientMock.trackFileAsync.mockImplementation(async () => {});
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
transformProjectArchive,
|
transformProjectArchive,
|
||||||
transformWorkflow,
|
transformWorkflow,
|
||||||
} from '../graphql';
|
} from '../graphql';
|
||||||
import { buildProfileEnvironmentToEnvironment } from '../utils/environment';
|
|
||||||
|
|
||||||
export function transformJob(job: Android.Job): AndroidJobInput {
|
export function transformJob(job: Android.Job): AndroidJobInput {
|
||||||
return {
|
return {
|
||||||
@@ -30,7 +29,6 @@ export function transformJob(job: Android.Job): AndroidJobInput {
|
|||||||
experimental: job.experimental,
|
experimental: job.experimental,
|
||||||
mode: transformBuildMode(job.mode),
|
mode: transformBuildMode(job.mode),
|
||||||
customBuildConfig: job.customBuildConfig,
|
customBuildConfig: job.customBuildConfig,
|
||||||
environment: buildProfileEnvironmentToEnvironment(job.environment),
|
|
||||||
loggerLevel: job.loggerLevel
|
loggerLevel: job.loggerLevel
|
||||||
? loggerLevelToGraphQLWorkerLoggerLevel[job.loggerLevel]
|
? loggerLevelToGraphQLWorkerLoggerLevel[job.loggerLevel]
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -73,15 +73,11 @@ export async function prepareJobAsync(
|
|||||||
builderEnvironment: {
|
builderEnvironment: {
|
||||||
image: buildProfile.image,
|
image: buildProfile.image,
|
||||||
node: buildProfile.node,
|
node: buildProfile.node,
|
||||||
corepack: buildProfile.corepack,
|
|
||||||
pnpm: buildProfile.pnpm,
|
pnpm: buildProfile.pnpm,
|
||||||
bun: buildProfile.bun,
|
bun: buildProfile.bun,
|
||||||
yarn: buildProfile.yarn,
|
yarn: buildProfile.yarn,
|
||||||
ndk: buildProfile.ndk,
|
ndk: buildProfile.ndk,
|
||||||
env: {
|
env: buildProfile.env,
|
||||||
...buildProfile.env,
|
|
||||||
...(ctx.isVerboseLoggingEnabled ? { EAS_VERBOSE: '1' } : {}),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
...cacheDefaults,
|
...cacheDefaults,
|
||||||
@@ -96,7 +92,6 @@ export async function prepareJobAsync(
|
|||||||
gradleCommand: buildProfile.gradleCommand,
|
gradleCommand: buildProfile.gradleCommand,
|
||||||
applicationArchivePath: buildProfile.applicationArchivePath ?? buildProfile.artifactPath,
|
applicationArchivePath: buildProfile.applicationArchivePath ?? buildProfile.artifactPath,
|
||||||
buildArtifactPaths: buildProfile.buildArtifactPaths,
|
buildArtifactPaths: buildProfile.buildArtifactPaths,
|
||||||
environment: ctx.buildProfile.environment,
|
|
||||||
buildType,
|
buildType,
|
||||||
username,
|
username,
|
||||||
...(ctx.android.versionCodeOverride && {
|
...(ctx.android.versionCodeOverride && {
|
||||||
|
|||||||
@@ -35,10 +35,8 @@ import { collectMetadataAsync } from './metadata';
|
|||||||
import { printDeprecationWarnings } from './utils/printBuildInfo';
|
import { printDeprecationWarnings } from './utils/printBuildInfo';
|
||||||
import {
|
import {
|
||||||
LocalFile,
|
LocalFile,
|
||||||
assertProjectTarballSizeDoesNotExceedLimit,
|
|
||||||
makeProjectMetadataFileAsync,
|
makeProjectMetadataFileAsync,
|
||||||
makeProjectTarballAsync,
|
makeProjectTarballAsync,
|
||||||
maybeWarnAboutProjectTarballSize,
|
|
||||||
reviewAndCommitChangesAsync,
|
reviewAndCommitChangesAsync,
|
||||||
} from './utils/repository';
|
} from './utils/repository';
|
||||||
import { BuildEvent } from '../analytics/AnalyticsManager';
|
import { BuildEvent } from '../analytics/AnalyticsManager';
|
||||||
@@ -46,7 +44,6 @@ import { withAnalyticsAsync } from '../analytics/common';
|
|||||||
import { getExpoWebsiteBaseUrl } from '../api';
|
import { getExpoWebsiteBaseUrl } from '../api';
|
||||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
import { EasCommandError } from '../commandUtils/errors';
|
import { EasCommandError } from '../commandUtils/errors';
|
||||||
import { createFingerprintAsync } from '../fingerprint/cli';
|
|
||||||
import {
|
import {
|
||||||
AppPlatform,
|
AppPlatform,
|
||||||
BuildFragment,
|
BuildFragment,
|
||||||
@@ -69,6 +66,7 @@ import { maybeUploadFingerprintAsync } from '../project/maybeUploadFingerprintAs
|
|||||||
import { resolveRuntimeVersionAsync } from '../project/resolveRuntimeVersionAsync';
|
import { resolveRuntimeVersionAsync } from '../project/resolveRuntimeVersionAsync';
|
||||||
import { uploadFileAtPathToGCSAsync } from '../uploads';
|
import { uploadFileAtPathToGCSAsync } from '../uploads';
|
||||||
import { formatBytes } from '../utils/files';
|
import { formatBytes } from '../utils/files';
|
||||||
|
import { createFingerprintAsync } from '../utils/fingerprintCli';
|
||||||
import { printJsonOnlyOutput } from '../utils/json';
|
import { printJsonOnlyOutput } from '../utils/json';
|
||||||
import { createProgressTracker } from '../utils/progress';
|
import { createProgressTracker } from '../utils/progress';
|
||||||
import { sleepAsync } from '../utils/promise';
|
import { sleepAsync } from '../utils/promise';
|
||||||
@@ -277,8 +275,19 @@ async function uploadProjectAsync<TPlatform extends Platform>(
|
|||||||
);
|
);
|
||||||
const projectTarball = await makeProjectTarballAsync(ctx.vcsClient);
|
const projectTarball = await makeProjectTarballAsync(ctx.vcsClient);
|
||||||
|
|
||||||
maybeWarnAboutProjectTarballSize(projectTarball.size);
|
if (projectTarball.size > 1024 * 1024 * 100) {
|
||||||
assertProjectTarballSizeDoesNotExceedLimit(projectTarball.size);
|
Log.warn(
|
||||||
|
`Your project archive is ${formatBytes(
|
||||||
|
projectTarball.size
|
||||||
|
)}. You can reduce its size and the time it takes to upload by excluding files that are unnecessary for the build process in ${chalk.bold(
|
||||||
|
'.easignore'
|
||||||
|
)} file. ${learnMore('https://expo.fyi/eas-build-archive')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectTarball.size > 2 * 1024 * 1024 * 1024) {
|
||||||
|
throw new Error('Project archive is too big. Maximum allowed size is 2GB.');
|
||||||
|
}
|
||||||
|
|
||||||
projectTarballPath = projectTarball.path;
|
projectTarballPath = projectTarball.path;
|
||||||
const [bucketKey, { metadataLocation }] = await Promise.all([
|
const [bucketKey, { metadataLocation }] = await Promise.all([
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import fs from 'fs-extra';
|
|||||||
|
|
||||||
import { maybeBailOnRepoStatusAsync, reviewAndCommitChangesAsync } from './utils/repository';
|
import { maybeBailOnRepoStatusAsync, reviewAndCommitChangesAsync } from './utils/repository';
|
||||||
import Log, { learnMore } from '../log';
|
import Log, { learnMore } from '../log';
|
||||||
import { ora } from '../ora';
|
|
||||||
import { easCliVersion } from '../utils/easCli';
|
import { easCliVersion } from '../utils/easCli';
|
||||||
import { Client } from '../vcs/vcs';
|
import { Client } from '../vcs/vcs';
|
||||||
|
|
||||||
@@ -53,78 +52,6 @@ async function configureAsync({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doesBuildProfileExistAsync({
|
|
||||||
projectDir,
|
|
||||||
profileName,
|
|
||||||
}: {
|
|
||||||
projectDir: string;
|
|
||||||
profileName: string;
|
|
||||||
}): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
|
||||||
const easJson = await easJsonAccessor.readRawJsonAsync();
|
|
||||||
if (!easJson.build?.[profileName]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
Log.error(`We were unable to read ${chalk.bold('eas.json')} contents. Error: ${error}.`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createBuildProfileAsync({
|
|
||||||
projectDir,
|
|
||||||
profileName,
|
|
||||||
profileContents,
|
|
||||||
vcsClient,
|
|
||||||
nonInteractive,
|
|
||||||
}: {
|
|
||||||
projectDir: string;
|
|
||||||
profileName: string;
|
|
||||||
profileContents: Record<string, any>;
|
|
||||||
vcsClient: Client;
|
|
||||||
nonInteractive: boolean;
|
|
||||||
}): Promise<void> {
|
|
||||||
const spinner = ora(`Adding "${profileName}" build profile to ${chalk.bold('eas.json')}`).start();
|
|
||||||
try {
|
|
||||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
|
||||||
await easJsonAccessor.readRawJsonAsync();
|
|
||||||
|
|
||||||
easJsonAccessor.patch(easJsonRawObject => {
|
|
||||||
return {
|
|
||||||
...easJsonRawObject,
|
|
||||||
build: {
|
|
||||||
...easJsonRawObject.build,
|
|
||||||
[profileName]: profileContents,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
await easJsonAccessor.writeAsync();
|
|
||||||
spinner.succeed(
|
|
||||||
`Successfully added "${profileName}" build profile to ${chalk.bold('eas.json')}.`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (await vcsClient.isCommitRequiredAsync()) {
|
|
||||||
Log.newLine();
|
|
||||||
await reviewAndCommitChangesAsync(
|
|
||||||
vcsClient,
|
|
||||||
`Add "${profileName}" build profile to eas.json`,
|
|
||||||
{
|
|
||||||
nonInteractive,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
spinner.fail(
|
|
||||||
`We were not able to configure "${profileName}" build profile inside of ${chalk.bold(
|
|
||||||
'eas.json'
|
|
||||||
)}. Error: ${error}.`
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const EAS_JSON_DEFAULT: EasJson = {
|
const EAS_JSON_DEFAULT: EasJson = {
|
||||||
cli: {
|
cli: {
|
||||||
version: `>= ${easCliVersion}`,
|
version: `>= ${easCliVersion}`,
|
||||||
|
|||||||
@@ -63,6 +63,5 @@ export interface BuildContext<T extends Platform> {
|
|||||||
vcsClient: Client;
|
vcsClient: Client;
|
||||||
loggerLevel?: LoggerLevel;
|
loggerLevel?: LoggerLevel;
|
||||||
repack: boolean;
|
repack: boolean;
|
||||||
isVerboseLoggingEnabled: boolean;
|
|
||||||
env: Record<string, string>;
|
env: Record<string, string>;
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,6 @@ export async function createBuildContextAsync<T extends Platform>({
|
|||||||
buildLoggerLevel,
|
buildLoggerLevel,
|
||||||
freezeCredentials,
|
freezeCredentials,
|
||||||
repack,
|
repack,
|
||||||
isVerboseLoggingEnabled,
|
|
||||||
env,
|
env,
|
||||||
}: {
|
}: {
|
||||||
buildProfileName: string;
|
buildProfileName: string;
|
||||||
@@ -66,7 +65,6 @@ export async function createBuildContextAsync<T extends Platform>({
|
|||||||
buildLoggerLevel?: LoggerLevel;
|
buildLoggerLevel?: LoggerLevel;
|
||||||
freezeCredentials: boolean;
|
freezeCredentials: boolean;
|
||||||
repack: boolean;
|
repack: boolean;
|
||||||
isVerboseLoggingEnabled: boolean;
|
|
||||||
env: Record<string, string>;
|
env: Record<string, string>;
|
||||||
}): Promise<BuildContext<T>> {
|
}): Promise<BuildContext<T>> {
|
||||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
|
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
|
||||||
@@ -153,7 +151,6 @@ export async function createBuildContextAsync<T extends Platform>({
|
|||||||
requiredPackageManager,
|
requiredPackageManager,
|
||||||
loggerLevel: buildLoggerLevel,
|
loggerLevel: buildLoggerLevel,
|
||||||
repack,
|
repack,
|
||||||
isVerboseLoggingEnabled,
|
|
||||||
env,
|
env,
|
||||||
};
|
};
|
||||||
if (platform === Platform.ANDROID) {
|
if (platform === Platform.ANDROID) {
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import { Env } from '@expo/eas-build-job';
|
import { Env } from '@expo/eas-build-job';
|
||||||
import { BuildProfile } from '@expo/eas-json';
|
import { BuildProfile } from '@expo/eas-json';
|
||||||
|
|
||||||
import { isEnvironment } from './utils/environment';
|
|
||||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
import { EnvironmentVariableEnvironment } from '../graphql/generated';
|
import { EnvironmentVariableEnvironment } from '../graphql/generated';
|
||||||
import { EnvironmentVariablesQuery } from '../graphql/queries/EnvironmentVariablesQuery';
|
import { EnvironmentVariablesQuery } from '../graphql/queries/EnvironmentVariablesQuery';
|
||||||
import Log, { learnMore } from '../log';
|
import Log, { learnMore } from '../log';
|
||||||
|
|
||||||
|
function isEnvironment(env: string): env is EnvironmentVariableEnvironment {
|
||||||
|
return Object.values(EnvironmentVariableEnvironment).includes(
|
||||||
|
env as EnvironmentVariableEnvironment
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function evaluateConfigWithEnvVarsAsync<Config extends { projectId: string }, Opts>({
|
export async function evaluateConfigWithEnvVarsAsync<Config extends { projectId: string }, Opts>({
|
||||||
buildProfile,
|
buildProfile,
|
||||||
buildProfileName,
|
buildProfileName,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { IosCredentials } from '../../credentials/ios/types';
|
|||||||
import { BuildParamsInput } from '../../graphql/generated';
|
import { BuildParamsInput } from '../../graphql/generated';
|
||||||
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
|
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
|
||||||
import { ensureBundleIdentifierIsDefinedForManagedProjectAsync } from '../../project/ios/bundleIdentifier';
|
import { ensureBundleIdentifierIsDefinedForManagedProjectAsync } from '../../project/ios/bundleIdentifier';
|
||||||
import { ensureNonExemptEncryptionIsDefinedForManagedProjectAsync } from '../../project/ios/exemptEncryption';
|
|
||||||
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
|
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
|
||||||
import { findApplicationTarget, resolveTargetsAsync } from '../../project/ios/target';
|
import { findApplicationTarget, resolveTargetsAsync } from '../../project/ios/target';
|
||||||
import { BuildRequestSender, JobData, prepareBuildRequestForPlatformAsync } from '../build';
|
import { BuildRequestSender, JobData, prepareBuildRequestForPlatformAsync } from '../build';
|
||||||
@@ -29,7 +28,6 @@ export async function createIosContextAsync(
|
|||||||
|
|
||||||
if (ctx.workflow === Workflow.MANAGED) {
|
if (ctx.workflow === Workflow.MANAGED) {
|
||||||
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx);
|
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx);
|
||||||
await ensureNonExemptEncryptionIsDefinedForManagedProjectAsync(ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNodeEnvVariable(ctx);
|
checkNodeEnvVariable(ctx);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
transformProjectArchive,
|
transformProjectArchive,
|
||||||
transformWorkflow,
|
transformWorkflow,
|
||||||
} from '../graphql';
|
} from '../graphql';
|
||||||
import { buildProfileEnvironmentToEnvironment } from '../utils/environment';
|
|
||||||
|
|
||||||
export function transformJob(job: Ios.Job): IosJobInput {
|
export function transformJob(job: Ios.Job): IosJobInput {
|
||||||
return {
|
return {
|
||||||
@@ -32,7 +31,6 @@ export function transformJob(job: Ios.Job): IosJobInput {
|
|||||||
experimental: job.experimental,
|
experimental: job.experimental,
|
||||||
mode: transformBuildMode(job.mode),
|
mode: transformBuildMode(job.mode),
|
||||||
customBuildConfig: job.customBuildConfig,
|
customBuildConfig: job.customBuildConfig,
|
||||||
environment: buildProfileEnvironmentToEnvironment(job.environment),
|
|
||||||
loggerLevel: job.loggerLevel
|
loggerLevel: job.loggerLevel
|
||||||
? loggerLevelToGraphQLWorkerLoggerLevel[job.loggerLevel]
|
? loggerLevelToGraphQLWorkerLoggerLevel[job.loggerLevel]
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -65,17 +65,13 @@ export async function prepareJobAsync(
|
|||||||
builderEnvironment: {
|
builderEnvironment: {
|
||||||
image: buildProfile.image,
|
image: buildProfile.image,
|
||||||
node: buildProfile.node,
|
node: buildProfile.node,
|
||||||
corepack: buildProfile.corepack,
|
|
||||||
pnpm: buildProfile.pnpm,
|
pnpm: buildProfile.pnpm,
|
||||||
bun: buildProfile.bun,
|
bun: buildProfile.bun,
|
||||||
yarn: buildProfile.yarn,
|
yarn: buildProfile.yarn,
|
||||||
bundler: buildProfile.bundler,
|
bundler: buildProfile.bundler,
|
||||||
cocoapods: buildProfile.cocoapods,
|
cocoapods: buildProfile.cocoapods,
|
||||||
fastlane: buildProfile.fastlane,
|
fastlane: buildProfile.fastlane,
|
||||||
env: {
|
env: buildProfile.env,
|
||||||
...buildProfile.env,
|
|
||||||
...(ctx.isVerboseLoggingEnabled ? { EAS_VERBOSE: '1' } : {}),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
...cacheDefaults,
|
...cacheDefaults,
|
||||||
@@ -92,7 +88,6 @@ export async function prepareJobAsync(
|
|||||||
buildConfiguration: buildProfile.buildConfiguration,
|
buildConfiguration: buildProfile.buildConfiguration,
|
||||||
applicationArchivePath: buildProfile.applicationArchivePath ?? buildProfile.artifactPath,
|
applicationArchivePath: buildProfile.applicationArchivePath ?? buildProfile.artifactPath,
|
||||||
buildArtifactPaths: buildProfile.buildArtifactPaths,
|
buildArtifactPaths: buildProfile.buildArtifactPaths,
|
||||||
environment: ctx.buildProfile.environment,
|
|
||||||
username,
|
username,
|
||||||
...(ctx.ios.buildNumberOverride && {
|
...(ctx.ios.buildNumberOverride && {
|
||||||
version: {
|
version: {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Log from '../log';
|
|||||||
import { ora } from '../ora';
|
import { ora } from '../ora';
|
||||||
|
|
||||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||||
const PLUGIN_PACKAGE_VERSION = '1.0.171';
|
const PLUGIN_PACKAGE_VERSION = '1.0.163';
|
||||||
|
|
||||||
export enum LocalBuildMode {
|
export enum LocalBuildMode {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export async function collectMetadataAsync<T extends Platform>(
|
|||||||
requiredPackageManager: ctx.requiredPackageManager ?? undefined,
|
requiredPackageManager: ctx.requiredPackageManager ?? undefined,
|
||||||
selectedImage: ctx.buildProfile.image,
|
selectedImage: ctx.buildProfile.image,
|
||||||
customNodeVersion: ctx.buildProfile.node,
|
customNodeVersion: ctx.buildProfile.node,
|
||||||
|
environment: ctx.buildProfile.environment,
|
||||||
simulator: 'simulator' in ctx.buildProfile && ctx.buildProfile.simulator,
|
simulator: 'simulator' in ctx.buildProfile && ctx.buildProfile.simulator,
|
||||||
};
|
};
|
||||||
return sanitizeMetadata(metadata);
|
return sanitizeMetadata(metadata);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
import { LoggerLevel } from '@expo/logger';
|
import { LoggerLevel } from '@expo/logger';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { pathExists } from 'fs-extra';
|
|
||||||
import nullthrows from 'nullthrows';
|
import nullthrows from 'nullthrows';
|
||||||
|
|
||||||
import { prepareAndroidBuildAsync } from './android/build';
|
import { prepareAndroidBuildAsync } from './android/build';
|
||||||
@@ -63,7 +62,7 @@ import {
|
|||||||
validateBuildProfileVersionSettingsAsync,
|
validateBuildProfileVersionSettingsAsync,
|
||||||
} from '../project/remoteVersionSource';
|
} from '../project/remoteVersionSource';
|
||||||
import { confirmAsync } from '../prompts';
|
import { confirmAsync } from '../prompts';
|
||||||
import { getEasBuildRunCachedAppPath, runAsync } from '../run/run';
|
import { runAsync } from '../run/run';
|
||||||
import { isRunnableOnSimulatorOrEmulator } from '../run/utils';
|
import { isRunnableOnSimulatorOrEmulator } from '../run/utils';
|
||||||
import { createSubmissionContextAsync } from '../submit/context';
|
import { createSubmissionContextAsync } from '../submit/context';
|
||||||
import {
|
import {
|
||||||
@@ -98,32 +97,18 @@ export interface BuildFlags {
|
|||||||
buildLoggerLevel?: LoggerLevel;
|
buildLoggerLevel?: LoggerLevel;
|
||||||
freezeCredentials: boolean;
|
freezeCredentials: boolean;
|
||||||
repack: boolean;
|
repack: boolean;
|
||||||
isVerboseLoggingEnabled?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runBuildAndSubmitAsync({
|
export async function runBuildAndSubmitAsync(
|
||||||
graphqlClient,
|
graphqlClient: ExpoGraphqlClient,
|
||||||
analytics,
|
analytics: Analytics,
|
||||||
vcsClient,
|
vcsClient: Client,
|
||||||
projectDir,
|
projectDir: string,
|
||||||
flags,
|
flags: BuildFlags,
|
||||||
actor,
|
actor: Actor,
|
||||||
getDynamicPrivateProjectConfigAsync,
|
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn
|
||||||
downloadSimBuildAutoConfirm,
|
): Promise<{
|
||||||
envOverride,
|
|
||||||
}: {
|
|
||||||
graphqlClient: ExpoGraphqlClient;
|
|
||||||
analytics: Analytics;
|
|
||||||
vcsClient: Client;
|
|
||||||
projectDir: string;
|
|
||||||
flags: BuildFlags;
|
|
||||||
actor: Actor;
|
|
||||||
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
|
|
||||||
downloadSimBuildAutoConfirm?: boolean;
|
|
||||||
envOverride?: Env;
|
|
||||||
}): Promise<{
|
|
||||||
buildIds: string[];
|
buildIds: string[];
|
||||||
buildProfiles?: ProfileData<'build'>[];
|
|
||||||
}> {
|
}> {
|
||||||
await vcsClient.ensureRepoExistsAsync();
|
await vcsClient.ensureRepoExistsAsync();
|
||||||
await ensureRepoIsCleanAsync(vcsClient, flags.nonInteractive);
|
await ensureRepoIsCleanAsync(vcsClient, flags.nonInteractive);
|
||||||
@@ -207,15 +192,13 @@ export async function runBuildAndSubmitAsync({
|
|||||||
for (const buildProfile of buildProfiles) {
|
for (const buildProfile of buildProfiles) {
|
||||||
const platform = toAppPlatform(buildProfile.platform);
|
const platform = toAppPlatform(buildProfile.platform);
|
||||||
|
|
||||||
const { env } = !envOverride
|
const { env } = await evaluateConfigWithEnvVarsAsync({
|
||||||
? await evaluateConfigWithEnvVarsAsync({
|
|
||||||
buildProfile: buildProfile.profile,
|
buildProfile: buildProfile.profile,
|
||||||
buildProfileName: buildProfile.profileName,
|
buildProfileName: buildProfile.profileName,
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||||
opts: { env: buildProfile.profile.env },
|
opts: { env: buildProfile.profile.env },
|
||||||
})
|
});
|
||||||
: { env: envOverride };
|
|
||||||
|
|
||||||
const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({
|
const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({
|
||||||
projectDir,
|
projectDir,
|
||||||
@@ -325,7 +308,7 @@ export async function runBuildAndSubmitAsync({
|
|||||||
[BuildStatus.Errored, BuildStatus.Canceled, BuildStatus.PendingCancel].includes(build?.status)
|
[BuildStatus.Errored, BuildStatus.Canceled, BuildStatus.PendingCancel].includes(build?.status)
|
||||||
);
|
);
|
||||||
|
|
||||||
await maybeDownloadAndRunSimulatorBuildsAsync(builds, flags, downloadSimBuildAutoConfirm);
|
await maybeDownloadAndRunSimulatorBuildsAsync(builds, flags);
|
||||||
|
|
||||||
if (haveAllBuildsFailedOrCanceled || !flags.autoSubmit) {
|
if (haveAllBuildsFailedOrCanceled || !flags.autoSubmit) {
|
||||||
if (flags.json) {
|
if (flags.json) {
|
||||||
@@ -350,7 +333,6 @@ export async function runBuildAndSubmitAsync({
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
buildIds: startedBuilds.map(({ build }) => build.id),
|
buildIds: startedBuilds.map(({ build }) => build.id),
|
||||||
buildProfiles,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,7 +384,6 @@ async function prepareAndStartBuildAsync({
|
|||||||
buildLoggerLevel: flags.buildLoggerLevel ?? (Log.isDebug ? LoggerLevel.DEBUG : undefined),
|
buildLoggerLevel: flags.buildLoggerLevel ?? (Log.isDebug ? LoggerLevel.DEBUG : undefined),
|
||||||
freezeCredentials: flags.freezeCredentials,
|
freezeCredentials: flags.freezeCredentials,
|
||||||
repack: flags.repack,
|
repack: flags.repack,
|
||||||
isVerboseLoggingEnabled: flags.isVerboseLoggingEnabled ?? false,
|
|
||||||
env,
|
env,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -550,29 +531,18 @@ function exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds: (BuildFragment | nul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadAndRunAsync(build: BuildFragment): Promise<void> {
|
async function downloadAndRunAsync(build: BuildFragment): Promise<void> {
|
||||||
assert(build.artifacts?.applicationArchiveUrl);
|
assert(build.artifacts?.applicationArchiveUrl);
|
||||||
const cachedAppPath = getEasBuildRunCachedAppPath(build.project.id, build.id, build.platform);
|
|
||||||
|
|
||||||
if (await pathExists(cachedAppPath)) {
|
|
||||||
Log.newLine();
|
|
||||||
Log.log(`Using cached app...`);
|
|
||||||
await runAsync(cachedAppPath, build.platform);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildPath = await downloadAndMaybeExtractAppAsync(
|
const buildPath = await downloadAndMaybeExtractAppAsync(
|
||||||
build.artifacts.applicationArchiveUrl,
|
build.artifacts.applicationArchiveUrl,
|
||||||
build.platform,
|
build.platform
|
||||||
cachedAppPath
|
|
||||||
);
|
);
|
||||||
await runAsync(buildPath, build.platform);
|
await runAsync(buildPath, build.platform);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function maybeDownloadAndRunSimulatorBuildsAsync(
|
async function maybeDownloadAndRunSimulatorBuildsAsync(
|
||||||
builds: MaybeBuildFragment[],
|
builds: MaybeBuildFragment[],
|
||||||
flags: BuildFlags,
|
flags: BuildFlags
|
||||||
autoConfirm?: boolean
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const simBuilds = builds.filter(truthy).filter(isRunnableOnSimulatorOrEmulator);
|
const simBuilds = builds.filter(truthy).filter(isRunnableOnSimulatorOrEmulator);
|
||||||
|
|
||||||
@@ -580,15 +550,11 @@ async function maybeDownloadAndRunSimulatorBuildsAsync(
|
|||||||
for (const simBuild of simBuilds) {
|
for (const simBuild of simBuilds) {
|
||||||
if (simBuild.platform === AppPlatform.Android || process.platform === 'darwin') {
|
if (simBuild.platform === AppPlatform.Android || process.platform === 'darwin') {
|
||||||
Log.newLine();
|
Log.newLine();
|
||||||
const confirm =
|
const confirm = await confirmAsync({
|
||||||
autoConfirm ??
|
|
||||||
(await confirmAsync({
|
|
||||||
message: `Install and run the ${
|
message: `Install and run the ${
|
||||||
simBuild.platform === AppPlatform.Android ? 'Android' : 'iOS'
|
simBuild.platform === AppPlatform.Android ? 'Android' : 'iOS'
|
||||||
} build on ${
|
} build on ${simBuild.platform === AppPlatform.Android ? 'an emulator' : 'a simulator'}?`,
|
||||||
simBuild.platform === AppPlatform.Android ? 'an emulator' : 'a simulator'
|
});
|
||||||
}?`,
|
|
||||||
}));
|
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
await downloadAndRunAsync(simBuild);
|
await downloadAndRunAsync(simBuild);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { BuildProfile } from '@expo/eas-json';
|
|
||||||
|
|
||||||
import { EnvironmentVariableEnvironment } from '../../graphql/generated';
|
|
||||||
|
|
||||||
type Environment = NonNullable<BuildProfile['environment']>;
|
|
||||||
|
|
||||||
const BuildProfileEnvironmentToEnvironment: Record<Environment, EnvironmentVariableEnvironment> = {
|
|
||||||
production: EnvironmentVariableEnvironment.Production,
|
|
||||||
preview: EnvironmentVariableEnvironment.Preview,
|
|
||||||
development: EnvironmentVariableEnvironment.Development,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function isEnvironment(env: string): env is EnvironmentVariableEnvironment {
|
|
||||||
return Object.values(EnvironmentVariableEnvironment).includes(
|
|
||||||
env as EnvironmentVariableEnvironment
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildProfileEnvironmentToEnvironment(
|
|
||||||
environment: BuildProfile['environment']
|
|
||||||
): EnvironmentVariableEnvironment | null {
|
|
||||||
if (!environment) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return BuildProfileEnvironmentToEnvironment[environment];
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ import path from 'path';
|
|||||||
import tar from 'tar';
|
import tar from 'tar';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import Log, { learnMore } from '../../log';
|
import Log from '../../log';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { confirmAsync, promptAsync } from '../../prompts';
|
import { confirmAsync, promptAsync } from '../../prompts';
|
||||||
import { formatBytes } from '../../utils/files';
|
import { formatBytes } from '../../utils/files';
|
||||||
@@ -169,34 +169,6 @@ export async function makeProjectTarballAsync(vcsClient: Client): Promise<LocalF
|
|||||||
return { size, path: tarPath };
|
return { size, path: tarPath };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function maybeWarnAboutProjectTarballSize(size: number): void {
|
|
||||||
if (size <= 150 /* MiB */ * 1024 /* KiB */ * 1024 /* B */) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.warn(
|
|
||||||
`Your project archive is ${formatBytes(
|
|
||||||
size
|
|
||||||
)}. You can reduce its size and the time it takes to upload by excluding files that are unnecessary for the build process in ${chalk.bold(
|
|
||||||
'.easignore'
|
|
||||||
)} file. ${learnMore('https://expo.fyi/eas-build-archive')}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAX_ALLOWED_PROJECT_TARBALL_SIZE =
|
|
||||||
2 /* GiB */ * 1024 /* MiB */ * 1024 /* KiB */ * 1024; /* B */
|
|
||||||
export function assertProjectTarballSizeDoesNotExceedLimit(size: number): void {
|
|
||||||
if (size <= MAX_ALLOWED_PROJECT_TARBALL_SIZE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
`Project archive is too big. Maximum allowed size is ${formatBytes(
|
|
||||||
MAX_ALLOWED_PROJECT_TARBALL_SIZE
|
|
||||||
)}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ShouldCommitChanges {
|
enum ShouldCommitChanges {
|
||||||
Yes,
|
Yes,
|
||||||
ShowDiffFirst,
|
ShowDiffFirst,
|
||||||
|
|||||||
@@ -72,12 +72,3 @@ export function getProjectGitHubSettingsUrl(accountName: string, projectName: st
|
|||||||
getExpoWebsiteBaseUrl()
|
getExpoWebsiteBaseUrl()
|
||||||
).toString();
|
).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHostingDeploymentsUrl(accountName: string, projectName: string): string {
|
|
||||||
return new URL(
|
|
||||||
`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(
|
|
||||||
projectName
|
|
||||||
)}/hosting/deployments`,
|
|
||||||
getExpoWebsiteBaseUrl()
|
|
||||||
).toString();
|
|
||||||
}
|
|
||||||
@@ -289,7 +289,7 @@ export async function doesChannelExistAsync(
|
|||||||
{ appId, channelName }: { appId: string; channelName: string }
|
{ appId, channelName }: { appId: string; channelName: string }
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await ChannelQuery.viewUpdateChannelBasicInfoAsync(graphqlClient, {
|
await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||||
appId,
|
appId,
|
||||||
channelName,
|
channelName,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
createAnalyticsAsync,
|
createAnalyticsAsync,
|
||||||
} from '../analytics/AnalyticsManager';
|
} from '../analytics/AnalyticsManager';
|
||||||
import { EnvironmentVariableEnvironment } from '../graphql/generated';
|
import { EnvironmentVariableEnvironment } from '../graphql/generated';
|
||||||
import Log, { link } from '../log';
|
import Log from '../log';
|
||||||
import SessionManager from '../user/SessionManager';
|
import SessionManager from '../user/SessionManager';
|
||||||
import { Client } from '../vcs/vcs';
|
import { Client } from '../vcs/vcs';
|
||||||
|
|
||||||
@@ -264,16 +264,6 @@ export default abstract class EasCommand extends Command {
|
|||||||
const defaultMsg = `${messageLine}${requestIdLine}`;
|
const defaultMsg = `${messageLine}${requestIdLine}`;
|
||||||
|
|
||||||
if (graphQLError.extensions?.errorCode === 'UNAUTHORIZED_ERROR') {
|
if (graphQLError.extensions?.errorCode === 'UNAUTHORIZED_ERROR') {
|
||||||
if (defaultMsg.includes('ScopedAccountActorViewerContext') && process.env.EAS_BUILD) {
|
|
||||||
// We're in EAS, authenticated with a scoped account actor access token.
|
|
||||||
// We may have not added the scoped actor privacy rule to the right place yet.
|
|
||||||
return `${chalk.bold(
|
|
||||||
`You don't have the required permissions to perform this operation.`
|
|
||||||
)}\n\nWe are in the process of migrating EAS to a more granular permissioning system. If you believe what you're doing is a legitimate operation you should be able to perform, report this to us at ${link(
|
|
||||||
'https://expo.dev/contact'
|
|
||||||
)}\n\nOriginal error message: ${defaultMsg}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${chalk.bold(
|
return `${chalk.bold(
|
||||||
`You don't have the required permissions to perform this operation.`
|
`You don't have the required permissions to perform this operation.`
|
||||||
)}\n\nThis can sometimes happen if you are logged in as incorrect user.\nRun ${chalk.bold(
|
)}\n\nThis can sometimes happen if you are logged in as incorrect user.\nRun ${chalk.bold(
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export async function fetchBuildsAsync({
|
|||||||
platform?: RequestedPlatform;
|
platform?: RequestedPlatform;
|
||||||
profile?: string;
|
profile?: string;
|
||||||
hasFingerprint?: boolean;
|
hasFingerprint?: boolean;
|
||||||
fingerprintHash?: string;
|
|
||||||
};
|
};
|
||||||
}): Promise<BuildFragment[]> {
|
}): Promise<BuildFragment[]> {
|
||||||
let builds: BuildFragment[];
|
let builds: BuildFragment[];
|
||||||
@@ -57,9 +56,6 @@ export async function fetchBuildsAsync({
|
|||||||
if (filters?.hasFingerprint) {
|
if (filters?.hasFingerprint) {
|
||||||
queryFilters['hasFingerprint'] = filters.hasFingerprint;
|
queryFilters['hasFingerprint'] = filters.hasFingerprint;
|
||||||
}
|
}
|
||||||
if (filters?.fingerprintHash) {
|
|
||||||
queryFilters['fingerprintHash'] = filters.fingerprintHash;
|
|
||||||
}
|
|
||||||
if (!filters?.statuses) {
|
if (!filters?.statuses) {
|
||||||
builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||||
appId: projectId,
|
appId: projectId,
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
|||||||
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
|
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
|
||||||
import { getPublicExpoConfigAsync } from '../../project/expoConfig';
|
import { getPublicExpoConfigAsync } from '../../project/expoConfig';
|
||||||
|
|
||||||
export type GetServerSideEnvironmentVariablesFn = (
|
type GetServerSideEnvironmentVariablesFn = (
|
||||||
maybeEnv?: Record<string, string>
|
maybeEnv?: Record<string, string>
|
||||||
) => Promise<Record<string, string>>;
|
) => Promise<Record<string, string>>;
|
||||||
|
|
||||||
|
|||||||
+17
-112
@@ -78,12 +78,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
|
|
||||||
it('gets the project ID from app config if exists', async () => {
|
it('gets the project ID from app config if exists', async () => {
|
||||||
jest.mocked(getConfig).mockReturnValue({
|
jest.mocked(getConfig).mockReturnValue({
|
||||||
exp: {
|
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||||
sdkVersion: '52.0.0',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'test',
|
|
||||||
extra: { eas: { projectId: '1234' } },
|
|
||||||
},
|
|
||||||
} as any);
|
} as any);
|
||||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||||
id: '1234',
|
id: '1234',
|
||||||
@@ -96,7 +91,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
await expect(
|
await expect(
|
||||||
getProjectIdAsync(
|
getProjectIdAsync(
|
||||||
sessionManager,
|
sessionManager,
|
||||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||||
{ nonInteractive: false }
|
{ nonInteractive: false }
|
||||||
)
|
)
|
||||||
).resolves.toEqual('1234');
|
).resolves.toEqual('1234');
|
||||||
@@ -104,13 +99,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
|
|
||||||
it('throws when the owner is out of sync', async () => {
|
it('throws when the owner is out of sync', async () => {
|
||||||
jest.mocked(getConfig).mockReturnValue({
|
jest.mocked(getConfig).mockReturnValue({
|
||||||
exp: {
|
exp: { name: 'test', slug: 'test', owner: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||||
sdkVersion: '54.0.0',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'test',
|
|
||||||
owner: 'wat',
|
|
||||||
extra: { eas: { projectId: '1234' } },
|
|
||||||
},
|
|
||||||
} as any);
|
} as any);
|
||||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||||
id: '1234',
|
id: '1234',
|
||||||
@@ -123,13 +112,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
await expect(
|
await expect(
|
||||||
getProjectIdAsync(
|
getProjectIdAsync(
|
||||||
sessionManager,
|
sessionManager,
|
||||||
{
|
{ name: 'test', slug: 'test', owner: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||||
sdkVersion: '52.0.0',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'test',
|
|
||||||
owner: 'wat',
|
|
||||||
extra: { eas: { projectId: '1234' } },
|
|
||||||
},
|
|
||||||
{ nonInteractive: false }
|
{ nonInteractive: false }
|
||||||
)
|
)
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
@@ -139,15 +122,9 @@ describe(getProjectIdAsync, () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when sdkVersion is less than 53', () => {
|
|
||||||
it('throws when the owner is not specified and is different than logged in user', async () => {
|
it('throws when the owner is not specified and is different than logged in user', async () => {
|
||||||
jest.mocked(getConfig).mockReturnValue({
|
jest.mocked(getConfig).mockReturnValue({
|
||||||
exp: {
|
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||||
sdkVersion: '52.0.0',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'test',
|
|
||||||
extra: { eas: { projectId: '1234' } },
|
|
||||||
},
|
|
||||||
} as any);
|
} as any);
|
||||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||||
id: '1234',
|
id: '1234',
|
||||||
@@ -160,12 +137,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
await expect(
|
await expect(
|
||||||
getProjectIdAsync(
|
getProjectIdAsync(
|
||||||
sessionManager,
|
sessionManager,
|
||||||
{
|
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||||
sdkVersion: '52.0.0',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'test',
|
|
||||||
extra: { eas: { projectId: '1234' } },
|
|
||||||
},
|
|
||||||
{ nonInteractive: false }
|
{ nonInteractive: false }
|
||||||
)
|
)
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
@@ -201,12 +173,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
const sessionManagerRobot = instance(sessionManagerMock);
|
const sessionManagerRobot = instance(sessionManagerMock);
|
||||||
|
|
||||||
jest.mocked(getConfig).mockReturnValue({
|
jest.mocked(getConfig).mockReturnValue({
|
||||||
exp: {
|
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||||
sdkVersion: '52.0.0',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'test',
|
|
||||||
extra: { eas: { projectId: '1234' } },
|
|
||||||
},
|
|
||||||
} as any);
|
} as any);
|
||||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||||
id: '1234',
|
id: '1234',
|
||||||
@@ -219,12 +186,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
await expect(
|
await expect(
|
||||||
getProjectIdAsync(
|
getProjectIdAsync(
|
||||||
sessionManagerRobot,
|
sessionManagerRobot,
|
||||||
{
|
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||||
sdkVersion: '52.0.0',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'test',
|
|
||||||
extra: { eas: { projectId: '1234' } },
|
|
||||||
},
|
|
||||||
{ nonInteractive: false }
|
{ nonInteractive: false }
|
||||||
)
|
)
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
@@ -233,49 +195,10 @@ describe(getProjectIdAsync, () => {
|
|||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('when sdkVersion is 53 or higher', () => {
|
|
||||||
it('does not throw when the owner is not specified', async () => {
|
|
||||||
jest.mocked(getConfig).mockReturnValue({
|
|
||||||
exp: {
|
|
||||||
sdkVersion: '53.0.0',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'test',
|
|
||||||
extra: { eas: { projectId: '1234' } },
|
|
||||||
},
|
|
||||||
} as any);
|
|
||||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
|
||||||
id: '1234',
|
|
||||||
fullName: '@totallybrent/test',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'test',
|
|
||||||
ownerAccount: { name: 'totallybrent' } as any,
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
getProjectIdAsync(
|
|
||||||
sessionManager,
|
|
||||||
{
|
|
||||||
sdkVersion: '53.0.0',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'test',
|
|
||||||
extra: { eas: { projectId: '1234' } },
|
|
||||||
},
|
|
||||||
{ nonInteractive: false }
|
|
||||||
)
|
|
||||||
).resolves.not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws when the slug is out of sync', async () => {
|
it('throws when the slug is out of sync', async () => {
|
||||||
jest.mocked(getConfig).mockReturnValue({
|
jest.mocked(getConfig).mockReturnValue({
|
||||||
exp: {
|
exp: { name: 'test', slug: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||||
sdkVersion: '52.0.0',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'wat',
|
|
||||||
extra: { eas: { projectId: '1234' } },
|
|
||||||
},
|
|
||||||
} as any);
|
} as any);
|
||||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||||
id: '1234',
|
id: '1234',
|
||||||
@@ -288,7 +211,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
await expect(
|
await expect(
|
||||||
getProjectIdAsync(
|
getProjectIdAsync(
|
||||||
sessionManager,
|
sessionManager,
|
||||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'wat', extra: { eas: { projectId: '1234' } } },
|
{ name: 'test', slug: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||||
{ nonInteractive: false }
|
{ nonInteractive: false }
|
||||||
)
|
)
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
@@ -299,17 +222,10 @@ describe(getProjectIdAsync, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('fetches the project ID when not in app config, and sets it in the config', async () => {
|
it('fetches the project ID when not in app config, and sets it in the config', async () => {
|
||||||
jest
|
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||||
.mocked(getConfig)
|
|
||||||
.mockReturnValue({ exp: { sdkVersion: '52.0.0', name: 'test', slug: 'test' } } as any);
|
|
||||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
config: {
|
config: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } },
|
||||||
sdkVersion: '52.0.0',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'test',
|
|
||||||
extra: { eas: { projectId: '2345' } },
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
jest
|
jest
|
||||||
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
||||||
@@ -317,7 +233,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
|
|
||||||
const projectId = await getProjectIdAsync(
|
const projectId = await getProjectIdAsync(
|
||||||
sessionManager,
|
sessionManager,
|
||||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'test' },
|
{ name: 'test', slug: 'test' },
|
||||||
{
|
{
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
}
|
}
|
||||||
@@ -336,9 +252,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('throws if writing the ID back to the config fails', async () => {
|
it('throws if writing the ID back to the config fails', async () => {
|
||||||
jest
|
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||||
.mocked(getConfig)
|
|
||||||
.mockReturnValue({ exp: { sdkVersion: '52.0.0', name: 'test', slug: 'test' } } as any);
|
|
||||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||||
type: 'fail',
|
type: 'fail',
|
||||||
config: null,
|
config: null,
|
||||||
@@ -348,22 +262,13 @@ describe(getProjectIdAsync, () => {
|
|||||||
.mockImplementation(async () => '4567');
|
.mockImplementation(async () => '4567');
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
getProjectIdAsync(
|
getProjectIdAsync(sessionManager, { name: 'test', slug: 'test' }, { nonInteractive: false })
|
||||||
sessionManager,
|
|
||||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'test' },
|
|
||||||
{ nonInteractive: false }
|
|
||||||
)
|
|
||||||
).rejects.toThrow();
|
).rejects.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws if extra.eas.projectId is not a string', async () => {
|
it('throws if extra.eas.projectId is not a string', async () => {
|
||||||
jest.mocked(getConfig).mockReturnValue({
|
jest.mocked(getConfig).mockReturnValue({
|
||||||
exp: {
|
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: 1234 } } },
|
||||||
sdkVersion: '52.0.0',
|
|
||||||
name: 'test',
|
|
||||||
slug: 'test',
|
|
||||||
extra: { eas: { projectId: 1234 } },
|
|
||||||
},
|
|
||||||
} as any);
|
} as any);
|
||||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||||
id: '1234',
|
id: '1234',
|
||||||
@@ -376,7 +281,7 @@ describe(getProjectIdAsync, () => {
|
|||||||
await expect(
|
await expect(
|
||||||
getProjectIdAsync(
|
getProjectIdAsync(
|
||||||
sessionManager,
|
sessionManager,
|
||||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'wat', extra: { eas: { projectId: 1234 } } },
|
{ name: 'test', slug: 'wat', extra: { eas: { projectId: 1234 } } },
|
||||||
{ nonInteractive: false }
|
{ nonInteractive: false }
|
||||||
)
|
)
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { ExpoConfig, getProjectConfigDescription } from '@expo/config';
|
import { ExpoConfig, getProjectConfigDescription } from '@expo/config';
|
||||||
import { Env } from '@expo/eas-build-job';
|
import { Env } from '@expo/eas-build-job';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import semver from 'semver';
|
|
||||||
|
|
||||||
import { ExpoGraphqlClient, createGraphqlClient } from './createGraphqlClient';
|
import { ExpoGraphqlClient, createGraphqlClient } from './createGraphqlClient';
|
||||||
import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync';
|
import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync';
|
||||||
@@ -128,9 +127,6 @@ export async function validateOrSetProjectIdAsync({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sdkVersion = exp.sdkVersion;
|
|
||||||
// SDK 53 and above no longer require owner field in app config
|
|
||||||
if (sdkVersion && semver.satisfies(sdkVersion, '< 53.0.0')) {
|
|
||||||
const actorUsername = getActorUsername(actor);
|
const actorUsername = getActorUsername(actor);
|
||||||
if (!exp.owner && appForProjectId.ownerAccount.name !== actorUsername) {
|
if (!exp.owner && appForProjectId.ownerAccount.name !== actorUsername) {
|
||||||
if (actorUsername) {
|
if (actorUsername) {
|
||||||
@@ -156,7 +152,6 @@ export async function validateOrSetProjectIdAsync({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (exp.slug && exp.slug !== appForProjectId.slug) {
|
if (exp.slug && exp.slug !== appForProjectId.slug) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -1,351 +0,0 @@
|
|||||||
import { Platform } from '@expo/eas-build-job';
|
|
||||||
import { BuildProfile, EasJsonAccessor } from '@expo/eas-json';
|
|
||||||
import { Errors, Flags } from '@oclif/core';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
|
|
||||||
import {
|
|
||||||
createBuildProfileAsync,
|
|
||||||
doesBuildProfileExistAsync,
|
|
||||||
ensureProjectConfiguredAsync,
|
|
||||||
} from '../../build/configure';
|
|
||||||
import { evaluateConfigWithEnvVarsAsync } from '../../build/evaluateConfigWithEnvVarsAsync';
|
|
||||||
import { downloadAndRunAsync, runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
|
||||||
import { ensureRepoIsCleanAsync } from '../../build/utils/repository';
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
|
||||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
|
||||||
import { createFingerprintAsync } from '../../fingerprint/cli';
|
|
||||||
import { BuildFragment, BuildStatus, DistributionType } from '../../graphql/generated';
|
|
||||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
|
||||||
import { toAppPlatform } from '../../graphql/types/AppPlatform';
|
|
||||||
import Log from '../../log';
|
|
||||||
import { RequestedPlatform } from '../../platform';
|
|
||||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
|
||||||
import { confirmAsync, promptAsync } from '../../prompts';
|
|
||||||
import { expoCommandAsync } from '../../utils/expoCli';
|
|
||||||
import { ProfileData, getProfilesAsync } from '../../utils/profiles';
|
|
||||||
import { Client } from '../../vcs/vcs';
|
|
||||||
|
|
||||||
const DEFAULT_EAS_BUILD_RUN_PROFILE_NAME = 'development-simulator';
|
|
||||||
|
|
||||||
export default class BuildDev extends EasCommand {
|
|
||||||
static override hidden: true;
|
|
||||||
|
|
||||||
static override description =
|
|
||||||
'run dev client simulator/emulator build with matching fingerprint or create a new one';
|
|
||||||
|
|
||||||
static override flags = {
|
|
||||||
platform: Flags.enum<Platform.IOS | Platform.ANDROID>({
|
|
||||||
char: 'p',
|
|
||||||
options: [Platform.IOS, Platform.ANDROID],
|
|
||||||
}),
|
|
||||||
profile: Flags.string({
|
|
||||||
char: 'e',
|
|
||||||
description: `Name of the build profile from eas.json. It must be a profile allowing to create emulator/simulator internal distribution dev client builds. The "${DEFAULT_EAS_BUILD_RUN_PROFILE_NAME}" build profile will be selected by default.`,
|
|
||||||
helpValue: 'PROFILE_NAME',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
static override contextDefinition = {
|
|
||||||
...this.ContextOptions.LoggedIn,
|
|
||||||
...this.ContextOptions.DynamicProjectConfig,
|
|
||||||
...this.ContextOptions.ProjectDir,
|
|
||||||
...this.ContextOptions.Vcs,
|
|
||||||
...this.ContextOptions.Analytics,
|
|
||||||
...this.ContextOptions.ProjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override async runAsync(): Promise<any> {
|
|
||||||
const { flags } = await this.parse(BuildDev);
|
|
||||||
|
|
||||||
const {
|
|
||||||
loggedIn: { actor, graphqlClient },
|
|
||||||
getDynamicPrivateProjectConfigAsync,
|
|
||||||
projectDir,
|
|
||||||
analytics,
|
|
||||||
vcsClient,
|
|
||||||
projectId,
|
|
||||||
} = await this.getContextAsync(BuildDev, {
|
|
||||||
nonInteractive: false,
|
|
||||||
withServerSideEnvironment: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const platform = await this.selectPlatformAsync(flags.platform);
|
|
||||||
if (process.platform !== 'darwin' && platform === Platform.IOS) {
|
|
||||||
Errors.error('Running iOS builds in simulator is only supported on macOS.', { exit: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
await vcsClient.ensureRepoExistsAsync();
|
|
||||||
await ensureRepoIsCleanAsync(vcsClient, flags.nonInteractive);
|
|
||||||
await ensureProjectConfiguredAsync({
|
|
||||||
projectDir,
|
|
||||||
nonInteractive: false,
|
|
||||||
vcsClient,
|
|
||||||
});
|
|
||||||
|
|
||||||
const buildProfile = await this.ensureValidBuildRunProfileExistsAsync({
|
|
||||||
projectDir,
|
|
||||||
platform,
|
|
||||||
selectedBuildProfileName: flags.profile,
|
|
||||||
vcsClient,
|
|
||||||
});
|
|
||||||
|
|
||||||
const workflow = await resolveWorkflowAsync(projectDir, platform, vcsClient);
|
|
||||||
|
|
||||||
const { env } = await evaluateConfigWithEnvVarsAsync({
|
|
||||||
buildProfile: buildProfile.profile,
|
|
||||||
buildProfileName: buildProfile.profileName,
|
|
||||||
graphqlClient,
|
|
||||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
|
||||||
opts: { env: buildProfile.profile.env },
|
|
||||||
});
|
|
||||||
|
|
||||||
const fingerprint = await createFingerprintAsync(projectDir, {
|
|
||||||
env,
|
|
||||||
workflow,
|
|
||||||
platforms: [platform],
|
|
||||||
});
|
|
||||||
if (!fingerprint) {
|
|
||||||
Errors.error('Failed to calculate fingerprint', { exit: 1 });
|
|
||||||
}
|
|
||||||
Log.log(`✨ Calculated fingerprint hash: ${fingerprint.hash}`);
|
|
||||||
Log.newLine();
|
|
||||||
|
|
||||||
const builds = await this.getBuildsAsync({
|
|
||||||
graphqlClient,
|
|
||||||
projectId,
|
|
||||||
platform,
|
|
||||||
fingerprint,
|
|
||||||
});
|
|
||||||
if (builds.length !== 0) {
|
|
||||||
const build = builds[0];
|
|
||||||
Log.succeed(
|
|
||||||
`🎯 Found successful build with matching fingerprint on EAS servers. Running it...`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (build.artifacts?.applicationArchiveUrl) {
|
|
||||||
await downloadAndRunAsync(build);
|
|
||||||
await this.startDevServerAsync({ projectDir, platform });
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
Log.warn('Artifacts for this build expired. New build will be started.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.log('🚀 No successful build with matching fingerprint found. Starting a new build...');
|
|
||||||
|
|
||||||
const previousBuildsForSelectedProfile = await this.getBuildsAsync({
|
|
||||||
graphqlClient,
|
|
||||||
projectId,
|
|
||||||
platform,
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
previousBuildsForSelectedProfile.length > 0 &&
|
|
||||||
previousBuildsForSelectedProfile[0].metrics?.buildDuration
|
|
||||||
) {
|
|
||||||
Log.log(
|
|
||||||
`🕒 Previous build for "${buildProfile.profileName}" profile completed in ${Math.floor(
|
|
||||||
previousBuildsForSelectedProfile[0].metrics.buildDuration / 60000
|
|
||||||
)} minutes.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await runBuildAndSubmitAsync({
|
|
||||||
graphqlClient,
|
|
||||||
analytics,
|
|
||||||
vcsClient,
|
|
||||||
projectDir,
|
|
||||||
flags: {
|
|
||||||
requestedPlatform:
|
|
||||||
platform === Platform.ANDROID ? RequestedPlatform.Android : RequestedPlatform.Ios,
|
|
||||||
nonInteractive: false,
|
|
||||||
freezeCredentials: false,
|
|
||||||
wait: true,
|
|
||||||
clearCache: false,
|
|
||||||
json: false,
|
|
||||||
autoSubmit: false,
|
|
||||||
localBuildOptions: {},
|
|
||||||
repack: false,
|
|
||||||
profile: flags.profile ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
|
||||||
},
|
|
||||||
actor,
|
|
||||||
getDynamicPrivateProjectConfigAsync,
|
|
||||||
downloadSimBuildAutoConfirm: true,
|
|
||||||
envOverride: env,
|
|
||||||
});
|
|
||||||
await this.startDevServerAsync({ projectDir, platform });
|
|
||||||
}
|
|
||||||
|
|
||||||
private async selectPlatformAsync(platform?: Platform): Promise<Platform> {
|
|
||||||
if (platform) {
|
|
||||||
return platform;
|
|
||||||
}
|
|
||||||
const { resolvedPlatform } = await promptAsync({
|
|
||||||
type: 'select',
|
|
||||||
message: 'Select platform',
|
|
||||||
name: 'resolvedPlatform',
|
|
||||||
choices: [
|
|
||||||
{ title: 'Android', value: Platform.ANDROID },
|
|
||||||
{ title: 'iOS', value: Platform.IOS },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
return resolvedPlatform;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async validateBuildRunProfileAsync({
|
|
||||||
platform,
|
|
||||||
buildProfile,
|
|
||||||
buildProfileName,
|
|
||||||
}: {
|
|
||||||
platform: Platform;
|
|
||||||
buildProfile: BuildProfile;
|
|
||||||
buildProfileName: string;
|
|
||||||
}): Promise<void> {
|
|
||||||
if (buildProfile.developmentClient !== true) {
|
|
||||||
Errors.error(
|
|
||||||
`Profile "${buildProfileName}" must specify "developmentClient: true" to create a dev client build. Select a different profile or update the profile in eas.json.`,
|
|
||||||
{ exit: 1 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (buildProfile.distribution !== 'internal') {
|
|
||||||
Errors.error(
|
|
||||||
`Profile "${buildProfileName}" must specify "distribution: internal" in order to work with eas build:dev command. Select a different profile or update the profile in eas.json.`,
|
|
||||||
{ exit: 1 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (platform === Platform.IOS) {
|
|
||||||
const iosProfile = buildProfile as BuildProfile<Platform.IOS>;
|
|
||||||
if (iosProfile.simulator !== true && iosProfile.withoutCredentials !== true) {
|
|
||||||
Errors.error(
|
|
||||||
`Profile "${buildProfileName}" must specify "ios.simulator: true" or "withoutCredentials: true" to create an iOS simulator build. Select a different profile or update the profile in eas.json.`,
|
|
||||||
{ exit: 1 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const androidProfile = buildProfile as BuildProfile<Platform.ANDROID>;
|
|
||||||
if (
|
|
||||||
androidProfile.distribution !== 'internal' &&
|
|
||||||
androidProfile.withoutCredentials !== true
|
|
||||||
) {
|
|
||||||
Errors.error(
|
|
||||||
`Profile "${buildProfileName}" must specify "distribution: internal" or "withoutCredentials: true" to create an Android emulator build. Select a different profile or update the profile in eas.json.`,
|
|
||||||
{ exit: 1 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ensureValidBuildRunProfileExistsAsync({
|
|
||||||
projectDir,
|
|
||||||
platform,
|
|
||||||
selectedBuildProfileName,
|
|
||||||
vcsClient,
|
|
||||||
}: {
|
|
||||||
projectDir: string;
|
|
||||||
platform: Platform;
|
|
||||||
selectedBuildProfileName?: string;
|
|
||||||
vcsClient: Client;
|
|
||||||
}): Promise<ProfileData<'build'>> {
|
|
||||||
if (
|
|
||||||
!!selectedBuildProfileName ||
|
|
||||||
(await doesBuildProfileExistAsync({
|
|
||||||
projectDir,
|
|
||||||
profileName: DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
|
||||||
}))
|
|
||||||
) {
|
|
||||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
|
||||||
const [buildProfile] = await getProfilesAsync({
|
|
||||||
type: 'build',
|
|
||||||
easJsonAccessor,
|
|
||||||
platforms: [platform],
|
|
||||||
profileName: selectedBuildProfileName ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
|
||||||
projectDir,
|
|
||||||
});
|
|
||||||
await this.validateBuildRunProfileAsync({
|
|
||||||
buildProfileName: selectedBuildProfileName ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
|
||||||
platform,
|
|
||||||
buildProfile: buildProfile.profile,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const createBuildProfile = await confirmAsync({
|
|
||||||
message: `We want to go ahead and generate "${DEFAULT_EAS_BUILD_RUN_PROFILE_NAME}" build profile for you, that matches eas build:dev criteria. Do you want to proceed?`,
|
|
||||||
});
|
|
||||||
if (!createBuildProfile) {
|
|
||||||
Errors.error(
|
|
||||||
'Come back later or specify different build compliant with eas build:dev requirements by using "--profile" flag.',
|
|
||||||
{ exit: 1 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await createBuildProfileAsync({
|
|
||||||
projectDir,
|
|
||||||
profileName: DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
|
||||||
profileContents: {
|
|
||||||
developmentClient: true,
|
|
||||||
distribution: 'internal',
|
|
||||||
ios: {
|
|
||||||
simulator: true,
|
|
||||||
},
|
|
||||||
environment: 'development',
|
|
||||||
},
|
|
||||||
nonInteractive: false,
|
|
||||||
vcsClient,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
|
||||||
const [buildProfile] = await getProfilesAsync({
|
|
||||||
type: 'build',
|
|
||||||
easJsonAccessor,
|
|
||||||
platforms: [platform],
|
|
||||||
profileName: selectedBuildProfileName ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
|
||||||
projectDir,
|
|
||||||
});
|
|
||||||
return buildProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getBuildsAsync({
|
|
||||||
graphqlClient,
|
|
||||||
projectId,
|
|
||||||
platform,
|
|
||||||
fingerprint,
|
|
||||||
}: {
|
|
||||||
graphqlClient: ExpoGraphqlClient;
|
|
||||||
projectId: string;
|
|
||||||
platform: Platform;
|
|
||||||
fingerprint?: { hash: string };
|
|
||||||
}): Promise<BuildFragment[]> {
|
|
||||||
return await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
|
||||||
appId: projectId,
|
|
||||||
filter: {
|
|
||||||
platform: toAppPlatform(platform),
|
|
||||||
fingerprintHash: fingerprint?.hash,
|
|
||||||
status: BuildStatus.Finished,
|
|
||||||
simulator: platform === Platform.IOS ? true : undefined,
|
|
||||||
distribution: platform === Platform.ANDROID ? DistributionType.Internal : undefined,
|
|
||||||
developmentClient: true,
|
|
||||||
},
|
|
||||||
offset: 0,
|
|
||||||
limit: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async startDevServerAsync({
|
|
||||||
projectDir,
|
|
||||||
platform,
|
|
||||||
}: {
|
|
||||||
projectDir: string;
|
|
||||||
platform: Platform;
|
|
||||||
}): Promise<void> {
|
|
||||||
Log.newLine();
|
|
||||||
Log.log(
|
|
||||||
`Starting development server: ${chalk.dim(
|
|
||||||
`npx expo start --dev-client ${platform === Platform.IOS ? '--ios' : '--android'}`
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
await expoCommandAsync(projectDir, [
|
|
||||||
'start',
|
|
||||||
'--dev-client',
|
|
||||||
platform === Platform.IOS ? '--ios' : '--android',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
import { Platform } from '@expo/eas-build-job';
|
|
||||||
import { Errors, Flags } from '@oclif/core';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { pathExists } from 'fs-extra';
|
|
||||||
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
|
||||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
|
||||||
import { AppPlatform, BuildFragment, BuildStatus, DistributionType } from '../../graphql/generated';
|
|
||||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
|
||||||
import { toAppPlatform } from '../../graphql/types/AppPlatform';
|
|
||||||
import Log from '../../log';
|
|
||||||
import { promptAsync } from '../../prompts';
|
|
||||||
import { getEasBuildRunCachedAppPath } from '../../run/run';
|
|
||||||
import { downloadAndMaybeExtractAppAsync } from '../../utils/download';
|
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
|
||||||
|
|
||||||
export default class Download extends EasCommand {
|
|
||||||
static override description = 'download simulator/emulator builds for a given fingerprint hash';
|
|
||||||
|
|
||||||
static override flags = {
|
|
||||||
fingerprint: Flags.string({
|
|
||||||
description: 'Fingerprint hash of the build to download',
|
|
||||||
required: true,
|
|
||||||
}),
|
|
||||||
platform: Flags.enum<Platform.IOS | Platform.ANDROID>({
|
|
||||||
char: 'p',
|
|
||||||
options: [Platform.IOS, Platform.ANDROID],
|
|
||||||
}),
|
|
||||||
'dev-client': Flags.boolean({
|
|
||||||
description: 'Filter only dev-client builds.',
|
|
||||||
allowNo: true,
|
|
||||||
}),
|
|
||||||
...EasNonInteractiveAndJsonFlags,
|
|
||||||
};
|
|
||||||
|
|
||||||
static override contextDefinition = {
|
|
||||||
...this.ContextOptions.LoggedIn,
|
|
||||||
...this.ContextOptions.ProjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
|
||||||
const {
|
|
||||||
flags: {
|
|
||||||
json: jsonFlag,
|
|
||||||
platform,
|
|
||||||
fingerprint,
|
|
||||||
'dev-client': developmentClient,
|
|
||||||
'non-interactive': nonInteractive,
|
|
||||||
},
|
|
||||||
} = await this.parse(Download);
|
|
||||||
|
|
||||||
const {
|
|
||||||
loggedIn: { graphqlClient },
|
|
||||||
projectId,
|
|
||||||
} = await this.getContextAsync(Download, {
|
|
||||||
nonInteractive,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (jsonFlag) {
|
|
||||||
enableJsonOutput();
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedPlatform = await resolvePlatformAsync({ nonInteractive, platform });
|
|
||||||
const build = await this.getBuildAsync({
|
|
||||||
graphqlClient,
|
|
||||||
projectId,
|
|
||||||
platform: selectedPlatform,
|
|
||||||
fingerprintHash: fingerprint,
|
|
||||||
developmentClient,
|
|
||||||
});
|
|
||||||
const buildArtifactPath = await this.getPathToBuildArtifactAsync(build, selectedPlatform);
|
|
||||||
if (jsonFlag) {
|
|
||||||
const jsonResults = { path: buildArtifactPath };
|
|
||||||
printJsonOnlyOutput(jsonResults);
|
|
||||||
} else {
|
|
||||||
Log.log(`Build downloaded to ${chalk.bold(buildArtifactPath)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getBuildAsync({
|
|
||||||
graphqlClient,
|
|
||||||
projectId,
|
|
||||||
platform,
|
|
||||||
fingerprintHash,
|
|
||||||
developmentClient,
|
|
||||||
}: {
|
|
||||||
graphqlClient: ExpoGraphqlClient;
|
|
||||||
projectId: string;
|
|
||||||
platform: AppPlatform;
|
|
||||||
fingerprintHash: string;
|
|
||||||
developmentClient: boolean;
|
|
||||||
}): Promise<BuildFragment> {
|
|
||||||
const builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
|
||||||
appId: projectId,
|
|
||||||
filter: {
|
|
||||||
platform,
|
|
||||||
fingerprintHash,
|
|
||||||
status: BuildStatus.Finished,
|
|
||||||
simulator: platform === AppPlatform.Ios ? true : undefined,
|
|
||||||
distribution: platform === AppPlatform.Android ? DistributionType.Internal : undefined,
|
|
||||||
developmentClient,
|
|
||||||
},
|
|
||||||
offset: 0,
|
|
||||||
limit: 1,
|
|
||||||
});
|
|
||||||
if (builds.length === 0) {
|
|
||||||
throw new Errors.CLIError(
|
|
||||||
`No builds available for ${platform} with fingerprint hash ${fingerprintHash}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.succeed(`🎯 Found successful build with matching fingerprint on EAS servers.`);
|
|
||||||
return builds[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPathToBuildArtifactAsync(build: BuildFragment, platform: AppPlatform): Promise<string> {
|
|
||||||
const cachedBuildArtifactPath = getEasBuildRunCachedAppPath(
|
|
||||||
build.project.id,
|
|
||||||
build.id,
|
|
||||||
platform
|
|
||||||
);
|
|
||||||
if (await pathExists(cachedBuildArtifactPath)) {
|
|
||||||
Log.newLine();
|
|
||||||
Log.log(`Using cached build...`);
|
|
||||||
return cachedBuildArtifactPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!build.artifacts?.applicationArchiveUrl) {
|
|
||||||
throw new Error('Build does not have an application archive url');
|
|
||||||
}
|
|
||||||
|
|
||||||
return await downloadAndMaybeExtractAppAsync(
|
|
||||||
build.artifacts.applicationArchiveUrl,
|
|
||||||
platform,
|
|
||||||
cachedBuildArtifactPath
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolvePlatformAsync({
|
|
||||||
nonInteractive,
|
|
||||||
platform,
|
|
||||||
}: {
|
|
||||||
nonInteractive: boolean;
|
|
||||||
platform?: Platform;
|
|
||||||
}): Promise<AppPlatform> {
|
|
||||||
if (nonInteractive && !platform) {
|
|
||||||
throw new Error('Platform must be provided in non-interactive mode');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (platform) {
|
|
||||||
return toAppPlatform(platform);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { selectedPlatform } = await promptAsync({
|
|
||||||
type: 'select',
|
|
||||||
message: 'Select platform',
|
|
||||||
name: 'selectedPlatform',
|
|
||||||
choices: [
|
|
||||||
{ title: 'Android', value: AppPlatform.Android },
|
|
||||||
{ title: 'iOS', value: AppPlatform.Ios },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
return selectedPlatform;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Platform } from '@expo/eas-build-job';
|
import { Platform } from '@expo/eas-build-job';
|
||||||
import { BuildProfile, EasJsonAccessor, EasJsonUtils, ResourceClass } from '@expo/eas-json';
|
import { EasJsonAccessor, EasJsonUtils, ResourceClass } from '@expo/eas-json';
|
||||||
import { LoggerLevel } from '@expo/logger';
|
import { LoggerLevel } from '@expo/logger';
|
||||||
import { Errors, Flags } from '@oclif/core';
|
import { Errors, Flags } from '@oclif/core';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
@@ -17,7 +17,6 @@ import { RequestedPlatform, selectRequestedPlatformAsync } from '../../platform'
|
|||||||
import { selectAsync } from '../../prompts';
|
import { selectAsync } from '../../prompts';
|
||||||
import uniq from '../../utils/expodash/uniq';
|
import uniq from '../../utils/expodash/uniq';
|
||||||
import { enableJsonOutput } from '../../utils/json';
|
import { enableJsonOutput } from '../../utils/json';
|
||||||
import { ProfileData } from '../../utils/profiles';
|
|
||||||
import { maybeWarnAboutEasOutagesAsync } from '../../utils/statuspageService';
|
import { maybeWarnAboutEasOutagesAsync } from '../../utils/statuspageService';
|
||||||
|
|
||||||
interface RawBuildFlags {
|
interface RawBuildFlags {
|
||||||
@@ -37,7 +36,6 @@ interface RawBuildFlags {
|
|||||||
message?: string;
|
message?: string;
|
||||||
'build-logger-level'?: LoggerLevel;
|
'build-logger-level'?: LoggerLevel;
|
||||||
'freeze-credentials': boolean;
|
'freeze-credentials': boolean;
|
||||||
'verbose-logs'?: boolean;
|
|
||||||
repack: boolean;
|
repack: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,12 +78,10 @@ export default class Build extends EasCommand {
|
|||||||
description: 'Clear cache before the build',
|
description: 'Clear cache before the build',
|
||||||
}),
|
}),
|
||||||
'auto-submit': Flags.boolean({
|
'auto-submit': Flags.boolean({
|
||||||
char: 's',
|
|
||||||
default: false,
|
default: false,
|
||||||
description:
|
description:
|
||||||
'Submit on build complete using the submit profile with the same name as the build profile',
|
'Submit on build complete using the submit profile with the same name as the build profile',
|
||||||
exclusive: ['auto-submit-with-profile'],
|
exclusive: ['auto-submit-with-profile'],
|
||||||
aliases: ['submit'],
|
|
||||||
}),
|
}),
|
||||||
'auto-submit-with-profile': Flags.string({
|
'auto-submit-with-profile': Flags.string({
|
||||||
description: 'Submit on build complete using the submit profile with provided name',
|
description: 'Submit on build complete using the submit profile with provided name',
|
||||||
@@ -119,10 +115,6 @@ export default class Build extends EasCommand {
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
description: 'Use the golden dev client build repack flow as it works for onboarding',
|
description: 'Use the golden dev client build repack flow as it works for onboarding',
|
||||||
}),
|
}),
|
||||||
'verbose-logs': Flags.boolean({
|
|
||||||
default: false,
|
|
||||||
description: 'Use verbose logs for the build process',
|
|
||||||
}),
|
|
||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -166,20 +158,15 @@ export default class Build extends EasCommand {
|
|||||||
|
|
||||||
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
|
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
|
||||||
|
|
||||||
const { buildProfiles } = await runBuildAndSubmitAsync({
|
await runBuildAndSubmitAsync(
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
analytics,
|
analytics,
|
||||||
vcsClient,
|
vcsClient,
|
||||||
projectDir,
|
projectDir,
|
||||||
flags: flagsWithPlatform,
|
flagsWithPlatform,
|
||||||
actor,
|
actor,
|
||||||
getDynamicPrivateProjectConfigAsync,
|
getDynamicPrivateProjectConfigAsync
|
||||||
});
|
);
|
||||||
|
|
||||||
this.maybeSuggestUsingEasBuildDev({
|
|
||||||
buildProfiles,
|
|
||||||
nonInteractive: flags.nonInteractive,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sanitizeFlags(
|
private sanitizeFlags(
|
||||||
@@ -243,7 +230,6 @@ export default class Build extends EasCommand {
|
|||||||
message,
|
message,
|
||||||
buildLoggerLevel: flags['build-logger-level'],
|
buildLoggerLevel: flags['build-logger-level'],
|
||||||
freezeCredentials: flags['freeze-credentials'],
|
freezeCredentials: flags['freeze-credentials'],
|
||||||
isVerboseLoggingEnabled: flags['verbose-logs'],
|
|
||||||
repack: flags.repack,
|
repack: flags.repack,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -280,51 +266,6 @@ export default class Build extends EasCommand {
|
|||||||
requestedPlatform,
|
requestedPlatform,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private maybeSuggestUsingEasBuildDev({
|
|
||||||
buildProfiles,
|
|
||||||
nonInteractive,
|
|
||||||
}: {
|
|
||||||
buildProfiles: ProfileData<'build'>[] | undefined;
|
|
||||||
nonInteractive: boolean;
|
|
||||||
}): void {
|
|
||||||
// suggest using eas build:dev if the build configuration results in simulator/emulator dev client build
|
|
||||||
if (
|
|
||||||
!nonInteractive &&
|
|
||||||
!process.env.CI &&
|
|
||||||
buildProfiles?.some(({ profile, platform }) => {
|
|
||||||
if (profile.developmentClient !== true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (profile.distribution !== 'internal') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (platform === Platform.IOS) {
|
|
||||||
const iosProfile = profile as BuildProfile<Platform.IOS>;
|
|
||||||
if (iosProfile.simulator !== true && iosProfile.withoutCredentials !== true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const androidProfile = profile as BuildProfile<Platform.ANDROID>;
|
|
||||||
if (
|
|
||||||
androidProfile.distribution !== 'internal' &&
|
|
||||||
androidProfile.withoutCredentials !== true
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
Log.newLine();
|
|
||||||
Log.log(
|
|
||||||
`🔎 TIP: You are using a build configuration that could benefit from using ${chalk.bold(
|
|
||||||
'eas build:dev'
|
|
||||||
)} command. Run it to install and run cached development build, or create a new one if a compatible build doesn't exist yet.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleDeprecatedEasJsonAsync(
|
export async function handleDeprecatedEasJsonAsync(
|
||||||
|
|||||||
@@ -99,12 +99,12 @@ export default class BuildInspect extends EasCommand {
|
|||||||
await this.copyToOutputDirAsync(tmpWorkingdir, outputDirectory);
|
await this.copyToOutputDirAsync(tmpWorkingdir, outputDirectory);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await runBuildAndSubmitAsync({
|
await runBuildAndSubmitAsync(
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
analytics,
|
analytics,
|
||||||
vcsClient,
|
vcsClient,
|
||||||
projectDir,
|
projectDir,
|
||||||
flags: {
|
{
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
freezeCredentials: false,
|
freezeCredentials: false,
|
||||||
wait: true,
|
wait: true,
|
||||||
@@ -124,8 +124,8 @@ export default class BuildInspect extends EasCommand {
|
|||||||
repack: false,
|
repack: false,
|
||||||
},
|
},
|
||||||
actor,
|
actor,
|
||||||
getDynamicPrivateProjectConfigAsync,
|
getDynamicPrivateProjectConfigAsync
|
||||||
});
|
);
|
||||||
if (!flags.verbose) {
|
if (!flags.verbose) {
|
||||||
Log.log(chalk.green('Build successful'));
|
Log.log(chalk.green('Build successful'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import { runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
|||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand from '../../commandUtils/EasCommand';
|
||||||
import { RequestedPlatform } from '../../platform';
|
import { RequestedPlatform } from '../../platform';
|
||||||
import { enableJsonOutput } from '../../utils/json';
|
import { enableJsonOutput } from '../../utils/json';
|
||||||
import GitClient from '../../vcs/clients/git';
|
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
|
||||||
|
import NoVcsClient from '../../vcs/clients/noVcs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This command will be run on the EAS Build workers, when building
|
* This command will be run on the EAS Build workers, when building
|
||||||
@@ -63,24 +64,18 @@ export default class BuildInternal extends EasCommand {
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
} = await this.getContextAsync(BuildInternal, {
|
} = await this.getContextAsync(BuildInternal, {
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
|
vcsClientOverride: process.env.EAS_NO_VCS ? new NoVcsClient() : new GitNoCommitClient(),
|
||||||
withServerSideEnvironment: null,
|
withServerSideEnvironment: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (vcsClient instanceof GitClient) {
|
|
||||||
// `build:internal` is run on EAS workers and the repo may have been changed
|
|
||||||
// by pre-install hooks or other scripts. We don't want to require committing changes
|
|
||||||
// to continue the build.
|
|
||||||
vcsClient.requireCommit = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||||
|
|
||||||
await runBuildAndSubmitAsync({
|
await runBuildAndSubmitAsync(
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
analytics,
|
analytics,
|
||||||
vcsClient,
|
vcsClient,
|
||||||
projectDir,
|
projectDir,
|
||||||
flags: {
|
{
|
||||||
requestedPlatform: flags.platform as RequestedPlatform,
|
requestedPlatform: flags.platform as RequestedPlatform,
|
||||||
profile: flags.profile,
|
profile: flags.profile,
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
@@ -96,7 +91,7 @@ export default class BuildInternal extends EasCommand {
|
|||||||
repack: false,
|
repack: false,
|
||||||
},
|
},
|
||||||
actor,
|
actor,
|
||||||
getDynamicPrivateProjectConfigAsync,
|
getDynamicPrivateProjectConfigAsync
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Errors, Flags } from '@oclif/core';
|
import { Errors, Flags } from '@oclif/core';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import { pathExists } from 'fs-extra';
|
import { pathExists } from 'fs-extra';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
import { getLatestBuildAsync, listAndSelectBuildOnAppAsync } from '../../build/queries';
|
import { getLatestBuildAsync, listAndSelectBuildOnAppAsync } from '../../build/queries';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand from '../../commandUtils/EasCommand';
|
||||||
@@ -16,12 +17,13 @@ import Log from '../../log';
|
|||||||
import { appPlatformDisplayNames } from '../../platform';
|
import { appPlatformDisplayNames } from '../../platform';
|
||||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import { promptAsync } from '../../prompts';
|
import { promptAsync } from '../../prompts';
|
||||||
import { RunArchiveFlags, getEasBuildRunCachedAppPath, runAsync } from '../../run/run';
|
import { RunArchiveFlags, runAsync } from '../../run/run';
|
||||||
import { isRunnableOnSimulatorOrEmulator } from '../../run/utils';
|
import { isRunnableOnSimulatorOrEmulator } from '../../run/utils';
|
||||||
import {
|
import {
|
||||||
downloadAndMaybeExtractAppAsync,
|
downloadAndMaybeExtractAppAsync,
|
||||||
extractAppFromLocalArchiveAsync,
|
extractAppFromLocalArchiveAsync,
|
||||||
} from '../../utils/download';
|
} from '../../utils/download';
|
||||||
|
import { getEasBuildRunCacheDirectoryPath } from '../../utils/paths';
|
||||||
|
|
||||||
interface RawRunFlags {
|
interface RawRunFlags {
|
||||||
latest?: boolean;
|
latest?: boolean;
|
||||||
@@ -243,6 +245,17 @@ async function maybeGetBuildAsync(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getEasBuildRunCachedAppPath(
|
||||||
|
projectId: string,
|
||||||
|
buildId: string,
|
||||||
|
platform: AppPlatform
|
||||||
|
): string {
|
||||||
|
return path.join(
|
||||||
|
getEasBuildRunCacheDirectoryPath(),
|
||||||
|
`${projectId}_${buildId}.${platform === AppPlatform.Ios ? 'app' : 'apk'}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function getPathToSimulatorBuildAppAsync(
|
async function getPathToSimulatorBuildAppAsync(
|
||||||
graphqlClient: ExpoGraphqlClient,
|
graphqlClient: ExpoGraphqlClient,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default class ChannelDelete extends EasCommand {
|
|||||||
|
|
||||||
let channelId, channelName;
|
let channelId, channelName;
|
||||||
if (nameArg) {
|
if (nameArg) {
|
||||||
const { id, name } = await ChannelQuery.viewUpdateChannelBasicInfoAsync(graphqlClient, {
|
const { id, name } = await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||||
appId: projectId,
|
appId: projectId,
|
||||||
channelName: nameArg,
|
channelName: nameArg,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export default class ChannelEdit extends EasCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const existingChannel = args.name
|
const existingChannel = args.name
|
||||||
? await ChannelQuery.viewUpdateChannelBasicInfoAsync(graphqlClient, {
|
? await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||||
appId: projectId,
|
appId: projectId,
|
||||||
channelName: args.name,
|
channelName: args.name,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export default class ChannelPause extends EasCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const existingChannel = args.name
|
const existingChannel = args.name
|
||||||
? await ChannelQuery.viewUpdateChannelBasicInfoAsync(graphqlClient, {
|
? await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||||
appId: projectId,
|
appId: projectId,
|
||||||
channelName: args.name,
|
channelName: args.name,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export default class ChannelResume extends EasCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const existingChannel = args.name
|
const existingChannel = args.name
|
||||||
? await ChannelQuery.viewUpdateChannelBasicInfoAsync(graphqlClient, {
|
? await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||||
appId: projectId,
|
appId: projectId,
|
||||||
channelName: args.name,
|
channelName: args.name,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import { format as formatTimeAgo } from '@expo/timeago.js';
|
import { format as formatTimeAgo } from '@expo/timeago.js';
|
||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
import { CombinedError } from '@urql/core';
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
|
|
||||||
import { getHostingDeploymentsUrl } from '../../build/utils/url';
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand from '../../commandUtils/EasCommand';
|
||||||
import { EASEnvironmentFlag, EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EASEnvironmentFlag, EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { EnvironmentVariableEnvironment } from '../../graphql/generated';
|
import { EnvironmentVariableEnvironment } from '../../graphql/generated';
|
||||||
import Log, { link } from '../../log';
|
import Log from '../../log';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
import { createProgressTracker } from '../../utils/progress';
|
import { createProgressTracker } from '../../utils/progress';
|
||||||
import * as WorkerAssets from '../../worker/assets';
|
import * as WorkerAssets from '../../worker/assets';
|
||||||
@@ -68,7 +65,7 @@ interface DeployInProgressParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class WorkerDeploy extends EasCommand {
|
export default class WorkerDeploy extends EasCommand {
|
||||||
static override description = 'deploy your Expo Router web build and API Routes';
|
static override description = 'Deploy your Expo Router web build and API Routes.';
|
||||||
static override aliases = ['worker:deploy'];
|
static override aliases = ['worker:deploy'];
|
||||||
static override usage = [chalk`deploy {dim [options]}`, `deploy --prod`];
|
static override usage = [chalk`deploy {dim [options]}`, `deploy --prod`];
|
||||||
static override state = 'preview';
|
static override state = 'preview';
|
||||||
@@ -123,10 +120,7 @@ export default class WorkerDeploy extends EasCommand {
|
|||||||
} = await this.getContextAsync(WorkerDeploy, { ...flags, withServerSideEnvironment: null });
|
} = await this.getContextAsync(WorkerDeploy, { ...flags, withServerSideEnvironment: null });
|
||||||
|
|
||||||
const projectDist = await resolveExportedProjectAsync(flags, projectDir);
|
const projectDist = await resolveExportedProjectAsync(flags, projectDir);
|
||||||
const { projectId, exp } = await getDynamicPrivateProjectConfigAsync();
|
const { projectId } = await getDynamicPrivateProjectConfigAsync();
|
||||||
|
|
||||||
const projectName = exp.slug;
|
|
||||||
const accountName = (await getOwnerAccountForProjectIdAsync(graphqlClient, projectId)).name;
|
|
||||||
|
|
||||||
logExportedProjectInfo(projectDist);
|
logExportedProjectInfo(projectDist);
|
||||||
|
|
||||||
@@ -305,32 +299,6 @@ export default class WorkerDeploy extends EasCommand {
|
|||||||
progress.succeed('Created deployment');
|
progress.succeed('Created deployment');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
progress.fail('Failed to create deployment');
|
progress.fail('Failed to create deployment');
|
||||||
|
|
||||||
if (
|
|
||||||
flags.isProduction &&
|
|
||||||
error instanceof CombinedError &&
|
|
||||||
error.graphQLErrors.some(err => {
|
|
||||||
return (
|
|
||||||
err.extensions?.errorCode === 'UNAUTHORIZED_ERROR' &&
|
|
||||||
err.message.includes('AppDevDomainNameEntity') &&
|
|
||||||
err.message.includes('CREATE')
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
`You have specified the new deployment should be a production deployment, but a production domain has not been set up yet for this app.\n\nRun ${chalk.bold(
|
|
||||||
'eas deploy --prod'
|
|
||||||
)} as an app admin on your machine or promote an existing deployment to production on the ${link(
|
|
||||||
getHostingDeploymentsUrl(accountName, projectName),
|
|
||||||
{
|
|
||||||
dim: false,
|
|
||||||
text: 'Hosting Dashboard',
|
|
||||||
fallback: `Hosting Dashboard (${getHostingDeploymentsUrl(accountName, projectName)})`,
|
|
||||||
}
|
|
||||||
)} to set up a production domain and then try again.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,106 +1,35 @@
|
|||||||
|
import { Platform } from '@expo/eas-build-job';
|
||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
import openBrowserAsync from 'better-opn';
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
import { getExpoWebsiteBaseUrl } from '../../api';
|
|
||||||
import { selectBranchOnAppAsync } from '../../branch/queries';
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand from '../../commandUtils/EasCommand';
|
||||||
import { fetchBuildsAsync, formatBuild } from '../../commandUtils/builds';
|
import { fetchBuildsAsync, formatBuild } from '../../commandUtils/builds';
|
||||||
import { GetServerSideEnvironmentVariablesFn } from '../../commandUtils/context/ServerSideEnvironmentVariablesContextField';
|
|
||||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
import {
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
EasEnvironmentFlagParameters,
|
import { AppPlatform, BuildStatus } from '../../graphql/generated';
|
||||||
EasNonInteractiveAndJsonFlags,
|
import { FingerprintMutation } from '../../graphql/mutations/FingerprintMutation';
|
||||||
} from '../../commandUtils/flags';
|
|
||||||
import { diffFingerprint } from '../../fingerprint/cli';
|
|
||||||
import { abridgedDiff } from '../../fingerprint/diff';
|
|
||||||
import { Fingerprint, FingerprintDiffItem } from '../../fingerprint/types';
|
|
||||||
import {
|
|
||||||
getFingerprintInfoFromLocalProjectForPlatformsAsync,
|
|
||||||
stringToAppPlatform,
|
|
||||||
} from '../../fingerprint/utils';
|
|
||||||
import {
|
|
||||||
AppPlatform,
|
|
||||||
BuildFragment,
|
|
||||||
BuildStatus,
|
|
||||||
EnvironmentVariableEnvironment,
|
|
||||||
FingerprintFragment,
|
|
||||||
UpdateFragment,
|
|
||||||
} from '../../graphql/generated';
|
|
||||||
import { AppQuery } from '../../graphql/queries/AppQuery';
|
|
||||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||||
import { FingerprintQuery } from '../../graphql/queries/FingerprintQuery';
|
import Log from '../../log';
|
||||||
import { UpdateQuery } from '../../graphql/queries/UpdateQuery';
|
|
||||||
import Log, { learnMore } from '../../log';
|
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { RequestedPlatform } from '../../platform';
|
import { RequestedPlatform } from '../../platform';
|
||||||
|
import { maybeUploadFingerprintAsync } from '../../project/maybeUploadFingerprintAsync';
|
||||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import { promptAsync, selectAsync } from '../../prompts';
|
import { resolveWorkflowPerPlatformAsync } from '../../project/workflow';
|
||||||
import { selectUpdateGroupOnBranchAsync } from '../../update/queries';
|
import { selectAsync } from '../../prompts';
|
||||||
|
import { Fingerprint, FingerprintDiffItem } from '../../utils/fingerprint';
|
||||||
|
import { createFingerprintAsync, diffFingerprint } from '../../utils/fingerprintCli';
|
||||||
|
import { abridgedDiff } from '../../utils/fingerprintDiff';
|
||||||
import formatFields, { FormatFieldsItem } from '../../utils/formatFields';
|
import formatFields, { FormatFieldsItem } from '../../utils/formatFields';
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput } from '../../utils/json';
|
||||||
import { Client } from '../../vcs/vcs';
|
|
||||||
|
|
||||||
enum FingerprintOriginType {
|
|
||||||
Build = 'build',
|
|
||||||
Update = 'update',
|
|
||||||
Hash = 'hash',
|
|
||||||
Project = 'project',
|
|
||||||
}
|
|
||||||
|
|
||||||
type FingerprintOrigin = {
|
|
||||||
type: FingerprintOriginType;
|
|
||||||
build?: BuildFragment;
|
|
||||||
update?: UpdateFragment;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class FingerprintCompare extends EasCommand {
|
export default class FingerprintCompare extends EasCommand {
|
||||||
static override description = 'compare fingerprints of the current project, builds, and updates';
|
static override description = 'compare fingerprints of the current project, builds and updates';
|
||||||
static override strict = false;
|
static override hidden = true;
|
||||||
|
|
||||||
static override examples = [
|
|
||||||
'$ eas fingerprint:compare \t # Compare fingerprints in interactive mode',
|
|
||||||
'$ eas fingerprint:compare <FINGERPRINT-HASH> \t # Compare fingerprint against local directory',
|
|
||||||
'$ eas fingerprint:compare <FINGERPRINT-HASH-1> <FINGERPRINT-HASH-2> \t # Compare provided fingerprints',
|
|
||||||
'$ eas fingerprint:compare --build-id <BUILD-ID> \t # Compare fingerprint from build against local directory',
|
|
||||||
'$ eas fingerprint:compare --build-id <BUILD-ID> --environment production \t # Compare fingerprint from build against local directory with the "production" environment',
|
|
||||||
'$ eas fingerprint:compare --build-id <BUILD-ID-1> --build-id <BUILD-ID-2>\t # Compare fingerprint from a build against another build',
|
|
||||||
'$ eas fingerprint:compare --build-id <BUILD-ID> --update-id <UPDATE-ID>\t # Compare fingerprint from build against fingerprint from update',
|
|
||||||
'$ eas fingerprint:compare <FINGERPRINT-HASH> --update-id <UPDATE-ID> \t # Compare fingerprint from update against provided fingerprint',
|
|
||||||
];
|
|
||||||
|
|
||||||
static override args = [
|
|
||||||
{
|
|
||||||
name: 'hash1',
|
|
||||||
description:
|
|
||||||
"If provided alone, HASH1 is compared against the current project's fingerprint.",
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'hash2',
|
|
||||||
description: 'If two hashes are provided, HASH1 is compared against HASH2.',
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
static override flags = {
|
static override flags = {
|
||||||
'build-id': Flags.string({
|
'build-id': Flags.string({
|
||||||
aliases: ['buildId'],
|
aliases: ['buildId'],
|
||||||
description: 'Compare the fingerprint with the build with the specified ID',
|
description: 'Compare the fingerprint with the build with the specified ID',
|
||||||
multiple: true,
|
|
||||||
}),
|
|
||||||
'update-id': Flags.string({
|
|
||||||
aliases: ['updateId'],
|
|
||||||
description: 'Compare the fingerprint with the update with the specified ID',
|
|
||||||
multiple: true,
|
|
||||||
}),
|
|
||||||
open: Flags.boolean({
|
|
||||||
description: 'Open the fingerprint comparison in the browser',
|
|
||||||
}),
|
|
||||||
environment: Flags.enum<EnvironmentVariableEnvironment>({
|
|
||||||
...EasEnvironmentFlagParameters,
|
|
||||||
description:
|
|
||||||
'If generating a fingerprint from the local directory, use the specified environment.',
|
|
||||||
}),
|
}),
|
||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
@@ -110,95 +39,85 @@ export default class FingerprintCompare extends EasCommand {
|
|||||||
...this.ContextOptions.ProjectConfig,
|
...this.ContextOptions.ProjectConfig,
|
||||||
...this.ContextOptions.LoggedIn,
|
...this.ContextOptions.LoggedIn,
|
||||||
...this.ContextOptions.Vcs,
|
...this.ContextOptions.Vcs,
|
||||||
...this.ContextOptions.ServerSideEnvironmentVariables,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { args, flags } = await this.parse(FingerprintCompare);
|
const { flags } = await this.parse(FingerprintCompare);
|
||||||
const { hash1, hash2 } = args;
|
const { json: jsonFlag, 'non-interactive': nonInteractive, buildId: buildIdFromArg } = flags;
|
||||||
const {
|
|
||||||
json,
|
|
||||||
'non-interactive': nonInteractive,
|
|
||||||
'build-id': buildIds,
|
|
||||||
'update-id': updateIds,
|
|
||||||
open,
|
|
||||||
environment,
|
|
||||||
} = flags;
|
|
||||||
const [buildId1, buildId2] = buildIds ?? [];
|
|
||||||
const [updateId1, updateId2] = updateIds ?? [];
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
projectId,
|
projectId,
|
||||||
privateProjectConfig: { projectDir },
|
privateProjectConfig: { projectDir },
|
||||||
loggedIn: { graphqlClient },
|
loggedIn: { graphqlClient },
|
||||||
vcsClient,
|
vcsClient,
|
||||||
getServerSideEnvironmentVariablesAsync,
|
|
||||||
} = await this.getContextAsync(FingerprintCompare, {
|
} = await this.getContextAsync(FingerprintCompare, {
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
withServerSideEnvironment: environment ?? null,
|
withServerSideEnvironment: null,
|
||||||
});
|
});
|
||||||
if (json) {
|
if (jsonFlag) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstFingerprintInfo = await getFingerprintInfoAsync(
|
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||||
graphqlClient,
|
let buildId: string | null = buildIdFromArg;
|
||||||
projectDir,
|
if (!buildId) {
|
||||||
projectId,
|
if (nonInteractive) {
|
||||||
vcsClient,
|
throw new Error('Build ID must be provided in non-interactive mode');
|
||||||
getServerSideEnvironmentVariablesAsync,
|
|
||||||
{
|
|
||||||
nonInteractive,
|
|
||||||
buildId: buildId1,
|
|
||||||
updateId: updateId1,
|
|
||||||
hash: hash1,
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
const { fingerprint: firstFingerprint, origin: firstFingerprintOrigin } = firstFingerprintInfo;
|
|
||||||
|
|
||||||
const isFirstFingerprintSpecifiedByFlagOrArg = hash1 || buildId1 || updateId1;
|
buildId = await selectBuildToCompareAsync(graphqlClient, projectId, displayName, {
|
||||||
const isSecondFingerprintSpecifiedByFlagOrArg = hash2 || buildId2 || updateId2;
|
filters: { hasFingerprint: true },
|
||||||
const secondFingerprintInfo = await getFingerprintInfoAsync(
|
});
|
||||||
graphqlClient,
|
if (!buildId) {
|
||||||
projectDir,
|
return;
|
||||||
projectId,
|
}
|
||||||
vcsClient,
|
}
|
||||||
getServerSideEnvironmentVariablesAsync,
|
|
||||||
{
|
|
||||||
nonInteractive,
|
|
||||||
buildId: buildId2,
|
|
||||||
updateId: updateId2,
|
|
||||||
hash: hash2,
|
|
||||||
useProjectFingerprint:
|
|
||||||
isFirstFingerprintSpecifiedByFlagOrArg && !isSecondFingerprintSpecifiedByFlagOrArg,
|
|
||||||
environmentForProjectFingerprint: environment,
|
|
||||||
},
|
|
||||||
firstFingerprintInfo
|
|
||||||
);
|
|
||||||
const { fingerprint: secondFingerprint, origin: secondFingerprintOrigin } =
|
|
||||||
secondFingerprintInfo;
|
|
||||||
|
|
||||||
if (json) {
|
Log.log(`Comparing fingerprints of the current project and build ${buildId}…`);
|
||||||
printJsonOnlyOutput({ fingerprint1: firstFingerprint, fingerprint2: secondFingerprint });
|
const buildWithFingerprint = await BuildQuery.withFingerprintByIdAsync(graphqlClient, buildId);
|
||||||
|
const fingerprintDebugUrl = buildWithFingerprint.fingerprint?.debugInfoUrl;
|
||||||
|
if (!fingerprintDebugUrl) {
|
||||||
|
Log.error('A fingerprint for the build could not be found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fingerprintResponse = await fetch(fingerprintDebugUrl);
|
||||||
|
const fingerprint = (await fingerprintResponse.json()) as Fingerprint;
|
||||||
|
const workflows = await resolveWorkflowPerPlatformAsync(projectDir, vcsClient);
|
||||||
|
const buildPlatform = buildWithFingerprint.platform;
|
||||||
|
const workflow = workflows[appPlatformToPlatform(buildPlatform)];
|
||||||
|
|
||||||
|
const projectFingerprint = await createFingerprintAsync(projectDir, {
|
||||||
|
workflow,
|
||||||
|
platforms: [appPlatformToString(buildPlatform)],
|
||||||
|
debug: true,
|
||||||
|
env: undefined,
|
||||||
|
});
|
||||||
|
if (!projectFingerprint) {
|
||||||
|
Log.error('Project fingerprints can only be computed for projects with SDK 52 or higher');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstFingerprint.hash === secondFingerprint.hash) {
|
const uploadedFingerprint = await maybeUploadFingerprintAsync({
|
||||||
Log.log(
|
hash: fingerprint.hash,
|
||||||
`✅ ${capitalizeFirstLetter(
|
fingerprint: {
|
||||||
prettyPrintFingerprint(firstFingerprint, firstFingerprintOrigin)
|
fingerprintSources: fingerprint.sources,
|
||||||
)} matches ${prettyPrintFingerprint(secondFingerprint, secondFingerprintOrigin)}`
|
isDebugFingerprintSource: Log.isDebug,
|
||||||
);
|
},
|
||||||
|
graphqlClient,
|
||||||
|
});
|
||||||
|
await FingerprintMutation.createFingerprintAsync(graphqlClient, projectId, {
|
||||||
|
hash: uploadedFingerprint.hash,
|
||||||
|
source: uploadedFingerprint.fingerprintSource,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fingerprint.hash === projectFingerprint.hash) {
|
||||||
|
Log.log(`✅ Project fingerprint matches build`);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
Log.log(
|
Log.log(`🔄 Project fingerprint differs from build`);
|
||||||
`🔄 ${capitalizeFirstLetter(
|
|
||||||
prettyPrintFingerprint(firstFingerprint, firstFingerprintOrigin)
|
|
||||||
)} differs from ${prettyPrintFingerprint(secondFingerprint, secondFingerprintOrigin)}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fingerprintDiffs = diffFingerprint(projectDir, firstFingerprint, secondFingerprint);
|
const fingerprintDiffs = diffFingerprint(projectDir, fingerprint, projectFingerprint);
|
||||||
if (!fingerprintDiffs) {
|
if (!fingerprintDiffs) {
|
||||||
Log.error('Fingerprint diffs can only be computed for projects with SDK 52 or higher');
|
Log.error('Fingerprint diffs can only be computed for projects with SDK 52 or higher');
|
||||||
return;
|
return;
|
||||||
@@ -248,388 +167,7 @@ export default class FingerprintCompare extends EasCommand {
|
|||||||
for (const diff of contentDiffs) {
|
for (const diff of contentDiffs) {
|
||||||
printContentDiff(diff);
|
printContentDiff(diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nonInteractive) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const project = await AppQuery.byIdAsync(graphqlClient, projectId);
|
|
||||||
const fingerprintCompareUrl = new URL(
|
|
||||||
`/accounts/${project.ownerAccount.name}/projects/${project.slug}/fingerprints/compare/${firstFingerprintInfo.fingerprint.hash}/${secondFingerprintInfo.fingerprint.hash}`,
|
|
||||||
getExpoWebsiteBaseUrl()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!open) {
|
|
||||||
Log.newLine();
|
|
||||||
Log.withInfo(
|
|
||||||
`💡 Use the --open flag to view the comparison in the browser. ${learnMore(
|
|
||||||
fingerprintCompareUrl.toString()
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await openBrowserAsync(fingerprintCompareUrl.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getFingerprintInfoAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
projectDir: string,
|
|
||||||
projectId: string,
|
|
||||||
vcsClient: Client,
|
|
||||||
getServerSideEnvironmentVariablesAsync: GetServerSideEnvironmentVariablesFn,
|
|
||||||
{
|
|
||||||
buildId,
|
|
||||||
updateId,
|
|
||||||
hash,
|
|
||||||
useProjectFingerprint,
|
|
||||||
environmentForProjectFingerprint,
|
|
||||||
nonInteractive,
|
|
||||||
}: {
|
|
||||||
buildId?: string;
|
|
||||||
updateId?: string;
|
|
||||||
hash: string;
|
|
||||||
useProjectFingerprint?: boolean;
|
|
||||||
environmentForProjectFingerprint?: string;
|
|
||||||
nonInteractive: boolean;
|
|
||||||
},
|
|
||||||
firstFingerprintInfo?: {
|
|
||||||
fingerprint: Fingerprint;
|
|
||||||
platforms?: AppPlatform[];
|
|
||||||
origin: FingerprintOrigin;
|
|
||||||
}
|
|
||||||
): Promise<{ fingerprint: Fingerprint; origin: FingerprintOrigin }> {
|
|
||||||
if (hash) {
|
|
||||||
return await getFingerprintInfoFromHashAsync(graphqlClient, projectId, hash);
|
|
||||||
} else if (updateId) {
|
|
||||||
return await getFingerprintInfoFromUpdateGroupIdOrUpdateIdAsync(
|
|
||||||
graphqlClient,
|
|
||||||
projectId,
|
|
||||||
nonInteractive,
|
|
||||||
updateId
|
|
||||||
);
|
|
||||||
} else if (buildId) {
|
|
||||||
return await getFingerprintInfoFromBuildIdAsync(graphqlClient, buildId);
|
|
||||||
} else if (useProjectFingerprint) {
|
|
||||||
if (!firstFingerprintInfo) {
|
|
||||||
throw new Error(
|
|
||||||
'First fingerprint must be provided in order to compare against the project.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return await getFingerprintInfoFromLocalProjectAsync({
|
|
||||||
graphqlClient,
|
|
||||||
projectDir,
|
|
||||||
projectId,
|
|
||||||
vcsClient,
|
|
||||||
getServerSideEnvironmentVariablesAsync,
|
|
||||||
firstFingerprintInfo,
|
|
||||||
environment: environmentForProjectFingerprint,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nonInteractive) {
|
|
||||||
throw new Error(
|
|
||||||
'Insufficent arguments provided for fingerprint comparison in non-interactive mode'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return await getFingerprintInfoInteractiveAsync({
|
|
||||||
graphqlClient,
|
|
||||||
projectDir,
|
|
||||||
projectId,
|
|
||||||
vcsClient,
|
|
||||||
getServerSideEnvironmentVariablesAsync,
|
|
||||||
firstFingerprintInfo,
|
|
||||||
environmentForProjectFingerprint,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getFingerprintInfoInteractiveAsync({
|
|
||||||
graphqlClient,
|
|
||||||
projectDir,
|
|
||||||
projectId,
|
|
||||||
vcsClient,
|
|
||||||
getServerSideEnvironmentVariablesAsync,
|
|
||||||
firstFingerprintInfo,
|
|
||||||
environmentForProjectFingerprint,
|
|
||||||
}: {
|
|
||||||
graphqlClient: ExpoGraphqlClient;
|
|
||||||
projectDir: string;
|
|
||||||
projectId: string;
|
|
||||||
vcsClient: Client;
|
|
||||||
getServerSideEnvironmentVariablesAsync: GetServerSideEnvironmentVariablesFn;
|
|
||||||
firstFingerprintInfo?: {
|
|
||||||
fingerprint: Fingerprint;
|
|
||||||
platforms?: AppPlatform[];
|
|
||||||
origin: FingerprintOrigin;
|
|
||||||
};
|
|
||||||
environmentForProjectFingerprint?: string;
|
|
||||||
}): Promise<{ fingerprint: Fingerprint; platforms?: AppPlatform[]; origin: FingerprintOrigin }> {
|
|
||||||
const prompt = firstFingerprintInfo
|
|
||||||
? 'Select the second fingerprint to compare against'
|
|
||||||
: 'Select a reference fingerprint for comparison';
|
|
||||||
const originType = await selectAsync<FingerprintOriginType>(prompt, [
|
|
||||||
...(firstFingerprintInfo
|
|
||||||
? [{ title: 'Current project fingerprint', value: FingerprintOriginType.Project }]
|
|
||||||
: []),
|
|
||||||
{ title: 'Build fingerprint', value: FingerprintOriginType.Build },
|
|
||||||
{ title: 'Update fingerprint', value: FingerprintOriginType.Update },
|
|
||||||
{ title: 'Enter a fingerprint hash manually', value: FingerprintOriginType.Hash },
|
|
||||||
]);
|
|
||||||
if (originType === FingerprintOriginType.Project) {
|
|
||||||
if (!firstFingerprintInfo) {
|
|
||||||
throw new Error(
|
|
||||||
'First fingerprint must be provided in order to compare against the project.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return await getFingerprintInfoFromLocalProjectAsync({
|
|
||||||
graphqlClient,
|
|
||||||
projectDir,
|
|
||||||
projectId,
|
|
||||||
vcsClient,
|
|
||||||
getServerSideEnvironmentVariablesAsync,
|
|
||||||
firstFingerprintInfo,
|
|
||||||
environment: environmentForProjectFingerprint,
|
|
||||||
});
|
|
||||||
} else if (originType === FingerprintOriginType.Build) {
|
|
||||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
|
||||||
const buildId = await selectBuildToCompareAsync(graphqlClient, projectId, displayName, {
|
|
||||||
filters: { hasFingerprint: true },
|
|
||||||
});
|
|
||||||
if (!buildId) {
|
|
||||||
throw new Error('Must select build with fingerprint for comparison.');
|
|
||||||
}
|
|
||||||
return await getFingerprintInfoFromBuildIdAsync(graphqlClient, buildId);
|
|
||||||
} else if (originType === FingerprintOriginType.Update) {
|
|
||||||
const selectedBranch = await selectBranchOnAppAsync(graphqlClient, {
|
|
||||||
projectId,
|
|
||||||
promptTitle: 'On which branch would you like search for an update?',
|
|
||||||
displayTextForListItem: updateBranch => ({
|
|
||||||
title: updateBranch.name,
|
|
||||||
}),
|
|
||||||
paginatedQueryOptions: {
|
|
||||||
json: false,
|
|
||||||
nonInteractive: false,
|
|
||||||
offset: 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedUpdateGroup = await selectUpdateGroupOnBranchAsync(graphqlClient, {
|
|
||||||
projectId,
|
|
||||||
branchName: selectedBranch.name,
|
|
||||||
paginatedQueryOptions: {
|
|
||||||
json: false,
|
|
||||||
nonInteractive: false,
|
|
||||||
offset: 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const updateGroupId = selectedUpdateGroup[0].group;
|
|
||||||
return await getFingerprintInfoFromUpdateGroupIdOrUpdateIdAsync(
|
|
||||||
graphqlClient,
|
|
||||||
projectId,
|
|
||||||
false,
|
|
||||||
updateGroupId
|
|
||||||
);
|
|
||||||
} else if (originType === FingerprintOriginType.Hash) {
|
|
||||||
const { hash } = await promptAsync({
|
|
||||||
type: 'text',
|
|
||||||
name: 'hash',
|
|
||||||
message: 'Provide the fingerprint hash',
|
|
||||||
validate: (value: string) => !!value.trim(),
|
|
||||||
hint: '0000000000000000000000000000000000000000',
|
|
||||||
});
|
|
||||||
return await getFingerprintInfoFromHashAsync(graphqlClient, projectId, hash);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unsupported fingerprint origin type: ${originType}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getFingerprintInfoFromLocalProjectAsync({
|
|
||||||
graphqlClient,
|
|
||||||
projectDir,
|
|
||||||
projectId,
|
|
||||||
vcsClient,
|
|
||||||
getServerSideEnvironmentVariablesAsync,
|
|
||||||
firstFingerprintInfo,
|
|
||||||
environment,
|
|
||||||
}: {
|
|
||||||
graphqlClient: ExpoGraphqlClient;
|
|
||||||
projectDir: string;
|
|
||||||
projectId: string;
|
|
||||||
vcsClient: Client;
|
|
||||||
getServerSideEnvironmentVariablesAsync: GetServerSideEnvironmentVariablesFn;
|
|
||||||
firstFingerprintInfo: {
|
|
||||||
fingerprint: Fingerprint;
|
|
||||||
platforms?: AppPlatform[];
|
|
||||||
origin: FingerprintOrigin;
|
|
||||||
};
|
|
||||||
environment?: string;
|
|
||||||
}): Promise<{ fingerprint: Fingerprint; platforms?: AppPlatform[]; origin: FingerprintOrigin }> {
|
|
||||||
const firstFingerprintPlatforms = firstFingerprintInfo.platforms;
|
|
||||||
if (!firstFingerprintPlatforms || firstFingerprintPlatforms.length === 0) {
|
|
||||||
throw new Error(
|
|
||||||
`Cannot compare the local directory against the provided fingerprint hash "${firstFingerprintInfo.fingerprint.hash}" because the associated platform could not be determined. Ensure the fingerprint is linked to a build or update to identify the platform.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (environment) {
|
|
||||||
Log.log(`🔧 Using environment: ${environment}`);
|
|
||||||
}
|
|
||||||
const env = environment
|
|
||||||
? { ...(await getServerSideEnvironmentVariablesAsync()), EXPO_NO_DOTENV: '1' }
|
|
||||||
: undefined;
|
|
||||||
const fingerprint = await getFingerprintInfoFromLocalProjectForPlatformsAsync(
|
|
||||||
graphqlClient,
|
|
||||||
projectDir,
|
|
||||||
projectId,
|
|
||||||
vcsClient,
|
|
||||||
firstFingerprintPlatforms,
|
|
||||||
{ env }
|
|
||||||
);
|
|
||||||
return { fingerprint, origin: { type: FingerprintOriginType.Project } };
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getFingerprintFromUpdateFragmentAsync(
|
|
||||||
updateWithFingerprint: UpdateFragment
|
|
||||||
): Promise<{ fingerprint: Fingerprint; platforms?: AppPlatform[]; origin: FingerprintOrigin }> {
|
|
||||||
if (!updateWithFingerprint.fingerprint) {
|
|
||||||
throw new Error(`Fingerprint for update ${updateWithFingerprint.id} was not computed.`);
|
|
||||||
} else if (!updateWithFingerprint.fingerprint.debugInfoUrl) {
|
|
||||||
throw new Error(`Fingerprint source for update ${updateWithFingerprint.id} was not computed.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
fingerprint: await getFingerprintFromFingerprintFragmentAsync(
|
|
||||||
updateWithFingerprint.fingerprint
|
|
||||||
),
|
|
||||||
platforms: [stringToAppPlatform(updateWithFingerprint.platform)],
|
|
||||||
origin: {
|
|
||||||
type: FingerprintOriginType.Update,
|
|
||||||
update: updateWithFingerprint,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getFingerprintInfoFromHashAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
projectId: string,
|
|
||||||
hash: string
|
|
||||||
): Promise<{ fingerprint: Fingerprint; platforms?: AppPlatform[]; origin: FingerprintOrigin }> {
|
|
||||||
const fingerprintFragment = await getFingerprintFragmentFromHashAsync(
|
|
||||||
graphqlClient,
|
|
||||||
projectId,
|
|
||||||
hash
|
|
||||||
);
|
|
||||||
const fingerprint = await getFingerprintFromFingerprintFragmentAsync(fingerprintFragment);
|
|
||||||
let platforms;
|
|
||||||
const fingerprintBuilds = fingerprintFragment.builds?.edges.map(edge => edge.node) ?? [];
|
|
||||||
const fingerprintUpdates = fingerprintFragment.updates?.edges.map(edge => edge.node) ?? [];
|
|
||||||
if (fingerprintBuilds.length > 0) {
|
|
||||||
platforms = [fingerprintBuilds[0].platform];
|
|
||||||
} else if (fingerprintUpdates.length > 0) {
|
|
||||||
platforms = [stringToAppPlatform(fingerprintUpdates[0].platform)];
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
fingerprint,
|
|
||||||
platforms,
|
|
||||||
origin: {
|
|
||||||
type: FingerprintOriginType.Hash,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getFingerprintInfoFromUpdateGroupIdOrUpdateIdAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
projectId: string,
|
|
||||||
nonInteractive: boolean,
|
|
||||||
updateGroupIdOrUpdateId: string
|
|
||||||
): Promise<{ fingerprint: Fingerprint; platforms?: AppPlatform[]; origin: FingerprintOrigin }> {
|
|
||||||
// Some people may pass in update group id instead of update id, so add interactive support for that
|
|
||||||
try {
|
|
||||||
const maybeUpdateGroupId = updateGroupIdOrUpdateId;
|
|
||||||
const updateGroup = await UpdateQuery.viewUpdateGroupAsync(graphqlClient, {
|
|
||||||
groupId: maybeUpdateGroupId,
|
|
||||||
});
|
|
||||||
if (updateGroup.length === 1) {
|
|
||||||
const update = updateGroup[0];
|
|
||||||
return await getFingerprintFromUpdateFragmentAsync(update);
|
|
||||||
}
|
|
||||||
if (nonInteractive) {
|
|
||||||
const project = await AppQuery.byIdAsync(graphqlClient, projectId);
|
|
||||||
const updateUrl =
|
|
||||||
getExpoWebsiteBaseUrl() +
|
|
||||||
`/accounts/${project.ownerAccount.name}/projects/${project.slug}/updates/${maybeUpdateGroupId}`;
|
|
||||||
throw new Error(
|
|
||||||
`Please pass in your update ID from ${updateUrl} or use interactive mode to select the update ID.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const update = await selectAsync<UpdateFragment>(
|
|
||||||
'Select a platform to compute the fingerprint from',
|
|
||||||
updateGroup.map(update => ({
|
|
||||||
title: update.platform,
|
|
||||||
value: update,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
return await getFingerprintFromUpdateFragmentAsync(update);
|
|
||||||
} catch (error: any) {
|
|
||||||
if (!error?.message.includes('Could not find any updates with group ID')) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateId = updateGroupIdOrUpdateId;
|
|
||||||
const updateWithFingerprint = await UpdateQuery.viewByUpdateAsync(graphqlClient, {
|
|
||||||
updateId,
|
|
||||||
});
|
|
||||||
return await getFingerprintFromUpdateFragmentAsync(updateWithFingerprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getFingerprintInfoFromBuildIdAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
buildId: string
|
|
||||||
): Promise<{ fingerprint: Fingerprint; platforms?: AppPlatform[]; origin: FingerprintOrigin }> {
|
|
||||||
const buildWithFingerprint = await BuildQuery.withFingerprintByIdAsync(graphqlClient, buildId);
|
|
||||||
if (!buildWithFingerprint.fingerprint) {
|
|
||||||
throw new Error(`Fingerprint for build ${buildId} was not computed.`);
|
|
||||||
} else if (!buildWithFingerprint.fingerprint.debugInfoUrl) {
|
|
||||||
throw new Error(`Fingerprint source for build ${buildId} was not computed.`);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
fingerprint: await getFingerprintFromFingerprintFragmentAsync(buildWithFingerprint.fingerprint),
|
|
||||||
platforms: [buildWithFingerprint.platform],
|
|
||||||
origin: {
|
|
||||||
type: FingerprintOriginType.Build,
|
|
||||||
build: buildWithFingerprint,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getFingerprintFragmentFromHashAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
projectId: string,
|
|
||||||
hash: string
|
|
||||||
): Promise<FingerprintFragment> {
|
|
||||||
const fingerprint = await FingerprintQuery.byHashAsync(graphqlClient, {
|
|
||||||
appId: projectId,
|
|
||||||
hash,
|
|
||||||
});
|
|
||||||
if (!fingerprint) {
|
|
||||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
|
||||||
throw new Error(`Fingerprint with hash ${hash} was not uploaded for ${displayName}.`);
|
|
||||||
}
|
|
||||||
return fingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getFingerprintFromFingerprintFragmentAsync(
|
|
||||||
fingerprintFragment: Pick<FingerprintFragment, 'debugInfoUrl' | 'hash'>
|
|
||||||
): Promise<Fingerprint> {
|
|
||||||
const fingerprintDebugUrl = fingerprintFragment.debugInfoUrl;
|
|
||||||
if (!fingerprintDebugUrl) {
|
|
||||||
throw new Error(
|
|
||||||
`The source for fingerprint hash ${fingerprintFragment.hash} was not computed.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const fingerprintResponse = await fetch(fingerprintDebugUrl);
|
|
||||||
return (await fingerprintResponse.json()) as Fingerprint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function printContentDiff(diff: FingerprintDiffItem): void {
|
function printContentDiff(diff: FingerprintDiffItem): void {
|
||||||
@@ -737,10 +275,7 @@ const PRETTY_CONTENT_ID: Record<string, string> = {
|
|||||||
'expoAutolinkingConfig:ios': 'Expo autolinking config (iOS)',
|
'expoAutolinkingConfig:ios': 'Expo autolinking config (iOS)',
|
||||||
'expoAutolinkingConfig:android': 'Expo autolinking config (Android)',
|
'expoAutolinkingConfig:android': 'Expo autolinking config (Android)',
|
||||||
'packageJson:scripts': 'package.json scripts',
|
'packageJson:scripts': 'package.json scripts',
|
||||||
expoConfig: 'Expo app config',
|
expoConfig: 'Expo config',
|
||||||
'package:react-native': 'React Native package.json',
|
|
||||||
'rncoreAutolinkingConfig:ios': 'React Native Community autolinking config (iOS)',
|
|
||||||
'rncoreAutolinkingConfig:android': 'React Native Community autolinking config (Android)',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function printContentSource({
|
function printContentSource({
|
||||||
@@ -784,23 +319,6 @@ function printContentsDiff(contents1: string | Buffer, contents2: string | Buffe
|
|||||||
abridgedDiff(prettifiedContents1, prettifiedContents2, 0);
|
abridgedDiff(prettifiedContents1, prettifiedContents2, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function prettyPrintFingerprint(fingerprint: Fingerprint, origin: FingerprintOrigin): string {
|
|
||||||
if (origin.type === FingerprintOriginType.Project) {
|
|
||||||
return `fingerprint ${fingerprint.hash} from local directory`;
|
|
||||||
} else if (origin.type === FingerprintOriginType.Update) {
|
|
||||||
return `fingerprint ${fingerprint.hash} from ${
|
|
||||||
origin.update?.platform ? stringToAppPlatform(origin.update?.platform) : ''
|
|
||||||
} ${origin.type}`;
|
|
||||||
} else if (origin.type === FingerprintOriginType.Build) {
|
|
||||||
return `fingerprint ${fingerprint.hash} from ${origin.build?.platform} ${origin.type}`;
|
|
||||||
}
|
|
||||||
return `fingerprint ${fingerprint.hash}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function capitalizeFirstLetter(string: string): string {
|
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isJSON(str: string): boolean {
|
function isJSON(str: string): boolean {
|
||||||
try {
|
try {
|
||||||
JSON.parse(str);
|
JSON.parse(str);
|
||||||
@@ -810,7 +328,29 @@ function isJSON(str: string): boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function selectBuildToCompareAsync(
|
function appPlatformToPlatform(platform: AppPlatform): Platform {
|
||||||
|
switch (platform) {
|
||||||
|
case AppPlatform.Android:
|
||||||
|
return Platform.ANDROID;
|
||||||
|
case AppPlatform.Ios:
|
||||||
|
return Platform.IOS;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported platform: ${platform}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appPlatformToString(platform: AppPlatform): string {
|
||||||
|
switch (platform) {
|
||||||
|
case AppPlatform.Android:
|
||||||
|
return 'android';
|
||||||
|
case AppPlatform.Ios:
|
||||||
|
return 'ios';
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported platform: ${platform}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function selectBuildToCompareAsync(
|
||||||
graphqlClient: ExpoGraphqlClient,
|
graphqlClient: ExpoGraphqlClient,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
projectDisplayName: string,
|
projectDisplayName: string,
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
import { Env } from '@expo/eas-build-job';
|
|
||||||
import { EasJsonAccessor } from '@expo/eas-json';
|
|
||||||
import { Flags } from '@oclif/core';
|
|
||||||
|
|
||||||
import { getExpoWebsiteBaseUrl } from '../../api';
|
|
||||||
import { evaluateConfigWithEnvVarsAsync } from '../../build/evaluateConfigWithEnvVarsAsync';
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
|
||||||
import {
|
|
||||||
EASEnvironmentFlag,
|
|
||||||
EasEnvironmentFlagParameters,
|
|
||||||
EasNonInteractiveAndJsonFlags,
|
|
||||||
} from '../../commandUtils/flags';
|
|
||||||
import {
|
|
||||||
appPlatformToPlatform,
|
|
||||||
getFingerprintInfoFromLocalProjectForPlatformsAsync,
|
|
||||||
stringToAppPlatform,
|
|
||||||
} from '../../fingerprint/utils';
|
|
||||||
import { AppPlatform, EnvironmentVariableEnvironment } from '../../graphql/generated';
|
|
||||||
import { AppQuery } from '../../graphql/queries/AppQuery';
|
|
||||||
import Log, { link } from '../../log';
|
|
||||||
import { promptAsync } from '../../prompts';
|
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
|
||||||
import { getProfilesAsync } from '../../utils/profiles';
|
|
||||||
|
|
||||||
export default class FingerprintGenerate extends EasCommand {
|
|
||||||
static override description = 'generate fingerprints from the current project';
|
|
||||||
static override strict = false;
|
|
||||||
|
|
||||||
static override examples = [
|
|
||||||
'$ eas fingerprint:generate \t # Generate fingerprint in interactive mode',
|
|
||||||
'$ eas fingerprint:generate --build-profile preview \t # Generate a fingerprint using the "preview" build profile',
|
|
||||||
'$ eas fingerprint:generate --environment preview \t # Generate a fingerprint using the "preview" environment',
|
|
||||||
'$ eas fingerprint:generate --json --non-interactive --platform android \t # Output fingerprint json to stdout',
|
|
||||||
];
|
|
||||||
|
|
||||||
static override flags = {
|
|
||||||
platform: Flags.enum({
|
|
||||||
char: 'p',
|
|
||||||
options: ['android', 'ios'],
|
|
||||||
}),
|
|
||||||
...EASEnvironmentFlag,
|
|
||||||
environment: Flags.enum<EnvironmentVariableEnvironment>({
|
|
||||||
...EasEnvironmentFlagParameters,
|
|
||||||
exclusive: ['build-profile'],
|
|
||||||
}),
|
|
||||||
'build-profile': Flags.string({
|
|
||||||
char: 'e',
|
|
||||||
description: 'Name of the build profile from eas.json.',
|
|
||||||
exclusive: ['environment'],
|
|
||||||
}),
|
|
||||||
...EasNonInteractiveAndJsonFlags,
|
|
||||||
};
|
|
||||||
|
|
||||||
static override contextDefinition = {
|
|
||||||
...this.ContextOptions.ProjectId,
|
|
||||||
...this.ContextOptions.ProjectConfig,
|
|
||||||
...this.ContextOptions.LoggedIn,
|
|
||||||
...this.ContextOptions.Vcs,
|
|
||||||
...this.ContextOptions.ServerSideEnvironmentVariables,
|
|
||||||
...this.ContextOptions.DynamicProjectConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
|
||||||
const { flags } = await this.parse(FingerprintGenerate);
|
|
||||||
const {
|
|
||||||
json,
|
|
||||||
'non-interactive': nonInteractive,
|
|
||||||
platform: platformStringFlag,
|
|
||||||
environment,
|
|
||||||
'build-profile': buildProfileName,
|
|
||||||
} = flags;
|
|
||||||
|
|
||||||
const {
|
|
||||||
projectId,
|
|
||||||
privateProjectConfig: { projectDir },
|
|
||||||
loggedIn: { graphqlClient },
|
|
||||||
vcsClient,
|
|
||||||
getServerSideEnvironmentVariablesAsync,
|
|
||||||
getDynamicPrivateProjectConfigAsync,
|
|
||||||
} = await this.getContextAsync(FingerprintGenerate, {
|
|
||||||
nonInteractive,
|
|
||||||
withServerSideEnvironment: environment ?? null,
|
|
||||||
});
|
|
||||||
if (json) {
|
|
||||||
enableJsonOutput();
|
|
||||||
}
|
|
||||||
|
|
||||||
let platform: AppPlatform;
|
|
||||||
if (platformStringFlag) {
|
|
||||||
platform = stringToAppPlatform(platformStringFlag);
|
|
||||||
} else {
|
|
||||||
if (nonInteractive) {
|
|
||||||
throw new Error('Platform must be specified in non-interactive mode with the --p flag');
|
|
||||||
}
|
|
||||||
platform = await selectRequestedPlatformAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
let env: Env | undefined = undefined;
|
|
||||||
if (environment) {
|
|
||||||
Log.log(`🔧 Using environment: ${environment}`);
|
|
||||||
env = { ...(await getServerSideEnvironmentVariablesAsync()), EXPO_NO_DOTENV: '1' };
|
|
||||||
} else if (buildProfileName) {
|
|
||||||
Log.log(`🔧 Using build profile: ${buildProfileName}`);
|
|
||||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
|
||||||
const buildProfile = (
|
|
||||||
await getProfilesAsync({
|
|
||||||
type: 'build',
|
|
||||||
easJsonAccessor,
|
|
||||||
platforms: [appPlatformToPlatform(platform)],
|
|
||||||
profileName: buildProfileName ?? undefined,
|
|
||||||
projectDir,
|
|
||||||
})
|
|
||||||
)[0];
|
|
||||||
if (!buildProfile) {
|
|
||||||
throw new Error(`Build profile ${buildProfile} not found for platform: ${platform}`);
|
|
||||||
}
|
|
||||||
const configResult = await evaluateConfigWithEnvVarsAsync({
|
|
||||||
buildProfile: buildProfile.profile,
|
|
||||||
buildProfileName: buildProfile.profileName,
|
|
||||||
graphqlClient,
|
|
||||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
|
||||||
opts: { env: buildProfile.profile.env },
|
|
||||||
});
|
|
||||||
env = configResult.env;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fingerprint = await getFingerprintInfoFromLocalProjectForPlatformsAsync(
|
|
||||||
graphqlClient,
|
|
||||||
projectDir,
|
|
||||||
projectId,
|
|
||||||
vcsClient,
|
|
||||||
[platform],
|
|
||||||
{ env }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (json) {
|
|
||||||
printJsonOnlyOutput(fingerprint);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.log(`✅ Fingerprint generated: ${fingerprint.hash}`);
|
|
||||||
|
|
||||||
const project = await AppQuery.byIdAsync(graphqlClient, projectId);
|
|
||||||
const fingerprintUrl = new URL(
|
|
||||||
`/accounts/${project.ownerAccount.name}/projects/${project.slug}/fingerprints/${fingerprint.hash}`,
|
|
||||||
getExpoWebsiteBaseUrl()
|
|
||||||
);
|
|
||||||
Log.log(`🔍 View the fingerprint at ${link(fingerprintUrl.toString())}`);
|
|
||||||
Log.log(`💡 If you want to see the entire fingerprint output, pass in the --json flag.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function selectRequestedPlatformAsync(): Promise<AppPlatform> {
|
|
||||||
const { requestedPlatform } = await promptAsync({
|
|
||||||
type: 'select',
|
|
||||||
message: 'Select platform',
|
|
||||||
name: 'requestedPlatform',
|
|
||||||
choices: [
|
|
||||||
{ title: 'Android', value: AppPlatform.Android },
|
|
||||||
{ title: 'iOS', value: AppPlatform.Ios },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
return requestedPlatform;
|
|
||||||
}
|
|
||||||
@@ -162,10 +162,7 @@ export default class Onboarding extends EasCommand {
|
|||||||
cloneMethod,
|
cloneMethod,
|
||||||
});
|
});
|
||||||
|
|
||||||
const vcsClient = new GitClient({
|
const vcsClient = new GitClient(finalTargetProjectDirectory);
|
||||||
maybeCwdOverride: finalTargetProjectDirectory,
|
|
||||||
requireCommit: false,
|
|
||||||
});
|
|
||||||
if (!app.githubRepository) {
|
if (!app.githubRepository) {
|
||||||
await fs.remove(path.join(finalTargetProjectDirectory, '.git'));
|
await fs.remove(path.join(finalTargetProjectDirectory, '.git'));
|
||||||
await runCommandAsync({
|
await runCommandAsync({
|
||||||
@@ -288,12 +285,12 @@ export default class Onboarding extends EasCommand {
|
|||||||
} else {
|
} else {
|
||||||
Log.log('🚀 Now we are going to trigger your first build');
|
Log.log('🚀 Now we are going to trigger your first build');
|
||||||
Log.log();
|
Log.log();
|
||||||
const { buildIds } = await runBuildAndSubmitAsync({
|
const { buildIds } = await runBuildAndSubmitAsync(
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
analytics,
|
analytics,
|
||||||
vcsClient,
|
vcsClient,
|
||||||
projectDir: finalTargetProjectDirectory,
|
finalTargetProjectDirectory,
|
||||||
flags: {
|
{
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
requestedPlatform:
|
requestedPlatform:
|
||||||
platform === Platform.ANDROID ? RequestedPlatform.Android : RequestedPlatform.Ios,
|
platform === Platform.ANDROID ? RequestedPlatform.Android : RequestedPlatform.Ios,
|
||||||
@@ -310,9 +307,8 @@ export default class Onboarding extends EasCommand {
|
|||||||
repack: true,
|
repack: true,
|
||||||
},
|
},
|
||||||
actor,
|
actor,
|
||||||
// eslint-disable-next-line async-protect/async-suffix
|
getDynamicProjectConfigFn
|
||||||
getDynamicPrivateProjectConfigAsync: getDynamicProjectConfigFn,
|
);
|
||||||
});
|
|
||||||
const buildId = buildIds[0];
|
const buildId = buildIds[0];
|
||||||
Log.log();
|
Log.log();
|
||||||
Log.log('🚀 You can now go back to the website to continue:');
|
Log.log('🚀 You can now go back to the website to continue:');
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import AndroidSubmitCommand from '../../submit/android/AndroidSubmitCommand';
|
|||||||
import { SubmissionContext, createSubmissionContextAsync } from '../../submit/context';
|
import { SubmissionContext, createSubmissionContextAsync } from '../../submit/context';
|
||||||
import IosSubmitCommand from '../../submit/ios/IosSubmitCommand';
|
import IosSubmitCommand from '../../submit/ios/IosSubmitCommand';
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
import GitClient from '../../vcs/clients/git';
|
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
|
||||||
|
import NoVcsClient from '../../vcs/clients/noVcs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This command will be run on the EAS workers.
|
* This command will be run on the EAS workers.
|
||||||
@@ -64,16 +65,10 @@ export default class SubmitInternal extends EasCommand {
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
} = await this.getContextAsync(SubmitInternal, {
|
} = await this.getContextAsync(SubmitInternal, {
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
|
vcsClientOverride: process.env.EAS_NO_VCS ? new NoVcsClient() : new GitNoCommitClient(),
|
||||||
withServerSideEnvironment: null,
|
withServerSideEnvironment: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (vcsClient instanceof GitClient) {
|
|
||||||
// `build:internal` is run on EAS workers and the repo may have been changed
|
|
||||||
// by pre-install hooks or other scripts. We don't want to require committing changes
|
|
||||||
// to continue the build.
|
|
||||||
vcsClient.requireCommit = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const submissionProfile = await EasJsonUtils.getSubmitProfileAsync(
|
const submissionProfile = await EasJsonUtils.getSubmitProfileAsync(
|
||||||
EasJsonAccessor.fromProjectPath(projectDir),
|
EasJsonAccessor.fromProjectPath(projectDir),
|
||||||
flags.platform,
|
flags.platform,
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export default class UpdateConfigure extends EasCommand {
|
|||||||
platform: flags['platform'],
|
platform: flags['platform'],
|
||||||
vcsClient,
|
vcsClient,
|
||||||
env: undefined,
|
env: undefined,
|
||||||
forceNativeConfigSync: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await ensureEASUpdateIsConfiguredInEasJsonAsync(projectDir);
|
await ensureEASUpdateIsConfiguredInEasJsonAsync(projectDir);
|
||||||
|
|||||||
@@ -6,43 +6,37 @@ import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/creat
|
|||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { withErrorHandlingAsync } from '../../graphql/client';
|
import { withErrorHandlingAsync } from '../../graphql/client';
|
||||||
import {
|
import {
|
||||||
BackgroundJobReceiptDataFragment,
|
DeleteUpdateGroupMutation,
|
||||||
ScheduleUpdateGroupDeletionMutation,
|
UpdateMutationDeleteUpdateGroupArgs,
|
||||||
ScheduleUpdateGroupDeletionMutationVariables,
|
|
||||||
} from '../../graphql/generated';
|
} from '../../graphql/generated';
|
||||||
import { BackgroundJobReceiptNode } from '../../graphql/types/BackgroundJobReceipt';
|
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { confirmAsync } from '../../prompts';
|
import { confirmAsync } from '../../prompts';
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
import { pollForBackgroundJobReceiptAsync } from '../../utils/pollForBackgroundJobReceiptAsync';
|
|
||||||
|
|
||||||
async function scheduleUpdateGroupDeletionAsync(
|
async function deleteUpdateGroupAsync(
|
||||||
graphqlClient: ExpoGraphqlClient,
|
graphqlClient: ExpoGraphqlClient,
|
||||||
{
|
{
|
||||||
group,
|
group,
|
||||||
}: {
|
}: {
|
||||||
group: string;
|
group: string;
|
||||||
}
|
}
|
||||||
): Promise<BackgroundJobReceiptDataFragment> {
|
): Promise<DeleteUpdateGroupMutation> {
|
||||||
const result = await withErrorHandlingAsync(
|
return await withErrorHandlingAsync(
|
||||||
graphqlClient
|
graphqlClient
|
||||||
.mutation<ScheduleUpdateGroupDeletionMutation, ScheduleUpdateGroupDeletionMutationVariables>(
|
.mutation<DeleteUpdateGroupMutation, UpdateMutationDeleteUpdateGroupArgs>(
|
||||||
gql`
|
gql`
|
||||||
mutation ScheduleUpdateGroupDeletion($group: ID!) {
|
mutation DeleteUpdateGroup($group: ID!) {
|
||||||
update {
|
update {
|
||||||
scheduleUpdateGroupDeletion(group: $group) {
|
deleteUpdateGroup(group: $group) {
|
||||||
id
|
group
|
||||||
...BackgroundJobReceiptData
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${BackgroundJobReceiptNode}
|
|
||||||
`,
|
`,
|
||||||
{ group }
|
{ group }
|
||||||
)
|
)
|
||||||
.toPromise()
|
.toPromise()
|
||||||
);
|
);
|
||||||
return result.update.scheduleUpdateGroupDeletion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class UpdateDelete extends EasCommand {
|
export default class UpdateDelete extends EasCommand {
|
||||||
@@ -96,9 +90,7 @@ export default class UpdateDelete extends EasCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const receipt = await scheduleUpdateGroupDeletionAsync(graphqlClient, { group });
|
await deleteUpdateGroupAsync(graphqlClient, { group });
|
||||||
const successfulReceipt = await pollForBackgroundJobReceiptAsync(graphqlClient, receipt);
|
|
||||||
Log.debug('Deletion result', { successfulReceipt });
|
|
||||||
|
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
printJsonOnlyOutput({ group });
|
printJsonOnlyOutput({ group });
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Errors, Flags } from '@oclif/core';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import nullthrows from 'nullthrows';
|
import nullthrows from 'nullthrows';
|
||||||
|
|
||||||
import { getExpoWebsiteBaseUrl } from '../../api';
|
|
||||||
import { ensureBranchExistsAsync } from '../../branch/queries';
|
import { ensureBranchExistsAsync } from '../../branch/queries';
|
||||||
import { transformFingerprintSource } from '../../build/graphql';
|
import { transformFingerprintSource } from '../../build/graphql';
|
||||||
import { ensureRepoIsCleanAsync } from '../../build/utils/repository';
|
import { ensureRepoIsCleanAsync } from '../../build/utils/repository';
|
||||||
@@ -13,9 +12,8 @@ import { EasNonInteractiveAndJsonFlags, EasUpdateEnvironmentFlag } from '../../c
|
|||||||
import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
||||||
import fetch from '../../fetch';
|
import fetch from '../../fetch';
|
||||||
import {
|
import {
|
||||||
AssetMapSourceInput,
|
|
||||||
EnvironmentVariableEnvironment,
|
EnvironmentVariableEnvironment,
|
||||||
FingerprintInfoGroup as GraphqlFingerprintInfoGroup,
|
FingerprintInfoGroup,
|
||||||
PublishUpdateGroupInput,
|
PublishUpdateGroupInput,
|
||||||
StatuspageServiceName,
|
StatuspageServiceName,
|
||||||
UpdateInfoGroup,
|
UpdateInfoGroup,
|
||||||
@@ -23,11 +21,9 @@ import {
|
|||||||
UpdateRolloutInfoGroup,
|
UpdateRolloutInfoGroup,
|
||||||
} from '../../graphql/generated';
|
} from '../../graphql/generated';
|
||||||
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
|
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
|
||||||
import { AppQuery } from '../../graphql/queries/AppQuery';
|
|
||||||
import Log, { learnMore, link } from '../../log';
|
import Log, { learnMore, link } from '../../log';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { RequestedPlatform } from '../../platform';
|
import { RequestedPlatform } from '../../platform';
|
||||||
import { maybeUploadAssetMapAsync } from '../../project/maybeUploadAssetMapAsync';
|
|
||||||
import { maybeUploadFingerprintAsync } from '../../project/maybeUploadFingerprintAsync';
|
import { maybeUploadFingerprintAsync } from '../../project/maybeUploadFingerprintAsync';
|
||||||
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
import {
|
||||||
@@ -37,7 +33,6 @@ import {
|
|||||||
buildUnsortedUpdateInfoGroupAsync,
|
buildUnsortedUpdateInfoGroupAsync,
|
||||||
collectAssetsAsync,
|
collectAssetsAsync,
|
||||||
filterCollectedAssetsByRequestedPlatforms,
|
filterCollectedAssetsByRequestedPlatforms,
|
||||||
findCompatibleBuildsAsync,
|
|
||||||
generateEasMetadataAsync,
|
generateEasMetadataAsync,
|
||||||
getBranchNameForCommandAsync,
|
getBranchNameForCommandAsync,
|
||||||
getRuntimeToPlatformsAndFingerprintInfoMappingFromRuntimeVersionInfoObjects,
|
getRuntimeToPlatformsAndFingerprintInfoMappingFromRuntimeVersionInfoObjects,
|
||||||
@@ -242,10 +237,6 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
jsonFlag,
|
jsonFlag,
|
||||||
});
|
});
|
||||||
|
|
||||||
const maybeServerEnv = environment
|
|
||||||
? { ...(await getServerSideEnvironmentVariablesAsync()), EXPO_NO_DOTENV: '1' }
|
|
||||||
: {};
|
|
||||||
|
|
||||||
// build bundle and upload assets for a new publish
|
// build bundle and upload assets for a new publish
|
||||||
if (!skipBundler) {
|
if (!skipBundler) {
|
||||||
const bundleSpinner = ora().start('Exporting...');
|
const bundleSpinner = ora().start('Exporting...');
|
||||||
@@ -256,7 +247,11 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
exp,
|
exp,
|
||||||
platformFlag: requestedPlatform,
|
platformFlag: requestedPlatform,
|
||||||
clearCache,
|
clearCache,
|
||||||
extraEnv: maybeServerEnv,
|
extraEnv: {
|
||||||
|
...(environment
|
||||||
|
? { ...(await getServerSideEnvironmentVariablesAsync()), EXPO_NO_DOTENV: '1' }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
bundleSpinner.succeed('Exported bundle(s)');
|
bundleSpinner.succeed('Exported bundle(s)');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -273,14 +268,9 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
let uploadedAssetCount = 0;
|
let uploadedAssetCount = 0;
|
||||||
let assetLimitPerUpdateGroup = 0;
|
let assetLimitPerUpdateGroup = 0;
|
||||||
let realizedPlatforms: UpdatePublishPlatform[] = [];
|
let realizedPlatforms: UpdatePublishPlatform[] = [];
|
||||||
let assetMapSource: AssetMapSourceInput | null = null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [collectedAssets, maybeAssetMapSource] = await Promise.all([
|
const collectedAssets = await collectAssetsAsync(distRoot);
|
||||||
collectAssetsAsync(distRoot),
|
|
||||||
maybeUploadAssetMapAsync(distRoot, graphqlClient),
|
|
||||||
]);
|
|
||||||
assetMapSource = maybeAssetMapSource;
|
|
||||||
const assets = filterCollectedAssetsByRequestedPlatforms(collectedAssets, requestedPlatform);
|
const assets = filterCollectedAssetsByRequestedPlatforms(collectedAssets, requestedPlatform);
|
||||||
realizedPlatforms = Object.keys(assets) as UpdatePublishPlatform[];
|
realizedPlatforms = Object.keys(assets) as UpdatePublishPlatform[];
|
||||||
|
|
||||||
@@ -403,7 +393,7 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
...workflows,
|
...workflows,
|
||||||
web: Workflow.UNKNOWN,
|
web: Workflow.UNKNOWN,
|
||||||
},
|
},
|
||||||
env: maybeServerEnv,
|
env: undefined,
|
||||||
});
|
});
|
||||||
const runtimeToPlatformsAndFingerprintInfoMapping =
|
const runtimeToPlatformsAndFingerprintInfoMapping =
|
||||||
getRuntimeToPlatformsAndFingerprintInfoMappingFromRuntimeVersionInfoObjects(
|
getRuntimeToPlatformsAndFingerprintInfoMappingFromRuntimeVersionInfoObjects(
|
||||||
@@ -488,19 +478,14 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{} as GraphqlFingerprintInfoGroup
|
{} as FingerprintInfoGroup
|
||||||
);
|
);
|
||||||
|
|
||||||
const assetMapGroup = assetMapSource
|
|
||||||
? Object.fromEntries(platforms.map(platform => [platform, assetMapSource]))
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
branchId: branch.id,
|
branchId: branch.id,
|
||||||
updateInfoGroup: localUpdateInfoGroup,
|
updateInfoGroup: localUpdateInfoGroup,
|
||||||
rolloutInfoGroup: localRolloutInfoGroup,
|
rolloutInfoGroup: localRolloutInfoGroup,
|
||||||
fingerprintInfoGroup: transformedFingerprintInfoGroup,
|
fingerprintInfoGroup: transformedFingerprintInfoGroup,
|
||||||
assetMapGroup,
|
|
||||||
runtimeVersion,
|
runtimeVersion,
|
||||||
message: updateMessage,
|
message: updateMessage,
|
||||||
gitCommitHash,
|
gitCommitHash,
|
||||||
@@ -574,9 +559,7 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
}
|
}
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
printJsonOnlyOutput(getUpdateJsonInfosForUpdates(newUpdates));
|
printJsonOnlyOutput(getUpdateJsonInfosForUpdates(newUpdates));
|
||||||
return;
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
if (new Set(newUpdates.map(update => update.group)).size > 1) {
|
if (new Set(newUpdates.map(update => update.group)).size > 1) {
|
||||||
Log.addNewLineIfNone();
|
Log.addNewLineIfNone();
|
||||||
Log.log(
|
Log.log(
|
||||||
@@ -586,12 +569,6 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
|
|
||||||
Log.addNewLineIfNone();
|
Log.addNewLineIfNone();
|
||||||
|
|
||||||
const runtimeToCompatibleBuilds = await Promise.all(
|
|
||||||
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping.map(obj =>
|
|
||||||
findCompatibleBuildsAsync(graphqlClient, projectId, obj)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const runtime of uniqBy(
|
for (const runtime of uniqBy(
|
||||||
runtimeToPlatformsAndFingerprintInfoMapping,
|
runtimeToPlatformsAndFingerprintInfoMapping,
|
||||||
version => version.runtimeVersion
|
version => version.runtimeVersion
|
||||||
@@ -622,7 +599,9 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
{ label: 'Runtime version', value: runtime.runtimeVersion },
|
{ label: 'Runtime version', value: runtime.runtimeVersion },
|
||||||
{ label: 'Platform', value: platforms.join(', ') },
|
{ label: 'Platform', value: platforms.join(', ') },
|
||||||
{ label: 'Update group ID', value: updateGroupId },
|
{ label: 'Update group ID', value: updateGroupId },
|
||||||
...(newAndroidUpdate ? [{ label: 'Android update ID', value: newAndroidUpdate.id }] : []),
|
...(newAndroidUpdate
|
||||||
|
? [{ label: 'Android update ID', value: newAndroidUpdate.id }]
|
||||||
|
: []),
|
||||||
...(newIosUpdate ? [{ label: 'iOS update ID', value: newIosUpdate.id }] : []),
|
...(newIosUpdate ? [{ label: 'iOS update ID', value: newIosUpdate.id }] : []),
|
||||||
...(newAndroidUpdate?.rolloutControlUpdate
|
...(newAndroidUpdate?.rolloutControlUpdate
|
||||||
? [
|
? [
|
||||||
@@ -653,7 +632,9 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
])
|
])
|
||||||
);
|
);
|
||||||
Log.addNewLineIfNone();
|
Log.addNewLineIfNone();
|
||||||
if (isUploadedAssetCountAboveWarningThreshold(uploadedAssetCount, assetLimitPerUpdateGroup)) {
|
if (
|
||||||
|
isUploadedAssetCountAboveWarningThreshold(uploadedAssetCount, assetLimitPerUpdateGroup)
|
||||||
|
) {
|
||||||
Log.warn(
|
Log.warn(
|
||||||
`This update group contains ${uploadedAssetCount} assets and is nearing the server cap of ${assetLimitPerUpdateGroup}.\n` +
|
`This update group contains ${uploadedAssetCount} assets and is nearing the server cap of ${assetLimitPerUpdateGroup}.\n` +
|
||||||
`${learnMore('https://docs.expo.dev/eas-update/optimize-assets/', {
|
`${learnMore('https://docs.expo.dev/eas-update/optimize-assets/', {
|
||||||
@@ -663,51 +644,7 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
);
|
);
|
||||||
Log.addNewLineIfNone();
|
Log.addNewLineIfNone();
|
||||||
}
|
}
|
||||||
|
|
||||||
const fingerprintsWithoutCompatibleBuilds = runtimeToCompatibleBuilds.find(
|
|
||||||
({ runtimeVersion }) => runtimeVersion === runtime.runtimeVersion
|
|
||||||
)?.fingerprintInfoGroupWithCompatibleBuilds;
|
|
||||||
if (fingerprintsWithoutCompatibleBuilds) {
|
|
||||||
const missingBuilds = Object.entries(fingerprintsWithoutCompatibleBuilds).filter(
|
|
||||||
([_platform, fingerprintInfo]) => !fingerprintInfo.build
|
|
||||||
);
|
|
||||||
if (missingBuilds.length > 0) {
|
|
||||||
const project = await AppQuery.byIdAsync(graphqlClient, projectId);
|
|
||||||
Log.warn('No compatible builds found for the following fingerprints:');
|
|
||||||
for (const [platform, fingerprintInfo] of missingBuilds) {
|
|
||||||
const fingerprintUrl = new URL(
|
|
||||||
`/accounts/${project.ownerAccount.name}/projects/${project.slug}/fingerprints/${fingerprintInfo.fingerprintHash}`,
|
|
||||||
getExpoWebsiteBaseUrl()
|
|
||||||
);
|
|
||||||
Log.warn(
|
|
||||||
formatFields(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
label: `${this.prettyPlatform(platform)} fingerprint`,
|
|
||||||
value: fingerprintInfo.fingerprintHash,
|
|
||||||
},
|
|
||||||
{ label: 'URL', value: fingerprintUrl.toString() },
|
|
||||||
],
|
|
||||||
{
|
|
||||||
labelFormat: label => ` ${chalk.dim(label)}:`,
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
);
|
|
||||||
Log.addNewLineIfNone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private prettyPlatform(updatePlatform: string): string {
|
|
||||||
switch (updatePlatform) {
|
|
||||||
case 'android':
|
|
||||||
return 'Android';
|
|
||||||
case 'ios':
|
|
||||||
return 'iOS';
|
|
||||||
default:
|
|
||||||
return updatePlatform;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,386 +0,0 @@
|
|||||||
import { IOSConfig } from '@expo/config-plugins';
|
|
||||||
import { Platform } from '@expo/eas-build-job';
|
|
||||||
import { Flags } from '@oclif/core';
|
|
||||||
import fg from 'fast-glob';
|
|
||||||
import fs from 'fs-extra';
|
|
||||||
import StreamZip from 'node-stream-zip';
|
|
||||||
import path from 'path';
|
|
||||||
import tar from 'tar';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
import { getBuildLogsUrl } from '../build/utils/url';
|
|
||||||
import EasCommand from '../commandUtils/EasCommand';
|
|
||||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../commandUtils/flags';
|
|
||||||
import {
|
|
||||||
DistributionType,
|
|
||||||
LocalBuildArchiveSourceType,
|
|
||||||
UploadSessionType,
|
|
||||||
} from '../graphql/generated';
|
|
||||||
import { FingerprintMutation } from '../graphql/mutations/FingerprintMutation';
|
|
||||||
import { LocalBuildMutation } from '../graphql/mutations/LocalBuildMutation';
|
|
||||||
import { toAppPlatform } from '../graphql/types/AppPlatform';
|
|
||||||
import Log from '../log';
|
|
||||||
import { promptAsync } from '../prompts';
|
|
||||||
import * as xcode from '../run/ios/xcode';
|
|
||||||
import { uploadFileAtPathToGCSAsync } from '../uploads';
|
|
||||||
import { fromNow } from '../utils/date';
|
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../utils/json';
|
|
||||||
import { getTmpDirectory } from '../utils/paths';
|
|
||||||
import { createProgressTracker } from '../utils/progress';
|
|
||||||
|
|
||||||
export default class BuildUpload extends EasCommand {
|
|
||||||
static override description = 'upload a local build and generate a sharable link';
|
|
||||||
static override hidden = true;
|
|
||||||
|
|
||||||
static override flags = {
|
|
||||||
platform: Flags.enum<Platform.IOS | Platform.ANDROID>({
|
|
||||||
char: 'p',
|
|
||||||
options: [Platform.IOS, Platform.ANDROID],
|
|
||||||
}),
|
|
||||||
'build-path': Flags.string({
|
|
||||||
description: 'Path for the local build',
|
|
||||||
}),
|
|
||||||
fingerprint: Flags.string({
|
|
||||||
description: 'Fingerprint hash of the local build',
|
|
||||||
}),
|
|
||||||
...EasNonInteractiveAndJsonFlags,
|
|
||||||
};
|
|
||||||
|
|
||||||
static override contextDefinition = {
|
|
||||||
...this.ContextOptions.ProjectId,
|
|
||||||
...this.ContextOptions.LoggedIn,
|
|
||||||
};
|
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
|
||||||
const { flags } = await this.parse(BuildUpload);
|
|
||||||
const {
|
|
||||||
'build-path': buildPath,
|
|
||||||
fingerprint: manualFingerprintHash,
|
|
||||||
json: jsonFlag,
|
|
||||||
'non-interactive': nonInteractive,
|
|
||||||
} = flags;
|
|
||||||
const {
|
|
||||||
projectId,
|
|
||||||
loggedIn: { graphqlClient },
|
|
||||||
} = await this.getContextAsync(BuildUpload, {
|
|
||||||
nonInteractive,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (jsonFlag) {
|
|
||||||
enableJsonOutput();
|
|
||||||
}
|
|
||||||
|
|
||||||
const platform = await this.selectPlatformAsync({ platform: flags.platform, nonInteractive });
|
|
||||||
const localBuildPath = await resolveLocalBuildPathAsync({
|
|
||||||
platform,
|
|
||||||
inputBuildPath: buildPath,
|
|
||||||
nonInteractive,
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
fingerprintHash: buildFingerprintHash,
|
|
||||||
developmentClient,
|
|
||||||
simulator,
|
|
||||||
} = await extractAppMetadataAsync(localBuildPath, platform);
|
|
||||||
|
|
||||||
let fingerprint = manualFingerprintHash ?? buildFingerprintHash;
|
|
||||||
if (fingerprint) {
|
|
||||||
if (
|
|
||||||
manualFingerprintHash &&
|
|
||||||
buildFingerprintHash &&
|
|
||||||
manualFingerprintHash !== buildFingerprintHash &&
|
|
||||||
!nonInteractive
|
|
||||||
) {
|
|
||||||
const selectedAnswer = await promptAsync({
|
|
||||||
name: 'fingerprint',
|
|
||||||
message: `The provided fingerprint hash ${manualFingerprintHash} does not match the fingerprint hash of the build ${buildFingerprintHash}. Which fingerprint do you want to use?`,
|
|
||||||
type: 'select',
|
|
||||||
choices: [
|
|
||||||
{ title: manualFingerprintHash, value: manualFingerprintHash },
|
|
||||||
{ title: buildFingerprintHash, value: buildFingerprintHash },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
fingerprint = String(selectedAnswer.fingerprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
await FingerprintMutation.createFingerprintAsync(graphqlClient, projectId, {
|
|
||||||
hash: fingerprint,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.log(`Using build ${localBuildPath}`);
|
|
||||||
Log.log(`Fingerprint hash: ${fingerprint ?? 'Unknown'}`);
|
|
||||||
|
|
||||||
Log.log('Uploading your app archive to EAS');
|
|
||||||
const bucketKey = await uploadAppArchiveAsync(graphqlClient, localBuildPath);
|
|
||||||
|
|
||||||
const build = await LocalBuildMutation.createLocalBuildAsync(
|
|
||||||
graphqlClient,
|
|
||||||
projectId,
|
|
||||||
{ platform: toAppPlatform(platform), simulator },
|
|
||||||
{ type: LocalBuildArchiveSourceType.Gcs, bucketKey },
|
|
||||||
{ distribution: DistributionType.Internal, fingerprintHash: fingerprint, developmentClient }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (jsonFlag) {
|
|
||||||
printJsonOnlyOutput({ url: getBuildLogsUrl(build) });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.withTick(`Shareable link to the build: ${getBuildLogsUrl(build)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async selectPlatformAsync({
|
|
||||||
nonInteractive,
|
|
||||||
platform,
|
|
||||||
}: {
|
|
||||||
nonInteractive: boolean;
|
|
||||||
platform?: Platform;
|
|
||||||
}): Promise<Platform> {
|
|
||||||
if (nonInteractive && !platform) {
|
|
||||||
throw new Error('Platform must be provided in non-interactive mode');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (platform) {
|
|
||||||
return platform;
|
|
||||||
}
|
|
||||||
const { resolvedPlatform } = await promptAsync({
|
|
||||||
type: 'select',
|
|
||||||
message: 'Select platform',
|
|
||||||
name: 'resolvedPlatform',
|
|
||||||
choices: [
|
|
||||||
{ title: 'Android', value: Platform.ANDROID },
|
|
||||||
{ title: 'iOS', value: Platform.IOS },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
return resolvedPlatform;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolveLocalBuildPathAsync({
|
|
||||||
platform,
|
|
||||||
inputBuildPath,
|
|
||||||
nonInteractive,
|
|
||||||
}: {
|
|
||||||
platform: Platform;
|
|
||||||
inputBuildPath?: string;
|
|
||||||
nonInteractive: boolean;
|
|
||||||
}): Promise<string> {
|
|
||||||
const rootDir = process.cwd();
|
|
||||||
let applicationArchivePatternOrPath: string[] = [];
|
|
||||||
|
|
||||||
if (inputBuildPath) {
|
|
||||||
applicationArchivePatternOrPath.push(inputBuildPath);
|
|
||||||
} else if (platform === Platform.ANDROID) {
|
|
||||||
applicationArchivePatternOrPath.push('android/app/build/outputs/**/*.{apk,aab}');
|
|
||||||
} else {
|
|
||||||
const xcworkspacePath = await xcode.resolveXcodeProjectAsync(rootDir);
|
|
||||||
const schemes = IOSConfig.BuildScheme.getRunnableSchemesFromXcodeproj(rootDir);
|
|
||||||
if (xcworkspacePath && schemes.length > 0) {
|
|
||||||
for (const scheme of schemes) {
|
|
||||||
const buildSettings = await xcode.getXcodeBuildSettingsAsync(xcworkspacePath, scheme.name);
|
|
||||||
applicationArchivePatternOrPath = applicationArchivePatternOrPath.concat(
|
|
||||||
buildSettings.map(({ buildSettings }) => `${buildSettings.BUILD_DIR}/**/*.app`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let applicationArchives = await findArtifactsAsync({
|
|
||||||
rootDir,
|
|
||||||
patternOrPathArray: applicationArchivePatternOrPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (applicationArchives.length === 0 && !nonInteractive && !inputBuildPath) {
|
|
||||||
Log.warn(`No application archives found at ${applicationArchivePatternOrPath}.`);
|
|
||||||
const { path } = await promptAsync({
|
|
||||||
type: 'text',
|
|
||||||
name: 'path',
|
|
||||||
message: 'Provide a path to the application archive:',
|
|
||||||
validate: value => (value ? true : 'Path may not be empty.'),
|
|
||||||
});
|
|
||||||
applicationArchives = await findArtifactsAsync({
|
|
||||||
rootDir,
|
|
||||||
patternOrPathArray: [path],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationArchives.length === 1) {
|
|
||||||
return applicationArchives[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationArchives.length > 1) {
|
|
||||||
// sort by modification time
|
|
||||||
const applicationArchivesInfo = await Promise.all(
|
|
||||||
applicationArchives.map(async archivePath => ({
|
|
||||||
path: archivePath,
|
|
||||||
stat: await fs.stat(archivePath),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
applicationArchivesInfo.sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs);
|
|
||||||
|
|
||||||
if (nonInteractive) {
|
|
||||||
return applicationArchivesInfo[0].path;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { selectedPath } = await promptAsync({
|
|
||||||
type: 'select',
|
|
||||||
name: 'selectedPath',
|
|
||||||
message: 'Found multiple application archives. Select one:',
|
|
||||||
choices: applicationArchivesInfo.map(archive => {
|
|
||||||
return {
|
|
||||||
title: `${
|
|
||||||
archive.path.startsWith(rootDir) ? path.relative(rootDir, archive.path) : archive.path
|
|
||||||
} (${fromNow(archive.stat.mtime)} ago)`,
|
|
||||||
value: archive.path,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
return selectedPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Found no application archives at ${inputBuildPath}.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function findArtifactsAsync({
|
|
||||||
rootDir,
|
|
||||||
patternOrPathArray,
|
|
||||||
}: {
|
|
||||||
rootDir: string;
|
|
||||||
patternOrPathArray: string[];
|
|
||||||
}): Promise<string[]> {
|
|
||||||
const files = new Set<string>();
|
|
||||||
for (const patternOrPath of patternOrPathArray) {
|
|
||||||
if (path.isAbsolute(patternOrPath) && (await fs.pathExists(patternOrPath))) {
|
|
||||||
files.add(patternOrPath);
|
|
||||||
} else {
|
|
||||||
const filesFound = await fg(patternOrPath, {
|
|
||||||
cwd: rootDir,
|
|
||||||
onlyFiles: false,
|
|
||||||
});
|
|
||||||
filesFound.forEach(file => files.add(file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...files].map(filePath => {
|
|
||||||
// User may provide an absolute path as input in which case
|
|
||||||
// fg will return an absolute path.
|
|
||||||
if (path.isAbsolute(filePath)) {
|
|
||||||
return filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// User may also provide a relative path in which case
|
|
||||||
// fg will return a path relative to rootDir.
|
|
||||||
return path.join(rootDir, filePath);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function uploadAppArchiveAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
originalPath: string
|
|
||||||
): Promise<string> {
|
|
||||||
let filePath = originalPath;
|
|
||||||
if ((await fs.stat(filePath)).isDirectory()) {
|
|
||||||
await fs.mkdirp(getTmpDirectory());
|
|
||||||
const tarPath = path.join(getTmpDirectory(), `${uuidv4()}.tar.gz`);
|
|
||||||
const parentPath = path.dirname(originalPath);
|
|
||||||
const folderName = path.basename(originalPath);
|
|
||||||
await tar.create({ cwd: parentPath, file: tarPath, gzip: true }, [folderName]);
|
|
||||||
filePath = tarPath;
|
|
||||||
}
|
|
||||||
const fileSize = (await fs.stat(filePath)).size;
|
|
||||||
const bucketKey = await uploadFileAtPathToGCSAsync(
|
|
||||||
graphqlClient,
|
|
||||||
UploadSessionType.EasShareGcsAppArchive,
|
|
||||||
filePath,
|
|
||||||
createProgressTracker({
|
|
||||||
total: fileSize,
|
|
||||||
message: 'Uploading to EAS',
|
|
||||||
completedMessage: 'Uploaded to EAS',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return bucketKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppMetadata = {
|
|
||||||
fingerprintHash?: string;
|
|
||||||
developmentClient: boolean;
|
|
||||||
simulator: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function extractAppMetadataAsync(
|
|
||||||
buildPath: string,
|
|
||||||
platform: Platform
|
|
||||||
): Promise<AppMetadata> {
|
|
||||||
let developmentClient = false;
|
|
||||||
let fingerprintHash: string | undefined;
|
|
||||||
const simulator = platform === Platform.IOS;
|
|
||||||
|
|
||||||
const basePath = platform === Platform.ANDROID ? 'assets/' : buildPath;
|
|
||||||
const fingerprintFilePath =
|
|
||||||
platform === Platform.ANDROID ? 'fingerprint' : 'EXUpdates.bundle/fingerprint';
|
|
||||||
const devMenuBundlePath =
|
|
||||||
platform === Platform.ANDROID ? 'EXDevMenuApp.android.js' : 'EXDevMenu.bundle/';
|
|
||||||
|
|
||||||
const buildExtension = path.extname(buildPath);
|
|
||||||
if (['.apk', '.aab'].includes(buildExtension)) {
|
|
||||||
const zip = new StreamZip.async({ file: buildPath });
|
|
||||||
try {
|
|
||||||
developmentClient = Boolean(await zip.entry(path.join(basePath, devMenuBundlePath)));
|
|
||||||
if (await zip.entry(path.join(basePath, fingerprintFilePath))) {
|
|
||||||
fingerprintHash = (await zip.entryData(path.join(basePath, fingerprintFilePath))).toString(
|
|
||||||
'utf-8'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Log.error(`Error reading ${buildExtension}: ${err}`);
|
|
||||||
} finally {
|
|
||||||
await zip.close();
|
|
||||||
}
|
|
||||||
} else if (buildExtension === '.app') {
|
|
||||||
developmentClient = await fs.exists(path.join(basePath, devMenuBundlePath));
|
|
||||||
|
|
||||||
if (await fs.exists(path.join(basePath, fingerprintFilePath))) {
|
|
||||||
fingerprintHash = await fs.readFile(path.join(basePath, fingerprintFilePath), 'utf8');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Use tar to list files in the archive
|
|
||||||
try {
|
|
||||||
let fingerprintHashPromise: Promise<string> | undefined;
|
|
||||||
await tar.list({
|
|
||||||
file: buildPath,
|
|
||||||
// eslint-disable-next-line async-protect/async-suffix
|
|
||||||
onentry: entry => {
|
|
||||||
if (entry.path.endsWith(devMenuBundlePath)) {
|
|
||||||
developmentClient = true;
|
|
||||||
}
|
|
||||||
if (entry.path.endsWith(fingerprintFilePath)) {
|
|
||||||
fingerprintHashPromise = new Promise<string>(async (resolve, reject) => {
|
|
||||||
try {
|
|
||||||
let content = '';
|
|
||||||
for await (const chunk of entry) {
|
|
||||||
content += chunk.toString('utf8');
|
|
||||||
}
|
|
||||||
resolve(content);
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (fingerprintHashPromise !== undefined) {
|
|
||||||
fingerprintHash = await fingerprintHashPromise;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Log.error(`Error reading ${buildExtension}: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
developmentClient,
|
|
||||||
fingerprintHash,
|
|
||||||
simulator,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -129,7 +129,7 @@ export default class WorkflowRun extends EasCommand {
|
|||||||
|
|
||||||
Log.newLine();
|
Log.newLine();
|
||||||
Log.succeed(
|
Log.succeed(
|
||||||
`Workflow run started successfully. See logs: ${link(
|
`Workflow started successfully. ${link(
|
||||||
getWorkflowRunUrl(account.name, projectName, workflowRunId)
|
getWorkflowRunUrl(account.name, projectName, workflowRunId)
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ export async function createAppAsync(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAppleError(error: any): error is {
|
function isAppleError(error: any): error is {
|
||||||
data: {
|
data: {
|
||||||
errors: {
|
errors: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -1,222 +0,0 @@
|
|||||||
import { App, BetaGroup, Session, User, UserRole } from '@expo/apple-utils';
|
|
||||||
|
|
||||||
import { isAppleError } from './ensureAppExists';
|
|
||||||
import Log from '../../../log';
|
|
||||||
import { ora } from '../../../ora';
|
|
||||||
import { confirmAsync } from '../../../prompts';
|
|
||||||
|
|
||||||
// The name of the internal TestFlight group, this should probably never change.
|
|
||||||
const AUTO_GROUP_NAME = 'Team (Expo)';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure a TestFlight internal group with access to all builds exists for the app and has all admin users invited to it.
|
|
||||||
* This allows users to instantly access their builds from TestFlight after it finishes processing.
|
|
||||||
*/
|
|
||||||
export async function ensureTestFlightGroupExistsAsync(app: App): Promise<void> {
|
|
||||||
if (process.env.EAS_NO_AUTO_TESTFLIGHT_SETUP) {
|
|
||||||
Log.debug('EAS_NO_AUTO_TESTFLIGHT_SETUP is set, skipping TestFlight setup');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const groups = await app.getBetaGroupsAsync({
|
|
||||||
query: {
|
|
||||||
includes: ['betaTesters'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (groups.length > 0) {
|
|
||||||
Log.debug(`Found ${groups.length} TestFlight groups`);
|
|
||||||
Log.debug('Skipping creating a new TestFlight group');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const group = await ensureInternalGroupAsync({
|
|
||||||
app,
|
|
||||||
groups,
|
|
||||||
});
|
|
||||||
const users = await User.getAsync(app.context);
|
|
||||||
const admins = users.filter(user => user.attributes.roles?.includes(UserRole.ADMIN));
|
|
||||||
|
|
||||||
await addAllUsersToInternalGroupAsync(group, admins);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ensureInternalGroupAsync({
|
|
||||||
groups,
|
|
||||||
app,
|
|
||||||
}: {
|
|
||||||
groups: BetaGroup[];
|
|
||||||
app: App;
|
|
||||||
}): Promise<BetaGroup> {
|
|
||||||
let betaGroup = groups.find(group => group.attributes.name === AUTO_GROUP_NAME);
|
|
||||||
if (!betaGroup) {
|
|
||||||
const spinner = ora().start('Creating TestFlight group...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Apple throw an error if you create the group too quickly after creating the app. We'll retry a few times.
|
|
||||||
await pollRetryAsync(
|
|
||||||
async () => {
|
|
||||||
betaGroup = await app.createBetaGroupAsync({
|
|
||||||
name: AUTO_GROUP_NAME,
|
|
||||||
isInternalGroup: true,
|
|
||||||
// Automatically add latest builds to the group without needing to run the command.
|
|
||||||
hasAccessToAllBuilds: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{
|
|
||||||
shouldRetry(error) {
|
|
||||||
if (isAppleError(error)) {
|
|
||||||
spinner.text = `TestFlight still preparing, retrying in 10 seconds...`;
|
|
||||||
|
|
||||||
return error.data.errors.some(
|
|
||||||
error => error.code === 'ENTITY_ERROR.RELATIONSHIP.INVALID'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
spinner.succeed(`TestFlight group created: ${AUTO_GROUP_NAME}`);
|
|
||||||
} catch (error: any) {
|
|
||||||
spinner.fail('Failed to create TestFlight group...');
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!betaGroup) {
|
|
||||||
throw new Error('Failed to create internal TestFlight group');
|
|
||||||
}
|
|
||||||
|
|
||||||
// `hasAccessToAllBuilds` is a newer feature that allows the group to automatically have access to all builds. This cannot be patched so we need to recreate the group.
|
|
||||||
if (!betaGroup.attributes.hasAccessToAllBuilds) {
|
|
||||||
if (
|
|
||||||
await confirmAsync({
|
|
||||||
message: 'Regenerate internal TestFlight group to allow automatic access to all builds?',
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
await BetaGroup.deleteAsync(app.context, { id: betaGroup.id });
|
|
||||||
return await ensureInternalGroupAsync({
|
|
||||||
app,
|
|
||||||
groups: await app.getBetaGroupsAsync({
|
|
||||||
query: {
|
|
||||||
includes: ['betaTesters'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return betaGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addAllUsersToInternalGroupAsync(group: BetaGroup, users: User[]): Promise<void> {
|
|
||||||
let emails = users
|
|
||||||
.filter(user => user.attributes.email)
|
|
||||||
.map(user => ({
|
|
||||||
email: user.attributes.email!,
|
|
||||||
firstName: user.attributes.firstName ?? '',
|
|
||||||
lastName: user.attributes.lastName ?? '',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const { betaTesters } = group.attributes;
|
|
||||||
const existingEmails = betaTesters?.map(tester => tester.attributes.email).filter(Boolean) ?? [];
|
|
||||||
// Filter out existing beta testers.
|
|
||||||
if (betaTesters) {
|
|
||||||
emails = emails.filter(
|
|
||||||
user => !existingEmails.find(existingEmail => existingEmail === user.email)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// No new users to add to the internal group.
|
|
||||||
if (!emails.length) {
|
|
||||||
// No need to log which users are here on subsequent runs as devs already know the drill at this point.
|
|
||||||
Log.debug(`All current admins are already added to the group: ${group.attributes.name}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.debug(`Adding ${emails.length} users to internal group: ${group.attributes.name}`);
|
|
||||||
Log.debug(`Users: ${emails.map(user => user.email).join(', ')}`);
|
|
||||||
|
|
||||||
const data = await group.createBulkBetaTesterAssignmentsAsync(emails);
|
|
||||||
|
|
||||||
const success = data.attributes.betaTesters.every(tester => {
|
|
||||||
if (tester.assignmentResult === 'FAILED') {
|
|
||||||
if (tester.errors && Array.isArray(tester.errors) && tester.errors.length) {
|
|
||||||
if (
|
|
||||||
tester.errors.length === 1 &&
|
|
||||||
tester.errors[0].key === 'Halliday.tester.already.exists'
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (const error of tester.errors) {
|
|
||||||
Log.error(
|
|
||||||
`Error adding user ${tester.email} to TestFlight group "${group.attributes.name}": ${error.key}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (tester.assignmentResult === 'NOT_QUALIFIED_FOR_INTERNAL_GROUP') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
const groupUrl = await getTestFlightGroupUrlAsync(group);
|
|
||||||
|
|
||||||
Log.error(
|
|
||||||
`Unable to add all admins to TestFlight group "${
|
|
||||||
group.attributes.name
|
|
||||||
}". You can add them manually in App Store Connect. ${groupUrl ?? ''}`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Log.log(
|
|
||||||
`TestFlight access enabled for: ` +
|
|
||||||
data.attributes.betaTesters
|
|
||||||
.map(tester => tester.email)
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(', ')
|
|
||||||
);
|
|
||||||
// TODO: When we have more TestFlight functionality, we can link to it from here.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getTestFlightGroupUrlAsync(group: BetaGroup): Promise<string | null> {
|
|
||||||
if (group.context.providerId) {
|
|
||||||
try {
|
|
||||||
const session = await Session.getSessionForProviderIdAsync(group.context.providerId);
|
|
||||||
|
|
||||||
return `https://appstoreconnect.apple.com/teams/${session.provider.publicProviderId}/apps/6741088859/testflight/groups/${group.id}`;
|
|
||||||
} catch (error) {
|
|
||||||
// Avoid crashing if we can't get the session.
|
|
||||||
Log.debug('Failed to get session for provider ID', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pollRetryAsync<T>(
|
|
||||||
fn: () => Promise<T>,
|
|
||||||
{
|
|
||||||
shouldRetry,
|
|
||||||
retries = 15,
|
|
||||||
// 25 seconds was the minium interval I calculated when measuring against 5 second intervals.
|
|
||||||
// Switching to 10 seconds to account for days where Apple APIs are faster.
|
|
||||||
interval = 10000,
|
|
||||||
}: { shouldRetry?: (error: Error) => boolean; retries?: number; interval?: number } = {}
|
|
||||||
): Promise<T> {
|
|
||||||
let lastError: Error | null = null;
|
|
||||||
for (let i = 0; i < retries; i++) {
|
|
||||||
try {
|
|
||||||
return await fn();
|
|
||||||
} catch (error: any) {
|
|
||||||
if (shouldRetry && !shouldRetry(error)) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
lastError = error;
|
|
||||||
await new Promise(resolve => setTimeout(resolve, interval));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
||||||
throw lastError;
|
|
||||||
}
|
|
||||||
@@ -29,8 +29,8 @@ function resolveProfileType(
|
|||||||
return resolveProfileTypeIos(profileClass, isEnterprise);
|
return resolveProfileTypeIos(profileClass, isEnterprise);
|
||||||
case ApplePlatform.TV_OS:
|
case ApplePlatform.TV_OS:
|
||||||
return resolveProfileTypeAppleTv(profileClass, isEnterprise);
|
return resolveProfileTypeAppleTv(profileClass, isEnterprise);
|
||||||
case ApplePlatform.VISION_OS:
|
|
||||||
case ApplePlatform.MAC_OS:
|
case ApplePlatform.MAC_OS:
|
||||||
|
case ApplePlatform.VISION_OS:
|
||||||
throw new Error(`${applePlatform} profiles are not supported`);
|
throw new Error(`${applePlatform} profiles are not supported`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
|
||||||
|
|
||||||
import { createFingerprintAsync } from './cli';
|
|
||||||
import { Fingerprint } from './types';
|
|
||||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
|
||||||
import { AppPlatform } from '../graphql/generated';
|
|
||||||
import { FingerprintMutation } from '../graphql/mutations/FingerprintMutation';
|
|
||||||
import Log from '../log';
|
|
||||||
import { maybeUploadFingerprintAsync } from '../project/maybeUploadFingerprintAsync';
|
|
||||||
import { resolveWorkflowPerPlatformAsync } from '../project/workflow';
|
|
||||||
import { Client } from '../vcs/vcs';
|
|
||||||
|
|
||||||
export async function getFingerprintInfoFromLocalProjectForPlatformsAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
projectDir: string,
|
|
||||||
projectId: string,
|
|
||||||
vcsClient: Client,
|
|
||||||
platforms: AppPlatform[],
|
|
||||||
{ env }: { env?: Record<string, string> } = {}
|
|
||||||
): Promise<Fingerprint> {
|
|
||||||
const workflows = await resolveWorkflowPerPlatformAsync(projectDir, vcsClient);
|
|
||||||
const optionsFromWorkflow = getFingerprintOptionsFromWorkflow(platforms, workflows);
|
|
||||||
|
|
||||||
const projectFingerprint = await createFingerprintAsync(projectDir, {
|
|
||||||
...optionsFromWorkflow,
|
|
||||||
platforms: platforms.map(appPlatformToString),
|
|
||||||
debug: true,
|
|
||||||
env,
|
|
||||||
});
|
|
||||||
if (!projectFingerprint) {
|
|
||||||
throw new Error('Project fingerprints can only be computed for projects with SDK 52 or higher');
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadedFingerprint = await maybeUploadFingerprintAsync({
|
|
||||||
hash: projectFingerprint.hash,
|
|
||||||
fingerprint: {
|
|
||||||
fingerprintSources: projectFingerprint.sources,
|
|
||||||
isDebugFingerprintSource: Log.isDebug,
|
|
||||||
},
|
|
||||||
graphqlClient,
|
|
||||||
});
|
|
||||||
await FingerprintMutation.createFingerprintAsync(graphqlClient, projectId, {
|
|
||||||
hash: uploadedFingerprint.hash,
|
|
||||||
source: uploadedFingerprint.fingerprintSource,
|
|
||||||
});
|
|
||||||
|
|
||||||
return projectFingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFingerprintOptionsFromWorkflow(
|
|
||||||
platforms: AppPlatform[],
|
|
||||||
workflowsByPlatform: Record<Platform, Workflow>
|
|
||||||
): { workflow?: Workflow; ignorePaths?: string[] } {
|
|
||||||
if (platforms.length === 0) {
|
|
||||||
throw new Error('Could not determine platform from fingerprint sources');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Single platform case
|
|
||||||
if (platforms.length === 1) {
|
|
||||||
const platform = platforms[0];
|
|
||||||
return { workflow: workflowsByPlatform[appPlatformToPlatform(platform)] };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiple platforms case
|
|
||||||
const workflows = platforms.map(platform => workflowsByPlatform[appPlatformToPlatform(platform)]);
|
|
||||||
|
|
||||||
// If all workflows are the same, return the common workflow
|
|
||||||
const [firstWorkflow, ...restWorkflows] = workflows;
|
|
||||||
if (restWorkflows.every(workflow => workflow === firstWorkflow)) {
|
|
||||||
return { workflow: firstWorkflow };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate ignorePaths for mixed workflows
|
|
||||||
const ignorePaths = platforms
|
|
||||||
.filter(platform => workflowsByPlatform[appPlatformToPlatform(platform)] === Workflow.MANAGED)
|
|
||||||
.map(platform => `${appPlatformToString(platform)}/**/*`);
|
|
||||||
|
|
||||||
return { ignorePaths };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function appPlatformToPlatform(platform: AppPlatform): Platform {
|
|
||||||
switch (platform) {
|
|
||||||
case AppPlatform.Android:
|
|
||||||
return Platform.ANDROID;
|
|
||||||
case AppPlatform.Ios:
|
|
||||||
return Platform.IOS;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported platform: ${platform}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function appPlatformToString(platform: AppPlatform): string {
|
|
||||||
switch (platform) {
|
|
||||||
case AppPlatform.Android:
|
|
||||||
return 'android';
|
|
||||||
case AppPlatform.Ios:
|
|
||||||
return 'ios';
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported platform: ${platform}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stringToAppPlatform(platform: string): AppPlatform {
|
|
||||||
switch (platform) {
|
|
||||||
case 'android':
|
|
||||||
return AppPlatform.Android;
|
|
||||||
case 'ios':
|
|
||||||
return AppPlatform.Ios;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported platform: ${platform}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+37
-374
File diff suppressed because it is too large.
Load diff
@@ -1,55 +0,0 @@
|
|||||||
import { print } from 'graphql';
|
|
||||||
import gql from 'graphql-tag';
|
|
||||||
|
|
||||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
|
||||||
import { withErrorHandlingAsync } from '../client';
|
|
||||||
import {
|
|
||||||
BuildFragment,
|
|
||||||
BuildMetadataInput,
|
|
||||||
CreateLocalBuildMutation,
|
|
||||||
LocalBuildArchiveSourceInput,
|
|
||||||
LocalBuildJobInput,
|
|
||||||
} from '../generated';
|
|
||||||
import { BuildFragmentNode } from '../types/Build';
|
|
||||||
|
|
||||||
export const LocalBuildMutation = {
|
|
||||||
async createLocalBuildAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
appId: string,
|
|
||||||
job: LocalBuildJobInput,
|
|
||||||
artifactSource: LocalBuildArchiveSourceInput,
|
|
||||||
metadata: BuildMetadataInput
|
|
||||||
): Promise<BuildFragment> {
|
|
||||||
const data = await withErrorHandlingAsync(
|
|
||||||
graphqlClient
|
|
||||||
.mutation<CreateLocalBuildMutation>(
|
|
||||||
gql`
|
|
||||||
mutation createLocalBuildMutation(
|
|
||||||
$appId: ID!
|
|
||||||
$jobInput: LocalBuildJobInput!
|
|
||||||
$artifactSource: LocalBuildArchiveSourceInput!
|
|
||||||
$metadata: BuildMetadataInput
|
|
||||||
) {
|
|
||||||
build {
|
|
||||||
createLocalBuild(
|
|
||||||
appId: $appId
|
|
||||||
job: $jobInput
|
|
||||||
artifactSource: $artifactSource
|
|
||||||
metadata: $metadata
|
|
||||||
) {
|
|
||||||
build {
|
|
||||||
id
|
|
||||||
...BuildFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
${print(BuildFragmentNode)}
|
|
||||||
`,
|
|
||||||
{ appId, jobInput: job, artifactSource, metadata }
|
|
||||||
)
|
|
||||||
.toPromise()
|
|
||||||
);
|
|
||||||
return data.build.createLocalBuild.build;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -20,22 +20,20 @@ export interface SignedUrl {
|
|||||||
export const UploadSessionMutation = {
|
export const UploadSessionMutation = {
|
||||||
async createUploadSessionAsync(
|
async createUploadSessionAsync(
|
||||||
graphqlClient: ExpoGraphqlClient,
|
graphqlClient: ExpoGraphqlClient,
|
||||||
type: UploadSessionType,
|
type: UploadSessionType
|
||||||
filename?: string
|
|
||||||
): Promise<SignedUrl> {
|
): Promise<SignedUrl> {
|
||||||
const data = await withErrorHandlingAsync(
|
const data = await withErrorHandlingAsync(
|
||||||
graphqlClient
|
graphqlClient
|
||||||
.mutation<CreateUploadSessionMutation, CreateUploadSessionMutationVariables>(
|
.mutation<CreateUploadSessionMutation, CreateUploadSessionMutationVariables>(
|
||||||
gql`
|
gql`
|
||||||
mutation CreateUploadSessionMutation($type: UploadSessionType!, $filename: String) {
|
mutation CreateUploadSessionMutation($type: UploadSessionType!) {
|
||||||
uploadSession {
|
uploadSession {
|
||||||
createUploadSession(type: $type, filename: $filename)
|
createUploadSession(type: $type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
type,
|
type,
|
||||||
filename,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.toPromise()
|
.toPromise()
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import gql from 'graphql-tag';
|
|
||||||
|
|
||||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
|
||||||
import { withErrorHandlingAsync } from '../client';
|
|
||||||
import {
|
|
||||||
BackgroundJobReceiptByIdQuery,
|
|
||||||
BackgroundJobReceiptByIdQueryVariables,
|
|
||||||
} from '../generated';
|
|
||||||
import { BackgroundJobReceiptNode } from '../types/BackgroundJobReceipt';
|
|
||||||
|
|
||||||
export const BackgroundJobReceiptQuery = {
|
|
||||||
async byIdAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
backgroundJobReceiptId: string
|
|
||||||
): Promise<BackgroundJobReceiptByIdQuery['backgroundJobReceipt']['byId'] | null> {
|
|
||||||
const data = await withErrorHandlingAsync(
|
|
||||||
graphqlClient
|
|
||||||
.query<BackgroundJobReceiptByIdQuery, BackgroundJobReceiptByIdQueryVariables>(
|
|
||||||
gql`
|
|
||||||
query BackgroundJobReceiptById($id: ID!) {
|
|
||||||
backgroundJobReceipt {
|
|
||||||
byId(id: $id) {
|
|
||||||
id
|
|
||||||
...BackgroundJobReceiptData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
${BackgroundJobReceiptNode}
|
|
||||||
`,
|
|
||||||
{ id: backgroundJobReceiptId },
|
|
||||||
{
|
|
||||||
additionalTypenames: ['BackgroundJobReceipt'],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.toPromise()
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.backgroundJobReceipt.byId ?? null;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -7,8 +7,6 @@ import { withErrorHandlingAsync } from '../client';
|
|||||||
import {
|
import {
|
||||||
UpdateBranchBasicInfoFragment,
|
UpdateBranchBasicInfoFragment,
|
||||||
UpdateFragment,
|
UpdateFragment,
|
||||||
ViewUpdateChannelBasicInfoOnAppQuery,
|
|
||||||
ViewUpdateChannelBasicInfoOnAppQueryVariables,
|
|
||||||
ViewUpdateChannelOnAppQuery,
|
ViewUpdateChannelOnAppQuery,
|
||||||
ViewUpdateChannelOnAppQueryVariables,
|
ViewUpdateChannelOnAppQueryVariables,
|
||||||
ViewUpdateChannelsOnAppQuery,
|
ViewUpdateChannelsOnAppQuery,
|
||||||
@@ -43,42 +41,6 @@ export function composeUpdateBranchObject(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ChannelQuery = {
|
export const ChannelQuery = {
|
||||||
async viewUpdateChannelBasicInfoAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
{ appId, channelName }: ViewUpdateChannelBasicInfoOnAppQueryVariables
|
|
||||||
): Promise<
|
|
||||||
NonNullable<ViewUpdateChannelBasicInfoOnAppQuery['app']['byId']['updateChannelByName']>
|
|
||||||
> {
|
|
||||||
const response = await withErrorHandlingAsync(
|
|
||||||
graphqlClient
|
|
||||||
.query<ViewUpdateChannelBasicInfoOnAppQuery, ViewUpdateChannelBasicInfoOnAppQueryVariables>(
|
|
||||||
gql`
|
|
||||||
query ViewUpdateChannelBasicInfoOnApp($appId: String!, $channelName: String!) {
|
|
||||||
app {
|
|
||||||
byId(appId: $appId) {
|
|
||||||
id
|
|
||||||
updateChannelByName(name: $channelName) {
|
|
||||||
id
|
|
||||||
...UpdateChannelBasicInfoFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
${print(UpdateChannelBasicInfoFragmentNode)}
|
|
||||||
`,
|
|
||||||
{ appId, channelName },
|
|
||||||
{ additionalTypenames: ['UpdateChannel', 'UpdateBranch', 'Update'] }
|
|
||||||
)
|
|
||||||
.toPromise()
|
|
||||||
);
|
|
||||||
|
|
||||||
const { updateChannelByName } = response.app.byId;
|
|
||||||
if (!updateChannelByName) {
|
|
||||||
throw new ChannelNotFoundError(`Could not find channel with the name ${channelName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return updateChannelByName;
|
|
||||||
},
|
|
||||||
async viewUpdateChannelAsync(
|
async viewUpdateChannelAsync(
|
||||||
graphqlClient: ExpoGraphqlClient,
|
graphqlClient: ExpoGraphqlClient,
|
||||||
{ appId, channelName, filter }: ViewUpdateChannelOnAppQueryVariables
|
{ appId, channelName, filter }: ViewUpdateChannelOnAppQueryVariables
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
import { print } from 'graphql';
|
|
||||||
import gql from 'graphql-tag';
|
|
||||||
|
|
||||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
|
||||||
import { withErrorHandlingAsync } from '../client';
|
|
||||||
import {
|
|
||||||
FingerprintFilterInput,
|
|
||||||
FingerprintFragment,
|
|
||||||
FingerprintsByAppIdQuery,
|
|
||||||
} from '../generated';
|
|
||||||
import { FingerprintFragmentNode } from '../types/Fingerprint';
|
|
||||||
|
|
||||||
export const FingerprintQuery = {
|
|
||||||
async byHashAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
{
|
|
||||||
appId,
|
|
||||||
hash,
|
|
||||||
}: {
|
|
||||||
appId: string;
|
|
||||||
hash: string;
|
|
||||||
}
|
|
||||||
): Promise<FingerprintFragment | null> {
|
|
||||||
const fingerprintConnection = await FingerprintQuery.getFingerprintsAsync(graphqlClient, {
|
|
||||||
appId,
|
|
||||||
fingerprintFilter: { hashes: [hash] },
|
|
||||||
first: 1,
|
|
||||||
});
|
|
||||||
const fingerprints = fingerprintConnection.edges.map(edge => edge.node);
|
|
||||||
return fingerprints[0] ?? null;
|
|
||||||
},
|
|
||||||
async getFingerprintsAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
{
|
|
||||||
appId,
|
|
||||||
first,
|
|
||||||
after,
|
|
||||||
last,
|
|
||||||
before,
|
|
||||||
fingerprintFilter,
|
|
||||||
}: {
|
|
||||||
appId: string;
|
|
||||||
first?: number;
|
|
||||||
after?: string;
|
|
||||||
last?: number;
|
|
||||||
before?: string;
|
|
||||||
fingerprintFilter?: FingerprintFilterInput;
|
|
||||||
}
|
|
||||||
): Promise<FingerprintsByAppIdQuery['app']['byId']['fingerprintsPaginated']> {
|
|
||||||
const data = await withErrorHandlingAsync(
|
|
||||||
graphqlClient
|
|
||||||
.query<FingerprintsByAppIdQuery>(
|
|
||||||
gql`
|
|
||||||
query FingerprintsByAppId(
|
|
||||||
$appId: String!
|
|
||||||
$after: String
|
|
||||||
$first: Int
|
|
||||||
$before: String
|
|
||||||
$last: Int
|
|
||||||
$fingerprintFilter: FingerprintFilterInput
|
|
||||||
) {
|
|
||||||
app {
|
|
||||||
byId(appId: $appId) {
|
|
||||||
id
|
|
||||||
fingerprintsPaginated(
|
|
||||||
after: $after
|
|
||||||
first: $first
|
|
||||||
before: $before
|
|
||||||
last: $last
|
|
||||||
filter: $fingerprintFilter
|
|
||||||
) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
...FingerprintFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pageInfo {
|
|
||||||
hasNextPage
|
|
||||||
hasPreviousPage
|
|
||||||
startCursor
|
|
||||||
endCursor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
${print(FingerprintFragmentNode)}
|
|
||||||
`,
|
|
||||||
{ appId, after, first, before, last, fingerprintFilter },
|
|
||||||
{ additionalTypenames: ['Fingerprint'] }
|
|
||||||
)
|
|
||||||
.toPromise()
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.app?.byId.fingerprintsPaginated;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -4,8 +4,6 @@ import gql from 'graphql-tag';
|
|||||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
import { withErrorHandlingAsync } from '../client';
|
import { withErrorHandlingAsync } from '../client';
|
||||||
import {
|
import {
|
||||||
UpdateByIdQuery,
|
|
||||||
UpdateByIdQueryVariables,
|
|
||||||
UpdateFragment,
|
UpdateFragment,
|
||||||
ViewUpdateGroupsOnAppQuery,
|
ViewUpdateGroupsOnAppQuery,
|
||||||
ViewUpdateGroupsOnAppQueryVariables,
|
ViewUpdateGroupsOnAppQueryVariables,
|
||||||
@@ -139,30 +137,4 @@ export const UpdateQuery = {
|
|||||||
|
|
||||||
return response.app.byId.updateGroups;
|
return response.app.byId.updateGroups;
|
||||||
},
|
},
|
||||||
async viewByUpdateAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
{ updateId }: UpdateByIdQueryVariables
|
|
||||||
): Promise<UpdateFragment> {
|
|
||||||
const data = await withErrorHandlingAsync(
|
|
||||||
graphqlClient
|
|
||||||
.query<UpdateByIdQuery, UpdateByIdQueryVariables>(
|
|
||||||
gql`
|
|
||||||
query UpdateByIdQuery($updateId: ID!) {
|
|
||||||
updates {
|
|
||||||
byId(updateId: $updateId) {
|
|
||||||
id
|
|
||||||
...UpdateFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
${print(UpdateFragmentNode)}
|
|
||||||
`,
|
|
||||||
{ updateId },
|
|
||||||
{ additionalTypenames: ['Update'] }
|
|
||||||
)
|
|
||||||
.toPromise()
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.updates.byId;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import gql from 'graphql-tag';
|
|
||||||
|
|
||||||
export const BackgroundJobReceiptNode = gql`
|
|
||||||
fragment BackgroundJobReceiptData on BackgroundJobReceipt {
|
|
||||||
id
|
|
||||||
state
|
|
||||||
tries
|
|
||||||
willRetry
|
|
||||||
resultId
|
|
||||||
resultType
|
|
||||||
resultData
|
|
||||||
errorCode
|
|
||||||
errorMessage
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -57,11 +57,6 @@ export const BuildFragmentNode = gql`
|
|||||||
completedAt
|
completedAt
|
||||||
expirationDate
|
expirationDate
|
||||||
isForIosSimulator
|
isForIosSimulator
|
||||||
metrics {
|
|
||||||
buildWaitTime
|
|
||||||
buildQueueTime
|
|
||||||
buildDuration
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -5,23 +5,5 @@ export const FingerprintFragmentNode = gql`
|
|||||||
id
|
id
|
||||||
hash
|
hash
|
||||||
debugInfoUrl
|
debugInfoUrl
|
||||||
builds(first: 1) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
... on Build {
|
|
||||||
platform
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updates(first: 1) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
platform
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -38,7 +38,6 @@ export const UpdateFragmentNode = gql`
|
|||||||
fingerprint {
|
fingerprint {
|
||||||
id
|
id
|
||||||
hash
|
hash
|
||||||
debugInfoUrl
|
|
||||||
source {
|
source {
|
||||||
type
|
type
|
||||||
bucketKey
|
bucketKey
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { createAppleTasks } from './apple/tasks';
|
|||||||
import { getAppStoreAuthAsync } from './auth';
|
import { getAppStoreAuthAsync } from './auth';
|
||||||
import { createAppleWriter, getStaticConfigFilePath } from './config/resolve';
|
import { createAppleWriter, getStaticConfigFilePath } from './config/resolve';
|
||||||
import { MetadataDownloadError, MetadataValidationError } from './errors';
|
import { MetadataDownloadError, MetadataValidationError } from './errors';
|
||||||
import { subscribeTelemetryAsync } from './utils/telemetry';
|
import { subscribeTelemetry } from './utils/telemetry';
|
||||||
import { Analytics, MetadataEvent } from '../analytics/AnalyticsManager';
|
import { Analytics, MetadataEvent } from '../analytics/AnalyticsManager';
|
||||||
import { CredentialsContext } from '../credentials/context';
|
import { CredentialsContext } from '../credentials/context';
|
||||||
import Log from '../log';
|
import Log from '../log';
|
||||||
@@ -51,7 +51,7 @@ export async function downloadMetadataAsync({
|
|||||||
profile,
|
profile,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { unsubscribeTelemetry, executionId } = await subscribeTelemetryAsync(
|
const { unsubscribeTelemetry, executionId } = subscribeTelemetry(
|
||||||
analytics,
|
analytics,
|
||||||
MetadataEvent.APPLE_METADATA_DOWNLOAD,
|
MetadataEvent.APPLE_METADATA_DOWNLOAD,
|
||||||
{ app, auth }
|
{ app, auth }
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { getAppStoreAuthAsync } from './auth';
|
|||||||
import { createAppleReader, loadConfigAsync } from './config/resolve';
|
import { createAppleReader, loadConfigAsync } from './config/resolve';
|
||||||
import { MetadataConfig } from './config/schema';
|
import { MetadataConfig } from './config/schema';
|
||||||
import { MetadataUploadError, MetadataValidationError, logMetadataValidationError } from './errors';
|
import { MetadataUploadError, MetadataValidationError, logMetadataValidationError } from './errors';
|
||||||
import { subscribeTelemetryAsync } from './utils/telemetry';
|
import { subscribeTelemetry } from './utils/telemetry';
|
||||||
import { Analytics, MetadataEvent } from '../analytics/AnalyticsManager';
|
import { Analytics, MetadataEvent } from '../analytics/AnalyticsManager';
|
||||||
import { CredentialsContext } from '../credentials/context';
|
import { CredentialsContext } from '../credentials/context';
|
||||||
import Log from '../log';
|
import Log from '../log';
|
||||||
@@ -38,7 +38,7 @@ export async function uploadMetadataAsync({
|
|||||||
profile,
|
profile,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { unsubscribeTelemetry, executionId } = await subscribeTelemetryAsync(
|
const { unsubscribeTelemetry, executionId } = subscribeTelemetry(
|
||||||
analytics,
|
analytics,
|
||||||
MetadataEvent.APPLE_METADATA_UPLOAD,
|
MetadataEvent.APPLE_METADATA_UPLOAD,
|
||||||
{ app, auth }
|
{ app, auth }
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { App } from '@expo/apple-utils';
|
import { App } from '@expo/apple-utils';
|
||||||
|
|
||||||
import { TelemetryContext, makeDataScrubberAsync } from '../telemetry';
|
import { TelemetryContext, makeDataScrubber } from '../telemetry';
|
||||||
|
|
||||||
describe(makeDataScrubberAsync, () => {
|
describe(makeDataScrubber, () => {
|
||||||
const stub: TelemetryContext = {
|
const stub: TelemetryContext = {
|
||||||
// Only the App ID is considered to be sensitive
|
// Only the App ID is considered to be sensitive
|
||||||
app: new App({} as any, 'SECRET_APP_ID', {} as any),
|
app: new App({} as any, 'SECRET_APP_ID', {} as any),
|
||||||
@@ -18,31 +18,25 @@ describe(makeDataScrubberAsync, () => {
|
|||||||
} as any,
|
} as any,
|
||||||
};
|
};
|
||||||
|
|
||||||
it('scrubs the app.id', async () => {
|
it('scrubs the app.id', () => {
|
||||||
expect((await makeDataScrubberAsync(stub))('some text SECRET_APP_ID')).toBe(
|
expect(makeDataScrubber(stub)('some text SECRET_APP_ID')).toBe('some text {APPLE_APP_ID}');
|
||||||
'some text {APPLE_APP_ID}'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('scrubs the auth.username', async () => {
|
it('scrubs the auth.username', () => {
|
||||||
expect((await makeDataScrubberAsync(stub))('some text SECRET_USERNAME')).toBe(
|
expect(makeDataScrubber(stub)('some text SECRET_USERNAME')).toBe('some text {APPLE_USERNAME}');
|
||||||
'some text {APPLE_USERNAME}'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('scrubs the auth.password', async () => {
|
it('scrubs the auth.password', () => {
|
||||||
expect((await makeDataScrubberAsync(stub))('SECRET_PASSWORD')).toBe('{APPLE_PASSWORD}');
|
expect(makeDataScrubber(stub)('SECRET_PASSWORD')).toBe('{APPLE_PASSWORD}');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('scrubs the auth.context.token', async () => {
|
it('scrubs the auth.context.token', () => {
|
||||||
expect((await makeDataScrubberAsync(stub))('some text SECRET_TOKEN')).toBe(
|
expect(makeDataScrubber(stub)('some text SECRET_TOKEN')).toBe('some text {APPLE_TOKEN}');
|
||||||
'some text {APPLE_TOKEN}'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('scrubs the auth.context.token when using token instances', async () => {
|
it('scrubs the auth.context.token when using token instances', () => {
|
||||||
const token = { getToken: () => 'SECRET_TOKEN_INSTANCE' } as any;
|
const token = { getToken: () => 'SECRET_TOKEN_INSTANCE' } as any;
|
||||||
const scrubber = await makeDataScrubberAsync({
|
const scrubber = makeDataScrubber({
|
||||||
...stub,
|
...stub,
|
||||||
auth: {
|
auth: {
|
||||||
...stub.auth,
|
...stub.auth,
|
||||||
@@ -56,31 +50,29 @@ describe(makeDataScrubberAsync, () => {
|
|||||||
expect(scrubber('some text SECRET_TOKEN_INSTANCE')).toBe('some text {APPLE_TOKEN}');
|
expect(scrubber('some text SECRET_TOKEN_INSTANCE')).toBe('some text {APPLE_TOKEN}');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('scrubs the auth.context.teamId', async () => {
|
it('scrubs the auth.context.teamId', () => {
|
||||||
expect((await makeDataScrubberAsync(stub))('SECRET_TEAM_ID')).toBe('{APPLE_TEAM_ID}');
|
expect(makeDataScrubber(stub)('SECRET_TEAM_ID')).toBe('{APPLE_TEAM_ID}');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('scrubs the auth.context.teamId', async () => {
|
it('scrubs the auth.context.teamId', () => {
|
||||||
expect((await makeDataScrubberAsync(stub))('some provider 1337')).toBe(
|
expect(makeDataScrubber(stub)('some provider 1337')).toBe('some provider {APPLE_PROVIDER_ID}');
|
||||||
'some provider {APPLE_PROVIDER_ID}'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('scrubber returns string representation of falsy values', async () => {
|
it('scrubber returns string representation of falsy values', () => {
|
||||||
const scrubber = await makeDataScrubberAsync(stub);
|
const scrubber = makeDataScrubber(stub);
|
||||||
expect(scrubber(null)).toBe('null');
|
expect(scrubber(null)).toBe('null');
|
||||||
expect(scrubber(undefined)).toBe('undefined');
|
expect(scrubber(undefined)).toBe('undefined');
|
||||||
expect(scrubber(false)).toBe('false');
|
expect(scrubber(false)).toBe('false');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('scrubs multiple sensitive values at once', async () => {
|
it('scrubs multiple sensitive values at once', () => {
|
||||||
expect(
|
expect(makeDataScrubber(stub)('SECRET_TOKEN SECRET_USERNAME SECRET_PASSWORD')).toBe(
|
||||||
(await makeDataScrubberAsync(stub))('SECRET_TOKEN SECRET_USERNAME SECRET_PASSWORD')
|
'{APPLE_TOKEN} {APPLE_USERNAME} {APPLE_PASSWORD}'
|
||||||
).toBe('{APPLE_TOKEN} {APPLE_USERNAME} {APPLE_PASSWORD}');
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('scrubs json and transforms it to string', async () => {
|
it('scrubs json and transforms it to string', () => {
|
||||||
const scrubber = await makeDataScrubberAsync(stub);
|
const scrubber = makeDataScrubber(stub);
|
||||||
expect(scrubber({ foo: 'bar' })).toBe('{"foo":"bar"}');
|
expect(scrubber({ foo: 'bar' })).toBe('{"foo":"bar"}');
|
||||||
expect(scrubber({ value: 'SECRET_APP_ID' })).toBe('{"value":"{APPLE_APP_ID}"}');
|
expect(scrubber({ value: 'SECRET_APP_ID' })).toBe('{"value":"{APPLE_APP_ID}"}');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,18 +14,18 @@ export type TelemetryContext = {
|
|||||||
* When providing the app and auth info, we can scrub that data from the telemetry.
|
* When providing the app and auth info, we can scrub that data from the telemetry.
|
||||||
* Returns an execution ID to group all events of a single run together, and a unsubscribe function.
|
* Returns an execution ID to group all events of a single run together, and a unsubscribe function.
|
||||||
*/
|
*/
|
||||||
export async function subscribeTelemetryAsync(
|
export function subscribeTelemetry(
|
||||||
analytics: Analytics,
|
analytics: Analytics,
|
||||||
event: MetadataEvent,
|
event: MetadataEvent,
|
||||||
options: TelemetryContext
|
options: TelemetryContext
|
||||||
): Promise<{
|
): {
|
||||||
/** Unsubscribe the telemetry from all apple-utils events */
|
/** Unsubscribe the telemetry from all apple-utils events */
|
||||||
unsubscribeTelemetry: () => void;
|
unsubscribeTelemetry: () => void;
|
||||||
/** The unique id added to all telemetry events from a single execution */
|
/** The unique id added to all telemetry events from a single execution */
|
||||||
executionId: string;
|
executionId: string;
|
||||||
}> {
|
} {
|
||||||
const executionId = uuidv4();
|
const executionId = uuidv4();
|
||||||
const scrubber = await makeDataScrubberAsync(options);
|
const scrubber = makeDataScrubber(options);
|
||||||
const { interceptors } = getRequestClient();
|
const { interceptors } = getRequestClient();
|
||||||
|
|
||||||
const responseInterceptorId = interceptors.response.use(
|
const responseInterceptorId = interceptors.response.use(
|
||||||
@@ -68,11 +68,8 @@ export async function subscribeTelemetryAsync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Exposed for testing */
|
/** Exposed for testing */
|
||||||
export async function makeDataScrubberAsync({
|
export function makeDataScrubber({ app, auth }: TelemetryContext): <T>(data: T) => string {
|
||||||
app,
|
const token = getAuthTokenString(auth);
|
||||||
auth,
|
|
||||||
}: TelemetryContext): Promise<<T>(data: T) => string> {
|
|
||||||
const token = await getAuthTokenStringAsync(auth);
|
|
||||||
const patterns: Record<string, RegExp | null> = {
|
const patterns: Record<string, RegExp | null> = {
|
||||||
APPLE_APP_ID: new RegExp(app.id, 'gi'),
|
APPLE_APP_ID: new RegExp(app.id, 'gi'),
|
||||||
APPLE_USERNAME: auth.username ? new RegExp(auth.username, 'gi') : null,
|
APPLE_USERNAME: auth.username ? new RegExp(auth.username, 'gi') : null,
|
||||||
@@ -101,13 +98,13 @@ export async function makeDataScrubberAsync({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAuthTokenStringAsync(auth: TelemetryContext['auth']): Promise<string | null> {
|
function getAuthTokenString(auth: TelemetryContext['auth']): string | null {
|
||||||
if (!auth.context?.token) {
|
if (!auth.context?.token) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof auth.context.token === 'object') {
|
if (typeof auth.context.token === 'object') {
|
||||||
return await auth.context.token.getToken();
|
return auth.context.token.getToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
return auth.context.token;
|
return auth.context.token;
|
||||||
|
|||||||
@@ -424,7 +424,7 @@ describe(collectAssetsAsync, () => {
|
|||||||
expect(await collectAssetsAsync(inputDir)).toEqual({
|
expect(await collectAssetsAsync(inputDir)).toEqual({
|
||||||
android: {
|
android: {
|
||||||
launchAsset: {
|
launchAsset: {
|
||||||
fileExtension: '.js',
|
fileExtension: '.bundle',
|
||||||
contentType: 'application/javascript',
|
contentType: 'application/javascript',
|
||||||
path: `${inputDir}/bundles/android.js`,
|
path: `${inputDir}/bundles/android.js`,
|
||||||
},
|
},
|
||||||
@@ -432,7 +432,7 @@ describe(collectAssetsAsync, () => {
|
|||||||
},
|
},
|
||||||
ios: {
|
ios: {
|
||||||
launchAsset: {
|
launchAsset: {
|
||||||
fileExtension: '.js',
|
fileExtension: '.bundle',
|
||||||
contentType: 'application/javascript',
|
contentType: 'application/javascript',
|
||||||
path: `${inputDir}/bundles/ios.js`,
|
path: `${inputDir}/bundles/ios.js`,
|
||||||
},
|
},
|
||||||
@@ -440,7 +440,7 @@ describe(collectAssetsAsync, () => {
|
|||||||
},
|
},
|
||||||
web: {
|
web: {
|
||||||
launchAsset: {
|
launchAsset: {
|
||||||
fileExtension: '.js',
|
fileExtension: '.bundle',
|
||||||
contentType: 'application/javascript',
|
contentType: 'application/javascript',
|
||||||
path: `${inputDir}/bundles/web.js`,
|
path: `${inputDir}/bundles/web.js`,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -75,9 +75,7 @@ export async function getApplicationIdFromBareAsync(
|
|||||||
gradleContext.flavor
|
gradleContext.flavor
|
||||||
);
|
);
|
||||||
if (applicationIdSuffix) {
|
if (applicationIdSuffix) {
|
||||||
throw new Error(
|
throw new Error('"applicationIdSuffix" in app/build.gradle is not supported.');
|
||||||
'"applicationIdSuffix" in app/build.gradle is not supported, configure the full application ID under productFlavors.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const applicationId = gradleUtils.resolveConfigValue(
|
const applicationId = gradleUtils.resolveConfigValue(
|
||||||
buildGradle,
|
buildGradle,
|
||||||
@@ -91,9 +89,7 @@ export async function getApplicationIdFromBareAsync(
|
|||||||
const buildGradle = await fs.readFile(buildGradlePath, 'utf8');
|
const buildGradle = await fs.readFile(buildGradlePath, 'utf8');
|
||||||
const matchResult = buildGradle.match(/applicationId ['"](.*)['"]/);
|
const matchResult = buildGradle.match(/applicationId ['"](.*)['"]/);
|
||||||
if (buildGradle.match(/applicationIdSuffix/)) {
|
if (buildGradle.match(/applicationIdSuffix/)) {
|
||||||
throw new Error(
|
throw new Error('"applicationIdSuffix" in app/build.gradle is not supported.');
|
||||||
'"applicationIdSuffix" in app/build.gradle is not supported, configure the full application ID under productFlavors.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (buildGradle.match(/productFlavors/)) {
|
if (buildGradle.match(/productFlavors/)) {
|
||||||
throw new AmbiguousApplicationIdError(
|
throw new AmbiguousApplicationIdError(
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export async function validateCustomBuildConfigAsync({
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
`Custom build configuration file ${chalk.bold(
|
`Custom build configuration file ${chalk.bold(
|
||||||
relativeConfigPath
|
relativeConfigPath
|
||||||
)} is ignored by your version control system or .easignore. Remove it from the ignore list to successfully create custom build.`
|
)} is ignored by your version control system. Remove it from the ignore list to successfully create custom build.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,234 +0,0 @@
|
|||||||
import { getConfig } from '@expo/config';
|
|
||||||
import { vol } from 'memfs';
|
|
||||||
|
|
||||||
import * as prompts from '../../../prompts';
|
|
||||||
import { ensureNonExemptEncryptionIsDefinedForManagedProjectAsync } from '../exemptEncryption';
|
|
||||||
|
|
||||||
jest.mock('fs');
|
|
||||||
jest.mock('../../../prompts');
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
vol.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('prompts non-exempt encryption is not used', async () => {
|
|
||||||
const projectRoot = '/project';
|
|
||||||
vol.fromJSON(
|
|
||||||
{
|
|
||||||
'package.json': JSON.stringify({}),
|
|
||||||
'app.json': JSON.stringify({
|
|
||||||
expo: {
|
|
||||||
name: 'myproject',
|
|
||||||
slug: 'myproject',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
projectRoot
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.mocked(prompts.confirmAsync).mockReset().mockResolvedValueOnce(true);
|
|
||||||
|
|
||||||
await ensureNonExemptEncryptionIsDefinedForManagedProjectAsync({
|
|
||||||
projectDir: projectRoot,
|
|
||||||
exp: getConfig(projectRoot, { skipSDKVersionRequirement: true }).exp,
|
|
||||||
nonInteractive: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getConfig(projectRoot, { skipSDKVersionRequirement: true }).rootConfig).toEqual({
|
|
||||||
expo: {
|
|
||||||
ios: {
|
|
||||||
infoPlist: {
|
|
||||||
ITSAppUsesNonExemptEncryption: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
name: 'myproject',
|
|
||||||
slug: 'myproject',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('does not prompt if the value is already set', async () => {
|
|
||||||
const projectRoot = '/project';
|
|
||||||
vol.fromJSON(
|
|
||||||
{
|
|
||||||
'package.json': JSON.stringify({}),
|
|
||||||
'app.json': JSON.stringify({
|
|
||||||
name: 'myproject',
|
|
||||||
slug: 'myproject',
|
|
||||||
ios: {
|
|
||||||
infoPlist: {
|
|
||||||
ITSAppUsesNonExemptEncryption: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
projectRoot
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reset but no mock values.
|
|
||||||
jest.mocked(prompts.confirmAsync).mockReset();
|
|
||||||
|
|
||||||
await ensureNonExemptEncryptionIsDefinedForManagedProjectAsync({
|
|
||||||
projectDir: projectRoot,
|
|
||||||
exp: getConfig(projectRoot, { skipSDKVersionRequirement: true }).exp,
|
|
||||||
nonInteractive: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(prompts.confirmAsync).toHaveBeenCalledTimes(0);
|
|
||||||
|
|
||||||
expect(getConfig(projectRoot, { skipSDKVersionRequirement: true }).rootConfig).toEqual({
|
|
||||||
ios: {
|
|
||||||
infoPlist: {
|
|
||||||
ITSAppUsesNonExemptEncryption: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
name: 'myproject',
|
|
||||||
slug: 'myproject',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('prompts non-exempt encryption in CI', async () => {
|
|
||||||
const projectRoot = '/project';
|
|
||||||
vol.fromJSON(
|
|
||||||
{
|
|
||||||
'package.json': JSON.stringify({}),
|
|
||||||
'app.json': JSON.stringify({
|
|
||||||
expo: {
|
|
||||||
name: 'myproject',
|
|
||||||
slug: 'myproject',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
projectRoot
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reset but no mock values.
|
|
||||||
jest.mocked(prompts.confirmAsync).mockReset();
|
|
||||||
|
|
||||||
await ensureNonExemptEncryptionIsDefinedForManagedProjectAsync({
|
|
||||||
projectDir: projectRoot,
|
|
||||||
exp: getConfig(projectRoot, { skipSDKVersionRequirement: true }).exp,
|
|
||||||
nonInteractive: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getConfig(projectRoot, { skipSDKVersionRequirement: true }).rootConfig).toEqual({
|
|
||||||
expo: {
|
|
||||||
ios: {
|
|
||||||
infoPlist: {
|
|
||||||
ITSAppUsesNonExemptEncryption: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
name: 'myproject',
|
|
||||||
slug: 'myproject',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('prompts non-exempt encryption is not used but app.config.js', async () => {
|
|
||||||
const projectRoot = '/project';
|
|
||||||
vol.fromJSON(
|
|
||||||
{
|
|
||||||
'package.json': JSON.stringify({}),
|
|
||||||
'app.config.js':
|
|
||||||
`module.exports = ` +
|
|
||||||
JSON.stringify({
|
|
||||||
expo: {
|
|
||||||
name: 'myproject',
|
|
||||||
slug: 'myproject',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
projectRoot
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.mocked(prompts.confirmAsync).mockReset().mockResolvedValueOnce(true);
|
|
||||||
|
|
||||||
await ensureNonExemptEncryptionIsDefinedForManagedProjectAsync({
|
|
||||||
projectDir: projectRoot,
|
|
||||||
exp: getConfig(projectRoot, { skipSDKVersionRequirement: true }).exp,
|
|
||||||
nonInteractive: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Not set.
|
|
||||||
expect(getConfig(projectRoot, { skipSDKVersionRequirement: true }).rootConfig).toEqual({});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('prompts non-exempt encryption is used', async () => {
|
|
||||||
const projectRoot = '/project';
|
|
||||||
vol.fromJSON(
|
|
||||||
{
|
|
||||||
'package.json': JSON.stringify({}),
|
|
||||||
'app.json': JSON.stringify({
|
|
||||||
expo: {
|
|
||||||
name: 'myproject',
|
|
||||||
slug: 'myproject',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
projectRoot
|
|
||||||
);
|
|
||||||
|
|
||||||
jest
|
|
||||||
.mocked(prompts.confirmAsync)
|
|
||||||
.mockReset()
|
|
||||||
.mockResolvedValueOnce(false)
|
|
||||||
// Are you sure?
|
|
||||||
.mockResolvedValueOnce(true);
|
|
||||||
|
|
||||||
await ensureNonExemptEncryptionIsDefinedForManagedProjectAsync({
|
|
||||||
projectDir: projectRoot,
|
|
||||||
exp: getConfig(projectRoot, { skipSDKVersionRequirement: true }).exp,
|
|
||||||
nonInteractive: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(prompts.confirmAsync).toHaveBeenCalledTimes(2);
|
|
||||||
|
|
||||||
expect(getConfig(projectRoot, { skipSDKVersionRequirement: true }).rootConfig).toEqual({
|
|
||||||
expo: {
|
|
||||||
name: 'myproject',
|
|
||||||
slug: 'myproject',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('prompts non-exempt encryption is used but backed out', async () => {
|
|
||||||
const projectRoot = '/project';
|
|
||||||
vol.fromJSON(
|
|
||||||
{
|
|
||||||
'package.json': JSON.stringify({}),
|
|
||||||
'app.json': JSON.stringify({
|
|
||||||
expo: {
|
|
||||||
name: 'myproject',
|
|
||||||
slug: 'myproject',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
projectRoot
|
|
||||||
);
|
|
||||||
|
|
||||||
jest
|
|
||||||
.mocked(prompts.confirmAsync)
|
|
||||||
.mockReset()
|
|
||||||
.mockResolvedValueOnce(false)
|
|
||||||
// Are you sure?
|
|
||||||
.mockResolvedValueOnce(false);
|
|
||||||
|
|
||||||
await ensureNonExemptEncryptionIsDefinedForManagedProjectAsync({
|
|
||||||
projectDir: projectRoot,
|
|
||||||
exp: getConfig(projectRoot, { skipSDKVersionRequirement: true }).exp,
|
|
||||||
nonInteractive: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getConfig(projectRoot, { skipSDKVersionRequirement: true }).rootConfig).toEqual({
|
|
||||||
expo: {
|
|
||||||
name: 'myproject',
|
|
||||||
slug: 'myproject',
|
|
||||||
ios: {
|
|
||||||
infoPlist: {
|
|
||||||
ITSAppUsesNonExemptEncryption: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
import { ExpoConfig, getProjectConfigDescription, modifyConfigAsync } from '@expo/config';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
|
|
||||||
import Log, { learnMore } from '../../log';
|
|
||||||
import { confirmAsync } from '../../prompts';
|
|
||||||
|
|
||||||
/** Non-exempt encryption must be set on every build in App Store Connect, we move it to before the build process to attempt only setting it once for the entire life-cycle of the project. */
|
|
||||||
export async function ensureNonExemptEncryptionIsDefinedForManagedProjectAsync({
|
|
||||||
projectDir,
|
|
||||||
exp,
|
|
||||||
nonInteractive,
|
|
||||||
}: {
|
|
||||||
projectDir: string;
|
|
||||||
exp: ExpoConfig;
|
|
||||||
nonInteractive: boolean;
|
|
||||||
}): Promise<void> {
|
|
||||||
// TODO: We could add bare workflow support in the future.
|
|
||||||
// TODO: We could add wizard support for non-exempt encryption in the future.
|
|
||||||
|
|
||||||
const ITSAppUsesNonExemptEncryption =
|
|
||||||
exp.ios?.infoPlist?.ITSAppUsesNonExemptEncryption ?? exp.ios?.config?.usesNonExemptEncryption;
|
|
||||||
|
|
||||||
if (ITSAppUsesNonExemptEncryption == null) {
|
|
||||||
await configureNonExemptEncryptionAsync({
|
|
||||||
projectDir,
|
|
||||||
exp,
|
|
||||||
nonInteractive,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Log.debug(`ITSAppUsesNonExemptEncryption is defined in the app config.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function configureNonExemptEncryptionAsync({
|
|
||||||
projectDir,
|
|
||||||
exp,
|
|
||||||
nonInteractive,
|
|
||||||
}: {
|
|
||||||
projectDir: string;
|
|
||||||
exp: ExpoConfig;
|
|
||||||
nonInteractive: boolean;
|
|
||||||
}): Promise<void> {
|
|
||||||
const description = getProjectConfigDescription(projectDir);
|
|
||||||
if (nonInteractive) {
|
|
||||||
Log.warn(
|
|
||||||
chalk`${description} is missing {bold ios.infoPlist.ITSAppUsesNonExemptEncryption} boolean. Manual configuration is required in App Store Connect before the app can be tested.`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let onlyExemptEncryption = await confirmAsync({
|
|
||||||
message: `iOS app only uses standard/exempt encryption? ${chalk.dim(
|
|
||||||
learnMore(
|
|
||||||
'https://developer.apple.com/documentation/Security/complying-with-encryption-export-regulations'
|
|
||||||
)
|
|
||||||
)}`,
|
|
||||||
initial: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!onlyExemptEncryption) {
|
|
||||||
const confirm = await confirmAsync({
|
|
||||||
message: `Are you sure your app uses non-exempt encryption? Selecting 'Yes' will require annual self-classification reports for the US government.`,
|
|
||||||
initial: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirm) {
|
|
||||||
Log.warn(
|
|
||||||
chalk`Set {bold ios.infoPlist.ITSAppUsesNonExemptEncryption} in ${description} to release Apple builds faster.`
|
|
||||||
);
|
|
||||||
onlyExemptEncryption = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ITSAppUsesNonExemptEncryption = !onlyExemptEncryption;
|
|
||||||
|
|
||||||
// Only set this value if the answer is no, this enables developers to see the more in-depth prompt in App Store Connect. They can set the value manually in the app.json to avoid the EAS prompt in subsequent builds.
|
|
||||||
if (ITSAppUsesNonExemptEncryption) {
|
|
||||||
Log.warn(
|
|
||||||
`You'll need to manually configure the encryption status in App Store Connect before your build can be tested.`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// NOTE: Is is it possible to assert that the config needs to be modifiable before building the app?
|
|
||||||
const modification = await modifyConfigAsync(
|
|
||||||
projectDir,
|
|
||||||
{
|
|
||||||
ios: {
|
|
||||||
...(exp.ios ?? {}),
|
|
||||||
infoPlist: {
|
|
||||||
...(exp.ios?.infoPlist ?? {}),
|
|
||||||
ITSAppUsesNonExemptEncryption,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
skipSDKVersionRequirement: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (modification.type !== 'success') {
|
|
||||||
Log.log();
|
|
||||||
if (modification.type === 'warn') {
|
|
||||||
// The project is using a dynamic config, give the user a helpful log and bail out.
|
|
||||||
Log.log(chalk.yellow(modification.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
const edits = {
|
|
||||||
ios: {
|
|
||||||
infoPlist: {
|
|
||||||
ITSAppUsesNonExemptEncryption,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Log.log(chalk.cyan(`Add the following to ${description}:`));
|
|
||||||
Log.log();
|
|
||||||
Log.log(JSON.stringify(edits, null, 2));
|
|
||||||
Log.log();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import chalk from 'chalk';
|
|
||||||
import fs from 'fs-extra';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
|
||||||
import { AssetMapSourceInput, AssetMapSourceType, UploadSessionType } from '../graphql/generated';
|
|
||||||
import Log from '../log';
|
|
||||||
import { uploadFileAtPathToGCSAsync } from '../uploads';
|
|
||||||
import { formatBytes } from '../utils/files';
|
|
||||||
import { createProgressTracker } from '../utils/progress';
|
|
||||||
|
|
||||||
export async function maybeUploadAssetMapAsync(
|
|
||||||
distRoot: string,
|
|
||||||
graphqlClient: ExpoGraphqlClient
|
|
||||||
): Promise<AssetMapSourceInput | null> {
|
|
||||||
const assetMapPath = path.join(distRoot, 'assetmap.json');
|
|
||||||
|
|
||||||
if (!(await fs.pathExists(assetMapPath))) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let gcsBucketKey = undefined;
|
|
||||||
const fileStat = await fs.promises.stat(assetMapPath);
|
|
||||||
try {
|
|
||||||
gcsBucketKey = await uploadFileAtPathToGCSAsync(
|
|
||||||
graphqlClient,
|
|
||||||
UploadSessionType.EasUpdateAssetsMetadata,
|
|
||||||
assetMapPath,
|
|
||||||
createProgressTracker({
|
|
||||||
total: fileStat.size,
|
|
||||||
message: ratio =>
|
|
||||||
`Uploading assetmap.json (${formatBytes(fileStat.size * ratio)} / ${formatBytes(
|
|
||||||
fileStat.size
|
|
||||||
)})`,
|
|
||||||
completedMessage: (duration: string) => `Uploaded assetmap.json ${chalk.dim(duration)}`,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (err: any) {
|
|
||||||
let errMessage = 'Failed to upload assetmap to EAS';
|
|
||||||
|
|
||||||
if (err.message) {
|
|
||||||
errMessage += `\n\nReason: ${err.message}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.warn(errMessage);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: AssetMapSourceType.Gcs,
|
|
||||||
bucketKey: gcsBucketKey,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -16,18 +16,10 @@ export async function validateMetroConfigForManagedWorkflowAsync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const metroConfig = await loadConfigAsync(ctx.projectDir);
|
const metroConfig = await loadConfigAsync(ctx.projectDir);
|
||||||
|
|
||||||
// SDK <50 | plugins whose presence can be used to determine if the config is extending Expo's Metro config
|
|
||||||
const hasHashAssetFilesPlugin = metroConfig.transformer?.assetPlugins?.find((plugin: string) =>
|
const hasHashAssetFilesPlugin = metroConfig.transformer?.assetPlugins?.find((plugin: string) =>
|
||||||
plugin.match(/expo-asset[/|\\]tools[/|\\]hashAssetFiles/)
|
plugin.match(/expo-asset[/|\\]tools[/|\\]hashAssetFiles/)
|
||||||
);
|
);
|
||||||
|
if (!hasHashAssetFilesPlugin) {
|
||||||
// SDK >=51 | this is a custom property that we inject to ensure cache invalidation between projects.
|
|
||||||
const isExtendingExpoMetroConfig = metroConfig.transformer.hasOwnProperty(
|
|
||||||
'_expoRelativeProjectRoot'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isExtendingExpoMetroConfig && !hasHashAssetFilesPlugin) {
|
|
||||||
Log.warn(
|
Log.warn(
|
||||||
`It looks like that you are using a custom ${chalk.bold(
|
`It looks like that you are using a custom ${chalk.bold(
|
||||||
'metro.config.js'
|
'metro.config.js'
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export function isUsingEASUpdate(exp: ExpoConfig, projectId: string): boolean {
|
|||||||
return exp.updates?.url === getEASUpdateURL(projectId);
|
return exp.updates?.url === getEASUpdateURL(projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getExpoUpdatesPackageVersionIfInstalledAsync(
|
async function getExpoUpdatesPackageVersionIfInstalledAsync(
|
||||||
projectDir: string
|
projectDir: string
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
const maybePackageJson = resolveFrom.silent(projectDir, 'expo-updates/package.json');
|
const maybePackageJson = resolveFrom.silent(projectDir, 'expo-updates/package.json');
|
||||||
|
|||||||
@@ -17,14 +17,11 @@ import { isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync } from '.
|
|||||||
import { resolveRuntimeVersionUsingCLIAsync } from './resolveRuntimeVersionAsync';
|
import { resolveRuntimeVersionUsingCLIAsync } from './resolveRuntimeVersionAsync';
|
||||||
import { selectBranchOnAppAsync } from '../branch/queries';
|
import { selectBranchOnAppAsync } from '../branch/queries';
|
||||||
import { getDefaultBranchNameAsync } from '../branch/utils';
|
import { getDefaultBranchNameAsync } from '../branch/utils';
|
||||||
import { fetchBuildsAsync } from '../commandUtils/builds';
|
|
||||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
import { PaginatedQueryOptions } from '../commandUtils/pagination';
|
import { PaginatedQueryOptions } from '../commandUtils/pagination';
|
||||||
import { FingerprintOptions, createFingerprintsByKeyAsync } from '../fingerprint/cli';
|
|
||||||
import {
|
import {
|
||||||
AppPlatform,
|
AppPlatform,
|
||||||
AssetMetadataStatus,
|
AssetMetadataStatus,
|
||||||
BuildFragment,
|
|
||||||
PartialManifestAsset,
|
PartialManifestAsset,
|
||||||
UpdateRolloutInfo,
|
UpdateRolloutInfo,
|
||||||
UpdateRolloutInfoGroup,
|
UpdateRolloutInfoGroup,
|
||||||
@@ -53,6 +50,7 @@ import { truthy } from '../utils/expodash/filter';
|
|||||||
import groupBy from '../utils/expodash/groupBy';
|
import groupBy from '../utils/expodash/groupBy';
|
||||||
import mapMapAsync from '../utils/expodash/mapMapAsync';
|
import mapMapAsync from '../utils/expodash/mapMapAsync';
|
||||||
import uniqBy from '../utils/expodash/uniqBy';
|
import uniqBy from '../utils/expodash/uniqBy';
|
||||||
|
import { FingerprintOptions, createFingerprintsByKeyAsync } from '../utils/fingerprintCli';
|
||||||
import { Client } from '../vcs/vcs';
|
import { Client } from '../vcs/vcs';
|
||||||
|
|
||||||
// update publish does not currently support web
|
// update publish does not currently support web
|
||||||
@@ -94,7 +92,7 @@ type UpdateInfoGroup = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Partial copy of `@expo/dev-server` `BundleAssetWithFileHashes`
|
// Partial copy of `@expo/dev-server` `BundleAssetWithFileHashes`
|
||||||
export type AssetMap = Record<
|
type AssetMap = Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
httpServerLocation: string;
|
httpServerLocation: string;
|
||||||
@@ -374,7 +372,7 @@ export function filterCollectedAssetsByRequestedPlatforms(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Try to load the asset map for logging the names of assets published */
|
/** Try to load the asset map for logging the names of assets published */
|
||||||
async function loadAssetMapAsync(distRoot: string): Promise<AssetMap | null> {
|
export async function loadAssetMapAsync(distRoot: string): Promise<AssetMap | null> {
|
||||||
const assetMapPath = path.join(distRoot, 'assetmap.json');
|
const assetMapPath = path.join(distRoot, 'assetmap.json');
|
||||||
|
|
||||||
if (!(await fs.pathExists(assetMapPath))) {
|
if (!(await fs.pathExists(assetMapPath))) {
|
||||||
@@ -421,8 +419,7 @@ export async function collectAssetsAsync(dir: string): Promise<CollectedAssets>
|
|||||||
for (const platform of Object.keys(metadata.fileMetadata) as ExpoConfigPlatform[]) {
|
for (const platform of Object.keys(metadata.fileMetadata) as ExpoConfigPlatform[]) {
|
||||||
collectedAssets[platform] = {
|
collectedAssets[platform] = {
|
||||||
launchAsset: {
|
launchAsset: {
|
||||||
// path.extname() returns an empty string when there's no extension so we use || to fall back to .bundle
|
fileExtension: '.bundle',
|
||||||
fileExtension: path.extname(metadata.fileMetadata[platform].bundle) || '.bundle',
|
|
||||||
contentType: 'application/javascript',
|
contentType: 'application/javascript',
|
||||||
path: path.resolve(dir, metadata.fileMetadata[platform].bundle),
|
path: path.resolve(dir, metadata.fileMetadata[platform].bundle),
|
||||||
},
|
},
|
||||||
@@ -1001,48 +998,6 @@ export async function maybeCalculateFingerprintForRuntimeVersionInfoObjectsWitho
|
|||||||
return [...runtimesWithComputedFingerprint, ...runtimesWithPreviouslyComputedFingerprints];
|
return [...runtimesWithComputedFingerprint, ...runtimesWithPreviouslyComputedFingerprints];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function findCompatibleBuildsAsync(
|
|
||||||
graphqlClient: ExpoGraphqlClient,
|
|
||||||
appId: string,
|
|
||||||
runtimeToPlatformsAndFingerprintInfoMapping: {
|
|
||||||
runtimeVersion: string;
|
|
||||||
platforms: UpdatePublishPlatform[];
|
|
||||||
fingerprintInfoGroup: FingerprintInfoGroup;
|
|
||||||
}
|
|
||||||
): Promise<{
|
|
||||||
runtimeVersion: string;
|
|
||||||
platforms: UpdatePublishPlatform[];
|
|
||||||
fingerprintInfoGroupWithCompatibleBuilds: {
|
|
||||||
android?: (FingerprintInfo & { build?: BuildFragment }) | undefined;
|
|
||||||
ios?: (FingerprintInfo & { build?: BuildFragment }) | undefined;
|
|
||||||
};
|
|
||||||
}> {
|
|
||||||
const { fingerprintInfoGroup } = runtimeToPlatformsAndFingerprintInfoMapping;
|
|
||||||
const entriesPromises = Object.entries(fingerprintInfoGroup).map(
|
|
||||||
async ([platform, fingerprintInfo]) => {
|
|
||||||
const build = (
|
|
||||||
await fetchBuildsAsync({
|
|
||||||
graphqlClient,
|
|
||||||
projectId: appId,
|
|
||||||
filters: {
|
|
||||||
fingerprintHash: fingerprintInfo.fingerprintHash,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)[0] as BuildFragment | undefined;
|
|
||||||
return [platform, { ...fingerprintInfo, build }];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const entries = await Promise.all(entriesPromises);
|
|
||||||
const fingerprintInfoGroupWithCompatibleBuilds = Object.fromEntries(entries) as {
|
|
||||||
android?: (FingerprintInfo & { build?: BuildFragment }) | undefined;
|
|
||||||
ios?: (FingerprintInfo & { build?: BuildFragment }) | undefined;
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
...runtimeToPlatformsAndFingerprintInfoMapping,
|
|
||||||
fingerprintInfoGroupWithCompatibleBuilds,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const platformDisplayNames: Record<UpdatePublishPlatform, string> = {
|
export const platformDisplayNames: Record<UpdatePublishPlatform, string> = {
|
||||||
android: 'Android',
|
android: 'Android',
|
||||||
ios: 'iOS',
|
ios: 'iOS',
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
|
||||||
import {
|
import { makeProjectTarballAsync } from '../build/utils/repository';
|
||||||
assertProjectTarballSizeDoesNotExceedLimit,
|
|
||||||
makeProjectTarballAsync,
|
|
||||||
maybeWarnAboutProjectTarballSize,
|
|
||||||
} from '../build/utils/repository';
|
|
||||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
import { AccountUploadSessionType } from '../graphql/generated';
|
import { AccountUploadSessionType } from '../graphql/generated';
|
||||||
import Log, { learnMore } from '../log';
|
import Log, { learnMore } from '../log';
|
||||||
@@ -40,8 +36,19 @@ export async function uploadAccountScopedProjectSourceAsync({
|
|||||||
const projectTarball = await makeProjectTarballAsync(vcsClient);
|
const projectTarball = await makeProjectTarballAsync(vcsClient);
|
||||||
projectTarballPath = projectTarball.path;
|
projectTarballPath = projectTarball.path;
|
||||||
|
|
||||||
maybeWarnAboutProjectTarballSize(projectTarball.size);
|
if (projectTarball.size > 1024 * 1024 * 100) {
|
||||||
assertProjectTarballSizeDoesNotExceedLimit(projectTarball.size);
|
Log.warn(
|
||||||
|
`Your project archive is ${formatBytes(
|
||||||
|
projectTarball.size
|
||||||
|
)}. You can reduce its size and the time it takes to upload by excluding files that are unnecessary for the build process in ${chalk.bold(
|
||||||
|
'.easignore'
|
||||||
|
)} file. ${learnMore('https://expo.fyi/eas-build-archive')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectTarball.size > 2 * 1024 * 1024 * 1024) {
|
||||||
|
throw new Error('Project archive is too big. Maximum allowed size is 2GB.');
|
||||||
|
}
|
||||||
|
|
||||||
const projectArchiveBucketKey = await uploadAccountScopedFileAtPathToGCSAsync(graphqlClient, {
|
const projectArchiveBucketKey = await uploadAccountScopedFileAtPathToGCSAsync(graphqlClient, {
|
||||||
accountId,
|
accountId,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import spawnAsync from '@expo/spawn-async';
|
import spawnAsync from '@expo/spawn-async';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import fg from 'fast-glob';
|
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
@@ -33,45 +32,6 @@ export async function getXcodeVersionAsync(): Promise<string | undefined> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type XcodeBuildSettings = {
|
|
||||||
action: string;
|
|
||||||
buildSettings: {
|
|
||||||
BUILD_DIR: string;
|
|
||||||
CONFIGURATION_BUILD_DIR: string;
|
|
||||||
EXECUTABLE_FOLDER_PATH: string;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER: string;
|
|
||||||
TARGET_BUILD_DIR: string;
|
|
||||||
UNLOCALIZED_RESOURCES_FOLDER_PATH: string;
|
|
||||||
};
|
|
||||||
target: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function getXcodeBuildSettingsAsync(
|
|
||||||
xcworkspacePath: string,
|
|
||||||
scheme: string
|
|
||||||
): Promise<XcodeBuildSettings[]> {
|
|
||||||
const { stdout } = await spawnAsync('xcodebuild', [
|
|
||||||
'-workspace',
|
|
||||||
xcworkspacePath,
|
|
||||||
'-scheme',
|
|
||||||
scheme,
|
|
||||||
'-showBuildSettings',
|
|
||||||
'-json',
|
|
||||||
]);
|
|
||||||
return JSON.parse(stdout);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function resolveXcodeProjectAsync(projectRoot: string): Promise<string | undefined> {
|
|
||||||
const ignoredPaths = ['**/@(Carthage|Pods|vendor|node_modules)/**'];
|
|
||||||
const paths = await fg(`ios/*.xcworkspace`, {
|
|
||||||
cwd: projectRoot,
|
|
||||||
onlyDirectories: true,
|
|
||||||
ignore: ignoredPaths,
|
|
||||||
});
|
|
||||||
|
|
||||||
return paths[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function openAppStoreAsync(appId: string): Promise<void> {
|
export async function openAppStoreAsync(appId: string): Promise<void> {
|
||||||
const link = getAppStoreLink(appId);
|
const link = getAppStoreLink(appId);
|
||||||
await spawnAsync(`open`, [link]);
|
await spawnAsync(`open`, [link]);
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import path from 'path';
|
|
||||||
|
|
||||||
import { runAppOnAndroidEmulatorAsync } from './android/run';
|
import { runAppOnAndroidEmulatorAsync } from './android/run';
|
||||||
import { runAppOnIosSimulatorAsync } from './ios/run';
|
import { runAppOnIosSimulatorAsync } from './ios/run';
|
||||||
import { AppPlatform } from '../graphql/generated';
|
import { AppPlatform } from '../graphql/generated';
|
||||||
import { getEasBuildRunCacheDirectoryPath } from '../utils/paths';
|
|
||||||
|
|
||||||
export interface RunArchiveFlags {
|
export interface RunArchiveFlags {
|
||||||
latest?: boolean;
|
latest?: boolean;
|
||||||
@@ -22,14 +19,3 @@ export async function runAsync(
|
|||||||
await runAppOnAndroidEmulatorAsync(simulatorBuildPath);
|
await runAppOnAndroidEmulatorAsync(simulatorBuildPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEasBuildRunCachedAppPath(
|
|
||||||
projectId: string,
|
|
||||||
buildId: string,
|
|
||||||
platform: AppPlatform
|
|
||||||
): string {
|
|
||||||
return path.join(
|
|
||||||
getEasBuildRunCacheDirectoryPath(),
|
|
||||||
`${projectId}_${buildId}.${platform === AppPlatform.Ios ? 'app' : 'apk'}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ function isAab(build: BuildFragment): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function didArtifactsExpire(build: BuildFragment): boolean {
|
function didArtifactsExpire(build: BuildFragment): boolean {
|
||||||
return new Date().getTime() > new Date(build.expirationDate).getTime();
|
return new Date().getTime() - new Date(build.completedAt).getTime() > 30 * 24 * 60 * 60 * 1000; // 30 days
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isRunnableOnSimulatorOrEmulator(build: BuildFragment): boolean {
|
export function isRunnableOnSimulatorOrEmulator(build: BuildFragment): boolean {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
ensureAppExistsAsync,
|
ensureAppExistsAsync,
|
||||||
ensureBundleIdExistsWithNameAsync,
|
ensureBundleIdExistsWithNameAsync,
|
||||||
} from '../../credentials/ios/appstore/ensureAppExists';
|
} from '../../credentials/ios/appstore/ensureAppExists';
|
||||||
import { ensureTestFlightGroupExistsAsync } from '../../credentials/ios/appstore/ensureTestFlightGroup';
|
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { getBundleIdentifierAsync } from '../../project/ios/bundleIdentifier';
|
import { getBundleIdentifierAsync } from '../../project/ios/bundleIdentifier';
|
||||||
import { promptAsync } from '../../prompts';
|
import { promptAsync } from '../../prompts';
|
||||||
@@ -93,16 +92,6 @@ async function createAppStoreConnectAppAsync(
|
|||||||
sku,
|
sku,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
|
||||||
await ensureTestFlightGroupExistsAsync(app);
|
|
||||||
} catch (error: any) {
|
|
||||||
// This process is not critical to the app submission so we shouldn't let it fail the entire process.
|
|
||||||
Log.error(
|
|
||||||
'Failed to create an internal TestFlight group. This can be done manually in the App Store Connect website.'
|
|
||||||
);
|
|
||||||
Log.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ascAppIdentifier: app.id,
|
ascAppIdentifier: app.id,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ import { AndroidConfig, AndroidManifest, XML } from '@expo/config-plugins';
|
|||||||
import { Env, Workflow } from '@expo/eas-build-job';
|
import { Env, Workflow } from '@expo/eas-build-job';
|
||||||
|
|
||||||
import { RequestedPlatform } from '../../platform';
|
import { RequestedPlatform } from '../../platform';
|
||||||
import {
|
import { isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync } from '../../project/projectUtils';
|
||||||
getExpoUpdatesPackageVersionIfInstalledAsync,
|
|
||||||
isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { expoUpdatesCommandAsync } from '../../utils/expoUpdatesCli';
|
import { expoUpdatesCommandAsync } from '../../utils/expoUpdatesCli';
|
||||||
import { ensureValidVersions } from '../utils';
|
import { ensureValidVersions } from '../utils';
|
||||||
|
|
||||||
@@ -35,16 +32,13 @@ export async function syncUpdatesConfigurationAsync({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const expoUpdatesPackageVersion = await getExpoUpdatesPackageVersionIfInstalledAsync(projectDir);
|
|
||||||
|
|
||||||
// sync AndroidManifest.xml
|
// sync AndroidManifest.xml
|
||||||
const androidManifestPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectDir);
|
const androidManifestPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectDir);
|
||||||
const androidManifest = await getAndroidManifestAsync(projectDir);
|
const androidManifest = await getAndroidManifestAsync(projectDir);
|
||||||
const updatedAndroidManifest = await AndroidConfig.Updates.setUpdatesConfigAsync(
|
const updatedAndroidManifest = await AndroidConfig.Updates.setUpdatesConfigAsync(
|
||||||
projectDir,
|
projectDir,
|
||||||
exp,
|
exp,
|
||||||
androidManifest,
|
androidManifest
|
||||||
expoUpdatesPackageVersion
|
|
||||||
);
|
);
|
||||||
await AndroidConfig.Manifest.writeAndroidManifestAsync(
|
await AndroidConfig.Manifest.writeAndroidManifestAsync(
|
||||||
androidManifestPath,
|
androidManifestPath,
|
||||||
|
|||||||
@@ -371,7 +371,6 @@ export async function ensureEASUpdateIsConfiguredAsync({
|
|||||||
vcsClient,
|
vcsClient,
|
||||||
platform,
|
platform,
|
||||||
env,
|
env,
|
||||||
forceNativeConfigSync,
|
|
||||||
}: {
|
}: {
|
||||||
exp: ExpoConfig;
|
exp: ExpoConfig;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -379,7 +378,6 @@ export async function ensureEASUpdateIsConfiguredAsync({
|
|||||||
vcsClient: Client;
|
vcsClient: Client;
|
||||||
platform: RequestedPlatform | null;
|
platform: RequestedPlatform | null;
|
||||||
env: Env | undefined;
|
env: Env | undefined;
|
||||||
forceNativeConfigSync?: boolean;
|
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const hasExpoUpdates = isExpoUpdatesInstalledOrAvailable(
|
const hasExpoUpdates = isExpoUpdatesInstalledOrAvailable(
|
||||||
projectDir,
|
projectDir,
|
||||||
@@ -411,7 +409,7 @@ export async function ensureEASUpdateIsConfiguredAsync({
|
|||||||
workflows,
|
workflows,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (forceNativeConfigSync || projectChanged || !hasExpoUpdates) {
|
if (projectChanged || !hasExpoUpdates) {
|
||||||
await ensureEASUpdateIsConfiguredNativelyAsync(vcsClient, {
|
await ensureEASUpdateIsConfiguredNativelyAsync(vcsClient, {
|
||||||
exp: expWithUpdates,
|
exp: expWithUpdates,
|
||||||
projectDir,
|
projectDir,
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ import { IOSConfig } from '@expo/config-plugins';
|
|||||||
import { Env, Workflow } from '@expo/eas-build-job';
|
import { Env, Workflow } from '@expo/eas-build-job';
|
||||||
|
|
||||||
import { RequestedPlatform } from '../../platform';
|
import { RequestedPlatform } from '../../platform';
|
||||||
import {
|
import { isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync } from '../../project/projectUtils';
|
||||||
getExpoUpdatesPackageVersionIfInstalledAsync,
|
|
||||||
isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { expoUpdatesCommandAsync } from '../../utils/expoUpdatesCli';
|
import { expoUpdatesCommandAsync } from '../../utils/expoUpdatesCli';
|
||||||
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
|
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
|
||||||
import { Client } from '../../vcs/vcs';
|
import { Client } from '../../vcs/vcs';
|
||||||
@@ -36,14 +33,12 @@ export async function syncUpdatesConfigurationAsync({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const expoUpdatesPackageVersion = await getExpoUpdatesPackageVersionIfInstalledAsync(projectDir);
|
|
||||||
const expoPlist = await readExpoPlistAsync(projectDir);
|
const expoPlist = await readExpoPlistAsync(projectDir);
|
||||||
// TODO(wschurman): this dependency needs to be updated for fingerprint
|
// TODO(wschurman): this dependency needs to be updated for fingerprint
|
||||||
const updatedExpoPlist = await IOSConfig.Updates.setUpdatesConfigAsync(
|
const updatedExpoPlist = await IOSConfig.Updates.setUpdatesConfigAsync(
|
||||||
projectDir,
|
projectDir,
|
||||||
exp,
|
exp,
|
||||||
expoPlist,
|
expoPlist
|
||||||
expoUpdatesPackageVersion
|
|
||||||
);
|
);
|
||||||
await writeExpoPlistAsync(vcsClient, projectDir, updatedExpoPlist);
|
await writeExpoPlistAsync(vcsClient, projectDir, updatedExpoPlist);
|
||||||
}
|
}
|
||||||
|
|||||||
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user