mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
122
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8d7ac68da | ||
|
|
1b713af42f | ||
|
|
3ac6472682 | ||
|
|
4b8a9d195f | ||
|
|
30c5e9f4c1 | ||
|
|
d0c8f6bab7 | ||
|
|
faf6bffc62 | ||
|
|
ad55fdae37 | ||
|
|
b5f77404cb | ||
|
|
a1d4f26236 | ||
|
|
6c7bae2349 | ||
|
|
d7e48c2894 | ||
|
|
918a7f9501 | ||
|
|
852dac030f | ||
|
|
911cc90b93 | ||
|
|
b9234abff1 | ||
|
|
574bde9831 | ||
|
|
78837a413a | ||
|
|
6f63315f8e | ||
|
|
e8ac97c8cc | ||
|
|
a6fc31787d | ||
|
|
3056d8e23a | ||
|
|
1bf6757a72 | ||
|
|
b948dd5674 | ||
|
|
0e7a8bfe5e | ||
|
|
b86696fcdf | ||
|
|
f334bdbcc7 | ||
|
|
f16433048a | ||
|
|
e83061de36 | ||
|
|
73e0170836 | ||
|
|
078c7287d3 | ||
|
|
a65e691325 | ||
|
|
b306109676 | ||
|
|
035ca03b96 | ||
|
|
b3d092423b | ||
|
|
b768966ec4 | ||
|
|
1b40c1c20a | ||
|
|
98344b8fc2 | ||
|
|
f5480b4770 | ||
|
|
a2929ad364 | ||
|
|
6deee68a00 | ||
|
|
16d190ab84 | ||
|
|
6f2fc7c37b | ||
|
|
1b78407f15 | ||
|
|
05c8fde824 | ||
|
|
d9af6cb301 | ||
|
|
365e71f042 | ||
|
|
e74c606efa | ||
|
|
28f3a92c38 | ||
|
|
be9fb59e6d | ||
|
|
365a7465f4 | ||
|
|
85a66629c8 | ||
|
|
8a2d68fcb5 | ||
|
|
62819e0956 | ||
|
|
5c6880f4ce | ||
|
|
d5f562edf8 | ||
|
|
077d8b3d86 | ||
|
|
21c8131bcf | ||
|
|
18bf41782f | ||
|
|
0513fc51a6 | ||
|
|
528988dc0f | ||
|
|
7f46baeb21 | ||
|
|
608d61ed69 | ||
|
|
2b2b20a0f9 | ||
|
|
77423d1179 | ||
|
|
ebc277dfe4 | ||
|
|
427c8f152d | ||
|
|
1c245d23b4 | ||
|
|
29c4695351 | ||
|
|
7b968f6e0d | ||
|
|
8a38cc24e1 | ||
|
|
a85b9e2d80 | ||
|
|
e0f69327da | ||
|
|
76d4e65d92 | ||
|
|
58917ecbb4 | ||
|
|
e75a102361 | ||
|
|
061f626b2c | ||
|
|
3159550468 | ||
|
|
a20bd0051e | ||
|
|
0b64d1c8df | ||
|
|
fe37600323 | ||
|
|
ab507d0bdd | ||
|
|
7505b8d40a | ||
|
|
b31ddee6e7 | ||
|
|
01ce802b73 | ||
|
|
f75ef22154 | ||
|
|
7d3f4e8fd4 | ||
|
|
004caf4bb7 | ||
|
|
5a16f3d6d8 | ||
|
|
d2e4cb5fc6 | ||
|
|
2c1c9b91cb | ||
|
|
b447194c85 | ||
|
|
9ab095597a | ||
|
|
b1edff7242 | ||
|
|
4d9e37269c | ||
|
|
e7b48949db | ||
|
|
1c3edbe492 | ||
|
|
deaa3a9105 | ||
|
|
cbfcdaec5d | ||
|
|
37a32c93ee | ||
|
|
6461a78fa2 | ||
|
|
86c8ac093f | ||
|
|
608cdff981 | ||
|
|
4dc4ef85fa | ||
|
|
eaf15acb76 | ||
|
|
ae4ca91658 | ||
|
|
23ebb77952 | ||
|
|
ed2352f289 | ||
|
|
38ae8b68f1 | ||
|
|
5e02807ac9 | ||
|
|
f05d6ca3a2 | ||
|
|
b57082647d | ||
|
|
9d4fdc3c75 | ||
|
|
d1af9e385f | ||
|
|
59bbe19dde | ||
|
|
5e5b55f9e4 | ||
|
|
a1d086ffc6 | ||
|
|
2a96d04be3 | ||
|
|
d126ee988a | ||
|
|
4bb550e3bb | ||
|
|
6a55c65908 | ||
|
|
ab3495da5e |
No files matched your search
@@ -226,16 +226,3 @@ jobs:
|
||||
SLACK_USERNAME: 'EAS CLI CI'
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
SLACK_FOOTER: ''
|
||||
open-pr-in-universe:
|
||||
name: Open a PR with `@expo/eas-json` bump in universe
|
||||
needs: publish-to-npm
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Open a PR with `@expo/eas-json` version bump in universe
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
repo: expo/universe
|
||||
ref: main
|
||||
workflow: update-eas-json.yml
|
||||
token: ${{ secrets.EXPO_BOT_PAT }}
|
||||
@@ -28,6 +28,9 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: yarn typecheck
|
||||
- run: |
|
||||
git config --global user.email "[email protected]"
|
||||
git config --global user.name "Test"
|
||||
- run: yarn test
|
||||
if: ${{ !matrix.coverage }}
|
||||
env:
|
||||
@@ -49,7 +52,7 @@ jobs:
|
||||
|
||||
notify-slack:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
needs: [test]
|
||||
name: Notify Slack
|
||||
if: ${{ github.ref == 'refs/heads/main' && always() }}
|
||||
steps:
|
||||
|
||||
+190
@@ -8,7 +8,197 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Fix `eas fingerprint:compare` URL generation and pretty prints. ([#2909](https://github.com/expo/eas-cli/pull/2909) by [@quinlanj](https://github.com/quinlanj))
|
||||
- fix formatFields to handle empty array. ([#2914](https://github.com/expo/eas-cli/pull/2914) by [@quinlanj](https://github.com/quinlanj))
|
||||
- fix git diff header in `fingerprint:compare`. ([#2915](https://github.com/expo/eas-cli/pull/2915) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [15.0.12](https://github.com/expo/eas-cli/releases/tag/v15.0.12) - 2025-02-22
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix APNS key creation. ([#2916](https://github.com/expo/eas-cli/pull/2916) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
|
||||
## [15.0.11](https://github.com/expo/eas-cli/releases/tag/v15.0.11) - 2025-02-21
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix internal TestFlight group creation. ([#2906](https://github.com/expo/eas-cli/pull/2906) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Narrow amount of data queried for basic update channel operations. ([#2901](https://github.com/expo/eas-cli/pull/2901) by [@wschurman](https://github.com/wschurman))
|
||||
- Fix `eas fingerprint:compare` description. ([#2908](https://github.com/expo/eas-cli/pull/2908) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Skip auto-creation of TestFlight group when there are already exisitng TestFlight groups and allow to opt out of the behavior by setting `EAS_NO_AUTO_TESTFLIGHT_SETUP` env var. ([#2856](https://github.com/expo/eas-cli/pull/2856) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [15.0.10](https://github.com/expo/eas-cli/releases/tag/v15.0.10) - 2025-02-11
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix files deleted in working directory not being removed from the project archive when `requireCommit` is false. ([#2900](https://github.com/expo/eas-cli/pull/2900) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
## [15.0.9](https://github.com/expo/eas-cli/releases/tag/v15.0.9) - 2025-02-09
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix files not being ignored when creating a tarball on Windows in Git repository in no `requireCommit` mode. ([#2894](https://github.com/expo/eas-cli/pull/2894) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
## [15.0.8](https://github.com/expo/eas-cli/releases/tag/v15.0.8) - 2025-02-09
|
||||
|
||||
## [15.0.7](https://github.com/expo/eas-cli/releases/tag/v15.0.7) - 2025-02-09
|
||||
|
||||
## [15.0.6](https://github.com/expo/eas-cli/releases/tag/v15.0.6) - 2025-02-07
|
||||
|
||||
## [15.0.5](https://github.com/expo/eas-cli/releases/tag/v15.0.5) - 2025-02-06
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Do not copy files over onto a cloned Git repository when packing the project archive if `requireCommit` is true. ([#2885](https://github.com/expo/eas-cli/pull/2885) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
- Fix `EISDIR` error when archiving project with submodules ignored. ([#2884](https://github.com/expo/eas-cli/pull/2884) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
## [15.0.4](https://github.com/expo/eas-cli/releases/tag/v15.0.4) - 2025-02-05
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fixed `GitClient` not respecting `.easignore` file. ([#2873](https://github.com/expo/eas-cli/pull/2873) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
- Fix symlink support in `makeShallowCopyAsync`. ([#2874](https://github.com/expo/eas-cli/pull/2874) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
- Allow excluding `.git` directory from project archive by adding it to `.easignore`. ([#2879](https://github.com/expo/eas-cli/pull/2879) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Popup website in fingerprint:compare. ([#2859](https://github.com/expo/eas-cli/pull/2859) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Fix fingerprint:compare URL. ([#2861](https://github.com/expo/eas-cli/pull/2861) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Make less gql calls in fingerprint:compare. ([#2860](https://github.com/expo/eas-cli/pull/2860) by [@quinlanj](https://github.com/quinlanj))
|
||||
- No longer require owner field for SDK >= 53 or canary. ([#2835](https://github.com/expo/eas-cli/pull/2835) by [@wschurman](https://github.com/wschurman))
|
||||
- Add --open flag to fingerprint:compare. ([#2872](https://github.com/expo/eas-cli/pull/2872) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [15.0.3](https://github.com/expo/eas-cli/releases/tag/v15.0.3) - 2025-02-04
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fixed EAS server environment variables does not pass to `npx expo-updates runtimeversion:resolve` call. ([#2867](https://github.com/expo/eas-cli/pull/2867) by [@kudo](https://github.com/kudo))
|
||||
|
||||
## [15.0.2](https://github.com/expo/eas-cli/releases/tag/v15.0.2) - 2025-02-04
|
||||
|
||||
## [15.0.1](https://github.com/expo/eas-cli/releases/tag/v15.0.1) - 2025-02-04
|
||||
|
||||
## [15.0.0](https://github.com/expo/eas-cli/releases/tag/v15.0.0) - 2025-02-04
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Use Git to archive projects containing a Git repository. (Previously, Git would only be used if `requireCommit` flag in `eas.json` was set to `true`.) ([#2841](https://github.com/expo/eas-cli/pull/2841) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Print warning for `NoVcsClient` only once. ([#2863](https://github.com/expo/eas-cli/pull/2863) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add update support for fingerprint:compare. ([#2850](https://github.com/expo/eas-cli/pull/2850) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add update group id support for fingerprint:compare. ([#2851](https://github.com/expo/eas-cli/pull/2851) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add better interactive support for fingerprint:compare. ([#2854](https://github.com/expo/eas-cli/pull/2854) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [14.7.1](https://github.com/expo/eas-cli/releases/tag/v14.7.1) - 2025-01-31
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Account for `ios.config.usesNonExemptEncryption` in non-exempt encryption status prompt. ([#2852](https://github.com/expo/eas-cli/pull/2852) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
|
||||
## [14.7.0](https://github.com/expo/eas-cli/releases/tag/v14.7.0) - 2025-01-30
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `--submit` and `-s` as aliases for `--auto-submit` flag. ([#2846](https://github.com/expo/eas-cli/pull/2846) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Skip non-exempt check in non-interactive mode. ([#2849](https://github.com/expo/eas-cli/pull/2849) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
|
||||
## [14.6.0](https://github.com/expo/eas-cli/releases/tag/v14.6.0) - 2025-01-30
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Prompt to set non-exempt encryption status for the iOS app to support faster store submissions. ([#2843](https://github.com/expo/eas-cli/pull/2843) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Automatically create internal TestFlight group in EAS Submit command. ([#2839](https://github.com/expo/eas-cli/pull/2839) by [@evanbacon](https://github.com/evanbacon))
|
||||
- Sanitize and generate names for EAS Submit to prevent failures due to invalid characters or taken names. ([#2842](https://github.com/expo/eas-cli/pull/2842) by [@evanbacon](https://github.com/evanbacon))
|
||||
- Release `eas fingerprint:compare`. ([#2821](https://github.com/expo/eas-cli/pull/2821) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Make new autoIncremented builds start at nr 1 by default ([#2828](https://github.com/expo/eas-cli/pull/2828) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
|
||||
## [14.5.0](https://github.com/expo/eas-cli/releases/tag/v14.5.0) - 2025-01-22
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Allow filtering by `--fingerprint_hash` in `eas build:list` command. ([#2818](https://github.com/expo/eas-cli/pull/2818) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Ensure that the AASA file is served with content type application/json ([#2829](https://github.com/expo/eas-cli/pull/2829) by [@kadikraman](https://github.com/kadikraman))
|
||||
- Ensure that the AppleID provided in prompt or saved to cache does not contain invalid unprintable characters ([#2830](https://github.com/expo/eas-cli/pull/2830) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Fix logs typos in the `eas deploy` command. ([#2822](https://github.com/expo/eas-cli/pull/2822) by [@kadikraman](https://github.com/kadikraman))
|
||||
- Make `deploy` the top level command for hosting. ([#2824](https://github.com/expo/eas-cli/pull/2824) by [@kadikraman](https://github.com/kadikraman))
|
||||
- Allow longer submit profile extension chain (up to 5, same as build profile) ([#2831](https://github.com/expo/eas-cli/pull/2831) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
- Make variable naming more explicit, remove deprecated runtimeFingerprintSource uses. ([#2816](https://github.com/expo/eas-cli/pull/2816) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
## [14.4.1](https://github.com/expo/eas-cli/releases/tag/v14.4.1) - 2025-01-15
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Enable shell execution for env:exec commands. ([#2788](https://github.com/expo/eas-cli/pull/2788) by [@tharakadesilva](https://github.com/tharakadesilva))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Make automatic env resolution message shorter. ([#2806](https://github.com/expo/eas-cli/pull/2806) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Make "No remote versions are configured" message green instead of yellow. ([#2805](https://github.com/expo/eas-cli/pull/2805) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Upload local fingerprint on `eas fingerprint:compare`. ([#2808](https://github.com/expo/eas-cli/pull/2808) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Upgrade `eas-cli-local-build-plugin` to `1.0.163` to support Bun's new text-based lock file in local builds. ([#2817](https://github.com/expo/eas-cli/pull/2817) by [@shiroyasha9](https://github.com/shiroyasha9))
|
||||
|
||||
## [14.4.0](https://github.com/expo/eas-cli/releases/tag/v14.4.0) - 2025-01-09
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Load `.env` variables even when `--environment` is specified for `deploy` command. Conflicts will be highlighted by a warning message. ([#2783](https://github.com/expo/eas-cli/pull/2783) by [@kitten](https://github.com/kitten))
|
||||
- Silence all non-command output in non-interactive mode of eas env:exec. ([#2800](https://github.com/expo/eas-cli/pull/2800) by [@wschurman](https://github.com/wschurman))
|
||||
- Unhide `deploy` and `deploy:alias` commands ([#2807](https://github.com/expo/eas-cli/pull/2807) by [@kitten](https://github.com/kitten))
|
||||
|
||||
## [14.3.1](https://github.com/expo/eas-cli/releases/tag/v14.3.1) - 2025-01-08
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Bump `@expo/[email protected]` to support Bun text-based lock files. ([#2801](https://github.com/expo/eas-cli/pull/2801) by [@kudo](https://github.com/kudo))
|
||||
|
||||
## [14.3.0](https://github.com/expo/eas-cli/releases/tag/v14.3.0) - 2025-01-07
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Upload `package.json` when uploading workflow sources. ([#2786](https://github.com/expo/eas-cli/pull/2786) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Show `eas deploy` upload error messages. ([#2771](https://github.com/expo/eas-cli/pull/2771) by [@kadikraman](https://github.com/kadikraman))
|
||||
- Prevent EAS CLI dependencies check from running repeatedly. ([#2781](https://github.com/expo/eas-cli/pull/2781) by [@kitten](https://github.com/kitten))
|
||||
- Prevent optimistic request body parsing for `eas deploy`. ([#2784](https://github.com/expo/eas-cli/pull/2784) by [@kadikraman](https://github.com/kadikraman))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Update log output for `worker` deploy and alias commands. ([#2780](https://github.com/expo/eas-cli/pull/2780) by [@kitten](https://github.com/kitten))
|
||||
- Update various messages wording. ([#2790](https://github.com/expo/eas-cli/pull/2790) by [@simek](https://github.com/simek))
|
||||
- Use node18 as tsconfig base. ([#2739](https://github.com/expo/eas-cli/pull/2739) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [14.2.0](https://github.com/expo/eas-cli/releases/tag/v14.2.0) - 2024-12-13
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `eas deploy --dry-run` flag to output tarball. ([#2761](https://github.com/expo/eas-cli/pull/2761) by [@kitten](https://github.com/kitten))
|
||||
- Allow specifying credentials for android builds. ([#2775](https://github.com/expo/eas-cli/pull/2775) by [@khamilowicz](https://github.com/khamilowicz))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"version": "14.2.0",
|
||||
"version": "15.0.12",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
+271
-76
@@ -69,6 +69,7 @@ eas --help COMMAND
|
||||
* [`eas build:cancel [BUILD_ID]`](#eas-buildcancel-build_id)
|
||||
* [`eas build:configure`](#eas-buildconfigure)
|
||||
* [`eas build:delete [BUILD_ID]`](#eas-builddelete-build_id)
|
||||
* [`eas build:dev`](#eas-builddev)
|
||||
* [`eas build:inspect`](#eas-buildinspect)
|
||||
* [`eas build:list`](#eas-buildlist)
|
||||
* [`eas build:resign`](#eas-buildresign)
|
||||
@@ -88,6 +89,9 @@ eas --help COMMAND
|
||||
* [`eas config`](#eas-config)
|
||||
* [`eas credentials`](#eas-credentials)
|
||||
* [`eas credentials:configure-build`](#eas-credentialsconfigure-build)
|
||||
* [`eas deploy [2m[options][22m`](#eas-deploy-2moptions22m)
|
||||
* [`eas deploy:alias`](#eas-deployalias)
|
||||
* [`eas deploy:promote`](#eas-deploypromote)
|
||||
* [`eas device:create`](#eas-devicecreate)
|
||||
* [`eas device:delete`](#eas-devicedelete)
|
||||
* [`eas device:list`](#eas-devicelist)
|
||||
@@ -102,6 +106,7 @@ eas --help COMMAND
|
||||
* [`eas env:pull [ENVIRONMENT]`](#eas-envpull-environment)
|
||||
* [`eas env:push [ENVIRONMENT]`](#eas-envpush-environment)
|
||||
* [`eas env:update [ENVIRONMENT]`](#eas-envupdate-environment)
|
||||
* [`eas fingerprint:compare [HASH1] [HASH2]`](#eas-fingerprintcompare-hash1-hash2)
|
||||
* [`eas help [COMMAND]`](#eas-help-command)
|
||||
* [`eas init`](#eas-init)
|
||||
* [`eas init:onboarding [TARGET_PROJECT_DIRECTORY]`](#eas-initonboarding-target_project_directory)
|
||||
@@ -131,6 +136,8 @@ eas --help COMMAND
|
||||
* [`eas webhook:update`](#eas-webhookupdate)
|
||||
* [`eas webhook:view ID`](#eas-webhookview-id)
|
||||
* [`eas whoami`](#eas-whoami)
|
||||
* [`eas worker:alias`](#eas-workeralias)
|
||||
* [`eas deploy [2m[options][22m`](#eas-deploy-2moptions22m-1)
|
||||
* [`eas workflow:create [NAME]`](#eas-workflowcreate-name)
|
||||
* [`eas workflow:run [FILE]`](#eas-workflowrun-file)
|
||||
* [`eas workflow:validate PATH`](#eas-workflowvalidate-path)
|
||||
@@ -153,7 +160,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -170,7 +177,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -187,7 +194,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -201,7 +208,7 @@ DESCRIPTION
|
||||
display or change analytics settings
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v15.0.12/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -253,7 +260,7 @@ DESCRIPTION
|
||||
create a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -274,7 +281,7 @@ DESCRIPTION
|
||||
delete a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -294,7 +301,7 @@ DESCRIPTION
|
||||
list all branches
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -314,7 +321,7 @@ DESCRIPTION
|
||||
rename a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -337,7 +344,7 @@ DESCRIPTION
|
||||
view a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -345,16 +352,16 @@ start a build
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build [-p android|ios|all] [-e <value>] [--local] [--output <value>] [--wait] [--clear-cache]
|
||||
[--auto-submit | --auto-submit-with-profile <value>] [-m <value>] [--build-logger-level
|
||||
trace|debug|info|warn|error|fatal] [--freeze-credentials] [--json --non-interactive]
|
||||
$ eas build [-p android|ios|all] [-e <value>] [--local] [--output <value>] [--wait] [--clear-cache] [-s |
|
||||
--auto-submit-with-profile <value>] [-m <value>] [--build-logger-level trace|debug|info|warn|error|fatal]
|
||||
[--freeze-credentials] [--json --non-interactive]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to
|
||||
"production" if defined in eas.json.
|
||||
-m, --message=<value> A short message describing the build
|
||||
-p, --platform=(android|ios|all)
|
||||
--auto-submit Submit on build complete using the submit profile with the
|
||||
-s, --auto-submit Submit on build complete using the submit profile with the
|
||||
same name as the build profile
|
||||
--auto-submit-with-profile=PROFILE_NAME Submit on build complete using the submit profile with
|
||||
provided name
|
||||
@@ -374,7 +381,7 @@ DESCRIPTION
|
||||
start a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -393,7 +400,7 @@ DESCRIPTION
|
||||
cancel a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -410,7 +417,7 @@ DESCRIPTION
|
||||
configure the project to support EAS Build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:delete [BUILD_ID]`
|
||||
|
||||
@@ -429,7 +436,27 @@ DESCRIPTION
|
||||
delete a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/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/v15.0.12/packages/eas-cli/src/commands/build/dev.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -464,7 +491,7 @@ DESCRIPTION
|
||||
inspect the state of the project at specific build stages, useful for troubleshooting
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -475,8 +502,8 @@ USAGE
|
||||
$ eas build:list [-p android|ios|all] [--status
|
||||
new|in-queue|in-progress|pending-cancel|errored|finished|canceled] [--distribution store|internal|simulator]
|
||||
[--channel <value>] [--app-version <value>] [--app-build-version <value>] [--sdk-version <value>] [--runtime-version
|
||||
<value>] [--app-identifier <value>] [-e <value>] [--git-commit-hash <value>] [--offset <value>] [--limit <value>]
|
||||
[--json --non-interactive] [--simulator]
|
||||
<value>] [--app-identifier <value>] [-e <value>] [--git-commit-hash <value>] [--fingerprint-hash <value>] [--offset
|
||||
<value>] [--limit <value>] [--json --non-interactive] [--simulator]
|
||||
|
||||
FLAGS
|
||||
-e, --build-profile=<value> Filter only builds created with the
|
||||
@@ -491,6 +518,8 @@ FLAGS
|
||||
--channel=<value>
|
||||
--distribution=(store|internal|simulator) Filter only builds with the specified
|
||||
distribution type
|
||||
--fingerprint-hash=<value> Filter only builds with the specified
|
||||
fingerprint hash
|
||||
--git-commit-hash=<value> Filter only builds created with the
|
||||
specified git commit hash
|
||||
--json Enable JSON output, non-JSON messages
|
||||
@@ -514,7 +543,7 @@ DESCRIPTION
|
||||
list all builds for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:resign`
|
||||
|
||||
@@ -543,7 +572,7 @@ DESCRIPTION
|
||||
re-sign a build archive
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
|
||||
## `eas build:run`
|
||||
|
||||
@@ -569,7 +598,7 @@ DESCRIPTION
|
||||
run simulator/emulator builds from eas-cli
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/build/run.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -619,7 +648,7 @@ DESCRIPTION
|
||||
get the latest version from EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
|
||||
## `eas build:version:set`
|
||||
|
||||
@@ -638,7 +667,7 @@ DESCRIPTION
|
||||
update version of an app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
|
||||
## `eas build:version:sync`
|
||||
|
||||
@@ -657,7 +686,7 @@ DESCRIPTION
|
||||
update a version in native code with a value stored on EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -674,7 +703,7 @@ DESCRIPTION
|
||||
view a build for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -695,7 +724,7 @@ DESCRIPTION
|
||||
create a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -717,7 +746,7 @@ DESCRIPTION
|
||||
point a channel at a new branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -737,7 +766,7 @@ DESCRIPTION
|
||||
list all channels
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:pause [NAME]`
|
||||
|
||||
@@ -759,7 +788,7 @@ DESCRIPTION
|
||||
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/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/channel/pause.ts)_
|
||||
|
||||
## `eas channel:resume [NAME]`
|
||||
|
||||
@@ -781,7 +810,7 @@ DESCRIPTION
|
||||
resume a channel to start sending updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/channel/resume.ts)_
|
||||
|
||||
## `eas channel:rollout [CHANNEL]`
|
||||
|
||||
@@ -814,7 +843,7 @@ DESCRIPTION
|
||||
Roll a new branch out on a channel incrementally.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -837,7 +866,7 @@ DESCRIPTION
|
||||
view a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -858,7 +887,7 @@ DESCRIPTION
|
||||
display project configuration (app.json + eas.json)
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v15.0.12/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -875,7 +904,7 @@ DESCRIPTION
|
||||
manage credentials
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
|
||||
## `eas credentials:configure-build`
|
||||
|
||||
@@ -893,7 +922,83 @@ DESCRIPTION
|
||||
Set up credentials for building your project.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
|
||||
## `eas deploy [2m[options][22m`
|
||||
|
||||
Deploy your Expo Router web build and API Routes.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas deploy [options]
|
||||
$ eas deploy --prod
|
||||
|
||||
FLAGS
|
||||
--alias=name Custom alias to assign to the new deployment.
|
||||
--dry-run Outputs a tarball of the new deployment instead of uploading it.
|
||||
--environment=(development|preview|production) Environment variable's environment
|
||||
--export-dir=dir [default: dist] Directory where the Expo project was exported.
|
||||
--id=xyz123 Custom unique identifier for the new deployment.
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--prod Create a new production deployment.
|
||||
|
||||
DESCRIPTION
|
||||
Deploy your Expo Router web build and API Routes.
|
||||
|
||||
ALIASES
|
||||
$ eas worker:deploy
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v15.0.12/packages/eas-cli/src/commands/deploy/index.ts)_
|
||||
|
||||
## `eas deploy:alias`
|
||||
|
||||
Assign deployment aliases.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas deploy:alias [--prod] [--alias <value>] [--id <value>] [--json --non-interactive]
|
||||
|
||||
FLAGS
|
||||
--alias=name Custom alias to assign to the existing deployment.
|
||||
--id=xyz123 Unique identifier of an existing deployment.
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--prod Promote an existing deployment to production.
|
||||
|
||||
DESCRIPTION
|
||||
Assign deployment aliases.
|
||||
|
||||
ALIASES
|
||||
$ eas worker:alias
|
||||
$ eas deploy:promote
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias.ts](https://github.com/expo/eas-cli/blob/v15.0.12/packages/eas-cli/src/commands/deploy/alias.ts)_
|
||||
|
||||
## `eas deploy:promote`
|
||||
|
||||
Assign deployment aliases.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas deploy:promote [--prod] [--alias <value>] [--id <value>] [--json --non-interactive]
|
||||
|
||||
FLAGS
|
||||
--alias=name Custom alias to assign to the existing deployment.
|
||||
--id=xyz123 Unique identifier of an existing deployment.
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--prod Promote an existing deployment to production.
|
||||
|
||||
DESCRIPTION
|
||||
Assign deployment aliases.
|
||||
|
||||
ALIASES
|
||||
$ eas worker:alias
|
||||
$ eas deploy:promote
|
||||
```
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -907,7 +1012,7 @@ DESCRIPTION
|
||||
register new Apple Devices to use for internal distribution
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -927,7 +1032,7 @@ DESCRIPTION
|
||||
remove a registered device from your account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -948,7 +1053,7 @@ DESCRIPTION
|
||||
list all registered devices for your account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:rename`
|
||||
|
||||
@@ -969,7 +1074,7 @@ DESCRIPTION
|
||||
rename a registered device
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -983,7 +1088,7 @@ DESCRIPTION
|
||||
view a device for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -997,7 +1102,7 @@ DESCRIPTION
|
||||
display environment info
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v15.0.12/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas env:create [ENVIRONMENT]`
|
||||
|
||||
@@ -1026,7 +1131,7 @@ DESCRIPTION
|
||||
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/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/env/create.ts)_
|
||||
|
||||
## `eas env:delete [ENVIRONMENT]`
|
||||
|
||||
@@ -1050,7 +1155,7 @@ DESCRIPTION
|
||||
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/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/env/delete.ts)_
|
||||
|
||||
## `eas env:exec ENVIRONMENT BASH_COMMAND`
|
||||
|
||||
@@ -1071,7 +1176,7 @@ DESCRIPTION
|
||||
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/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/env/exec.ts)_
|
||||
|
||||
## `eas env:get [ENVIRONMENT]`
|
||||
|
||||
@@ -1096,7 +1201,7 @@ DESCRIPTION
|
||||
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/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/env/get.ts)_
|
||||
|
||||
## `eas env:list [ENVIRONMENT]`
|
||||
|
||||
@@ -1121,7 +1226,7 @@ DESCRIPTION
|
||||
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/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/env/list.ts)_
|
||||
|
||||
## `eas env:pull [ENVIRONMENT]`
|
||||
|
||||
@@ -1144,7 +1249,7 @@ DESCRIPTION
|
||||
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/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/env/pull.ts)_
|
||||
|
||||
## `eas env:push [ENVIRONMENT]`
|
||||
|
||||
@@ -1165,7 +1270,7 @@ DESCRIPTION
|
||||
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/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/env/push.ts)_
|
||||
|
||||
## `eas env:update [ENVIRONMENT]`
|
||||
|
||||
@@ -1196,7 +1301,48 @@ DESCRIPTION
|
||||
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/v14.2.0/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/v15.0.12/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] [--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
|
||||
--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-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/v15.0.12/packages/eas-cli/src/commands/fingerprint/compare.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -1304,7 +1450,7 @@ DESCRIPTION
|
||||
validate the local store configuration
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
|
||||
## `eas metadata:pull`
|
||||
|
||||
@@ -1321,7 +1467,7 @@ DESCRIPTION
|
||||
generate the local store configuration from the app stores
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -1338,7 +1484,7 @@ DESCRIPTION
|
||||
sync the local store configuration to the app stores
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -1368,7 +1514,7 @@ DESCRIPTION
|
||||
open the project page in a web browser
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/open.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v15.0.12/packages/eas-cli/src/commands/open.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -1382,7 +1528,7 @@ DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -1405,7 +1551,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -1423,7 +1569,7 @@ ALIASES
|
||||
$ eas onboarding
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -1454,7 +1600,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v15.0.12/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -1496,7 +1642,7 @@ DESCRIPTION
|
||||
publish an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -1516,7 +1662,7 @@ DESCRIPTION
|
||||
configure the project to support EAS Update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -1537,7 +1683,7 @@ DESCRIPTION
|
||||
delete all the updates in an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:edit [GROUPID]`
|
||||
|
||||
@@ -1561,7 +1707,7 @@ DESCRIPTION
|
||||
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/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/update/edit.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -1583,7 +1729,7 @@ DESCRIPTION
|
||||
view the recent updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:republish`
|
||||
|
||||
@@ -1615,7 +1761,7 @@ DESCRIPTION
|
||||
roll back to an existing update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
|
||||
## `eas update:roll-back-to-embedded`
|
||||
|
||||
@@ -1643,7 +1789,7 @@ DESCRIPTION
|
||||
roll back to the embedded update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
|
||||
## `eas update:rollback`
|
||||
|
||||
@@ -1663,7 +1809,7 @@ DESCRIPTION
|
||||
roll back to an embedded update or an existing update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -1683,7 +1829,7 @@ DESCRIPTION
|
||||
update group details
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -1704,7 +1850,7 @@ DESCRIPTION
|
||||
create a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -1724,7 +1870,7 @@ DESCRIPTION
|
||||
delete a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -1742,7 +1888,7 @@ DESCRIPTION
|
||||
list webhooks
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -1764,7 +1910,7 @@ DESCRIPTION
|
||||
update a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -1781,7 +1927,7 @@ DESCRIPTION
|
||||
view a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
@@ -1798,6 +1944,55 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
## `eas worker:alias`
|
||||
|
||||
Assign deployment aliases.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas worker:alias [--prod] [--alias <value>] [--id <value>] [--json --non-interactive]
|
||||
|
||||
FLAGS
|
||||
--alias=name Custom alias to assign to the existing deployment.
|
||||
--id=xyz123 Unique identifier of an existing deployment.
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--prod Promote an existing deployment to production.
|
||||
|
||||
DESCRIPTION
|
||||
Assign deployment aliases.
|
||||
|
||||
ALIASES
|
||||
$ eas worker:alias
|
||||
$ eas deploy:promote
|
||||
```
|
||||
|
||||
## `eas deploy [2m[options][22m`
|
||||
|
||||
Deploy your Expo Router web build and API Routes.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas deploy [options]
|
||||
$ eas deploy --prod
|
||||
|
||||
FLAGS
|
||||
--alias=name Custom alias to assign to the new deployment.
|
||||
--dry-run Outputs a tarball of the new deployment instead of uploading it.
|
||||
--environment=(development|preview|production) Environment variable's environment
|
||||
--export-dir=dir [default: dist] Directory where the Expo project was exported.
|
||||
--id=xyz123 Custom unique identifier for the new deployment.
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--prod Create a new production deployment.
|
||||
|
||||
DESCRIPTION
|
||||
Deploy your Expo Router web build and API Routes.
|
||||
|
||||
ALIASES
|
||||
$ eas worker:deploy
|
||||
```
|
||||
|
||||
## `eas workflow:create [NAME]`
|
||||
|
||||
create a new workflow configuration YAML file
|
||||
@@ -1816,7 +2011,7 @@ DESCRIPTION
|
||||
create a new workflow configuration YAML file
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/workflow/create.ts)_
|
||||
|
||||
## `eas workflow:run [FILE]`
|
||||
|
||||
@@ -1836,7 +2031,7 @@ DESCRIPTION
|
||||
Run an EAS workflow
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/workflow/run.ts)_
|
||||
|
||||
## `eas workflow:validate PATH`
|
||||
|
||||
@@ -1856,5 +2051,5 @@ DESCRIPTION
|
||||
validate a workflow configuration yaml file
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v14.2.0/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/v15.0.12/packages/eas-cli/src/commands/workflow/validate.ts)_
|
||||
<!-- commandsstop -->
|
||||
@@ -12,3 +12,6 @@ if (process.env.TMPDIR) {
|
||||
(fs.realpath as any).native = jest.fn();
|
||||
|
||||
module.exports = fs;
|
||||
|
||||
// Ensure requiring node:fs returns the mock too. This is needed for expo/config and expo/json-file.
|
||||
jest.mock('node:fs', () => fs);
|
||||
Generated
+2577
-86
File diff suppressed because it is too large.
Load diff
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "14.2.0",
|
||||
"version": "15.0.12",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
},
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/apple-utils": "2.1.5",
|
||||
"@expo/apple-utils": "2.1.11",
|
||||
"@expo/code-signing-certificates": "0.0.5",
|
||||
"@expo/config": "10.0.6",
|
||||
"@expo/config-plugins": "9.0.12",
|
||||
"@expo/eas-build-job": "1.0.154",
|
||||
"@expo/eas-json": "14.2.0",
|
||||
"@expo/env": "^0.4.0",
|
||||
"@expo/eas-build-job": "1.0.165",
|
||||
"@expo/eas-json": "15.0.0",
|
||||
"@expo/env": "^1.0.0",
|
||||
"@expo/json-file": "8.3.3",
|
||||
"@expo/logger": "1.0.117",
|
||||
"@expo/multipart-body-parser": "2.0.0",
|
||||
"@expo/osascript": "2.1.4",
|
||||
"@expo/package-manager": "1.6.1",
|
||||
"@expo/package-manager": "1.7.0",
|
||||
"@expo/pkcs12": "0.1.3",
|
||||
"@expo/plist": "0.2.0",
|
||||
"@expo/plugin-help": "5.1.23",
|
||||
@@ -28,7 +28,7 @@
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.7.2",
|
||||
"@expo/steps": "1.0.154",
|
||||
"@expo/steps": "1.0.166",
|
||||
"@expo/timeago.js": "1.0.0",
|
||||
"@oclif/core": "^1.26.2",
|
||||
"@oclif/plugin-autocomplete": "^2.3.10",
|
||||
@@ -95,6 +95,7 @@
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||
"@tsconfig/node18": "18.2.4",
|
||||
"@types/cli-progress": "3.11.5",
|
||||
"@types/dateformat": "3.0.1",
|
||||
"@types/diff": "6.0.0",
|
||||
|
||||
@@ -38,7 +38,7 @@ describe(ensureProjectConfiguredAsync, () => {
|
||||
}),
|
||||
});
|
||||
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeTruthy();
|
||||
const vcsClientMock = jest.mocked(new GitClient());
|
||||
const vcsClientMock = jest.mocked(new GitClient({ requireCommit: false }));
|
||||
vcsClientMock.showChangedFilesAsync.mockImplementation(async () => {});
|
||||
vcsClientMock.isCommitRequiredAsync.mockImplementation(async () => false);
|
||||
vcsClientMock.trackFileAsync.mockImplementation(async () => {});
|
||||
@@ -60,7 +60,7 @@ describe(ensureProjectConfiguredAsync, () => {
|
||||
});
|
||||
});
|
||||
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeFalsy();
|
||||
const vcsClientMock = jest.mocked(new GitClient());
|
||||
const vcsClientMock = jest.mocked(new GitClient({ requireCommit: false }));
|
||||
vcsClientMock.showChangedFilesAsync.mockImplementation(async () => {});
|
||||
vcsClientMock.isCommitRequiredAsync.mockImplementation(async () => false);
|
||||
vcsClientMock.trackFileAsync.mockImplementation(async () => {});
|
||||
|
||||
@@ -1,23 +1,43 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
import { AndroidBuildProfile } from '@expo/eas-json/build/build/types';
|
||||
import assert from 'assert';
|
||||
import fs from 'fs-extra';
|
||||
import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { instance, mock } from 'ts-mockito';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
import { getAppBuildGradleAsync, resolveConfigValue } from '../../../project/android/gradleUtils';
|
||||
import { resolveVcsClient } from '../../../vcs';
|
||||
import { Client } from '../../../vcs/vcs';
|
||||
import {
|
||||
BumpStrategy,
|
||||
bumpVersionAsync,
|
||||
bumpVersionInAppJsonAsync,
|
||||
maybeResolveVersionsAsync,
|
||||
resolveRemoteVersionCodeAsync,
|
||||
} from '../version';
|
||||
|
||||
const fsReal = jest.requireActual('fs').promises as typeof fs;
|
||||
jest.mock('fs');
|
||||
jest.mock('../../../commandUtils/context/contextUtils/createGraphqlClient');
|
||||
jest.mock('../../../vcs/vcs');
|
||||
jest.mock('../../../graphql/queries/AppVersionQuery');
|
||||
jest.mock('../../../graphql/mutations/AppVersionMutation');
|
||||
jest.mock('../../../ora', () => ({
|
||||
ora: jest.fn().mockImplementation(() => ({
|
||||
start: jest.fn().mockImplementation(() => ({
|
||||
succeed: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
fail: jest.fn(),
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
|
||||
const vcsClient = resolveVcsClient();
|
||||
|
||||
@@ -216,12 +236,160 @@ describe(maybeResolveVersionsAsync, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe(resolveRemoteVersionCodeAsync, () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('uses current remote versionCode when remote version set, autoIncrement=false', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue({
|
||||
buildVersion: '11',
|
||||
storeVersion: '1.2.3',
|
||||
});
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject();
|
||||
|
||||
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationId: 'fakeApplicationId',
|
||||
buildProfile: {} as AndroidBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('11');
|
||||
expect(createAppVersionAsyncSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('initializes versionCode from local files when remote version not set, autoIncrement=false', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ versionCode: 22, versionName: '2.3.4' });
|
||||
|
||||
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationId: 'fakeApplicationId',
|
||||
buildProfile: {} as AndroidBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('22');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('2.3.4');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('22');
|
||||
});
|
||||
|
||||
it('initializes versionCode starting with 1 when remote version not set and not set in local files, autoIncrement=false', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ versionCode: null, versionName: null });
|
||||
|
||||
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationId: 'fakeApplicationId',
|
||||
buildProfile: {} as AndroidBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('1');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('1');
|
||||
});
|
||||
|
||||
it('increments current remote versionCode when remote version set, autoIncrement=true', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue({
|
||||
buildVersion: '11',
|
||||
storeVersion: '1.2.3',
|
||||
});
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject();
|
||||
|
||||
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationId: 'fakeApplicationId',
|
||||
buildProfile: { autoIncrement: true } as AndroidBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('12');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('3.0.0');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('12');
|
||||
});
|
||||
|
||||
it('increments versionCode from local files when remote version not set, autoIncrement=true', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ versionCode: 22, versionName: '2.3.4' });
|
||||
|
||||
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationId: 'fakeApplicationId',
|
||||
buildProfile: { autoIncrement: true } as AndroidBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('23');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('2.3.4');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('23');
|
||||
});
|
||||
|
||||
it('initializes versionCode starting with 1 when remote version not set and not set in local files, autoIncrement=true', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ versionCode: null, versionName: null });
|
||||
|
||||
const result = await resolveRemoteVersionCodeAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationId: 'fakeApplicationId',
|
||||
buildProfile: { autoIncrement: true } as AndroidBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('1');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('1');
|
||||
});
|
||||
});
|
||||
|
||||
function initBareWorkflowProject({
|
||||
versionCode = 123,
|
||||
versionName = '3.0.0',
|
||||
}: {
|
||||
versionCode?: number;
|
||||
versionName?: string;
|
||||
versionCode?: number | null;
|
||||
versionName?: string | null;
|
||||
} = {}): ExpoConfig {
|
||||
const fakeExp: ExpoConfig = {
|
||||
name: 'myproject',
|
||||
@@ -239,8 +407,8 @@ function initBareWorkflowProject({
|
||||
'./android/app/build.gradle': `android {
|
||||
defaultConfig {
|
||||
applicationId "com.expo.testapp"
|
||||
versionCode ${versionCode}
|
||||
versionName "${versionName}"
|
||||
${versionCode ? `versionCode "${versionCode}"` : ''}
|
||||
${versionName ? `versionName "${versionName}"` : ''}
|
||||
}
|
||||
}`,
|
||||
'./android/app/src/main/AndroidManifest.xml': 'fake',
|
||||
|
||||
@@ -151,6 +151,7 @@ async function ensureAndroidCredentialsAsync(
|
||||
ctx.android.gradleContext
|
||||
);
|
||||
const provider = new AndroidCredentialsProvider(ctx.credentialsCtx, {
|
||||
name: ctx.buildProfile.keystoreName,
|
||||
app: {
|
||||
account: nullthrows(
|
||||
ctx.user.accounts.find(a => a.name === ctx.accountName),
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
transformProjectArchive,
|
||||
transformWorkflow,
|
||||
} from '../graphql';
|
||||
import { buildProfileEnvironmentToEnvironment } from '../utils/environment';
|
||||
|
||||
export function transformJob(job: Android.Job): AndroidJobInput {
|
||||
return {
|
||||
@@ -29,6 +30,7 @@ export function transformJob(job: Android.Job): AndroidJobInput {
|
||||
experimental: job.experimental,
|
||||
mode: transformBuildMode(job.mode),
|
||||
customBuildConfig: job.customBuildConfig,
|
||||
environment: buildProfileEnvironmentToEnvironment(job.environment),
|
||||
loggerLevel: job.loggerLevel
|
||||
? loggerLevelToGraphQLWorkerLoggerLevel[job.loggerLevel]
|
||||
: undefined,
|
||||
|
||||
@@ -92,6 +92,7 @@ export async function prepareJobAsync(
|
||||
gradleCommand: buildProfile.gradleCommand,
|
||||
applicationArchivePath: buildProfile.applicationArchivePath ?? buildProfile.artifactPath,
|
||||
buildArtifactPaths: buildProfile.buildArtifactPaths,
|
||||
environment: ctx.buildProfile.environment,
|
||||
buildType,
|
||||
username,
|
||||
...(ctx.android.versionCodeOverride && {
|
||||
|
||||
@@ -99,7 +99,7 @@ export async function maybeResolveVersionsAsync(
|
||||
exp: ExpoConfig,
|
||||
buildProfile: BuildProfile<Platform.ANDROID>,
|
||||
vcsClient: Client
|
||||
): Promise<{ appVersion?: string; appBuildVersion?: string }> {
|
||||
): Promise<{ appVersion?: string; appBuildVersion?: string; isVersionInitialized?: boolean }> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
||||
@@ -113,6 +113,11 @@ export async function maybeResolveVersionsAsync(
|
||||
resolveConfigValue(buildGradle, 'versionName', parsedGradleCommand?.flavor) ?? '1.0.0',
|
||||
appBuildVersion:
|
||||
resolveConfigValue(buildGradle, 'versionCode', parsedGradleCommand?.flavor) ?? '1',
|
||||
isVersionInitialized: !resolveConfigValue(
|
||||
buildGradle,
|
||||
'versionCode',
|
||||
parsedGradleCommand?.flavor
|
||||
),
|
||||
};
|
||||
} catch {
|
||||
return {};
|
||||
@@ -204,14 +209,18 @@ export async function resolveRemoteVersionCodeAsync(
|
||||
|
||||
const localVersions = await maybeResolveVersionsAsync(projectDir, exp, buildProfile, vcsClient);
|
||||
let currentBuildVersion: string;
|
||||
let shouldInitializeVersionCode = false;
|
||||
if (remoteVersions?.buildVersion) {
|
||||
currentBuildVersion = remoteVersions.buildVersion;
|
||||
} else {
|
||||
if (localVersions.appBuildVersion) {
|
||||
Log.warn(
|
||||
'No remote versions are configured for this project, versionCode will be initialized based on the value from the local project.'
|
||||
Log.log(
|
||||
chalk.green(
|
||||
'No remote versions are configured for this project, versionCode will be initialized based on the value from the local project.'
|
||||
)
|
||||
);
|
||||
currentBuildVersion = localVersions.appBuildVersion;
|
||||
shouldInitializeVersionCode = localVersions.isVersionInitialized ?? false;
|
||||
} else {
|
||||
Log.error(
|
||||
`Remote versions are not configured and EAS CLI was not able to read the current version from your project. Use "eas build:version:set" to initialize remote versions.`
|
||||
@@ -221,7 +230,10 @@ export async function resolveRemoteVersionCodeAsync(
|
||||
}
|
||||
if (!buildProfile.autoIncrement && remoteVersions?.buildVersion) {
|
||||
return currentBuildVersion;
|
||||
} else if (!buildProfile.autoIncrement && !remoteVersions?.buildVersion) {
|
||||
} else if (
|
||||
(!buildProfile.autoIncrement && !remoteVersions?.buildVersion) ||
|
||||
shouldInitializeVersionCode
|
||||
) {
|
||||
const spinner = ora(
|
||||
`Initializing versionCode with ${chalk.bold(currentBuildVersion)}.`
|
||||
).start();
|
||||
|
||||
@@ -35,8 +35,10 @@ import { collectMetadataAsync } from './metadata';
|
||||
import { printDeprecationWarnings } from './utils/printBuildInfo';
|
||||
import {
|
||||
LocalFile,
|
||||
assertProjectTarballSizeDoesNotExceedLimit,
|
||||
makeProjectMetadataFileAsync,
|
||||
makeProjectTarballAsync,
|
||||
maybeWarnAboutProjectTarballSize,
|
||||
reviewAndCommitChangesAsync,
|
||||
} from './utils/repository';
|
||||
import { BuildEvent } from '../analytics/AnalyticsManager';
|
||||
@@ -275,19 +277,8 @@ async function uploadProjectAsync<TPlatform extends Platform>(
|
||||
);
|
||||
const projectTarball = await makeProjectTarballAsync(ctx.vcsClient);
|
||||
|
||||
if (projectTarball.size > 1024 * 1024 * 100) {
|
||||
Log.warn(
|
||||
`Your project archive is ${formatBytes(
|
||||
projectTarball.size
|
||||
)}. You can reduce its size and the time it takes to upload by excluding files that are unnecessary for the build process in ${chalk.bold(
|
||||
'.easignore'
|
||||
)} file. ${learnMore('https://expo.fyi/eas-build-archive')}`
|
||||
);
|
||||
}
|
||||
|
||||
if (projectTarball.size > 2 * 1024 * 1024 * 1024) {
|
||||
throw new Error('Project archive is too big. Maximum allowed size is 2GB.');
|
||||
}
|
||||
maybeWarnAboutProjectTarballSize(projectTarball.size);
|
||||
assertProjectTarballSizeDoesNotExceedLimit(projectTarball.size);
|
||||
|
||||
projectTarballPath = projectTarball.path;
|
||||
const [bucketKey, { metadataLocation }] = await Promise.all([
|
||||
@@ -709,7 +700,10 @@ async function computeAndMaybeUploadFingerprintFromExpoUpdatesAsync<T extends Pl
|
||||
* It's ok for fingerprintSources or runtimeVersion to be empty
|
||||
* fingerprintSources only exist if the project is using runtimeVersion.policy: fingerprint
|
||||
*/
|
||||
if (!resolvedRuntimeVersion.fingerprint || !resolvedRuntimeVersion.runtimeVersion) {
|
||||
if (
|
||||
!resolvedRuntimeVersion.expoUpdatesRuntimeFingerprint ||
|
||||
!resolvedRuntimeVersion.runtimeVersion
|
||||
) {
|
||||
return {
|
||||
runtimeVersion: resolvedRuntimeVersion.runtimeVersion ?? undefined,
|
||||
};
|
||||
@@ -717,14 +711,14 @@ async function computeAndMaybeUploadFingerprintFromExpoUpdatesAsync<T extends Pl
|
||||
|
||||
const uploadedFingerprint = await maybeUploadFingerprintAsync({
|
||||
hash: resolvedRuntimeVersion.runtimeVersion,
|
||||
fingerprint: resolvedRuntimeVersion.fingerprint,
|
||||
fingerprint: resolvedRuntimeVersion.expoUpdatesRuntimeFingerprint,
|
||||
graphqlClient: ctx.graphqlClient,
|
||||
localBuildMode: ctx.localBuildOptions.localBuildMode,
|
||||
});
|
||||
return {
|
||||
runtimeVersion: uploadedFingerprint.hash,
|
||||
fingerprintSource: uploadedFingerprint.fingerprintSource,
|
||||
fingerprintHash: resolvedRuntimeVersion.fingerprintHash ?? undefined,
|
||||
fingerprintHash: resolvedRuntimeVersion.expoUpdatesRuntimeFingerprintHash ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import fs from 'fs-extra';
|
||||
|
||||
import { maybeBailOnRepoStatusAsync, reviewAndCommitChangesAsync } from './utils/repository';
|
||||
import Log, { learnMore } from '../log';
|
||||
import { ora } from '../ora';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { Client } from '../vcs/vcs';
|
||||
|
||||
@@ -52,6 +53,78 @@ async function configureAsync({
|
||||
}
|
||||
}
|
||||
|
||||
export async function doesBuildProfileExistAsync({
|
||||
projectDir,
|
||||
profileName,
|
||||
}: {
|
||||
projectDir: string;
|
||||
profileName: string;
|
||||
}): Promise<boolean> {
|
||||
try {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
const easJson = await easJsonAccessor.readRawJsonAsync();
|
||||
if (!easJson.build?.[profileName]) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
Log.error(`We were unable to read ${chalk.bold('eas.json')} contents. Error: ${error}.`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createBuildProfileAsync({
|
||||
projectDir,
|
||||
profileName,
|
||||
profileContents,
|
||||
vcsClient,
|
||||
nonInteractive,
|
||||
}: {
|
||||
projectDir: string;
|
||||
profileName: string;
|
||||
profileContents: Record<string, any>;
|
||||
vcsClient: Client;
|
||||
nonInteractive: boolean;
|
||||
}): Promise<void> {
|
||||
const spinner = ora(`Adding "${profileName}" build profile to ${chalk.bold('eas.json')}`).start();
|
||||
try {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
await easJsonAccessor.readRawJsonAsync();
|
||||
|
||||
easJsonAccessor.patch(easJsonRawObject => {
|
||||
return {
|
||||
...easJsonRawObject,
|
||||
build: {
|
||||
...easJsonRawObject.build,
|
||||
[profileName]: profileContents,
|
||||
},
|
||||
};
|
||||
});
|
||||
await easJsonAccessor.writeAsync();
|
||||
spinner.succeed(
|
||||
`Successfully added "${profileName}" build profile to ${chalk.bold('eas.json')}.`
|
||||
);
|
||||
|
||||
if (await vcsClient.isCommitRequiredAsync()) {
|
||||
Log.newLine();
|
||||
await reviewAndCommitChangesAsync(
|
||||
vcsClient,
|
||||
`Add "${profileName}" build profile to eas.json`,
|
||||
{
|
||||
nonInteractive,
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail(
|
||||
`We were not able to configure "${profileName}" build profile inside of ${chalk.bold(
|
||||
'eas.json'
|
||||
)}. Error: ${error}.`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const EAS_JSON_DEFAULT: EasJson = {
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { Env } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
|
||||
import { isEnvironment } from './utils/environment';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EnvironmentVariableEnvironment } from '../graphql/generated';
|
||||
import { EnvironmentVariablesQuery } from '../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log, { learnMore } from '../log';
|
||||
|
||||
function isEnvironment(env: string): env is EnvironmentVariableEnvironment {
|
||||
return Object.values(EnvironmentVariableEnvironment).includes(
|
||||
env as EnvironmentVariableEnvironment
|
||||
);
|
||||
}
|
||||
|
||||
export async function evaluateConfigWithEnvVarsAsync<Config extends { projectId: string }, Opts>({
|
||||
buildProfile,
|
||||
buildProfileName,
|
||||
@@ -84,13 +79,13 @@ async function resolveEnvVarsAsync({
|
||||
|
||||
if (Object.keys(serverEnvVars).length > 0) {
|
||||
Log.log(
|
||||
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS servers: ${Object.keys(
|
||||
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS: ${Object.keys(
|
||||
serverEnvVars
|
||||
).join(', ')}.`
|
||||
);
|
||||
} else {
|
||||
Log.log(
|
||||
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS servers.`
|
||||
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,7 +107,7 @@ async function resolveEnvVarsAsync({
|
||||
);
|
||||
if (overlappingKeys.length > 0) {
|
||||
Log.warn(
|
||||
`The following environment variables are defined in both the "${buildProfileName}" build profile "env" configuration and the "${environment.toLowerCase()}" environment on EAS servers: ${overlappingKeys.join(
|
||||
`The following environment variables are defined in both the "${buildProfileName}" build profile "env" configuration and the "${environment.toLowerCase()}" environment on EAS: ${overlappingKeys.join(
|
||||
', '
|
||||
)}. The values from the build profile configuration will be used.`
|
||||
);
|
||||
@@ -135,23 +130,18 @@ async function resolveEnvVarsAsync({
|
||||
function resolveSuggestedEnvironmentForBuildProfileConfiguration(
|
||||
buildProfile: BuildProfile
|
||||
): EnvironmentVariableEnvironment {
|
||||
const setEnvironmentMessage =
|
||||
'Set the "environment" field in the build profile if you want to specify the environment manually.';
|
||||
if (buildProfile.distribution === 'store') {
|
||||
Log.log(
|
||||
`We detected that you are building for the "store" distribution. Resolving the environment for environment variables used during the build to "production". ${setEnvironmentMessage}`
|
||||
);
|
||||
return EnvironmentVariableEnvironment.Production;
|
||||
} else {
|
||||
if (buildProfile.developmentClient) {
|
||||
Log.log(
|
||||
`We detected that you are building the development client. Resolving the environment for environment variables used during the build to "development". ${setEnvironmentMessage}`
|
||||
);
|
||||
return EnvironmentVariableEnvironment.Development;
|
||||
}
|
||||
Log.log(
|
||||
`We detected that you are building for the "internal" distribution. Resolving the environment for environment variables used during the build to "preview". ${setEnvironmentMessage}`
|
||||
);
|
||||
return EnvironmentVariableEnvironment.Preview;
|
||||
}
|
||||
const environment =
|
||||
buildProfile.distribution === 'store'
|
||||
? EnvironmentVariableEnvironment.Production
|
||||
: buildProfile.developmentClient
|
||||
? EnvironmentVariableEnvironment.Development
|
||||
: EnvironmentVariableEnvironment.Preview;
|
||||
|
||||
Log.log(
|
||||
`Resolved "${environment.toLowerCase()}" environment for the build. ${learnMore(
|
||||
'https://docs.expo.dev/eas/environment-variables/#setting-the-environment-for-your-builds'
|
||||
)}`
|
||||
);
|
||||
|
||||
return environment;
|
||||
}
|
||||
@@ -1,12 +1,19 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { IOSConfig } from '@expo/config-plugins';
|
||||
import { IosBuildProfile } from '@expo/eas-json/build/build/types';
|
||||
import fs from 'fs-extra';
|
||||
import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
import { instance, mock } from 'ts-mockito';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { Target } from '../../../credentials/ios/types';
|
||||
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
import { readPlistAsync } from '../../../utils/plist';
|
||||
import { resolveVcsClient } from '../../../vcs';
|
||||
import { Client } from '../../../vcs/vcs';
|
||||
import {
|
||||
BumpStrategy,
|
||||
bumpVersionAsync,
|
||||
@@ -15,9 +22,23 @@ import {
|
||||
getInfoPlistPath,
|
||||
readBuildNumberAsync,
|
||||
readShortVersionAsync,
|
||||
resolveRemoteBuildNumberAsync,
|
||||
} from '../version';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('../../../commandUtils/context/contextUtils/createGraphqlClient');
|
||||
jest.mock('../../../vcs/vcs');
|
||||
jest.mock('../../../graphql/queries/AppVersionQuery');
|
||||
jest.mock('../../../graphql/mutations/AppVersionMutation');
|
||||
jest.mock('../../../ora', () => ({
|
||||
ora: jest.fn().mockImplementation(() => ({
|
||||
start: jest.fn().mockImplementation(() => ({
|
||||
succeed: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
fail: jest.fn(),
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
|
||||
const vcsClient = resolveVcsClient();
|
||||
|
||||
@@ -359,6 +380,154 @@ describe(getInfoPlistPath, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe(resolveRemoteBuildNumberAsync, () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('uses current remote buildNumber when remote version set, autoIncrement=false', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue({
|
||||
buildVersion: '11',
|
||||
storeVersion: '1.2.3',
|
||||
});
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject();
|
||||
|
||||
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationTarget: {} as Target,
|
||||
buildProfile: {} as IosBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('11');
|
||||
expect(createAppVersionAsyncSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('initializes buildNumber from local files when remote version not set, autoIncrement=false', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ buildNumber: '22', appVersion: '2.3.4' });
|
||||
|
||||
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationTarget: {} as Target,
|
||||
buildProfile: {} as IosBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('22');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('2.3.4');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('22');
|
||||
});
|
||||
|
||||
it('initializes buildNumber starting with 1 when remote version not set and local version is 1, autoIncrement=false', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ buildNumber: '1', appVersion: '1.0.0' });
|
||||
|
||||
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationTarget: {} as Target,
|
||||
buildProfile: {} as IosBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('1');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('1');
|
||||
});
|
||||
|
||||
it('increments current remote buildNumber when remote version set, autoIncrement=true', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue({
|
||||
buildVersion: '11',
|
||||
storeVersion: '1.2.3',
|
||||
});
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject();
|
||||
|
||||
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationTarget: {} as Target,
|
||||
buildProfile: { autoIncrement: true } as IosBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('12');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('12');
|
||||
});
|
||||
|
||||
it('increments buildNumber from local files when remote version not set, autoIncrement=true', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ buildNumber: '22', appVersion: '2.3.4' });
|
||||
|
||||
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationTarget: {} as Target,
|
||||
buildProfile: { autoIncrement: true } as IosBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('23');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('2.3.4');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('23');
|
||||
});
|
||||
|
||||
it('initializes buildNumber starting with 1 when remote version not set and local version is 1, autoIncrement=true', async () => {
|
||||
const graphQLClientMock = instance(mock<ExpoGraphqlClient>());
|
||||
const vcsClientMock = instance(mock<Client>());
|
||||
vcsClientMock.getRootPathAsync = async () => '/app';
|
||||
jest.mocked(AppVersionQuery.latestVersionAsync).mockResolvedValue(null);
|
||||
const createAppVersionAsyncSpy = jest.spyOn(AppVersionMutation, 'createAppVersionAsync');
|
||||
const exp = initBareWorkflowProject({ buildNumber: '1', appVersion: '1.0.0' });
|
||||
|
||||
const result = await resolveRemoteBuildNumberAsync(graphQLClientMock, {
|
||||
projectDir: '/app',
|
||||
projectId: 'fakeProjectId',
|
||||
exp,
|
||||
applicationTarget: {} as Target,
|
||||
buildProfile: { autoIncrement: true } as IosBuildProfile,
|
||||
vcsClient: vcsClientMock,
|
||||
});
|
||||
|
||||
expect(result).toBe('1');
|
||||
expect(createAppVersionAsyncSpy).toHaveBeenCalled();
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].storeVersion).toBe('1.0.0');
|
||||
expect(createAppVersionAsyncSpy.mock.calls[0][1].buildVersion).toBe('1');
|
||||
});
|
||||
});
|
||||
|
||||
function initBareWorkflowProject({
|
||||
appVersion = '1.0.0',
|
||||
buildNumber = '1',
|
||||
|
||||
@@ -10,6 +10,7 @@ import { IosCredentials } from '../../credentials/ios/types';
|
||||
import { BuildParamsInput } from '../../graphql/generated';
|
||||
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
|
||||
import { ensureBundleIdentifierIsDefinedForManagedProjectAsync } from '../../project/ios/bundleIdentifier';
|
||||
import { ensureNonExemptEncryptionIsDefinedForManagedProjectAsync } from '../../project/ios/exemptEncryption';
|
||||
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
|
||||
import { findApplicationTarget, resolveTargetsAsync } from '../../project/ios/target';
|
||||
import { BuildRequestSender, JobData, prepareBuildRequestForPlatformAsync } from '../build';
|
||||
@@ -28,6 +29,7 @@ export async function createIosContextAsync(
|
||||
|
||||
if (ctx.workflow === Workflow.MANAGED) {
|
||||
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx);
|
||||
await ensureNonExemptEncryptionIsDefinedForManagedProjectAsync(ctx);
|
||||
}
|
||||
|
||||
checkNodeEnvVariable(ctx);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
transformProjectArchive,
|
||||
transformWorkflow,
|
||||
} from '../graphql';
|
||||
import { buildProfileEnvironmentToEnvironment } from '../utils/environment';
|
||||
|
||||
export function transformJob(job: Ios.Job): IosJobInput {
|
||||
return {
|
||||
@@ -31,6 +32,7 @@ export function transformJob(job: Ios.Job): IosJobInput {
|
||||
experimental: job.experimental,
|
||||
mode: transformBuildMode(job.mode),
|
||||
customBuildConfig: job.customBuildConfig,
|
||||
environment: buildProfileEnvironmentToEnvironment(job.environment),
|
||||
loggerLevel: job.loggerLevel
|
||||
? loggerLevelToGraphQLWorkerLoggerLevel[job.loggerLevel]
|
||||
: undefined,
|
||||
|
||||
@@ -88,6 +88,7 @@ export async function prepareJobAsync(
|
||||
buildConfiguration: buildProfile.buildConfiguration,
|
||||
applicationArchivePath: buildProfile.applicationArchivePath ?? buildProfile.artifactPath,
|
||||
buildArtifactPaths: buildProfile.buildArtifactPaths,
|
||||
environment: ctx.buildProfile.environment,
|
||||
username,
|
||||
...(ctx.ios.buildNumberOverride && {
|
||||
version: {
|
||||
|
||||
@@ -330,14 +330,18 @@ export async function resolveRemoteBuildNumberAsync(
|
||||
vcsClient
|
||||
);
|
||||
let currentBuildVersion: string;
|
||||
let shouldInitializeBuildNumber = false;
|
||||
if (remoteVersions?.buildVersion) {
|
||||
currentBuildVersion = remoteVersions.buildVersion;
|
||||
} else {
|
||||
if (localBuildNumber) {
|
||||
Log.warn(
|
||||
'No remote versions are configured for this project, buildNumber will be initialized based on the value from the local project.'
|
||||
Log.log(
|
||||
chalk.green(
|
||||
'No remote versions are configured for this project, buildNumber will be initialized based on the value from the local project.'
|
||||
)
|
||||
);
|
||||
currentBuildVersion = localBuildNumber;
|
||||
shouldInitializeBuildNumber = localBuildNumber === '1';
|
||||
} else {
|
||||
Log.error(
|
||||
`Remote versions are not configured and EAS CLI was not able to read the current version from your project. Use "eas build:version:set" to initialize remote versions.`
|
||||
@@ -347,7 +351,10 @@ export async function resolveRemoteBuildNumberAsync(
|
||||
}
|
||||
if (!buildProfile.autoIncrement && remoteVersions?.buildVersion) {
|
||||
return currentBuildVersion;
|
||||
} else if (!buildProfile.autoIncrement && !remoteVersions?.buildVersion) {
|
||||
} else if (
|
||||
(!buildProfile.autoIncrement && !remoteVersions?.buildVersion) ||
|
||||
shouldInitializeBuildNumber
|
||||
) {
|
||||
const spinner = ora(
|
||||
`Initializing buildNumber with ${chalk.bold(currentBuildVersion)}.`
|
||||
).start();
|
||||
|
||||
@@ -7,7 +7,7 @@ import Log from '../log';
|
||||
import { ora } from '../ora';
|
||||
|
||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||
const PLUGIN_PACKAGE_VERSION = '1.0.152';
|
||||
const PLUGIN_PACKAGE_VERSION = '1.0.168';
|
||||
|
||||
export enum LocalBuildMode {
|
||||
/**
|
||||
|
||||
@@ -62,7 +62,6 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
requiredPackageManager: ctx.requiredPackageManager ?? undefined,
|
||||
selectedImage: ctx.buildProfile.image,
|
||||
customNodeVersion: ctx.buildProfile.node,
|
||||
environment: ctx.buildProfile.environment,
|
||||
simulator: 'simulator' in ctx.buildProfile && ctx.buildProfile.simulator,
|
||||
};
|
||||
return sanitizeMetadata(metadata);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { LoggerLevel } from '@expo/logger';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import { pathExists } from 'fs-extra';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { prepareAndroidBuildAsync } from './android/build';
|
||||
@@ -62,7 +63,7 @@ import {
|
||||
validateBuildProfileVersionSettingsAsync,
|
||||
} from '../project/remoteVersionSource';
|
||||
import { confirmAsync } from '../prompts';
|
||||
import { runAsync } from '../run/run';
|
||||
import { getEasBuildRunCachedAppPath, runAsync } from '../run/run';
|
||||
import { isRunnableOnSimulatorOrEmulator } from '../run/utils';
|
||||
import { createSubmissionContextAsync } from '../submit/context';
|
||||
import {
|
||||
@@ -99,15 +100,27 @@ export interface BuildFlags {
|
||||
repack: boolean;
|
||||
}
|
||||
|
||||
export async function runBuildAndSubmitAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
analytics: Analytics,
|
||||
vcsClient: Client,
|
||||
projectDir: string,
|
||||
flags: BuildFlags,
|
||||
actor: Actor,
|
||||
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn
|
||||
): Promise<{
|
||||
export async function runBuildAndSubmitAsync({
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
projectDir,
|
||||
flags,
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
downloadSimBuildAutoConfirm,
|
||||
envOverride,
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
analytics: Analytics;
|
||||
vcsClient: Client;
|
||||
projectDir: string;
|
||||
flags: BuildFlags;
|
||||
actor: Actor;
|
||||
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
|
||||
downloadSimBuildAutoConfirm?: boolean;
|
||||
envOverride?: Env;
|
||||
}): Promise<{
|
||||
buildIds: string[];
|
||||
}> {
|
||||
await vcsClient.ensureRepoExistsAsync();
|
||||
@@ -192,13 +205,15 @@ export async function runBuildAndSubmitAsync(
|
||||
for (const buildProfile of buildProfiles) {
|
||||
const platform = toAppPlatform(buildProfile.platform);
|
||||
|
||||
const { env } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: buildProfile.profile,
|
||||
buildProfileName: buildProfile.profileName,
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: buildProfile.profile.env },
|
||||
});
|
||||
const { env } = !envOverride
|
||||
? await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: buildProfile.profile,
|
||||
buildProfileName: buildProfile.profileName,
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: buildProfile.profile.env },
|
||||
})
|
||||
: { env: envOverride };
|
||||
|
||||
const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({
|
||||
projectDir,
|
||||
@@ -308,7 +323,7 @@ export async function runBuildAndSubmitAsync(
|
||||
[BuildStatus.Errored, BuildStatus.Canceled, BuildStatus.PendingCancel].includes(build?.status)
|
||||
);
|
||||
|
||||
await maybeDownloadAndRunSimulatorBuildsAsync(builds, flags);
|
||||
await maybeDownloadAndRunSimulatorBuildsAsync(builds, flags, downloadSimBuildAutoConfirm);
|
||||
|
||||
if (haveAllBuildsFailedOrCanceled || !flags.autoSubmit) {
|
||||
if (flags.json) {
|
||||
@@ -531,18 +546,29 @@ function exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds: (BuildFragment | nul
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadAndRunAsync(build: BuildFragment): Promise<void> {
|
||||
export async function downloadAndRunAsync(build: BuildFragment): Promise<void> {
|
||||
assert(build.artifacts?.applicationArchiveUrl);
|
||||
const cachedAppPath = getEasBuildRunCachedAppPath(build.project.id, build.id, build.platform);
|
||||
|
||||
if (await pathExists(cachedAppPath)) {
|
||||
Log.newLine();
|
||||
Log.log(`Using cached app...`);
|
||||
await runAsync(cachedAppPath, build.platform);
|
||||
return;
|
||||
}
|
||||
|
||||
const buildPath = await downloadAndMaybeExtractAppAsync(
|
||||
build.artifacts.applicationArchiveUrl,
|
||||
build.platform
|
||||
build.platform,
|
||||
cachedAppPath
|
||||
);
|
||||
await runAsync(buildPath, build.platform);
|
||||
}
|
||||
|
||||
async function maybeDownloadAndRunSimulatorBuildsAsync(
|
||||
builds: MaybeBuildFragment[],
|
||||
flags: BuildFlags
|
||||
flags: BuildFlags,
|
||||
autoConfirm?: boolean
|
||||
): Promise<void> {
|
||||
const simBuilds = builds.filter(truthy).filter(isRunnableOnSimulatorOrEmulator);
|
||||
|
||||
@@ -550,11 +576,15 @@ async function maybeDownloadAndRunSimulatorBuildsAsync(
|
||||
for (const simBuild of simBuilds) {
|
||||
if (simBuild.platform === AppPlatform.Android || process.platform === 'darwin') {
|
||||
Log.newLine();
|
||||
const confirm = await confirmAsync({
|
||||
message: `Install and run the ${
|
||||
simBuild.platform === AppPlatform.Android ? 'Android' : 'iOS'
|
||||
} build on ${simBuild.platform === AppPlatform.Android ? 'an emulator' : 'a simulator'}?`,
|
||||
});
|
||||
const confirm =
|
||||
autoConfirm ??
|
||||
(await confirmAsync({
|
||||
message: `Install and run the ${
|
||||
simBuild.platform === AppPlatform.Android ? 'Android' : 'iOS'
|
||||
} build on ${
|
||||
simBuild.platform === AppPlatform.Android ? 'an emulator' : 'a simulator'
|
||||
}?`,
|
||||
}));
|
||||
if (confirm) {
|
||||
await downloadAndRunAsync(simBuild);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
|
||||
import { EnvironmentVariableEnvironment } from '../../graphql/generated';
|
||||
|
||||
type Environment = NonNullable<BuildProfile['environment']>;
|
||||
|
||||
const BuildProfileEnvironmentToEnvironment: Record<Environment, EnvironmentVariableEnvironment> = {
|
||||
production: EnvironmentVariableEnvironment.Production,
|
||||
preview: EnvironmentVariableEnvironment.Preview,
|
||||
development: EnvironmentVariableEnvironment.Development,
|
||||
};
|
||||
|
||||
export function isEnvironment(env: string): env is EnvironmentVariableEnvironment {
|
||||
return Object.values(EnvironmentVariableEnvironment).includes(
|
||||
env as EnvironmentVariableEnvironment
|
||||
);
|
||||
}
|
||||
|
||||
export function buildProfileEnvironmentToEnvironment(
|
||||
environment: BuildProfile['environment']
|
||||
): EnvironmentVariableEnvironment | null {
|
||||
if (!environment) {
|
||||
return null;
|
||||
}
|
||||
return BuildProfileEnvironmentToEnvironment[environment];
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import path from 'path';
|
||||
import tar from 'tar';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import Log from '../../log';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { confirmAsync, promptAsync } from '../../prompts';
|
||||
import { formatBytes } from '../../utils/files';
|
||||
@@ -169,6 +169,34 @@ export async function makeProjectTarballAsync(vcsClient: Client): Promise<LocalF
|
||||
return { size, path: tarPath };
|
||||
}
|
||||
|
||||
export function maybeWarnAboutProjectTarballSize(size: number): void {
|
||||
if (size <= 150 /* MiB */ * 1024 /* KiB */ * 1024 /* B */) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.warn(
|
||||
`Your project archive is ${formatBytes(
|
||||
size
|
||||
)}. You can reduce its size and the time it takes to upload by excluding files that are unnecessary for the build process in ${chalk.bold(
|
||||
'.easignore'
|
||||
)} file. ${learnMore('https://expo.fyi/eas-build-archive')}`
|
||||
);
|
||||
}
|
||||
|
||||
const MAX_ALLOWED_PROJECT_TARBALL_SIZE =
|
||||
2 /* GiB */ * 1024 /* MiB */ * 1024 /* KiB */ * 1024; /* B */
|
||||
export function assertProjectTarballSizeDoesNotExceedLimit(size: number): void {
|
||||
if (size <= MAX_ALLOWED_PROJECT_TARBALL_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Project archive is too big. Maximum allowed size is ${formatBytes(
|
||||
MAX_ALLOWED_PROJECT_TARBALL_SIZE
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
|
||||
enum ShouldCommitChanges {
|
||||
Yes,
|
||||
ShowDiffFirst,
|
||||
|
||||
@@ -289,7 +289,7 @@ export async function doesChannelExistAsync(
|
||||
{ appId, channelName }: { appId: string; channelName: string }
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||
await ChannelQuery.viewUpdateChannelBasicInfoAsync(graphqlClient, {
|
||||
appId,
|
||||
channelName,
|
||||
});
|
||||
|
||||
+174
-79
@@ -78,7 +78,12 @@ describe(getProjectIdAsync, () => {
|
||||
|
||||
it('gets the project ID from app config if exists', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
exp: {
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
@@ -91,7 +96,7 @@ describe(getProjectIdAsync, () => {
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).resolves.toEqual('1234');
|
||||
@@ -99,7 +104,13 @@ describe(getProjectIdAsync, () => {
|
||||
|
||||
it('throws when the owner is out of sync', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'test', owner: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
exp: {
|
||||
sdkVersion: '54.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
owner: 'wat',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
@@ -112,7 +123,13 @@ describe(getProjectIdAsync, () => {
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'test', owner: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
{
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
owner: 'wat',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
@@ -122,83 +139,143 @@ describe(getProjectIdAsync, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when the owner is not specified and is different than logged in user', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
describe('when sdkVersion is less than 53', () => {
|
||||
it('throws when the owner is not specified and is different than logged in user', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: {
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (totallybrent) does not match the logged in user (notnotbrent) and the "owner" field is not specified. To ensure all libraries work correctly, "owner": "totallybrent" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (totallybrent) does not match the logged in user (notnotbrent) and the "owner" field is not specified. To ensure all libraries work correctly, "owner": "totallybrent" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
it('throws when the owner is not specified and is different than logged in user which is a robot', async () => {
|
||||
const sessionManagerMock = mock<SessionManager>();
|
||||
when(sessionManagerMock.ensureLoggedInAsync(anything())).thenResolve({
|
||||
actor: {
|
||||
__typename: 'Robot',
|
||||
id: 'robot_id',
|
||||
accounts: [
|
||||
{
|
||||
id: 'account_id_1',
|
||||
name: 'notnotbrent',
|
||||
users: [{ role: Role.Admin, actor: { id: 'robot_id' } }],
|
||||
},
|
||||
{
|
||||
id: 'account_id_2',
|
||||
name: 'dominik',
|
||||
users: [{ role: Role.ViewOnly, actor: { id: 'robot_id' } }],
|
||||
},
|
||||
],
|
||||
isExpoAdmin: false,
|
||||
featureGates: {},
|
||||
},
|
||||
authenticationInfo: { accessToken: 'fake', sessionSecret: null },
|
||||
});
|
||||
const sessionManagerRobot = instance(sessionManagerMock);
|
||||
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: {
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManagerRobot,
|
||||
{
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (totallybrent) must be specified in "owner" field when using a robot access token. To ensure all libraries work correctly, "owner": "totallybrent" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when the owner is not specified and is different than logged in user which is a robot', async () => {
|
||||
const sessionManagerMock = mock<SessionManager>();
|
||||
when(sessionManagerMock.ensureLoggedInAsync(anything())).thenResolve({
|
||||
actor: {
|
||||
__typename: 'Robot',
|
||||
id: 'robot_id',
|
||||
accounts: [
|
||||
{
|
||||
id: 'account_id_1',
|
||||
name: 'notnotbrent',
|
||||
users: [{ role: Role.Admin, actor: { id: 'robot_id' } }],
|
||||
},
|
||||
{
|
||||
id: 'account_id_2',
|
||||
name: 'dominik',
|
||||
users: [{ role: Role.ViewOnly, actor: { id: 'robot_id' } }],
|
||||
},
|
||||
],
|
||||
isExpoAdmin: false,
|
||||
featureGates: {},
|
||||
},
|
||||
authenticationInfo: { accessToken: 'fake', sessionSecret: null },
|
||||
});
|
||||
const sessionManagerRobot = instance(sessionManagerMock);
|
||||
describe('when sdkVersion is 53 or higher', () => {
|
||||
it('does not throw when the owner is not specified', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: {
|
||||
sdkVersion: '53.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
});
|
||||
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{
|
||||
sdkVersion: '53.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManagerRobot,
|
||||
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (totallybrent) must be specified in "owner" field when using a robot access token. To ensure all libraries work correctly, "owner": "totallybrent" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when the slug is out of sync', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
exp: {
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'wat',
|
||||
extra: { eas: { projectId: '1234' } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
@@ -211,7 +288,7 @@ describe(getProjectIdAsync, () => {
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
@@ -222,10 +299,17 @@ describe(getProjectIdAsync, () => {
|
||||
});
|
||||
|
||||
it('fetches the project ID when not in app config, and sets it in the config', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||
jest
|
||||
.mocked(getConfig)
|
||||
.mockReturnValue({ exp: { sdkVersion: '52.0.0', name: 'test', slug: 'test' } } as any);
|
||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||
type: 'success',
|
||||
config: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } },
|
||||
config: {
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: '2345' } },
|
||||
},
|
||||
});
|
||||
jest
|
||||
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
||||
@@ -233,7 +317,7 @@ describe(getProjectIdAsync, () => {
|
||||
|
||||
const projectId = await getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'test' },
|
||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'test' },
|
||||
{
|
||||
nonInteractive: false,
|
||||
}
|
||||
@@ -252,7 +336,9 @@ describe(getProjectIdAsync, () => {
|
||||
});
|
||||
|
||||
it('throws if writing the ID back to the config fails', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||
jest
|
||||
.mocked(getConfig)
|
||||
.mockReturnValue({ exp: { sdkVersion: '52.0.0', name: 'test', slug: 'test' } } as any);
|
||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||
type: 'fail',
|
||||
config: null,
|
||||
@@ -262,13 +348,22 @@ describe(getProjectIdAsync, () => {
|
||||
.mockImplementation(async () => '4567');
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(sessionManager, { name: 'test', slug: 'test' }, { nonInteractive: false })
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'test' },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('throws if extra.eas.projectId is not a string', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: 1234 } } },
|
||||
exp: {
|
||||
sdkVersion: '52.0.0',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
extra: { eas: { projectId: 1234 } },
|
||||
},
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
@@ -281,7 +376,7 @@ describe(getProjectIdAsync, () => {
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'wat', extra: { eas: { projectId: 1234 } } },
|
||||
{ sdkVersion: '52.0.0', name: 'test', slug: 'wat', extra: { eas: { projectId: 1234 } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
|
||||
+6
-1
@@ -103,6 +103,8 @@ export async function findProjectRootAsync({
|
||||
}
|
||||
}
|
||||
|
||||
let ranEnsureEasCliIsNotInDependencies = false;
|
||||
|
||||
/**
|
||||
* Determine the project root directory and ensure some constraints about the project setup
|
||||
* like CLI version and dependencies.
|
||||
@@ -113,6 +115,9 @@ export async function findProjectRootAsync({
|
||||
export async function findProjectDirAndVerifyProjectSetupAsync(): Promise<string> {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
await applyCliConfigAsync(projectDir);
|
||||
await ensureEasCliIsNotInDependenciesAsync(projectDir);
|
||||
if (!ranEnsureEasCliIsNotInDependencies) {
|
||||
ranEnsureEasCliIsNotInDependencies = true;
|
||||
await ensureEasCliIsNotInDependenciesAsync(projectDir);
|
||||
}
|
||||
return projectDir;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ExpoConfig, getProjectConfigDescription } from '@expo/config';
|
||||
import { Env } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
import semver from 'semver';
|
||||
|
||||
import { ExpoGraphqlClient, createGraphqlClient } from './createGraphqlClient';
|
||||
import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync';
|
||||
@@ -127,29 +128,33 @@ export async function validateOrSetProjectIdAsync({
|
||||
);
|
||||
}
|
||||
|
||||
const actorUsername = getActorUsername(actor);
|
||||
if (!exp.owner && appForProjectId.ownerAccount.name !== actorUsername) {
|
||||
if (actorUsername) {
|
||||
throw new Error(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (${
|
||||
appForProjectId.ownerAccount.name
|
||||
}) does not match the logged in user (${actorUsername}) and the "owner" field is not specified. To ensure all libraries work correctly, "owner": "${
|
||||
appForProjectId.ownerAccount.name
|
||||
}" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
// robot caller
|
||||
throw new Error(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (${
|
||||
appForProjectId.ownerAccount.name
|
||||
}) must be specified in "owner" field when using a robot access token. To ensure all libraries work correctly, "owner": "${
|
||||
appForProjectId.ownerAccount.name
|
||||
}" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
const sdkVersion = exp.sdkVersion;
|
||||
// SDK 53 and above no longer require owner field in app config
|
||||
if (sdkVersion && semver.satisfies(sdkVersion, '< 53.0.0')) {
|
||||
const actorUsername = getActorUsername(actor);
|
||||
if (!exp.owner && appForProjectId.ownerAccount.name !== actorUsername) {
|
||||
if (actorUsername) {
|
||||
throw new Error(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (${
|
||||
appForProjectId.ownerAccount.name
|
||||
}) does not match the logged in user (${actorUsername}) and the "owner" field is not specified. To ensure all libraries work correctly, "owner": "${
|
||||
appForProjectId.ownerAccount.name
|
||||
}" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
// robot caller
|
||||
throw new Error(
|
||||
`Project config: Owner of project identified by "extra.eas.projectId" (${
|
||||
appForProjectId.ownerAccount.name
|
||||
}) must be specified in "owner" field when using a robot access token. To ensure all libraries work correctly, "owner": "${
|
||||
appForProjectId.ownerAccount.name
|
||||
}" should be added to the project config, which can be done automatically by re-running "eas init". ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -42,13 +42,13 @@ export async function loadServerSideEnvironmentVariablesAsync({
|
||||
|
||||
if (Object.keys(serverEnvVars).length > 0) {
|
||||
Log.log(
|
||||
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS servers: ${Object.keys(
|
||||
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS: ${Object.keys(
|
||||
serverEnvVars
|
||||
).join(', ')}.`
|
||||
);
|
||||
} else {
|
||||
Log.log(
|
||||
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS servers.`
|
||||
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS.`
|
||||
);
|
||||
}
|
||||
Log.newLine();
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { BuildProfile, EasJsonAccessor } from '@expo/eas-json';
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import {
|
||||
createBuildProfileAsync,
|
||||
doesBuildProfileExistAsync,
|
||||
ensureProjectConfiguredAsync,
|
||||
} from '../../build/configure';
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../../build/evaluateConfigWithEnvVarsAsync';
|
||||
import { downloadAndRunAsync, runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
||||
import { ensureRepoIsCleanAsync } from '../../build/utils/repository';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { BuildFragment, BuildStatus, DistributionType } from '../../graphql/generated';
|
||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
import { toAppPlatform } from '../../graphql/types/AppPlatform';
|
||||
import Log from '../../log';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { confirmAsync, promptAsync } from '../../prompts';
|
||||
import { expoCommandAsync } from '../../utils/expoCli';
|
||||
import { createFingerprintAsync } from '../../utils/fingerprintCli';
|
||||
import { ProfileData, getProfilesAsync } from '../../utils/profiles';
|
||||
import { Client } from '../../vcs/vcs';
|
||||
|
||||
const DEFAULT_EAS_BUILD_RUN_PROFILE_NAME = 'development-simulator';
|
||||
|
||||
export default class BuildDev extends EasCommand {
|
||||
static override hidden: true;
|
||||
|
||||
static override description =
|
||||
'run dev client simulator/emulator build with matching fingerprint or create a new one';
|
||||
|
||||
static override flags = {
|
||||
platform: Flags.enum<Platform.IOS | Platform.ANDROID>({
|
||||
char: 'p',
|
||||
options: [Platform.IOS, Platform.ANDROID],
|
||||
}),
|
||||
profile: Flags.string({
|
||||
char: 'e',
|
||||
description: `Name of the build profile from eas.json. It must be a profile allowing to create emulator/simulator internal distribution dev client builds. The "${DEFAULT_EAS_BUILD_RUN_PROFILE_NAME}" build profile will be selected by default.`,
|
||||
helpValue: 'PROFILE_NAME',
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
...this.ContextOptions.Vcs,
|
||||
...this.ContextOptions.Analytics,
|
||||
...this.ContextOptions.ProjectId,
|
||||
};
|
||||
|
||||
protected override async runAsync(): Promise<any> {
|
||||
const { flags } = await this.parse(BuildDev);
|
||||
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
projectDir,
|
||||
analytics,
|
||||
vcsClient,
|
||||
projectId,
|
||||
} = await this.getContextAsync(BuildDev, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const platform = await this.selectPlatformAsync(flags.platform);
|
||||
if (process.platform !== 'darwin' && platform === Platform.IOS) {
|
||||
Errors.error('Running iOS builds in simulator is only supported on macOS.', { exit: 1 });
|
||||
}
|
||||
|
||||
await vcsClient.ensureRepoExistsAsync();
|
||||
await ensureRepoIsCleanAsync(vcsClient, flags.nonInteractive);
|
||||
await ensureProjectConfiguredAsync({
|
||||
projectDir,
|
||||
nonInteractive: false,
|
||||
vcsClient,
|
||||
});
|
||||
|
||||
const buildProfile = await this.ensureValidBuildRunProfileExistsAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
selectedBuildProfileName: flags.profile,
|
||||
vcsClient,
|
||||
});
|
||||
|
||||
const workflow = await resolveWorkflowAsync(projectDir, platform, vcsClient);
|
||||
|
||||
const { env } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: buildProfile.profile,
|
||||
buildProfileName: buildProfile.profileName,
|
||||
graphqlClient,
|
||||
getProjectConfig: getDynamicPrivateProjectConfigAsync,
|
||||
opts: { env: buildProfile.profile.env },
|
||||
});
|
||||
|
||||
const fingerprint = await createFingerprintAsync(projectDir, {
|
||||
env,
|
||||
workflow,
|
||||
platforms: [platform],
|
||||
});
|
||||
if (!fingerprint) {
|
||||
Errors.error('Failed to calculate fingerprint', { exit: 1 });
|
||||
}
|
||||
Log.log(`✨ Calculated fingerprint hash: ${fingerprint.hash}`);
|
||||
Log.newLine();
|
||||
|
||||
const builds = await this.getBuildsAsync({
|
||||
graphqlClient,
|
||||
projectId,
|
||||
platform,
|
||||
fingerprint,
|
||||
});
|
||||
if (builds.length !== 0) {
|
||||
const build = builds[0];
|
||||
Log.succeed(
|
||||
`🎯 Found successful build with matching fingerprint on EAS servers. Running it...`
|
||||
);
|
||||
|
||||
if (build.artifacts?.applicationArchiveUrl) {
|
||||
await downloadAndRunAsync(build);
|
||||
await this.startDevServerAsync({ projectDir, platform });
|
||||
return;
|
||||
} else {
|
||||
Log.warn('Artifacts for this build expired. New build will be started.');
|
||||
}
|
||||
}
|
||||
|
||||
Log.log('🚀 No successful build with matching fingerprint found. Starting a new build...');
|
||||
|
||||
const previousBuildsForSelectedProfile = await this.getBuildsAsync({
|
||||
graphqlClient,
|
||||
projectId,
|
||||
platform,
|
||||
});
|
||||
if (
|
||||
previousBuildsForSelectedProfile.length > 0 &&
|
||||
previousBuildsForSelectedProfile[0].metrics?.buildDuration
|
||||
) {
|
||||
Log.log(
|
||||
`🕒 Previous build for "${buildProfile.profileName}" profile completed in ${Math.floor(
|
||||
previousBuildsForSelectedProfile[0].metrics.buildDuration / 60000
|
||||
)} minutes.`
|
||||
);
|
||||
}
|
||||
|
||||
await runBuildAndSubmitAsync({
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
projectDir,
|
||||
flags: {
|
||||
requestedPlatform:
|
||||
platform === Platform.ANDROID ? RequestedPlatform.Android : RequestedPlatform.Ios,
|
||||
nonInteractive: false,
|
||||
freezeCredentials: false,
|
||||
wait: true,
|
||||
clearCache: false,
|
||||
json: false,
|
||||
autoSubmit: false,
|
||||
localBuildOptions: {},
|
||||
repack: false,
|
||||
profile: flags.profile ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
||||
},
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
downloadSimBuildAutoConfirm: true,
|
||||
envOverride: env,
|
||||
});
|
||||
await this.startDevServerAsync({ projectDir, platform });
|
||||
}
|
||||
|
||||
private async selectPlatformAsync(platform?: Platform): Promise<Platform> {
|
||||
if (platform) {
|
||||
return platform;
|
||||
}
|
||||
const { resolvedPlatform } = await promptAsync({
|
||||
type: 'select',
|
||||
message: 'Select platform',
|
||||
name: 'resolvedPlatform',
|
||||
choices: [
|
||||
{ title: 'Android', value: Platform.ANDROID },
|
||||
{ title: 'iOS', value: Platform.IOS },
|
||||
],
|
||||
});
|
||||
return resolvedPlatform;
|
||||
}
|
||||
|
||||
private async validateBuildRunProfileAsync({
|
||||
platform,
|
||||
buildProfile,
|
||||
buildProfileName,
|
||||
}: {
|
||||
platform: Platform;
|
||||
buildProfile: BuildProfile;
|
||||
buildProfileName: string;
|
||||
}): Promise<void> {
|
||||
if (buildProfile.developmentClient !== true) {
|
||||
Errors.error(
|
||||
`Profile "${buildProfileName}" must specify "developmentClient: true" to create a dev client build. Select a different profile or update the profile in eas.json.`,
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
if (buildProfile.distribution !== 'internal') {
|
||||
Errors.error(
|
||||
`Profile "${buildProfileName}" must specify "distribution: internal" in order to work with eas build:dev command. Select a different profile or update the profile in eas.json.`,
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
|
||||
if (platform === Platform.IOS) {
|
||||
const iosProfile = buildProfile as BuildProfile<Platform.IOS>;
|
||||
if (iosProfile.simulator !== true && iosProfile.withoutCredentials !== true) {
|
||||
Errors.error(
|
||||
`Profile "${buildProfileName}" must specify "ios.simulator: true" or "withoutCredentials: true" to create an iOS simulator build. Select a different profile or update the profile in eas.json.`,
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const androidProfile = buildProfile as BuildProfile<Platform.ANDROID>;
|
||||
if (
|
||||
androidProfile.distribution !== 'internal' &&
|
||||
androidProfile.withoutCredentials !== true
|
||||
) {
|
||||
Errors.error(
|
||||
`Profile "${buildProfileName}" must specify "distribution: internal" or "withoutCredentials: true" to create an Android emulator build. Select a different profile or update the profile in eas.json.`,
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureValidBuildRunProfileExistsAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
selectedBuildProfileName,
|
||||
vcsClient,
|
||||
}: {
|
||||
projectDir: string;
|
||||
platform: Platform;
|
||||
selectedBuildProfileName?: string;
|
||||
vcsClient: Client;
|
||||
}): Promise<ProfileData<'build'>> {
|
||||
if (
|
||||
!!selectedBuildProfileName ||
|
||||
(await doesBuildProfileExistAsync({
|
||||
projectDir,
|
||||
profileName: DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
||||
}))
|
||||
) {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
const [buildProfile] = await getProfilesAsync({
|
||||
type: 'build',
|
||||
easJsonAccessor,
|
||||
platforms: [platform],
|
||||
profileName: selectedBuildProfileName ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
||||
projectDir,
|
||||
});
|
||||
await this.validateBuildRunProfileAsync({
|
||||
buildProfileName: selectedBuildProfileName ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
||||
platform,
|
||||
buildProfile: buildProfile.profile,
|
||||
});
|
||||
} else {
|
||||
const createBuildProfile = await confirmAsync({
|
||||
message: `We want to go ahead and generate "${DEFAULT_EAS_BUILD_RUN_PROFILE_NAME}" build profile for you, that matches eas build:dev criteria. Do you want to proceed?`,
|
||||
});
|
||||
if (!createBuildProfile) {
|
||||
Errors.error(
|
||||
'Come back later or specify different build compliant with eas build:dev requirements by using "--profile" flag.',
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
await createBuildProfileAsync({
|
||||
projectDir,
|
||||
profileName: DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
||||
profileContents: {
|
||||
developmentClient: true,
|
||||
distribution: 'internal',
|
||||
ios: {
|
||||
simulator: true,
|
||||
},
|
||||
environment: 'development',
|
||||
},
|
||||
nonInteractive: false,
|
||||
vcsClient,
|
||||
});
|
||||
}
|
||||
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
const [buildProfile] = await getProfilesAsync({
|
||||
type: 'build',
|
||||
easJsonAccessor,
|
||||
platforms: [platform],
|
||||
profileName: selectedBuildProfileName ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
|
||||
projectDir,
|
||||
});
|
||||
return buildProfile;
|
||||
}
|
||||
|
||||
private async getBuildsAsync({
|
||||
graphqlClient,
|
||||
projectId,
|
||||
platform,
|
||||
fingerprint,
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
projectId: string;
|
||||
platform: Platform;
|
||||
fingerprint?: { hash: string };
|
||||
}): Promise<BuildFragment[]> {
|
||||
return await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
filter: {
|
||||
platform: toAppPlatform(platform),
|
||||
fingerprintHash: fingerprint?.hash,
|
||||
status: BuildStatus.Finished,
|
||||
simulator: platform === Platform.IOS ? true : undefined,
|
||||
distribution: platform === Platform.ANDROID ? DistributionType.Internal : undefined,
|
||||
developmentClient: true,
|
||||
},
|
||||
offset: 0,
|
||||
limit: 1,
|
||||
});
|
||||
}
|
||||
|
||||
private async startDevServerAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
}: {
|
||||
projectDir: string;
|
||||
platform: Platform;
|
||||
}): Promise<void> {
|
||||
Log.newLine();
|
||||
Log.log(
|
||||
`Starting development server: ${chalk.dim(
|
||||
`npx expo start --dev-client ${platform === Platform.IOS ? '--ios' : '--android'}`
|
||||
)}`
|
||||
);
|
||||
await expoCommandAsync(projectDir, [
|
||||
'start',
|
||||
'--dev-client',
|
||||
platform === Platform.IOS ? '--ios' : '--android',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -78,10 +78,12 @@ export default class Build extends EasCommand {
|
||||
description: 'Clear cache before the build',
|
||||
}),
|
||||
'auto-submit': Flags.boolean({
|
||||
char: 's',
|
||||
default: false,
|
||||
description:
|
||||
'Submit on build complete using the submit profile with the same name as the build profile',
|
||||
exclusive: ['auto-submit-with-profile'],
|
||||
aliases: ['submit'],
|
||||
}),
|
||||
'auto-submit-with-profile': Flags.string({
|
||||
description: 'Submit on build complete using the submit profile with provided name',
|
||||
@@ -158,15 +160,15 @@ export default class Build extends EasCommand {
|
||||
|
||||
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
|
||||
|
||||
await runBuildAndSubmitAsync(
|
||||
await runBuildAndSubmitAsync({
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
projectDir,
|
||||
flagsWithPlatform,
|
||||
flags: flagsWithPlatform,
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync
|
||||
);
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
});
|
||||
}
|
||||
|
||||
private sanitizeFlags(
|
||||
|
||||
@@ -99,12 +99,12 @@ export default class BuildInspect extends EasCommand {
|
||||
await this.copyToOutputDirAsync(tmpWorkingdir, outputDirectory);
|
||||
} else {
|
||||
try {
|
||||
await runBuildAndSubmitAsync(
|
||||
await runBuildAndSubmitAsync({
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
projectDir,
|
||||
{
|
||||
flags: {
|
||||
nonInteractive: false,
|
||||
freezeCredentials: false,
|
||||
wait: true,
|
||||
@@ -124,8 +124,8 @@ export default class BuildInspect extends EasCommand {
|
||||
repack: false,
|
||||
},
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync
|
||||
);
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
});
|
||||
if (!flags.verbose) {
|
||||
Log.log(chalk.green('Build successful'));
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ import { runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
|
||||
import NoVcsClient from '../../vcs/clients/noVcs';
|
||||
import GitClient from '../../vcs/clients/git';
|
||||
|
||||
/**
|
||||
* This command will be run on the EAS Build workers, when building
|
||||
@@ -64,18 +63,24 @@ export default class BuildInternal extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildInternal, {
|
||||
nonInteractive: true,
|
||||
vcsClientOverride: process.env.EAS_NO_VCS ? new NoVcsClient() : new GitNoCommitClient(),
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
if (vcsClient instanceof GitClient) {
|
||||
// `build:internal` is run on EAS workers and the repo may have been changed
|
||||
// by pre-install hooks or other scripts. We don't want to require committing changes
|
||||
// to continue the build.
|
||||
vcsClient.requireCommit = false;
|
||||
}
|
||||
|
||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||
|
||||
await runBuildAndSubmitAsync(
|
||||
await runBuildAndSubmitAsync({
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
projectDir,
|
||||
{
|
||||
flags: {
|
||||
requestedPlatform: flags.platform as RequestedPlatform,
|
||||
profile: flags.profile,
|
||||
nonInteractive: true,
|
||||
@@ -91,7 +96,7 @@ export default class BuildInternal extends EasCommand {
|
||||
repack: false,
|
||||
},
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync
|
||||
);
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,9 @@ export default class BuildList extends EasCommand {
|
||||
aliases: ['gitCommitHash'],
|
||||
description: 'Filter only builds created with the specified git commit hash',
|
||||
}),
|
||||
'fingerprint-hash': Flags.string({
|
||||
description: 'Filter only builds with the specified fingerprint hash',
|
||||
}),
|
||||
...EasPaginatedQueryFlags,
|
||||
limit: getLimitFlagWithCustomValues({ defaultTo: 10, limit: BUILDS_LIMIT }),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
@@ -130,6 +133,7 @@ export default class BuildList extends EasCommand {
|
||||
buildProfile: flags['build-profile'],
|
||||
gitCommitHash: flags['git-commit-hash'],
|
||||
simulator: flags.simulator,
|
||||
fingerprintHash: flags['fingerprint-hash'],
|
||||
},
|
||||
paginatedQueryOptions,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
import assert from 'assert';
|
||||
import { pathExists } from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import { getLatestBuildAsync, listAndSelectBuildOnAppAsync } from '../../build/queries';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
@@ -17,13 +16,12 @@ import Log from '../../log';
|
||||
import { appPlatformDisplayNames } from '../../platform';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { RunArchiveFlags, runAsync } from '../../run/run';
|
||||
import { RunArchiveFlags, getEasBuildRunCachedAppPath, runAsync } from '../../run/run';
|
||||
import { isRunnableOnSimulatorOrEmulator } from '../../run/utils';
|
||||
import {
|
||||
downloadAndMaybeExtractAppAsync,
|
||||
extractAppFromLocalArchiveAsync,
|
||||
} from '../../utils/download';
|
||||
import { getEasBuildRunCacheDirectoryPath } from '../../utils/paths';
|
||||
|
||||
interface RawRunFlags {
|
||||
latest?: boolean;
|
||||
@@ -245,17 +243,6 @@ async function maybeGetBuildAsync(
|
||||
}
|
||||
}
|
||||
|
||||
function getEasBuildRunCachedAppPath(
|
||||
projectId: string,
|
||||
buildId: string,
|
||||
platform: AppPlatform
|
||||
): string {
|
||||
return path.join(
|
||||
getEasBuildRunCacheDirectoryPath(),
|
||||
`${projectId}_${buildId}.${platform === AppPlatform.Ios ? 'app' : 'apk'}`
|
||||
);
|
||||
}
|
||||
|
||||
async function getPathToSimulatorBuildAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
projectId: string,
|
||||
|
||||
@@ -120,7 +120,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, profileInfo.platform, vcsClient);
|
||||
if (!remoteVersions?.buildVersion) {
|
||||
Log.warn(
|
||||
`Skipping versions sync for ${platformDisplayName}. There are no versions configured on Expo servers, use "eas build:version:set" or run a build to initialize it.`
|
||||
`Skipping versions sync for ${platformDisplayName}. There are no versions configured on EAS, use "eas build:version:set" or run a build to initialize it.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export default class ChannelDelete extends EasCommand {
|
||||
|
||||
let channelId, channelName;
|
||||
if (nameArg) {
|
||||
const { id, name } = await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||
const { id, name } = await ChannelQuery.viewUpdateChannelBasicInfoAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
channelName: nameArg,
|
||||
});
|
||||
|
||||
@@ -94,7 +94,7 @@ export default class ChannelEdit extends EasCommand {
|
||||
}
|
||||
|
||||
const existingChannel = args.name
|
||||
? await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||
? await ChannelQuery.viewUpdateChannelBasicInfoAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
channelName: args.name,
|
||||
})
|
||||
|
||||
@@ -86,7 +86,7 @@ export default class ChannelPause extends EasCommand {
|
||||
}
|
||||
|
||||
const existingChannel = args.name
|
||||
? await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||
? await ChannelQuery.viewUpdateChannelBasicInfoAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
channelName: args.name,
|
||||
})
|
||||
|
||||
@@ -86,7 +86,7 @@ export default class ChannelResume extends EasCommand {
|
||||
}
|
||||
|
||||
const existingChannel = args.name
|
||||
? await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||
? await ChannelQuery.viewUpdateChannelBasicInfoAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
channelName: args.name,
|
||||
})
|
||||
|
||||
+4
-7
@@ -33,12 +33,9 @@ interface RawDeployAliasFlags {
|
||||
}
|
||||
|
||||
export default class WorkerAlias extends EasCommand {
|
||||
static override description = 'Assign deployment aliases';
|
||||
static override aliases = ['deploy:alias', 'deploy:promote'];
|
||||
|
||||
// TODO(@kitten): Keep command hidden until worker deployments are live
|
||||
static override hidden = true;
|
||||
static override state = 'beta';
|
||||
static override description = 'Assign deployment aliases.';
|
||||
static override aliases = ['worker:alias', 'deploy:promote'];
|
||||
static override state = 'preview';
|
||||
|
||||
static override flags = {
|
||||
prod: Flags.boolean({
|
||||
@@ -73,7 +70,7 @@ export default class WorkerAlias extends EasCommand {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
Log.warn('EAS Worker Deployments are in beta and subject to breaking changes.');
|
||||
Log.warn('EAS Hosting is still in preview and subject to changes.');
|
||||
|
||||
const {
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
+17
-12
@@ -65,13 +65,10 @@ interface DeployInProgressParams {
|
||||
}
|
||||
|
||||
export default class WorkerDeploy extends EasCommand {
|
||||
static override description = 'Deploy your Expo web build';
|
||||
static override aliases = ['deploy'];
|
||||
static override description = 'Deploy your Expo Router web build and API Routes.';
|
||||
static override aliases = ['worker:deploy'];
|
||||
static override usage = [chalk`deploy {dim [options]}`, `deploy --prod`];
|
||||
|
||||
// TODO(@kitten): Keep command hidden until worker deployments are live
|
||||
static override hidden = true;
|
||||
static override state = 'beta';
|
||||
static override state = 'preview';
|
||||
|
||||
static override flags = {
|
||||
prod: Flags.boolean({
|
||||
@@ -114,7 +111,7 @@ export default class WorkerDeploy extends EasCommand {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
Log.warn('EAS Worker Deployments are in beta and subject to breaking changes.');
|
||||
Log.warn('EAS Hosting is still in preview and subject to changes.');
|
||||
|
||||
const {
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
@@ -253,7 +250,7 @@ export default class WorkerDeploy extends EasCommand {
|
||||
let progress = ora('Preparing project').start();
|
||||
|
||||
try {
|
||||
const manifest = await WorkerAssets.createManifestAsync(
|
||||
const manifestResult = await WorkerAssets.createManifestAsync(
|
||||
{
|
||||
environment: flags.environment,
|
||||
projectDir,
|
||||
@@ -261,13 +258,20 @@ export default class WorkerDeploy extends EasCommand {
|
||||
},
|
||||
graphqlClient
|
||||
);
|
||||
if (manifestResult.conflictingVariableNames?.length) {
|
||||
Log.warn(
|
||||
'> The following environment variables were present in local .env files as well as EAS environment variables. ' +
|
||||
'In case of conflict, the EAS environment variable values will be used: ' +
|
||||
manifestResult.conflictingVariableNames.join(' ')
|
||||
);
|
||||
}
|
||||
assetMap = await WorkerAssets.createAssetMapAsync(
|
||||
projectDist.type === 'server' ? projectDist.clientPath : projectDist.path
|
||||
);
|
||||
tarPath = await WorkerAssets.packFilesIterableAsync(
|
||||
emitWorkerTarballAsync({
|
||||
assetMap,
|
||||
manifest,
|
||||
manifest: manifestResult.manifest,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -426,7 +430,7 @@ async function resolveExportedProjectAsync(
|
||||
|
||||
if (!exportDirStat?.isDirectory()) {
|
||||
throw new Error(
|
||||
`No "${flags.exportDir}/" folder found. Prepare your project for deployment with "npx expo export --platform web"`
|
||||
`No "${flags.exportDir}/" folder found. Export your app with "npx expo export --platform web"`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -451,7 +455,8 @@ function logExportedProjectInfo(
|
||||
// Only show the timestamp for exports older than 1 minute
|
||||
if (project.modifiedAt && Date.now() - project.modifiedAt.getTime() > 60_000) {
|
||||
modifiedAgo = ` - exported ${formatTimeAgo(project.modifiedAt)}`;
|
||||
Log.warn(`> Project export: ${project.type}${modifiedAgo}`);
|
||||
} else {
|
||||
Log.log(chalk`{dim > Project export: ${project.type}}`);
|
||||
}
|
||||
|
||||
Log.log(chalk`{dim > Project export: ${project.type}${modifiedAgo}}`);
|
||||
}
|
||||
@@ -147,10 +147,10 @@ export default class DeviceDelete extends EasCommand {
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
chosenDevice: AppleDevice | AppleDeviceQueryResult
|
||||
): Promise<void> {
|
||||
const removalSpinner = ora(`Removing Apple device on Expo`).start();
|
||||
const removalSpinner = ora(`Removing Apple device on EAS`).start();
|
||||
try {
|
||||
await AppleDeviceMutation.deleteAppleDeviceAsync(graphqlClient, chosenDevice.id);
|
||||
removalSpinner.succeed('Removed Apple device from Expo');
|
||||
removalSpinner.succeed('Removed Apple device from EAS');
|
||||
} catch (err) {
|
||||
removalSpinner.fail();
|
||||
throw err;
|
||||
|
||||
@@ -105,12 +105,12 @@ export default class DeviceRename extends EasCommand {
|
||||
chosenDevice: AppleDevice | AppleDeviceQueryResult,
|
||||
newDeviceName: string
|
||||
): Promise<void> {
|
||||
const removalSpinner = ora(`Renaming Apple device on Expo`).start();
|
||||
const removalSpinner = ora(`Renaming Apple device on EAS`).start();
|
||||
try {
|
||||
await AppleDeviceMutation.updateAppleDeviceAsync(graphqlClient, chosenDevice.id, {
|
||||
name: newDeviceName,
|
||||
});
|
||||
removalSpinner.succeed('Renamed Apple device on Expo');
|
||||
removalSpinner.succeed('Renamed Apple device on EAS');
|
||||
} catch (err) {
|
||||
removalSpinner.fail();
|
||||
throw err;
|
||||
|
||||
+69
-18
@@ -53,9 +53,10 @@ export default class EnvExec extends EasCommand {
|
||||
},
|
||||
];
|
||||
|
||||
private isNonInteractive: boolean = false;
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags, args } = await this.parse(EnvExec);
|
||||
|
||||
const parsedFlags = this.sanitizeFlagsAndArgs(flags, args);
|
||||
|
||||
const {
|
||||
@@ -65,6 +66,8 @@ export default class EnvExec extends EasCommand {
|
||||
nonInteractive: parsedFlags.nonInteractive,
|
||||
});
|
||||
|
||||
this.isNonInteractive = parsedFlags.nonInteractive;
|
||||
|
||||
const environment =
|
||||
parsedFlags.environment ??
|
||||
(await promptVariableEnvironmentAsync({ nonInteractive: parsedFlags.nonInteractive }));
|
||||
@@ -72,12 +75,20 @@ export default class EnvExec extends EasCommand {
|
||||
graphqlClient,
|
||||
projectId,
|
||||
environment,
|
||||
nonInteractive: parsedFlags.nonInteractive,
|
||||
});
|
||||
|
||||
await this.runCommandWithEnvVarsAsync({
|
||||
command: parsedFlags.command,
|
||||
environmentVariables,
|
||||
});
|
||||
if (parsedFlags.nonInteractive) {
|
||||
await this.runCommandNonInteractiveWithEnvVarsAsync({
|
||||
command: parsedFlags.command,
|
||||
environmentVariables,
|
||||
});
|
||||
} else {
|
||||
await this.runCommandWithEnvVarsAsync({
|
||||
command: parsedFlags.command,
|
||||
environmentVariables,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private sanitizeFlagsAndArgs(
|
||||
@@ -96,13 +107,48 @@ export default class EnvExec extends EasCommand {
|
||||
throw new Error("Invalid environment. Use one of 'production', 'preview', or 'development'.");
|
||||
}
|
||||
|
||||
const firstChar = bash_command[0];
|
||||
const lastChar = bash_command[bash_command.length - 1];
|
||||
const cleanCommand =
|
||||
(firstChar === '"' && lastChar === '"') || (firstChar === "'" && lastChar === "'")
|
||||
? bash_command.slice(1, -1)
|
||||
: bash_command;
|
||||
|
||||
return {
|
||||
nonInteractive: rawFlags['non-interactive'],
|
||||
environment,
|
||||
command: bash_command,
|
||||
command: cleanCommand,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
protected override async catch(err: Error): Promise<any> {
|
||||
// when in non-interactive, make the behavior of this command a pure passthrough, outputting only the subprocess' stdout and stderr and exiting with the
|
||||
// sub-command's exit status
|
||||
if (this.isNonInteractive) {
|
||||
process.exitCode = process.exitCode ?? (err as any).status ?? 1;
|
||||
} else {
|
||||
await super.catch(err);
|
||||
}
|
||||
}
|
||||
|
||||
private async runCommandNonInteractiveWithEnvVarsAsync({
|
||||
command,
|
||||
environmentVariables,
|
||||
}: {
|
||||
command: string;
|
||||
environmentVariables: Record<string, string>;
|
||||
}): Promise<void> {
|
||||
await spawnAsync(command, [], {
|
||||
shell: true,
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
...environmentVariables,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async runCommandWithEnvVarsAsync({
|
||||
command,
|
||||
environmentVariables,
|
||||
@@ -111,7 +157,8 @@ export default class EnvExec extends EasCommand {
|
||||
environmentVariables: Record<string, string>;
|
||||
}): Promise<void> {
|
||||
Log.log(`Running command: ${chalk.bold(command)}`);
|
||||
const spawnPromise = spawnAsync('bash', ['-c', command], {
|
||||
const spawnPromise = spawnAsync(command, [], {
|
||||
shell: true,
|
||||
stdio: ['inherit', 'pipe', 'pipe'],
|
||||
env: {
|
||||
...process.env,
|
||||
@@ -147,10 +194,12 @@ export default class EnvExec extends EasCommand {
|
||||
graphqlClient,
|
||||
projectId,
|
||||
environment,
|
||||
nonInteractive,
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
projectId: string;
|
||||
environment: EnvironmentVariableEnvironment;
|
||||
nonInteractive: boolean;
|
||||
}): Promise<Record<string, string>> {
|
||||
const environmentVariablesQueryResult =
|
||||
await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(graphqlClient, {
|
||||
@@ -162,18 +211,20 @@ export default class EnvExec extends EasCommand {
|
||||
({ value }) => !!value
|
||||
);
|
||||
|
||||
if (nonSecretEnvironmentVariables.length > 0) {
|
||||
Log.log(
|
||||
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS: ${nonSecretEnvironmentVariables
|
||||
.map(e => e.name)
|
||||
.join(', ')}.`
|
||||
);
|
||||
} else {
|
||||
Log.log(
|
||||
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS servers.`
|
||||
);
|
||||
if (!nonInteractive) {
|
||||
if (nonSecretEnvironmentVariables.length > 0) {
|
||||
Log.log(
|
||||
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS: ${nonSecretEnvironmentVariables
|
||||
.map(e => e.name)
|
||||
.join(', ')}.`
|
||||
);
|
||||
} else {
|
||||
Log.log(
|
||||
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS.`
|
||||
);
|
||||
}
|
||||
Log.newLine();
|
||||
}
|
||||
Log.newLine();
|
||||
|
||||
const environmentVariables: Record<string, string> = {};
|
||||
for (const { name, value } of nonSecretEnvironmentVariables) {
|
||||
|
||||
@@ -1,33 +1,95 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { Flags } from '@oclif/core';
|
||||
import openBrowserAsync from 'better-opn';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { getExpoWebsiteBaseUrl } from '../../api';
|
||||
import { selectBranchOnAppAsync } from '../../branch/queries';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { fetchBuildsAsync, formatBuild } from '../../commandUtils/builds';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
import { AppPlatform, BuildStatus } from '../../graphql/generated';
|
||||
import {
|
||||
AppPlatform,
|
||||
BuildFragment,
|
||||
BuildStatus,
|
||||
FingerprintFragment,
|
||||
UpdateFragment,
|
||||
} from '../../graphql/generated';
|
||||
import { FingerprintMutation } from '../../graphql/mutations/FingerprintMutation';
|
||||
import { AppQuery } from '../../graphql/queries/AppQuery';
|
||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
import Log from '../../log';
|
||||
import { FingerprintQuery } from '../../graphql/queries/FingerprintQuery';
|
||||
import { UpdateQuery } from '../../graphql/queries/UpdateQuery';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { maybeUploadFingerprintAsync } from '../../project/maybeUploadFingerprintAsync';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { resolveWorkflowPerPlatformAsync } from '../../project/workflow';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { promptAsync, selectAsync } from '../../prompts';
|
||||
import { selectUpdateGroupOnBranchAsync } from '../../update/queries';
|
||||
import { Fingerprint, FingerprintDiffItem } from '../../utils/fingerprint';
|
||||
import { createFingerprintAsync, diffFingerprint } from '../../utils/fingerprintCli';
|
||||
import { abridgedDiff } from '../../utils/fingerprintDiff';
|
||||
import formatFields, { FormatFieldsItem } from '../../utils/formatFields';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } 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 {
|
||||
static override description = 'compare fingerprints of the current project, builds and updates';
|
||||
static override hidden = true;
|
||||
static override description = 'compare fingerprints of the current project, builds, and updates';
|
||||
static override strict = false;
|
||||
|
||||
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-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 = {
|
||||
'build-id': Flags.string({
|
||||
aliases: ['buildId'],
|
||||
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',
|
||||
}),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
@@ -40,8 +102,17 @@ export default class FingerprintCompare extends EasCommand {
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(FingerprintCompare);
|
||||
const { json: jsonFlag, 'non-interactive': nonInteractive, buildId: buildIdFromArg } = flags;
|
||||
const { args, flags } = await this.parse(FingerprintCompare);
|
||||
const { hash1, hash2 } = args;
|
||||
const {
|
||||
json,
|
||||
'non-interactive': nonInteractive,
|
||||
'build-id': buildIds,
|
||||
'update-id': updateIds,
|
||||
open,
|
||||
} = flags;
|
||||
const [buildId1, buildId2] = buildIds ?? [];
|
||||
const [updateId1, updateId2] = updateIds ?? [];
|
||||
|
||||
const {
|
||||
projectId,
|
||||
@@ -52,56 +123,65 @@ export default class FingerprintCompare extends EasCommand {
|
||||
nonInteractive,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
if (jsonFlag) {
|
||||
if (json) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
let buildId: string | null = buildIdFromArg;
|
||||
if (!buildId) {
|
||||
if (nonInteractive) {
|
||||
throw new Error('Build ID must be provided in non-interactive mode');
|
||||
const firstFingerprintInfo = await getFingerprintInfoAsync(
|
||||
graphqlClient,
|
||||
projectDir,
|
||||
projectId,
|
||||
vcsClient,
|
||||
{
|
||||
nonInteractive,
|
||||
buildId: buildId1,
|
||||
updateId: updateId1,
|
||||
hash: hash1,
|
||||
}
|
||||
);
|
||||
const { fingerprint: firstFingerprint, origin: firstFingerprintOrigin } = firstFingerprintInfo;
|
||||
|
||||
buildId = await selectBuildToCompareAsync(graphqlClient, projectId, displayName, {
|
||||
filters: { hasFingerprint: true },
|
||||
});
|
||||
if (!buildId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const isFirstFingerprintSpecifiedByFlagOrArg = hash1 || buildId1 || updateId1;
|
||||
const isSecondFingerprintSpecifiedByFlagOrArg = hash2 || buildId2 || updateId2;
|
||||
const secondFingerprintInfo = await getFingerprintInfoAsync(
|
||||
graphqlClient,
|
||||
projectDir,
|
||||
projectId,
|
||||
vcsClient,
|
||||
{
|
||||
nonInteractive,
|
||||
buildId: buildId2,
|
||||
updateId: updateId2,
|
||||
hash: hash2,
|
||||
useProjectFingerprint:
|
||||
isFirstFingerprintSpecifiedByFlagOrArg && !isSecondFingerprintSpecifiedByFlagOrArg,
|
||||
},
|
||||
firstFingerprintInfo
|
||||
);
|
||||
const { fingerprint: secondFingerprint, origin: secondFingerprintOrigin } =
|
||||
secondFingerprintInfo;
|
||||
|
||||
Log.log(`Comparing fingerprints of the current project and build ${buildId}…`);
|
||||
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.');
|
||||
if (json) {
|
||||
printJsonOnlyOutput({ fingerprint1: firstFingerprint, fingerprint2: secondFingerprint });
|
||||
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;
|
||||
}
|
||||
if (fingerprint.hash === projectFingerprint.hash) {
|
||||
Log.log(`✅ Project fingerprint matches build`);
|
||||
if (firstFingerprint.hash === secondFingerprint.hash) {
|
||||
Log.log(
|
||||
`✅ ${capitalizeFirstLetter(
|
||||
prettyPrintFingerprint(firstFingerprint, firstFingerprintOrigin)
|
||||
)} matches ${prettyPrintFingerprint(secondFingerprint, secondFingerprintOrigin)}`
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
Log.log(`🔄 Project fingerprint differs from build`);
|
||||
Log.log(
|
||||
`🔄 ${capitalizeFirstLetter(
|
||||
prettyPrintFingerprint(firstFingerprint, firstFingerprintOrigin)
|
||||
)} differs from ${prettyPrintFingerprint(secondFingerprint, secondFingerprintOrigin)}`
|
||||
);
|
||||
}
|
||||
|
||||
const fingerprintDiffs = diffFingerprint(projectDir, fingerprint, projectFingerprint);
|
||||
const fingerprintDiffs = diffFingerprint(projectDir, firstFingerprint, secondFingerprint);
|
||||
if (!fingerprintDiffs) {
|
||||
Log.error('Fingerprint diffs can only be computed for projects with SDK 52 or higher');
|
||||
return;
|
||||
@@ -151,9 +231,407 @@ export default class FingerprintCompare extends EasCommand {
|
||||
for (const diff of contentDiffs) {
|
||||
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,
|
||||
{
|
||||
buildId,
|
||||
updateId,
|
||||
hash,
|
||||
useProjectFingerprint,
|
||||
nonInteractive,
|
||||
}: {
|
||||
buildId?: string;
|
||||
updateId?: string;
|
||||
hash: string;
|
||||
useProjectFingerprint?: boolean;
|
||||
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,
|
||||
firstFingerprintInfo
|
||||
);
|
||||
}
|
||||
|
||||
if (nonInteractive) {
|
||||
throw new Error(
|
||||
'Insufficent arguments provided for fingerprint comparison in non-interactive mode'
|
||||
);
|
||||
}
|
||||
return await getFingerprintInfoInteractiveAsync(
|
||||
graphqlClient,
|
||||
projectDir,
|
||||
projectId,
|
||||
vcsClient,
|
||||
firstFingerprintInfo
|
||||
);
|
||||
}
|
||||
|
||||
async function getFingerprintInfoInteractiveAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
projectDir: string,
|
||||
projectId: string,
|
||||
vcsClient: Client,
|
||||
firstFingerprintInfo?: {
|
||||
fingerprint: Fingerprint;
|
||||
platforms?: AppPlatform[];
|
||||
origin: FingerprintOrigin;
|
||||
}
|
||||
): 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,
|
||||
firstFingerprintInfo
|
||||
);
|
||||
} 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: ExpoGraphqlClient,
|
||||
projectDir: string,
|
||||
projectId: string,
|
||||
vcsClient: Client,
|
||||
firstFingerprintInfo: {
|
||||
fingerprint: Fingerprint;
|
||||
platforms?: AppPlatform[];
|
||||
origin: FingerprintOrigin;
|
||||
}
|
||||
): Promise<{ fingerprint: Fingerprint; platforms?: AppPlatform[]; origin: FingerprintOrigin }> {
|
||||
const firstFingerprintPlatforms = firstFingerprintInfo.platforms;
|
||||
if (!firstFingerprintPlatforms) {
|
||||
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.`
|
||||
);
|
||||
}
|
||||
|
||||
const workflows = await resolveWorkflowPerPlatformAsync(projectDir, vcsClient);
|
||||
const optionsFromWorkflow = getFingerprintOptionsFromWorkflow(
|
||||
firstFingerprintPlatforms,
|
||||
workflows
|
||||
);
|
||||
|
||||
const projectFingerprint = await createFingerprintAsync(projectDir, {
|
||||
...optionsFromWorkflow,
|
||||
platforms: firstFingerprintPlatforms.map(appPlatformToString),
|
||||
debug: true,
|
||||
env: undefined,
|
||||
});
|
||||
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 { fingerprint: projectFingerprint, 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 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 };
|
||||
}
|
||||
|
||||
function printContentDiff(diff: FingerprintDiffItem): void {
|
||||
if (diff.op === 'added') {
|
||||
const sourceType = diff.addedSource.type;
|
||||
@@ -259,7 +737,10 @@ const PRETTY_CONTENT_ID: Record<string, string> = {
|
||||
'expoAutolinkingConfig:ios': 'Expo autolinking config (iOS)',
|
||||
'expoAutolinkingConfig:android': 'Expo autolinking config (Android)',
|
||||
'packageJson:scripts': 'package.json scripts',
|
||||
expoConfig: 'Expo config',
|
||||
expoConfig: 'Expo app 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({
|
||||
@@ -303,6 +784,23 @@ function printContentsDiff(contents1: string | Buffer, contents2: string | Buffe
|
||||
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 {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
@@ -334,6 +832,17 @@ function appPlatformToString(platform: AppPlatform): string {
|
||||
}
|
||||
}
|
||||
|
||||
function stringToAppPlatform(platform: string): AppPlatform {
|
||||
switch (platform) {
|
||||
case 'android':
|
||||
return AppPlatform.Android;
|
||||
case 'ios':
|
||||
return AppPlatform.Ios;
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function selectBuildToCompareAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
projectId: string,
|
||||
|
||||
@@ -162,7 +162,10 @@ export default class Onboarding extends EasCommand {
|
||||
cloneMethod,
|
||||
});
|
||||
|
||||
const vcsClient = new GitClient(finalTargetProjectDirectory);
|
||||
const vcsClient = new GitClient({
|
||||
maybeCwdOverride: finalTargetProjectDirectory,
|
||||
requireCommit: false,
|
||||
});
|
||||
if (!app.githubRepository) {
|
||||
await fs.remove(path.join(finalTargetProjectDirectory, '.git'));
|
||||
await runCommandAsync({
|
||||
@@ -285,12 +288,12 @@ export default class Onboarding extends EasCommand {
|
||||
} else {
|
||||
Log.log('🚀 Now we are going to trigger your first build');
|
||||
Log.log();
|
||||
const { buildIds } = await runBuildAndSubmitAsync(
|
||||
const { buildIds } = await runBuildAndSubmitAsync({
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
finalTargetProjectDirectory,
|
||||
{
|
||||
projectDir: finalTargetProjectDirectory,
|
||||
flags: {
|
||||
nonInteractive: true,
|
||||
requestedPlatform:
|
||||
platform === Platform.ANDROID ? RequestedPlatform.Android : RequestedPlatform.Ios,
|
||||
@@ -307,8 +310,9 @@ export default class Onboarding extends EasCommand {
|
||||
repack: true,
|
||||
},
|
||||
actor,
|
||||
getDynamicProjectConfigFn
|
||||
);
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
getDynamicPrivateProjectConfigAsync: getDynamicProjectConfigFn,
|
||||
});
|
||||
const buildId = buildIds[0];
|
||||
Log.log();
|
||||
Log.log('🚀 You can now go back to the website to continue:');
|
||||
|
||||
@@ -18,8 +18,7 @@ import AndroidSubmitCommand from '../../submit/android/AndroidSubmitCommand';
|
||||
import { SubmissionContext, createSubmissionContextAsync } from '../../submit/context';
|
||||
import IosSubmitCommand from '../../submit/ios/IosSubmitCommand';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
|
||||
import NoVcsClient from '../../vcs/clients/noVcs';
|
||||
import GitClient from '../../vcs/clients/git';
|
||||
|
||||
/**
|
||||
* This command will be run on the EAS workers.
|
||||
@@ -65,10 +64,16 @@ export default class SubmitInternal extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(SubmitInternal, {
|
||||
nonInteractive: true,
|
||||
vcsClientOverride: process.env.EAS_NO_VCS ? new NoVcsClient() : new GitNoCommitClient(),
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
if (vcsClient instanceof GitClient) {
|
||||
// `build:internal` is run on EAS workers and the repo may have been changed
|
||||
// by pre-install hooks or other scripts. We don't want to require committing changes
|
||||
// to continue the build.
|
||||
vcsClient.requireCommit = false;
|
||||
}
|
||||
|
||||
const submissionProfile = await EasJsonUtils.getSubmitProfileAsync(
|
||||
EasJsonAccessor.fromProjectPath(projectDir),
|
||||
flags.platform,
|
||||
|
||||
@@ -237,6 +237,10 @@ export default class UpdatePublish extends EasCommand {
|
||||
jsonFlag,
|
||||
});
|
||||
|
||||
const maybeServerEnv = environment
|
||||
? { ...(await getServerSideEnvironmentVariablesAsync()), EXPO_NO_DOTENV: '1' }
|
||||
: {};
|
||||
|
||||
// build bundle and upload assets for a new publish
|
||||
if (!skipBundler) {
|
||||
const bundleSpinner = ora().start('Exporting...');
|
||||
@@ -247,11 +251,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
exp,
|
||||
platformFlag: requestedPlatform,
|
||||
clearCache,
|
||||
extraEnv: {
|
||||
...(environment
|
||||
? { ...(await getServerSideEnvironmentVariablesAsync()), EXPO_NO_DOTENV: '1' }
|
||||
: {}),
|
||||
},
|
||||
extraEnv: maybeServerEnv,
|
||||
});
|
||||
bundleSpinner.succeed('Exported bundle(s)');
|
||||
} catch (e) {
|
||||
@@ -393,7 +393,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
...workflows,
|
||||
web: Workflow.UNKNOWN,
|
||||
},
|
||||
env: undefined,
|
||||
env: maybeServerEnv,
|
||||
});
|
||||
const runtimeToPlatformsAndFingerprintInfoMapping =
|
||||
getRuntimeToPlatformsAndFingerprintInfoMappingFromRuntimeVersionInfoObjects(
|
||||
@@ -410,11 +410,11 @@ export default class UpdatePublish extends EasCommand {
|
||||
runtimeToPlatformsAndFingerprintInfoMapping.map(async info => {
|
||||
return {
|
||||
...info,
|
||||
fingerprintSource: info.fingerprint
|
||||
expoUpdatesRuntimeFingerprintSource: info.expoUpdatesRuntimeFingerprint
|
||||
? (
|
||||
await maybeUploadFingerprintAsync({
|
||||
hash: info.runtimeVersion,
|
||||
fingerprint: info.fingerprint,
|
||||
fingerprint: info.expoUpdatesRuntimeFingerprint,
|
||||
graphqlClient,
|
||||
})
|
||||
).fingerprintSource ?? null
|
||||
@@ -449,7 +449,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
// Sort the updates into different groups based on their platform specific runtime versions
|
||||
const updateGroups: PublishUpdateGroupInput[] =
|
||||
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping.map(
|
||||
({ runtimeVersion, platforms, fingerprintSource, fingerprintInfoGroup }) => {
|
||||
({ runtimeVersion, platforms, fingerprintInfoGroup }) => {
|
||||
const localUpdateInfoGroup = Object.fromEntries(
|
||||
platforms.map(platform => [
|
||||
platform,
|
||||
@@ -485,9 +485,6 @@ export default class UpdatePublish extends EasCommand {
|
||||
branchId: branch.id,
|
||||
updateInfoGroup: localUpdateInfoGroup,
|
||||
rolloutInfoGroup: localRolloutInfoGroup,
|
||||
runtimeFingerprintSource: fingerprintSource
|
||||
? transformFingerprintSource(fingerprintSource)
|
||||
: null,
|
||||
fingerprintInfoGroup: transformedFingerprintInfoGroup,
|
||||
runtimeVersion,
|
||||
message: updateMessage,
|
||||
|
||||
@@ -208,8 +208,8 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
platform,
|
||||
runtimeVersionInfo: {
|
||||
runtimeVersion: selectedRuntime,
|
||||
fingerprint: null,
|
||||
fingerprintHash: null,
|
||||
expoUpdatesRuntimeFingerprint: null,
|
||||
expoUpdatesRuntimeFingerprintHash: null,
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { WorkflowRevisionMutation } from '../../graphql/mutations/WorkflowRevisi
|
||||
import { WorkflowRunMutation } from '../../graphql/mutations/WorkflowRunMutation';
|
||||
import Log, { link } from '../../log';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { uploadAccountScopedEasJsonAsync } from '../../project/uploadAccountScopedEasJsonAsync';
|
||||
import { uploadAccountScopedFileAsync } from '../../project/uploadAccountScopedFileAsync';
|
||||
import { uploadAccountScopedProjectSourceAsync } from '../../project/uploadAccountScopedProjectSourceAsync';
|
||||
import { WorkflowFile } from '../../utils/workflowFile';
|
||||
|
||||
@@ -81,6 +81,7 @@ export default class WorkflowRun extends EasCommand {
|
||||
|
||||
let projectArchiveBucketKey: string;
|
||||
let easJsonBucketKey: string;
|
||||
let packageJsonBucketKey: string;
|
||||
|
||||
try {
|
||||
({ projectArchiveBucketKey } = await uploadAccountScopedProjectSourceAsync({
|
||||
@@ -88,10 +89,17 @@ export default class WorkflowRun extends EasCommand {
|
||||
vcsClient,
|
||||
accountId: account.id,
|
||||
}));
|
||||
({ easJsonBucketKey } = await uploadAccountScopedEasJsonAsync({
|
||||
({ fileBucketKey: easJsonBucketKey } = await uploadAccountScopedFileAsync({
|
||||
graphqlClient,
|
||||
accountId: account.id,
|
||||
projectDir,
|
||||
filePath: path.join(projectDir, 'eas.json'),
|
||||
maxSizeBytes: 1024 * 1024,
|
||||
}));
|
||||
({ fileBucketKey: packageJsonBucketKey } = await uploadAccountScopedFileAsync({
|
||||
graphqlClient,
|
||||
accountId: account.id,
|
||||
filePath: path.join(projectDir, 'package.json'),
|
||||
maxSizeBytes: 1024 * 1024,
|
||||
}));
|
||||
} catch (err) {
|
||||
Log.error('Failed to upload project sources.');
|
||||
@@ -113,6 +121,7 @@ export default class WorkflowRun extends EasCommand {
|
||||
type: WorkflowProjectSourceType.Gcs,
|
||||
projectArchiveBucketKey,
|
||||
easJsonBucketKey,
|
||||
packageJsonBucketKey,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -120,7 +129,7 @@ export default class WorkflowRun extends EasCommand {
|
||||
|
||||
Log.newLine();
|
||||
Log.succeed(
|
||||
`Workflow started successfully. ${link(
|
||||
`Workflow run started successfully. See logs: ${link(
|
||||
getWorkflowRunUrl(account.name, projectName, workflowRunId)
|
||||
)}`
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface AndroidCredentials {
|
||||
|
||||
interface Options {
|
||||
app: AppLookupParams;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export default class AndroidCredentialsProvider {
|
||||
@@ -37,7 +38,7 @@ export default class AndroidCredentialsProvider {
|
||||
}
|
||||
|
||||
private async getRemoteAsync(): Promise<AndroidCredentials> {
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentials({ app: this.options.app });
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentials(this.options);
|
||||
const buildCredentials = await setupBuildCredentialsAction.runAsync(this.ctx);
|
||||
return this.toAndroidCredentials(buildCredentials);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
import { Session } from '@expo/apple-utils';
|
||||
import nock from 'nock';
|
||||
|
||||
import { createAppAsync } from '../ensureAppExists';
|
||||
|
||||
const FIXTURE_SUCCESS = {
|
||||
data: {
|
||||
type: 'apps',
|
||||
id: '6741087677',
|
||||
attributes: {
|
||||
name: 'expo (xxx)',
|
||||
bundleId: 'com.bacon.jan27.x',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const FIXTURE_INVALID_NAME = {
|
||||
errors: [
|
||||
{
|
||||
id: 'b3e7ca18-e4ce-4e55-83ce-8fff35dbaeca',
|
||||
status: '409',
|
||||
code: 'ENTITY_ERROR.ATTRIBUTE.INVALID.INVALID_CHARACTERS',
|
||||
title: 'An attribute value has invalid characters.',
|
||||
detail:
|
||||
'App Name contains certain Unicode symbols, emoticons, diacritics, special characters, or private use characters that are not permitted.',
|
||||
source: {
|
||||
pointer: '/included/1/name',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const FIXTURE_ALREADY_USED_ON_ACCOUNT = {
|
||||
errors: [
|
||||
{
|
||||
id: 'b91aefc5-0e94-48d9-8613-5b1a464a20f0',
|
||||
status: '409',
|
||||
code: 'ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.SAME_ACCOUNT',
|
||||
title:
|
||||
'The provided entity includes an attribute with a value that has already been used on this account.',
|
||||
detail:
|
||||
'The app name you entered is already being used for another app in your account. If you would like to use the name for this app you will need to submit an update to your other app to change the name, or remove it from App Store Connect.',
|
||||
source: {
|
||||
pointer: '/included/1/name',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const FIXTURE_ALREADY_USED_ON_ANOTHER_ACCOUNT = {
|
||||
errors: [
|
||||
{
|
||||
id: '72b960f2-9e51-4f19-8d83-7cc08d42fec4',
|
||||
status: '409',
|
||||
code: 'ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.DIFFERENT_ACCOUNT',
|
||||
title:
|
||||
'The provided entity includes an attribute with a value that has already been used on a different account.',
|
||||
detail:
|
||||
'The App Name you entered is already being used. If you have trademark rights to this name and would like it released for your use, submit a claim.',
|
||||
source: {
|
||||
pointer: '/included/1/name',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const MOCK_CONTEXT = {
|
||||
providerId: 1337,
|
||||
teamId: 'test-team-id',
|
||||
token: 'test-token',
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
// Mock setup cookies API calls.
|
||||
nock('https://appstoreconnect.apple.com')
|
||||
.get(`/olympus/v1/session`)
|
||||
.reply(200, {
|
||||
provider: {
|
||||
providerId: 1337,
|
||||
publicProviderId: 'xxx-xxx-xxx-xxx-xxx',
|
||||
name: 'Evan Bacon',
|
||||
contentTypes: ['SOFTWARE'],
|
||||
subType: 'INDIVIDUAL',
|
||||
},
|
||||
});
|
||||
|
||||
await Session.fetchCurrentSessionInfoAsync();
|
||||
});
|
||||
|
||||
function getNameFromBody(body: any): any {
|
||||
return body.included.find((item: any) => item.id === '${new-appInfoLocalization-id}')?.attributes
|
||||
?.name;
|
||||
}
|
||||
|
||||
it('asserts invalid name cases', async () => {
|
||||
const scope = nock('https://appstoreconnect.apple.com')
|
||||
.post(`/iris/v1/apps`, body => {
|
||||
expect(getNameFromBody(body)).toBe('Expo 🚀');
|
||||
|
||||
return true;
|
||||
})
|
||||
.reply(409, FIXTURE_INVALID_NAME);
|
||||
|
||||
// Already used on same account
|
||||
nock('https://appstoreconnect.apple.com')
|
||||
.post(`/iris/v1/apps`, body => {
|
||||
expect(getNameFromBody(body)).toBe('Expo -');
|
||||
return true;
|
||||
})
|
||||
.reply(409, FIXTURE_ALREADY_USED_ON_ACCOUNT);
|
||||
|
||||
// Already used on different account
|
||||
nock('https://appstoreconnect.apple.com')
|
||||
.post(`/iris/v1/apps`, body => {
|
||||
expect(getNameFromBody(body)).toMatch(/Expo - \([\w\d]+\)/);
|
||||
return true;
|
||||
})
|
||||
.reply(409, FIXTURE_ALREADY_USED_ON_ANOTHER_ACCOUNT);
|
||||
|
||||
// Success
|
||||
nock('https://appstoreconnect.apple.com')
|
||||
.post(`/iris/v1/apps`, body => {
|
||||
expect(getNameFromBody(body)).toMatch(/Expo - \([\w\d]+\)/);
|
||||
return true;
|
||||
})
|
||||
.reply(200, FIXTURE_SUCCESS);
|
||||
|
||||
await createAppAsync(MOCK_CONTEXT, {
|
||||
bundleId: 'com.bacon.jan27.x',
|
||||
name: 'Expo 🚀',
|
||||
companyName: 'expo',
|
||||
});
|
||||
|
||||
expect(scope.isDone()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('works on first try', async () => {
|
||||
nock('https://appstoreconnect.apple.com').post(`/iris/v1/apps`).reply(200, FIXTURE_SUCCESS);
|
||||
|
||||
await createAppAsync(MOCK_CONTEXT, {
|
||||
bundleId: 'com.bacon.jan27.x',
|
||||
name: 'Expo',
|
||||
companyName: 'expo',
|
||||
});
|
||||
});
|
||||
|
||||
it('doubles up entropy', async () => {
|
||||
nock('https://appstoreconnect.apple.com')
|
||||
.post(`/iris/v1/apps`)
|
||||
.reply(409, FIXTURE_ALREADY_USED_ON_ANOTHER_ACCOUNT);
|
||||
|
||||
nock('https://appstoreconnect.apple.com')
|
||||
.post(`/iris/v1/apps`)
|
||||
.reply(409, FIXTURE_ALREADY_USED_ON_ANOTHER_ACCOUNT);
|
||||
|
||||
nock('https://appstoreconnect.apple.com')
|
||||
.post(`/iris/v1/apps`, body => {
|
||||
expect(getNameFromBody(body)).toMatch(/Expo \([\w\d]+\) \([\w\d]+\)/);
|
||||
return true;
|
||||
})
|
||||
.reply(200, FIXTURE_SUCCESS);
|
||||
|
||||
await createAppAsync(MOCK_CONTEXT, {
|
||||
bundleId: 'com.bacon.jan27.x',
|
||||
name: 'Expo',
|
||||
companyName: 'expo',
|
||||
});
|
||||
});
|
||||
+152
-1
@@ -1,11 +1,26 @@
|
||||
import { JsonFileCache } from '@expo/apple-utils';
|
||||
import * as fs from 'fs-extra';
|
||||
import { vol } from 'memfs';
|
||||
|
||||
import { promptAsync } from '../../../../prompts';
|
||||
import { AppleTeamType } from '../authenticateTypes';
|
||||
import { resolveAppleTeamAsync, resolveAscApiKeyAsync } from '../resolveCredentials';
|
||||
import * as Keychain from '../keychain';
|
||||
import {
|
||||
resolveAppleTeamAsync,
|
||||
resolveAscApiKeyAsync,
|
||||
resolveUserCredentialsAsync,
|
||||
} from '../resolveCredentials';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('fs');
|
||||
jest.mock('@expo/apple-utils', () => {
|
||||
return {
|
||||
getCacheAsync: jest.fn(),
|
||||
usernameCachePath: jest.fn(),
|
||||
...jest.requireActual('@expo/apple-utils'),
|
||||
};
|
||||
});
|
||||
jest.mock('../keychain');
|
||||
|
||||
const testAscApiKey = {
|
||||
keyP8: 'test-key-p8',
|
||||
@@ -89,3 +104,139 @@ describe(resolveAppleTeamAsync, () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(resolveUserCredentialsAsync, () => {
|
||||
let KeychainMock: any;
|
||||
beforeAll(() => {
|
||||
JsonFileCache.getCacheAsync = jest.fn().mockImplementation(async (filePath: string) => {
|
||||
const content = await fs.readFile(filePath, 'utf-8');
|
||||
return content ? JSON.parse(content) : null;
|
||||
});
|
||||
KeychainMock = jest.mocked(Keychain);
|
||||
});
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
KeychainMock.EXPO_NO_KEYCHAIN = '';
|
||||
});
|
||||
|
||||
it('uses credentials resolved from options', async () => {
|
||||
const result = await resolveUserCredentialsAsync({
|
||||
username: 'fakeUsername',
|
||||
password: 'fakePassword',
|
||||
});
|
||||
expect(result).toMatchObject({
|
||||
username: 'fakeUsername',
|
||||
password: 'fakePassword',
|
||||
});
|
||||
});
|
||||
|
||||
it('uses credentials resolved from environment', async () => {
|
||||
process.env.EXPO_APPLE_ID = 'fakeUsername';
|
||||
process.env.EXPO_APPLE_PASSWORD = 'fakePassword';
|
||||
|
||||
const result = await resolveUserCredentialsAsync({});
|
||||
expect(result).toMatchObject({
|
||||
username: 'fakeUsername',
|
||||
password: 'fakePassword',
|
||||
});
|
||||
});
|
||||
|
||||
it('uses credentials from prompt, does not update cache if they match', async () => {
|
||||
vol.fromJSON({
|
||||
'./.app-store/auth/username.json': `
|
||||
{
|
||||
"username": "fakeUsername"
|
||||
}
|
||||
`,
|
||||
});
|
||||
jest.mocked(promptAsync).mockResolvedValueOnce({ username: 'fakeUsername' });
|
||||
JsonFileCache.usernameCachePath = jest.fn().mockReturnValue('./.app-store/auth/username.json');
|
||||
const cacheAsyncSpy = jest.spyOn(JsonFileCache, 'cacheAsync');
|
||||
|
||||
const result = await resolveUserCredentialsAsync({});
|
||||
expect(result).toMatchObject({
|
||||
username: 'fakeUsername',
|
||||
});
|
||||
expect(cacheAsyncSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses credentials from prompt, updates cache if they don't match", async () => {
|
||||
vol.fromJSON({
|
||||
'./.app-store/auth/username.json': `
|
||||
{
|
||||
"username": "fakeUsername"
|
||||
}
|
||||
`,
|
||||
});
|
||||
jest.mocked(promptAsync).mockResolvedValueOnce({ username: 'newFakeUsername' });
|
||||
JsonFileCache.usernameCachePath = jest.fn().mockReturnValue('./.app-store/auth/username.json');
|
||||
const cacheAsyncSpy = jest.spyOn(JsonFileCache, 'cacheAsync');
|
||||
|
||||
expect(JSON.parse(await fs.readFile('./.app-store/auth/username.json', 'utf-8'))).toMatchObject(
|
||||
{ username: 'fakeUsername' }
|
||||
);
|
||||
const result = await resolveUserCredentialsAsync({});
|
||||
expect(result).toMatchObject({
|
||||
username: 'newFakeUsername',
|
||||
});
|
||||
expect(cacheAsyncSpy).toHaveBeenCalledWith('./.app-store/auth/username.json', {
|
||||
username: 'newFakeUsername',
|
||||
});
|
||||
expect(JSON.parse(await fs.readFile('./.app-store/auth/username.json', 'utf-8'))).toMatchObject(
|
||||
{ username: 'newFakeUsername' }
|
||||
);
|
||||
});
|
||||
|
||||
it("uses credentials from prompt, doesn't read or update cache if EXPO_NO_KEYCHAIN is set", async () => {
|
||||
vol.fromJSON({
|
||||
'./.app-store/auth/username.json': `
|
||||
{
|
||||
"username": "fakeUsername"
|
||||
}
|
||||
`,
|
||||
});
|
||||
jest.mocked(promptAsync).mockResolvedValueOnce({ username: 'newFakeUsername' });
|
||||
JsonFileCache.usernameCachePath = jest.fn().mockReturnValue('./.app-store/auth/username.json');
|
||||
KeychainMock.EXPO_NO_KEYCHAIN = 'true';
|
||||
const getCacheAsyncSpy = jest.spyOn(JsonFileCache, 'getCacheAsync');
|
||||
const cacheAsyncSpy = jest.spyOn(JsonFileCache, 'cacheAsync');
|
||||
|
||||
expect(JSON.parse(await fs.readFile('./.app-store/auth/username.json', 'utf-8'))).toMatchObject(
|
||||
{ username: 'fakeUsername' }
|
||||
);
|
||||
const result = await resolveUserCredentialsAsync({});
|
||||
expect(result).toMatchObject({
|
||||
username: 'newFakeUsername',
|
||||
});
|
||||
expect(getCacheAsyncSpy).not.toHaveBeenCalled();
|
||||
expect(cacheAsyncSpy).not.toHaveBeenCalled();
|
||||
await expect(fs.access('./.app-store/auth/username.json')).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('clears credentials from prompt if malformed before using and updating cache', async () => {
|
||||
vol.fromJSON({
|
||||
'./.app-store/auth/username.json': `
|
||||
{
|
||||
"username": "fakeUsername"
|
||||
}
|
||||
`,
|
||||
});
|
||||
jest.mocked(promptAsync).mockResolvedValueOnce({ username: '\x17newFake\x01Username\x1F' });
|
||||
JsonFileCache.usernameCachePath = jest.fn().mockReturnValue('./.app-store/auth/username.json');
|
||||
const cacheAsyncSpy = jest.spyOn(JsonFileCache, 'cacheAsync');
|
||||
|
||||
expect(JSON.parse(await fs.readFile('./.app-store/auth/username.json', 'utf-8'))).toMatchObject(
|
||||
{ username: 'fakeUsername' }
|
||||
);
|
||||
const result = await resolveUserCredentialsAsync({});
|
||||
expect(result).toMatchObject({
|
||||
username: 'newFakeUsername',
|
||||
});
|
||||
expect(cacheAsyncSpy).toHaveBeenCalledWith('./.app-store/auth/username.json', {
|
||||
username: 'newFakeUsername',
|
||||
});
|
||||
expect(JSON.parse(await fs.readFile('./.app-store/auth/username.json', 'utf-8'))).toMatchObject(
|
||||
{ username: 'newFakeUsername' }
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { App, BundleId } from '@expo/apple-utils';
|
||||
import { App, BundleId, RequestContext } from '@expo/apple-utils';
|
||||
import { JSONObject } from '@expo/json-file';
|
||||
import chalk from 'chalk';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
|
||||
import { getRequestContext, isUserAuthCtx } from './authenticate';
|
||||
import { AuthCtx, UserAuthCtx } from './authenticateTypes';
|
||||
@@ -188,7 +189,7 @@ export async function ensureAppExistsAsync(
|
||||
/**
|
||||
* **Does not support App Store Connect API (CI).**
|
||||
*/
|
||||
app = await App.createAsync(context, {
|
||||
app = await createAppAsync(context, {
|
||||
bundleId: bundleIdentifier,
|
||||
name,
|
||||
primaryLocale: language,
|
||||
@@ -215,3 +216,120 @@ export async function ensureAppExistsAsync(
|
||||
);
|
||||
return app;
|
||||
}
|
||||
|
||||
function sanitizeName(name: string): string {
|
||||
return (
|
||||
name
|
||||
// Replace emojis with a `-`
|
||||
.replace(/[\p{Emoji}]/gu, '-')
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
export async function createAppAsync(
|
||||
context: RequestContext,
|
||||
props: {
|
||||
bundleId: string;
|
||||
name: string;
|
||||
primaryLocale?: string;
|
||||
companyName?: string;
|
||||
sku?: string;
|
||||
},
|
||||
retryCount = 0
|
||||
): Promise<App> {
|
||||
try {
|
||||
/**
|
||||
* **Does not support App Store Connect API (CI).**
|
||||
*/
|
||||
return await App.createAsync(context, props);
|
||||
} catch (error) {
|
||||
if (retryCount >= 5) {
|
||||
throw error;
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
const handleDuplicateNameErrorAsync = async (): Promise<App> => {
|
||||
const generatedName = props.name + ` (${randomBytes(3).toString('hex')})`;
|
||||
Log.warn(
|
||||
`App name "${props.name}" is already taken. Using generated name "${generatedName}" which can be changed later from https://appstoreconnect.apple.com.`
|
||||
);
|
||||
// Sanitize the name and try again.
|
||||
return await createAppAsync(
|
||||
context,
|
||||
{
|
||||
...props,
|
||||
name: generatedName,
|
||||
},
|
||||
retryCount + 1
|
||||
);
|
||||
};
|
||||
|
||||
if (isAppleError(error)) {
|
||||
// New error class that is thrown when the name is already taken but belongs to you.
|
||||
if (
|
||||
error.data.errors.some(
|
||||
e =>
|
||||
e.code === 'ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.SAME_ACCOUNT' ||
|
||||
e.code === 'ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.DIFFERENT_ACCOUNT'
|
||||
)
|
||||
) {
|
||||
return await handleDuplicateNameErrorAsync();
|
||||
}
|
||||
}
|
||||
|
||||
if ('code' in error && typeof error.code === 'string') {
|
||||
if (
|
||||
// Name is invalid
|
||||
error.code === 'APP_CREATE_NAME_INVALID'
|
||||
// UnexpectedAppleResponse: An attribute value has invalid characters. - App Name contains certain Unicode symbols, emoticons, diacritics, special characters, or private use characters that are not permitted.
|
||||
// Name is taken
|
||||
) {
|
||||
const sanitizedName = sanitizeName(props.name);
|
||||
if (sanitizedName === props.name) {
|
||||
throw error;
|
||||
}
|
||||
Log.warn(
|
||||
`App name "${props.name}" contains invalid characters. Using sanitized name "${sanitizedName}" which can be changed later from https://appstoreconnect.apple.com.`
|
||||
);
|
||||
// Sanitize the name and try again.
|
||||
return await createAppAsync(
|
||||
context,
|
||||
{
|
||||
...props,
|
||||
name: sanitizedName,
|
||||
},
|
||||
retryCount + 1
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
// UnexpectedAppleResponse: The provided entity includes an attribute with a value that has already been used on a different account. - The App Name you entered is already being used. If you have trademark rights to
|
||||
// this name and would like it released for your use, submit a claim.
|
||||
error.code === 'APP_CREATE_NAME_UNAVAILABLE'
|
||||
) {
|
||||
return await handleDuplicateNameErrorAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function isAppleError(error: any): error is {
|
||||
data: {
|
||||
errors: {
|
||||
id: string;
|
||||
status: string;
|
||||
/** 'ENTITY_ERROR.ATTRIBUTE.INVALID.INVALID_CHARACTERS' */
|
||||
code: string;
|
||||
/** 'An attribute value has invalid characters.' */
|
||||
title: string;
|
||||
/** 'App Name contains certain Unicode symbols, emoticons, diacritics, special characters, or private use characters that are not permitted.' */
|
||||
detail: string;
|
||||
}[];
|
||||
};
|
||||
} {
|
||||
return 'data' in error && 'errors' in error.data && Array.isArray(error.data.errors);
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
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 not ready, retrying in 25 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 = 10,
|
||||
// 25 seconds was the minium interval I calculated when measuring against 5 second intervals.
|
||||
interval = 25000,
|
||||
}: { 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,6 +29,7 @@ function resolveProfileType(
|
||||
return resolveProfileTypeIos(profileClass, isEnterprise);
|
||||
case ApplePlatform.TV_OS:
|
||||
return resolveProfileTypeAppleTv(profileClass, isEnterprise);
|
||||
case ApplePlatform.VISION_OS:
|
||||
case ApplePlatform.MAC_OS:
|
||||
throw new Error(`${applePlatform} profiles are not supported`);
|
||||
}
|
||||
|
||||
@@ -208,13 +208,18 @@ async function promptUsernameAsync(): Promise<string> {
|
||||
// the default value for quicker authentication.
|
||||
const lastAppleId = await getCachedUsernameAsync();
|
||||
|
||||
const { username } = await promptAsync({
|
||||
let { username } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
message: `Apple ID:`,
|
||||
validate: (val: string) => val !== '',
|
||||
initial: lastAppleId ?? undefined,
|
||||
});
|
||||
// https://github.com/expo/eas-cli/issues/2254
|
||||
// user got an invalid unprintable character in their username, which was saved to cache and used to prefill the prompt
|
||||
// which made them accept the prefilled value not knowing it contains the character and fail to log in
|
||||
// this replace makes sure no such characters are used or cached
|
||||
username = username.replace(/[\x00-\x1F]/gi, '');
|
||||
|
||||
if (username && username !== lastAppleId) {
|
||||
await cacheUsernameAsync(username);
|
||||
|
||||
@@ -48,7 +48,7 @@ async function collectDataAndRegisterDeviceAsync(
|
||||
}
|
||||
|
||||
const { udid, deviceClass, name } = deviceData;
|
||||
const spinner = ora(`Registering Apple device on Expo`).start();
|
||||
const spinner = ora(`Registering Apple device on EAS`).start();
|
||||
try {
|
||||
await AppleDeviceMutation.createAppleDeviceAsync(
|
||||
graphqlClient,
|
||||
|
||||
@@ -45,7 +45,7 @@ async function collectDataAndRegisterDeviceAsync(
|
||||
): Promise<void> {
|
||||
const { udid, deviceClass, name } = await collectDeviceDataAsync(appleTeam);
|
||||
|
||||
const spinner = ora(`Registering Apple device on Expo`).start();
|
||||
const spinner = ora(`Registering Apple device on EAS`).start();
|
||||
try {
|
||||
await AppleDeviceMutation.createAppleDeviceAsync(
|
||||
graphqlClient,
|
||||
|
||||
+387
-39
@@ -24,6 +24,7 @@ export type Scalars = {
|
||||
JSON: { input: any; output: any; }
|
||||
JSONObject: { input: any; output: any; }
|
||||
WorkerDeploymentIdentifier: { input: any; output: any; }
|
||||
WorkerDeploymentRequestID: { input: any; output: any; }
|
||||
};
|
||||
|
||||
export type AcceptUserInvitationResult = {
|
||||
@@ -133,9 +134,12 @@ export type Account = {
|
||||
id: Scalars['ID']['output'];
|
||||
isCurrent: Scalars['Boolean']['output'];
|
||||
isDisabled: Scalars['Boolean']['output'];
|
||||
/** Whether an Account plan falls into AppDevDomainName's free or paid tier */
|
||||
isFreeAppDevDomainTier: Scalars['Boolean']['output'];
|
||||
/** Whether this account has SSO enabled. Can be queried by all members. */
|
||||
isSSOEnabled: Scalars['Boolean']['output'];
|
||||
lastDeletionAttemptTime?: Maybe<Scalars['DateTime']['output']>;
|
||||
logRocketOrganization?: Maybe<LogRocketOrganization>;
|
||||
name: Scalars['String']['output'];
|
||||
/** Offers set on this account */
|
||||
offers?: Maybe<Array<Offer>>;
|
||||
@@ -143,6 +147,7 @@ export type Account = {
|
||||
owner?: Maybe<User>;
|
||||
/** Owning UserActor of this account if personal account */
|
||||
ownerUserActor?: Maybe<UserActor>;
|
||||
profileImageUrl: Scalars['String']['output'];
|
||||
pushSecurityEnabled: Scalars['Boolean']['output'];
|
||||
/** @deprecated Legacy access tokens are deprecated */
|
||||
requiresAccessTokenForPushSecurity: Scalars['Boolean']['output'];
|
||||
@@ -722,12 +727,7 @@ export type AccountUsageEasBuildMetadata = {
|
||||
waiverType?: Maybe<EasBuildWaiverType>;
|
||||
};
|
||||
|
||||
export type AccountUsageEasJobsMetadata = {
|
||||
__typename?: 'AccountUsageEASJobsMetadata';
|
||||
resourceClassDisplayName: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type AccountUsageMetadata = AccountUsageEasBuildMetadata | AccountUsageEasJobsMetadata;
|
||||
export type AccountUsageMetadata = AccountUsageEasBuildMetadata;
|
||||
|
||||
export type AccountUsageMetric = {
|
||||
__typename?: 'AccountUsageMetric';
|
||||
@@ -1090,6 +1090,7 @@ export type AndroidJobInput = {
|
||||
cache?: InputMaybe<BuildCacheInput>;
|
||||
customBuildConfig?: InputMaybe<CustomBuildConfigInput>;
|
||||
developmentClient?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
environment?: InputMaybe<EnvironmentVariableEnvironment>;
|
||||
experimental?: InputMaybe<Scalars['JSONObject']['input']>;
|
||||
gradleCommand?: InputMaybe<Scalars['String']['input']>;
|
||||
loggerLevel?: InputMaybe<WorkerLoggerLevel>;
|
||||
@@ -1251,6 +1252,7 @@ export type App = Project & {
|
||||
environmentVariables: Array<EnvironmentVariable>;
|
||||
/** Environment variables for an app with decrypted secret values */
|
||||
environmentVariablesIncludingSensitive: Array<EnvironmentVariableWithSecret>;
|
||||
fingerprintsPaginated: AppFingerprintsConnection;
|
||||
fullName: Scalars['String']['output'];
|
||||
githubBuildTriggers: Array<GitHubBuildTrigger>;
|
||||
githubJobRunTriggers: Array<GitHubJobRunTrigger>;
|
||||
@@ -1300,6 +1302,7 @@ export type App = Project & {
|
||||
likeCount: Scalars['Int']['output'];
|
||||
/** @deprecated 'likes' have been deprecated. */
|
||||
likedBy: Array<Maybe<User>>;
|
||||
logRocketProject?: Maybe<LogRocketProject>;
|
||||
name: Scalars['String']['output'];
|
||||
ownerAccount: Account;
|
||||
/** @deprecated No longer supported */
|
||||
@@ -1366,6 +1369,8 @@ export type App = Project & {
|
||||
/** EAS updates owned by an app */
|
||||
updates: Array<Update>;
|
||||
updatesPaginated: AppUpdatesConnection;
|
||||
/** Project query object for querying EAS usage metrics */
|
||||
usageMetrics: AppUsageMetrics;
|
||||
/** @deprecated Use ownerAccount.name instead */
|
||||
username: Scalars['String']['output'];
|
||||
/** @deprecated No longer supported */
|
||||
@@ -1479,6 +1484,16 @@ export type AppEnvironmentVariablesIncludingSensitiveArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppFingerprintsPaginatedArgs = {
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
before?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<FingerprintFilterInput>;
|
||||
first?: InputMaybe<Scalars['Int']['input']>;
|
||||
last?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppIosAppCredentialsArgs = {
|
||||
filter?: InputMaybe<IosAppCredentialsFilter>;
|
||||
@@ -1587,6 +1602,7 @@ export type AppUpdatesArgs = {
|
||||
export type AppUpdatesPaginatedArgs = {
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
before?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<UpdateFilterInput>;
|
||||
first?: InputMaybe<Scalars['Int']['input']>;
|
||||
last?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
@@ -1732,6 +1748,18 @@ export type AppDevDomainNameMutationChangeDevDomainNameArgs = {
|
||||
name: Scalars['DevDomainName']['input'];
|
||||
};
|
||||
|
||||
export type AppFingerprintEdge = {
|
||||
__typename?: 'AppFingerprintEdge';
|
||||
cursor: Scalars['String']['output'];
|
||||
node: Fingerprint;
|
||||
};
|
||||
|
||||
export type AppFingerprintsConnection = {
|
||||
__typename?: 'AppFingerprintsConnection';
|
||||
edges: Array<AppFingerprintEdge>;
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
export type AppIcon = {
|
||||
__typename?: 'AppIcon';
|
||||
/** @deprecated No longer supported */
|
||||
@@ -2053,6 +2081,44 @@ export enum AppUploadSessionType {
|
||||
ProfileImageUpload = 'PROFILE_IMAGE_UPLOAD'
|
||||
}
|
||||
|
||||
export type AppUsageMetric = {
|
||||
__typename?: 'AppUsageMetric';
|
||||
id: Scalars['ID']['output'];
|
||||
metricType: UsageMetricType;
|
||||
serviceMetric: EasServiceMetric;
|
||||
timestamp: Scalars['DateTime']['output'];
|
||||
value: Scalars['Float']['output'];
|
||||
};
|
||||
|
||||
export type AppUsageMetricTotal = {
|
||||
__typename?: 'AppUsageMetricTotal';
|
||||
billingPeriod: BillingPeriod;
|
||||
id: Scalars['ID']['output'];
|
||||
planMetrics: Array<EstimatedUsage>;
|
||||
/** Total cost of overages, in cents */
|
||||
totalCost: Scalars['Float']['output'];
|
||||
};
|
||||
|
||||
export type AppUsageMetrics = {
|
||||
__typename?: 'AppUsageMetrics';
|
||||
byBillingPeriod: AppUsageMetricTotal;
|
||||
metricsForServiceMetric: Array<AppUsageMetric>;
|
||||
};
|
||||
|
||||
|
||||
export type AppUsageMetricsByBillingPeriodArgs = {
|
||||
date: Scalars['DateTime']['input'];
|
||||
service?: InputMaybe<EasService>;
|
||||
};
|
||||
|
||||
|
||||
export type AppUsageMetricsMetricsForServiceMetricArgs = {
|
||||
filterParams?: InputMaybe<Scalars['JSONObject']['input']>;
|
||||
granularity: UsageMetricsGranularity;
|
||||
serviceMetric: EasServiceMetric;
|
||||
timespan: UsageMetricsTimespan;
|
||||
};
|
||||
|
||||
/** Represents Play Store/App Store version of an application */
|
||||
export type AppVersion = {
|
||||
__typename?: 'AppVersion';
|
||||
@@ -2838,7 +2904,7 @@ export type BuildArtifacts = {
|
||||
applicationArchiveUrl?: Maybe<Scalars['String']['output']>;
|
||||
buildArtifactsUrl?: Maybe<Scalars['String']['output']>;
|
||||
buildUrl?: Maybe<Scalars['String']['output']>;
|
||||
/** @deprecated Use 'runtime.fingerprintDebugInfoUrl' instead. */
|
||||
/** @deprecated Use 'runtime.fingerprint.debugInfoUrl' instead. */
|
||||
fingerprintUrl?: Maybe<Scalars['String']['output']>;
|
||||
xcodeBuildLogsUrl?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
@@ -2869,7 +2935,9 @@ export type BuildFilter = {
|
||||
appVersion?: InputMaybe<Scalars['String']['input']>;
|
||||
buildProfile?: InputMaybe<Scalars['String']['input']>;
|
||||
channel?: InputMaybe<Scalars['String']['input']>;
|
||||
developmentClient?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
distribution?: InputMaybe<DistributionType>;
|
||||
fingerprintHash?: InputMaybe<Scalars['String']['input']>;
|
||||
gitCommitHash?: InputMaybe<Scalars['String']['input']>;
|
||||
hasFingerprint?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
platform?: InputMaybe<AppPlatform>;
|
||||
@@ -2883,6 +2951,7 @@ export type BuildFilterInput = {
|
||||
channel?: InputMaybe<Scalars['String']['input']>;
|
||||
developmentClient?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
distributions?: InputMaybe<Array<DistributionType>>;
|
||||
fingerprintHash?: InputMaybe<Scalars['String']['input']>;
|
||||
hasFingerprint?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
platforms?: InputMaybe<Array<AppPlatform>>;
|
||||
releaseChannel?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -3294,6 +3363,7 @@ export enum CrashSampleFor {
|
||||
}
|
||||
|
||||
export type CrashesFilters = {
|
||||
crashKind?: InputMaybe<Array<WorkerDeploymentCrashKind>>;
|
||||
name?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
};
|
||||
|
||||
@@ -3346,6 +3416,11 @@ export type CreateEnvironmentVariableInput = {
|
||||
visibility: EnvironmentVariableVisibility;
|
||||
};
|
||||
|
||||
export type CreateFingerprintInput = {
|
||||
hash: Scalars['String']['input'];
|
||||
source?: InputMaybe<FingerprintSourceInput>;
|
||||
};
|
||||
|
||||
export type CreateGitHubAppInstallationInput = {
|
||||
accountId: Scalars['ID']['input'];
|
||||
installationIdentifier: Scalars['Int']['input'];
|
||||
@@ -3596,6 +3671,16 @@ export type DeleteIosAppCredentialsResult = {
|
||||
id: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
export type DeleteLogRocketOrganizationResult = {
|
||||
__typename?: 'DeleteLogRocketOrganizationResult';
|
||||
accountId: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
export type DeleteLogRocketProjectResult = {
|
||||
__typename?: 'DeleteLogRocketProjectResult';
|
||||
id: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
export type DeleteRobotResult = {
|
||||
__typename?: 'DeleteRobotResult';
|
||||
id: Scalars['ID']['output'];
|
||||
@@ -3918,6 +4003,9 @@ export enum EntityTypeName {
|
||||
IosAppCredentialsEntity = 'IosAppCredentialsEntity',
|
||||
UserInvitationEntity = 'UserInvitationEntity',
|
||||
UserPermissionEntity = 'UserPermissionEntity',
|
||||
WorkerCustomDomainEntity = 'WorkerCustomDomainEntity',
|
||||
WorkerDeploymentAliasEntity = 'WorkerDeploymentAliasEntity',
|
||||
WorkerEntity = 'WorkerEntity',
|
||||
WorkflowEntity = 'WorkflowEntity',
|
||||
WorkflowRevisionEntity = 'WorkflowRevisionEntity'
|
||||
}
|
||||
@@ -4007,6 +4095,8 @@ export type EnvironmentVariableMutation = {
|
||||
createEnvironmentVariableForAccount: EnvironmentVariable;
|
||||
/** Create an environment variable for an App */
|
||||
createEnvironmentVariableForApp: EnvironmentVariable;
|
||||
/** Bulk delete environment variables */
|
||||
deleteBulkEnvironmentVariables: Array<DeleteEnvironmentVariableResult>;
|
||||
/** Delete an environment variable */
|
||||
deleteEnvironmentVariable: DeleteEnvironmentVariableResult;
|
||||
/** Bulk link shared environment variables */
|
||||
@@ -4015,6 +4105,8 @@ export type EnvironmentVariableMutation = {
|
||||
linkSharedEnvironmentVariable: EnvironmentVariable;
|
||||
/** Unlink shared environment variable */
|
||||
unlinkSharedEnvironmentVariable: EnvironmentVariable;
|
||||
/** Bulk update environment variables */
|
||||
updateBulkEnvironmentVariables: Array<EnvironmentVariable>;
|
||||
/** Update an environment variable */
|
||||
updateEnvironmentVariable: EnvironmentVariable;
|
||||
};
|
||||
@@ -4044,6 +4136,11 @@ export type EnvironmentVariableMutationCreateEnvironmentVariableForAppArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type EnvironmentVariableMutationDeleteBulkEnvironmentVariablesArgs = {
|
||||
ids: Array<Scalars['ID']['input']>;
|
||||
};
|
||||
|
||||
|
||||
export type EnvironmentVariableMutationDeleteEnvironmentVariableArgs = {
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
@@ -4068,6 +4165,11 @@ export type EnvironmentVariableMutationUnlinkSharedEnvironmentVariableArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type EnvironmentVariableMutationUpdateBulkEnvironmentVariablesArgs = {
|
||||
environmentVariablesData: Array<UpdateEnvironmentVariableInput>;
|
||||
};
|
||||
|
||||
|
||||
export type EnvironmentVariableMutationUpdateEnvironmentVariableArgs = {
|
||||
environmentVariableData: UpdateEnvironmentVariableInput;
|
||||
};
|
||||
@@ -4180,12 +4282,44 @@ export enum Feature {
|
||||
export type Fingerprint = {
|
||||
__typename?: 'Fingerprint';
|
||||
app: App;
|
||||
builds: AppBuildsConnection;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
debugInfoUrl?: Maybe<Scalars['String']['output']>;
|
||||
hash: Scalars['String']['output'];
|
||||
id: Scalars['ID']['output'];
|
||||
source?: Maybe<FingerprintSource>;
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
updates: AppUpdatesConnection;
|
||||
};
|
||||
|
||||
|
||||
export type FingerprintBuildsArgs = {
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
before?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<FingerprintBuildsFilterInput>;
|
||||
first?: InputMaybe<Scalars['Int']['input']>;
|
||||
last?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
|
||||
export type FingerprintUpdatesArgs = {
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
before?: InputMaybe<Scalars['String']['input']>;
|
||||
first?: InputMaybe<Scalars['Int']['input']>;
|
||||
last?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
export type FingerprintBuildsFilterInput = {
|
||||
channel?: InputMaybe<Scalars['String']['input']>;
|
||||
developmentClient?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
distributions?: InputMaybe<Array<DistributionType>>;
|
||||
platforms?: InputMaybe<Array<AppPlatform>>;
|
||||
releaseChannel?: InputMaybe<Scalars['String']['input']>;
|
||||
simulator?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
};
|
||||
|
||||
export type FingerprintFilterInput = {
|
||||
hashes?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
};
|
||||
|
||||
export type FingerprintInfo = {
|
||||
@@ -4199,6 +4333,18 @@ export type FingerprintInfoGroup = {
|
||||
web?: InputMaybe<FingerprintInfo>;
|
||||
};
|
||||
|
||||
export type FingerprintMutation = {
|
||||
__typename?: 'FingerprintMutation';
|
||||
/** Create or get an existing fingerprint for an App */
|
||||
createOrGetExistingFingerprint: Fingerprint;
|
||||
};
|
||||
|
||||
|
||||
export type FingerprintMutationCreateOrGetExistingFingerprintArgs = {
|
||||
appId: Scalars['ID']['input'];
|
||||
fingerprintData: CreateFingerprintInput;
|
||||
};
|
||||
|
||||
export type FingerprintSource = {
|
||||
__typename?: 'FingerprintSource';
|
||||
bucketKey: Scalars['String']['output'];
|
||||
@@ -4226,6 +4372,22 @@ export type FutureSubscription = {
|
||||
startDate: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
export type GenerateLogRocketOrganizationLinkResult = {
|
||||
__typename?: 'GenerateLogRocketOrganizationLinkResult';
|
||||
url: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type GenerateLogRocketOrganizationLinkingUrlInput = {
|
||||
accountId: Scalars['ID']['input'];
|
||||
callbackUrl: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type GenerateLogRocketReplayTokenResult = {
|
||||
__typename?: 'GenerateLogRocketReplayTokenResult';
|
||||
orgSlug: Scalars['String']['output'];
|
||||
replayToken: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type GetSignedAssetUploadSpecificationsResult = {
|
||||
__typename?: 'GetSignedAssetUploadSpecificationsResult';
|
||||
specifications: Array<Scalars['String']['output']>;
|
||||
@@ -4239,6 +4401,7 @@ export enum GitHubAppEnvironment {
|
||||
|
||||
export type GitHubAppInstallation = {
|
||||
__typename?: 'GitHubAppInstallation';
|
||||
/** The Expo account that owns the installation entity. */
|
||||
account: Account;
|
||||
actor?: Maybe<Actor>;
|
||||
id: Scalars['ID']['output'];
|
||||
@@ -4258,10 +4421,17 @@ export type GitHubAppInstallationAccessibleRepository = {
|
||||
url: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export enum GitHubAppInstallationAccountType {
|
||||
Organization = 'ORGANIZATION',
|
||||
User = 'USER'
|
||||
}
|
||||
|
||||
export type GitHubAppInstallationMetadata = {
|
||||
__typename?: 'GitHubAppInstallationMetadata';
|
||||
githubAccountAvatarUrl?: Maybe<Scalars['String']['output']>;
|
||||
/** The login of the GitHub account that owns the installation. Not the display name. */
|
||||
githubAccountName?: Maybe<Scalars['String']['output']>;
|
||||
githubAccountType?: Maybe<GitHubAppInstallationAccountType>;
|
||||
installationStatus: GitHubAppInstallationStatus;
|
||||
};
|
||||
|
||||
@@ -4905,6 +5075,7 @@ export type IosJobInput = {
|
||||
developmentClient?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
/** @deprecated */
|
||||
distribution?: InputMaybe<DistributionType>;
|
||||
environment?: InputMaybe<EnvironmentVariableEnvironment>;
|
||||
experimental?: InputMaybe<Scalars['JSONObject']['input']>;
|
||||
loggerLevel?: InputMaybe<WorkerLoggerLevel>;
|
||||
mode?: InputMaybe<BuildMode>;
|
||||
@@ -4997,6 +5168,7 @@ export type IosSubmissionConfigInput = {
|
||||
export type JobRun = {
|
||||
__typename?: 'JobRun';
|
||||
app: App;
|
||||
artifacts: Array<WorkflowArtifact>;
|
||||
/** @deprecated No longer supported */
|
||||
childJobRun?: Maybe<JobRun>;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
@@ -5106,6 +5278,15 @@ export type LineDataset = {
|
||||
label: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type LinkLogRocketOrganizationToExpoAccountInput = {
|
||||
accountId: Scalars['ID']['input'];
|
||||
client_id: Scalars['String']['input'];
|
||||
client_secret: Scalars['String']['input'];
|
||||
orgName: Scalars['String']['input'];
|
||||
orgSlug: Scalars['String']['input'];
|
||||
state: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type LinkSharedEnvironmentVariableInput = {
|
||||
appId: Scalars['ID']['input'];
|
||||
environment?: InputMaybe<EnvironmentVariableEnvironment>;
|
||||
@@ -5118,6 +5299,76 @@ export type LogNameTypeMapping = {
|
||||
typeName: EntityTypeName;
|
||||
};
|
||||
|
||||
export type LogRocketOrganization = {
|
||||
__typename?: 'LogRocketOrganization';
|
||||
account: Account;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
id: Scalars['ID']['output'];
|
||||
orgName: Scalars['String']['output'];
|
||||
orgSlug: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type LogRocketOrganizationMutation = {
|
||||
__typename?: 'LogRocketOrganizationMutation';
|
||||
/** Delete a LogRocket organization by ID */
|
||||
deleteLogRocketOrganization: DeleteLogRocketOrganizationResult;
|
||||
/** Generate a LogRocket linking URL */
|
||||
generateLogRocketOrganizationLinkingURL: GenerateLogRocketOrganizationLinkResult;
|
||||
/** Generate a LogRocket replay token for an organization */
|
||||
generateLogRocketReplayToken: GenerateLogRocketReplayTokenResult;
|
||||
/** Link a LogRocket organization to an Expo account */
|
||||
linkLogRocketOrganizationToExpoAccount: LogRocketOrganization;
|
||||
};
|
||||
|
||||
|
||||
export type LogRocketOrganizationMutationDeleteLogRocketOrganizationArgs = {
|
||||
accountId: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type LogRocketOrganizationMutationGenerateLogRocketOrganizationLinkingUrlArgs = {
|
||||
input: GenerateLogRocketOrganizationLinkingUrlInput;
|
||||
};
|
||||
|
||||
|
||||
export type LogRocketOrganizationMutationGenerateLogRocketReplayTokenArgs = {
|
||||
accountId: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type LogRocketOrganizationMutationLinkLogRocketOrganizationToExpoAccountArgs = {
|
||||
input: LinkLogRocketOrganizationToExpoAccountInput;
|
||||
};
|
||||
|
||||
export type LogRocketProject = {
|
||||
__typename?: 'LogRocketProject';
|
||||
app: App;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
id: Scalars['ID']['output'];
|
||||
logRocketOrgId: Scalars['ID']['output'];
|
||||
logRocketProjectSlug: Scalars['String']['output'];
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
export type LogRocketProjectMutation = {
|
||||
__typename?: 'LogRocketProjectMutation';
|
||||
/** Create a LogRocket project */
|
||||
createLogRocketProject: LogRocketProject;
|
||||
/** Delete a LogRocket project by ID */
|
||||
deleteLogRocketProject: DeleteLogRocketProjectResult;
|
||||
};
|
||||
|
||||
|
||||
export type LogRocketProjectMutationCreateLogRocketProjectArgs = {
|
||||
appId: Scalars['ID']['input'];
|
||||
logRocketProjectSlug: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type LogRocketProjectMutationDeleteLogRocketProjectArgs = {
|
||||
logRocketProjectId: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
export type LogsTimespan = {
|
||||
end: Scalars['DateTime']['input'];
|
||||
start?: InputMaybe<Scalars['DateTime']['input']>;
|
||||
@@ -5512,6 +5763,7 @@ export type PublicArtifacts = {
|
||||
export type PublishUpdateGroupInput = {
|
||||
awaitingCodeSigningInfo?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
branchId: Scalars['String']['input'];
|
||||
environment?: InputMaybe<EnvironmentVariableEnvironment>;
|
||||
excludedAssets?: InputMaybe<Array<PartialManifestAsset>>;
|
||||
fingerprintInfoGroup?: InputMaybe<FingerprintInfoGroup>;
|
||||
gitCommitHash?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -5519,7 +5771,6 @@ export type PublishUpdateGroupInput = {
|
||||
message?: InputMaybe<Scalars['String']['input']>;
|
||||
rollBackToEmbeddedInfoGroup?: InputMaybe<UpdateRollBackToEmbeddedGroup>;
|
||||
rolloutInfoGroup?: InputMaybe<UpdateRolloutInfoGroup>;
|
||||
runtimeFingerprintSource?: InputMaybe<FingerprintSourceInput>;
|
||||
runtimeVersion: Scalars['String']['input'];
|
||||
turtleJobRunId?: InputMaybe<Scalars['String']['input']>;
|
||||
updateInfoGroup?: InputMaybe<UpdateInfoGroup>;
|
||||
@@ -5541,10 +5792,12 @@ export type RequestsFilters = {
|
||||
hasCustomDomainOrigin?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
isAsset?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
isCrash?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
isLimitExceeded?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
isVerifiedBot?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
method?: InputMaybe<Array<RequestMethod>>;
|
||||
os?: InputMaybe<Array<UserAgentOs>>;
|
||||
pathname?: InputMaybe<Scalars['String']['input']>;
|
||||
requestId?: InputMaybe<Array<Scalars['WorkerDeploymentRequestID']['input']>>;
|
||||
responseType?: InputMaybe<Array<ResponseType>>;
|
||||
status?: InputMaybe<Array<Scalars['Int']['input']>>;
|
||||
statusType?: InputMaybe<Array<ResponseStatusType>>;
|
||||
@@ -5617,8 +5870,11 @@ export type Robot = Actor & {
|
||||
*/
|
||||
featureGates: Scalars['JSONObject']['output'];
|
||||
firstName?: Maybe<Scalars['String']['output']>;
|
||||
/** GitHub App Installations that manage this actor */
|
||||
githubAppInstallations: Array<GitHubAppInstallation>;
|
||||
id: Scalars['ID']['output'];
|
||||
isExpoAdmin: Scalars['Boolean']['output'];
|
||||
isManagedByGitHubApp: Scalars['Boolean']['output'];
|
||||
lastDeletionAttemptTime?: Maybe<Scalars['DateTime']['output']>;
|
||||
};
|
||||
|
||||
@@ -5731,6 +5987,8 @@ export type RootMutation = {
|
||||
environmentSecret: EnvironmentSecretMutation;
|
||||
/** Mutations that create and delete EnvironmentVariables */
|
||||
environmentVariable: EnvironmentVariableMutation;
|
||||
/** Mutations that modify App fingerprints */
|
||||
fingerprint: FingerprintMutation;
|
||||
/** Mutations that utilize services facilitated by the GitHub App */
|
||||
githubApp: GitHubAppMutation;
|
||||
/** Mutations for GitHub App installations */
|
||||
@@ -5753,6 +6011,10 @@ export type RootMutation = {
|
||||
/** Mutations that modify an EAS Build */
|
||||
jobRun: JobRunMutation;
|
||||
keystoreGenerationUrl: KeystoreGenerationUrlMutation;
|
||||
/** Mutations for LogRocket organizations */
|
||||
logRocketOrganization: LogRocketOrganizationMutation;
|
||||
/** Mutations for LogRocket projects */
|
||||
logRocketProject: LogRocketProjectMutation;
|
||||
/** Mutations that modify the currently authenticated User */
|
||||
me: MeMutation;
|
||||
/** Mutations that modify a NotificationSubscription */
|
||||
@@ -5946,7 +6208,7 @@ export type Runtime = {
|
||||
builds: AppBuildsConnection;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
deployments: DeploymentsConnection;
|
||||
fingerprintDebugInfoUrl?: Maybe<Scalars['String']['output']>;
|
||||
fingerprint?: Maybe<Fingerprint>;
|
||||
firstBuildCreatedAt?: Maybe<Scalars['DateTime']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
@@ -6383,7 +6645,7 @@ export enum SubmissionAndroidTrack {
|
||||
}
|
||||
|
||||
export type SubmissionArchiveSourceInput = {
|
||||
/** Required if the archive source type is GCS_BUILD_APPLICATION_ARCHIVE or GCS_SUBMIT_ARCHIVE */
|
||||
/** Required if the archive source type is GCS_BUILD_APPLICATION_ARCHIVE, GCS_BUILD_APPLICATION_ARCHIVE_ORCHESTRATOR or GCS_SUBMIT_ARCHIVE */
|
||||
bucketKey?: InputMaybe<Scalars['String']['input']>;
|
||||
type: SubmissionArchiveSourceType;
|
||||
/** Required if the archive source type is URL */
|
||||
@@ -6392,6 +6654,7 @@ export type SubmissionArchiveSourceInput = {
|
||||
|
||||
export enum SubmissionArchiveSourceType {
|
||||
GcsBuildApplicationArchive = 'GCS_BUILD_APPLICATION_ARCHIVE',
|
||||
GcsBuildApplicationArchiveOrchestrator = 'GCS_BUILD_APPLICATION_ARCHIVE_ORCHESTRATOR',
|
||||
GcsSubmitArchive = 'GCS_SUBMIT_ARCHIVE',
|
||||
Url = 'URL'
|
||||
}
|
||||
@@ -6549,6 +6812,7 @@ export type Update = ActivityTimelineProjectActivity & {
|
||||
codeSigningInfo?: Maybe<CodeSigningInfo>;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
deployments: DeploymentResult;
|
||||
environment?: Maybe<EnvironmentVariableEnvironment>;
|
||||
expoGoSDKVersion?: Maybe<Scalars['String']['output']>;
|
||||
fingerprint?: Maybe<Fingerprint>;
|
||||
gitCommitHash?: Maybe<Scalars['String']['output']>;
|
||||
@@ -6746,6 +7010,12 @@ export type UpdateEnvironmentVariableInput = {
|
||||
visibility?: InputMaybe<EnvironmentVariableVisibility>;
|
||||
};
|
||||
|
||||
export type UpdateFilterInput = {
|
||||
fingerprintHash?: InputMaybe<Scalars['String']['input']>;
|
||||
hasFingerprint?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
runtimeVersion?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type UpdateGitHubBuildTriggerInput = {
|
||||
autoSubmit: Scalars['Boolean']['input'];
|
||||
buildProfile: Scalars['String']['input'];
|
||||
@@ -7641,6 +7911,7 @@ export type WorkerDeployment = ActivityTimelineProjectActivity & {
|
||||
activityTimestamp: Scalars['DateTime']['output'];
|
||||
actor?: Maybe<Actor>;
|
||||
aliases?: Maybe<Array<WorkerDeploymentAlias>>;
|
||||
app: App;
|
||||
crashes?: Maybe<WorkerDeploymentCrashes>;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
deploymentDomain: Scalars['String']['output'];
|
||||
@@ -7651,7 +7922,6 @@ export type WorkerDeployment = ActivityTimelineProjectActivity & {
|
||||
logs?: Maybe<WorkerDeploymentLogs>;
|
||||
requests?: Maybe<WorkerDeploymentRequests>;
|
||||
subdomain: Scalars['String']['output'];
|
||||
tier?: Maybe<WorkerDeploymentTier>;
|
||||
url: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
@@ -7705,9 +7975,19 @@ export type WorkerDeploymentCrashEdge = {
|
||||
request?: Maybe<WorkerDeploymentRequestNode>;
|
||||
};
|
||||
|
||||
export enum WorkerDeploymentCrashKind {
|
||||
ExceededCpu = 'EXCEEDED_CPU',
|
||||
ExceededMemory = 'EXCEEDED_MEMORY',
|
||||
ExceededSubrequests = 'EXCEEDED_SUBREQUESTS',
|
||||
Generic = 'GENERIC',
|
||||
Internal = 'INTERNAL',
|
||||
ResponseStreamDisconnected = 'RESPONSE_STREAM_DISCONNECTED'
|
||||
}
|
||||
|
||||
export type WorkerDeploymentCrashNode = {
|
||||
__typename?: 'WorkerDeploymentCrashNode';
|
||||
crashHash: Scalars['ID']['output'];
|
||||
crashKind: WorkerDeploymentCrashKind;
|
||||
crashTimestamp: Scalars['DateTime']['output'];
|
||||
deploymentIdentifier: Scalars['String']['output'];
|
||||
firstStackLine?: Maybe<Scalars['String']['output']>;
|
||||
@@ -7819,6 +8099,7 @@ export type WorkerDeploymentRequestNode = {
|
||||
hasCustomDomainOrigin: Scalars['Boolean']['output'];
|
||||
isAsset: Scalars['Boolean']['output'];
|
||||
isCrash: Scalars['Boolean']['output'];
|
||||
isLimitExceeded: Scalars['Boolean']['output'];
|
||||
isRejected: Scalars['Boolean']['output'];
|
||||
isStaleIfError: Scalars['Boolean']['output'];
|
||||
isStaleWhileRevalidate: Scalars['Boolean']['output'];
|
||||
@@ -7828,6 +8109,7 @@ export type WorkerDeploymentRequestNode = {
|
||||
os?: Maybe<UserAgentOs>;
|
||||
pathname: Scalars['String']['output'];
|
||||
region?: Maybe<Scalars['String']['output']>;
|
||||
requestId: Scalars['WorkerDeploymentRequestID']['output'];
|
||||
requestTimestamp: Scalars['DateTime']['output'];
|
||||
responseType: ResponseType;
|
||||
scriptName: Scalars['String']['output'];
|
||||
@@ -7929,6 +8211,8 @@ export type WorkerDeploymentRequestsAggregationNode = {
|
||||
durationP50: Scalars['Float']['output'];
|
||||
durationP90: Scalars['Float']['output'];
|
||||
durationP99: Scalars['Float']['output'];
|
||||
limitExceededPerMs?: Maybe<Scalars['Float']['output']>;
|
||||
limitExceededSum: Scalars['Int']['output'];
|
||||
requestsPerMs?: Maybe<Scalars['Float']['output']>;
|
||||
requestsSum: Scalars['Int']['output'];
|
||||
sampleRate?: Maybe<Scalars['Float']['output']>;
|
||||
@@ -8004,11 +8288,6 @@ export type WorkerDeploymentRequestsTimeseriesEdge = {
|
||||
timestamp: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
export enum WorkerDeploymentTier {
|
||||
Free = 'FREE',
|
||||
Paid = 'PAID'
|
||||
}
|
||||
|
||||
export type WorkerDeploymentsConnection = {
|
||||
__typename?: 'WorkerDeploymentsConnection';
|
||||
edges: Array<WorkerDeploymentEdge>;
|
||||
@@ -8052,6 +8331,19 @@ export type WorkflowRunsPaginatedArgs = {
|
||||
last?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
export type WorkflowArtifact = {
|
||||
__typename?: 'WorkflowArtifact';
|
||||
contentType?: Maybe<Scalars['String']['output']>;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
downloadUrl?: Maybe<Scalars['String']['output']>;
|
||||
fileSizeBytes?: Maybe<Scalars['Int']['output']>;
|
||||
filename: Scalars['String']['output'];
|
||||
id: Scalars['ID']['output'];
|
||||
jobRun: JobRun;
|
||||
name: Scalars['String']['output'];
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
export type WorkflowJob = {
|
||||
__typename?: 'WorkflowJob';
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
@@ -8112,6 +8404,7 @@ export enum WorkflowJobType {
|
||||
AppleDeviceRegistrationRequest = 'APPLE_DEVICE_REGISTRATION_REQUEST',
|
||||
Build = 'BUILD',
|
||||
Custom = 'CUSTOM',
|
||||
Deploy = 'DEPLOY',
|
||||
GetBuild = 'GET_BUILD',
|
||||
MaestroTest = 'MAESTRO_TEST',
|
||||
RequireApproval = 'REQUIRE_APPROVAL',
|
||||
@@ -8121,6 +8414,7 @@ export enum WorkflowJobType {
|
||||
|
||||
export type WorkflowProjectSourceInput = {
|
||||
easJsonBucketKey: Scalars['String']['input'];
|
||||
packageJsonBucketKey?: InputMaybe<Scalars['String']['input']>;
|
||||
projectArchiveBucketKey: Scalars['String']['input'];
|
||||
type: WorkflowProjectSourceType;
|
||||
};
|
||||
@@ -8204,7 +8498,11 @@ export type WorkflowRun = ActivityTimelineProjectActivity & {
|
||||
name: Scalars['String']['output'];
|
||||
pullRequestNumber?: Maybe<Scalars['Int']['output']>;
|
||||
requestedGitRef?: Maybe<Scalars['String']['output']>;
|
||||
retriedWorkflowRun?: Maybe<WorkflowRun>;
|
||||
retries: Array<WorkflowRun>;
|
||||
sourceExpiresAt?: Maybe<Scalars['DateTime']['output']>;
|
||||
status: WorkflowRunStatus;
|
||||
triggerEventType: WorkflowRunTriggerEventType;
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
workflow: Workflow;
|
||||
workflowRevision?: Maybe<WorkflowRevision>;
|
||||
@@ -8230,6 +8528,7 @@ export type WorkflowRunMutation = {
|
||||
__typename?: 'WorkflowRunMutation';
|
||||
cancelWorkflowRun: WorkflowRun;
|
||||
createWorkflowRun: WorkflowRun;
|
||||
retryWorkflowRun: WorkflowRun;
|
||||
};
|
||||
|
||||
|
||||
@@ -8244,6 +8543,12 @@ export type WorkflowRunMutationCreateWorkflowRunArgs = {
|
||||
workflowRunInput: WorkflowRunInput;
|
||||
};
|
||||
|
||||
|
||||
export type WorkflowRunMutationRetryWorkflowRunArgs = {
|
||||
fromFailedJobs?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
workflowRunId: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
export type WorkflowRunQuery = {
|
||||
__typename?: 'WorkflowRunQuery';
|
||||
byId: WorkflowRun;
|
||||
@@ -8264,6 +8569,14 @@ export enum WorkflowRunStatus {
|
||||
Success = 'SUCCESS'
|
||||
}
|
||||
|
||||
export enum WorkflowRunTriggerEventType {
|
||||
GithubPullRequestOpened = 'GITHUB_PULL_REQUEST_OPENED',
|
||||
GithubPullRequestReopened = 'GITHUB_PULL_REQUEST_REOPENED',
|
||||
GithubPullRequestSynchronize = 'GITHUB_PULL_REQUEST_SYNCHRONIZE',
|
||||
GithubPush = 'GITHUB_PUSH',
|
||||
Manual = 'MANUAL'
|
||||
}
|
||||
|
||||
export type WorkflowRunsConnection = {
|
||||
__typename?: 'WorkflowRunsConnection';
|
||||
edges: Array<WorkflowRunEdge>;
|
||||
@@ -8819,7 +9132,7 @@ export type CreateAndroidBuildMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateAndroidBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createAndroidBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
|
||||
export type CreateAndroidBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createAndroidBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string }, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
|
||||
|
||||
export type CreateIosBuildMutationVariables = Exact<{
|
||||
appId: Scalars['ID']['input'];
|
||||
@@ -8829,7 +9142,7 @@ export type CreateIosBuildMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateIosBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createIosBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
|
||||
export type CreateIosBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createIosBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string }, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
|
||||
|
||||
export type UpdateBuildMetadataMutationVariables = Exact<{
|
||||
buildId: Scalars['ID']['input'];
|
||||
@@ -8837,7 +9150,7 @@ export type UpdateBuildMetadataMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateBuildMetadataMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', updateBuildMetadata: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
|
||||
export type UpdateBuildMetadataMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', updateBuildMetadata: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string }, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null } } };
|
||||
|
||||
export type RetryIosBuildMutationVariables = Exact<{
|
||||
buildId: Scalars['ID']['input'];
|
||||
@@ -8845,7 +9158,7 @@ export type RetryIosBuildMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type RetryIosBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', retryIosBuild: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
|
||||
export type RetryIosBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', retryIosBuild: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string }, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null } } };
|
||||
|
||||
export type CreateEnvironmentSecretForAccountMutationVariables = Exact<{
|
||||
input: CreateEnvironmentSecretInput;
|
||||
@@ -8926,6 +9239,14 @@ export type CreateBulkEnvironmentVariablesForAppMutationVariables = Exact<{
|
||||
|
||||
export type CreateBulkEnvironmentVariablesForAppMutation = { __typename?: 'RootMutation', environmentVariable: { __typename?: 'EnvironmentVariableMutation', createBulkEnvironmentVariablesForApp: Array<{ __typename?: 'EnvironmentVariable', id: string }> } };
|
||||
|
||||
export type CreateFingeprintMutationVariables = Exact<{
|
||||
fingerprintData: CreateFingerprintInput;
|
||||
appId: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateFingeprintMutation = { __typename?: 'RootMutation', fingerprint: { __typename?: 'FingerprintMutation', createOrGetExistingFingerprint: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, builds: { __typename?: 'AppBuildsConnection', edges: Array<{ __typename?: 'AppBuildEdge', node: { __typename?: 'Build', platform: AppPlatform, id: string } }> }, updates: { __typename?: 'AppUpdatesConnection', edges: Array<{ __typename?: 'AppUpdateEdge', node: { __typename?: 'Update', id: string, platform: string } }> } } } };
|
||||
|
||||
export type CreateKeystoreGenerationUrlMutationVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
@@ -8943,7 +9264,7 @@ export type UpdatePublishMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdatePublishMutation = { __typename?: 'RootMutation', updateBranch: { __typename?: 'UpdateBranchMutation', publishUpdateGroups: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }> } };
|
||||
export type UpdatePublishMutation = { __typename?: 'RootMutation', updateBranch: { __typename?: 'UpdateBranchMutation', publishUpdateGroups: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }> } };
|
||||
|
||||
export type SetCodeSigningInfoMutationVariables = Exact<{
|
||||
updateId: Scalars['ID']['input'];
|
||||
@@ -8959,7 +9280,7 @@ export type SetRolloutPercentageMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type SetRolloutPercentageMutation = { __typename?: 'RootMutation', update: { __typename?: 'UpdateMutation', setRolloutPercentage: { __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null } } };
|
||||
export type SetRolloutPercentageMutation = { __typename?: 'RootMutation', update: { __typename?: 'UpdateMutation', setRolloutPercentage: { __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null } } };
|
||||
|
||||
export type CreateAndroidSubmissionMutationVariables = Exact<{
|
||||
appId: Scalars['ID']['input'];
|
||||
@@ -9098,7 +9419,7 @@ export type BranchesByAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type BranchesByAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }> }> } } };
|
||||
export type BranchesByAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }> }> } } };
|
||||
|
||||
export type BranchesBasicPaginatedOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -9119,28 +9440,28 @@ export type ViewBranchesOnUpdateChannelQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewBranchesOnUpdateChannelQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> }> } | null } } };
|
||||
export type ViewBranchesOnUpdateChannelQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> }> } | null } } };
|
||||
|
||||
export type BuildsByIdQueryVariables = Exact<{
|
||||
buildId: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type BuildsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
|
||||
export type BuildsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string }, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null } } };
|
||||
|
||||
export type BuildsWithSubmissionsByIdQueryVariables = Exact<{
|
||||
buildId: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type BuildsWithSubmissionsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, submissions: Array<{ __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logFiles: Array<string>, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null, rollout?: number | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null }>, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
|
||||
export type BuildsWithSubmissionsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, submissions: Array<{ __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logFiles: Array<string>, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null, rollout?: number | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null }>, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string }, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null } } };
|
||||
|
||||
export type BuildsWithFingerprintByIdQueryVariables = Exact<{
|
||||
buildId: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type BuildsWithFingerprintByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null } | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
|
||||
export type BuildsWithFingerprintByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, builds: { __typename?: 'AppBuildsConnection', edges: Array<{ __typename?: 'AppBuildEdge', node: { __typename?: 'Build', platform: AppPlatform, id: string } }> }, updates: { __typename?: 'AppUpdatesConnection', edges: Array<{ __typename?: 'AppUpdateEdge', node: { __typename?: 'Update', id: string, platform: string } }> } } | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string }, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null } } };
|
||||
|
||||
export type ViewBuildsOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -9150,7 +9471,15 @@ export type ViewBuildsOnAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewBuildsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, builds: Array<{ __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }> } } };
|
||||
export type ViewBuildsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, builds: Array<{ __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string }, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null }> } } };
|
||||
|
||||
export type ViewUpdateChannelBasicInfoOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
channelName: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateChannelBasicInfoOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, name: string, branchMapping: string } | null } } };
|
||||
|
||||
export type ViewUpdateChannelOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -9159,7 +9488,7 @@ export type ViewUpdateChannelOnAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateChannelOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, isPaused: boolean, name: string, updatedAt: any, createdAt: any, branchMapping: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> }> } | null } } };
|
||||
export type ViewUpdateChannelOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, isPaused: boolean, name: string, updatedAt: any, createdAt: any, branchMapping: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> }> } | null } } };
|
||||
|
||||
export type ViewUpdateChannelsOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -9168,7 +9497,7 @@ export type ViewUpdateChannelsOnAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateChannelsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannels: Array<{ __typename?: 'UpdateChannel', id: string, isPaused: boolean, name: string, updatedAt: any, createdAt: any, branchMapping: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> }> }> } } };
|
||||
export type ViewUpdateChannelsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannels: Array<{ __typename?: 'UpdateChannel', id: string, isPaused: boolean, name: string, updatedAt: any, createdAt: any, branchMapping: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> }> }> } } };
|
||||
|
||||
export type ViewUpdateChannelsPaginatedOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -9228,6 +9557,18 @@ export type EnvironmentVariablesSharedWithSensitiveQueryVariables = Exact<{
|
||||
|
||||
export type EnvironmentVariablesSharedWithSensitiveQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, ownerAccount: { __typename?: 'Account', id: string, environmentVariablesIncludingSensitive: Array<{ __typename?: 'EnvironmentVariableWithSecret', id: string, name: string, value?: string | null, environments?: Array<EnvironmentVariableEnvironment> | null, createdAt: any, updatedAt: any, scope: EnvironmentVariableScope, visibility: EnvironmentVariableVisibility, type: EnvironmentSecretType, valueWithFileContent?: string | null }> } } } };
|
||||
|
||||
export type FingerprintsByAppIdQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
first?: InputMaybe<Scalars['Int']['input']>;
|
||||
before?: InputMaybe<Scalars['String']['input']>;
|
||||
last?: InputMaybe<Scalars['Int']['input']>;
|
||||
fingerprintFilter?: InputMaybe<FingerprintFilterInput>;
|
||||
}>;
|
||||
|
||||
|
||||
export type FingerprintsByAppIdQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, fingerprintsPaginated: { __typename?: 'AppFingerprintsConnection', edges: Array<{ __typename?: 'AppFingerprintEdge', node: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, builds: { __typename?: 'AppBuildsConnection', edges: Array<{ __typename?: 'AppBuildEdge', node: { __typename?: 'Build', platform: AppPlatform, id: string } }> }, updates: { __typename?: 'AppUpdatesConnection', edges: Array<{ __typename?: 'AppUpdateEdge', node: { __typename?: 'Update', id: string, platform: string } }> } } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null } } } } };
|
||||
|
||||
export type GoogleServiceAccountKeyByIdQueryVariables = Exact<{
|
||||
ascApiKeyId: Scalars['ID']['input'];
|
||||
}>;
|
||||
@@ -9292,7 +9633,7 @@ export type ViewUpdatesByGroupQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdatesByGroupQuery = { __typename?: 'RootQuery', updatesByGroup: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }> };
|
||||
export type ViewUpdatesByGroupQuery = { __typename?: 'RootQuery', updatesByGroup: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }> };
|
||||
|
||||
export type ViewUpdateGroupsOnBranchQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -9303,7 +9644,7 @@ export type ViewUpdateGroupsOnBranchQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateGroupsOnBranchQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranchByName?: { __typename?: 'UpdateBranch', id: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> } | null } } };
|
||||
export type ViewUpdateGroupsOnBranchQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranchByName?: { __typename?: 'UpdateBranch', id: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> } | null } } };
|
||||
|
||||
export type ViewUpdateGroupsOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -9313,7 +9654,14 @@ export type ViewUpdateGroupsOnAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateGroupsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> } } };
|
||||
export type ViewUpdateGroupsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> } } };
|
||||
|
||||
export type UpdateByIdQueryVariables = Exact<{
|
||||
updateId: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateByIdQuery = { __typename?: 'RootQuery', updates: { __typename?: 'UpdateQuery', byId: { __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null } } };
|
||||
|
||||
export type CurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -9339,11 +9687,11 @@ export type AccountFragment = { __typename?: 'Account', id: string, name: string
|
||||
|
||||
export type AppFragment = { __typename?: 'App', id: string, name: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> }, githubRepository?: { __typename?: 'GitHubRepository', id: string, metadata: { __typename?: 'GitHubRepositoryMetadata', githubRepoOwnerName: string, githubRepoName: string } } | null };
|
||||
|
||||
export type BuildFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
|
||||
export type BuildFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string }, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null };
|
||||
|
||||
export type BuildWithSubmissionsFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, submissions: Array<{ __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logFiles: Array<string>, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null, rollout?: number | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null }>, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
|
||||
export type BuildWithSubmissionsFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, submissions: Array<{ __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logFiles: Array<string>, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null, rollout?: number | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null }>, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string }, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null };
|
||||
|
||||
export type BuildWithFingerprintFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null } | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
|
||||
export type BuildWithFingerprintFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, builds: { __typename?: 'AppBuildsConnection', edges: Array<{ __typename?: 'AppBuildEdge', node: { __typename?: 'Build', platform: AppPlatform, id: string } }> }, updates: { __typename?: 'AppUpdatesConnection', edges: Array<{ __typename?: 'AppUpdateEdge', node: { __typename?: 'Update', id: string, platform: string } }> } } | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string }, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null };
|
||||
|
||||
export type EnvironmentSecretFragment = { __typename?: 'EnvironmentSecret', id: string, name: string, type: EnvironmentSecretType, createdAt: any };
|
||||
|
||||
@@ -9351,7 +9699,7 @@ export type EnvironmentVariableFragment = { __typename?: 'EnvironmentVariable',
|
||||
|
||||
export type EnvironmentVariableWithSecretFragment = { __typename?: 'EnvironmentVariableWithSecret', id: string, name: string, value?: string | null, environments?: Array<EnvironmentVariableEnvironment> | null, createdAt: any, updatedAt: any, scope: EnvironmentVariableScope, visibility: EnvironmentVariableVisibility, type: EnvironmentSecretType };
|
||||
|
||||
export type FingerprintFragment = { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null };
|
||||
export type FingerprintFragment = { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, builds: { __typename?: 'AppBuildsConnection', edges: Array<{ __typename?: 'AppBuildEdge', node: { __typename?: 'Build', platform: AppPlatform, id: string } }> }, updates: { __typename?: 'AppUpdatesConnection', edges: Array<{ __typename?: 'AppUpdateEdge', node: { __typename?: 'Update', id: string, platform: string } }> } };
|
||||
|
||||
export type RuntimeFragment = { __typename?: 'Runtime', id: string, version: string };
|
||||
|
||||
@@ -9359,9 +9707,9 @@ export type StatuspageServiceFragment = { __typename?: 'StatuspageService', id:
|
||||
|
||||
export type SubmissionFragment = { __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logFiles: Array<string>, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null, rollout?: number | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null };
|
||||
|
||||
export type UpdateFragment = { __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null };
|
||||
export type UpdateFragment = { __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null };
|
||||
|
||||
export type UpdateBranchFragment = { __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }> };
|
||||
export type UpdateBranchFragment = { __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }> };
|
||||
|
||||
export type UpdateBranchBasicInfoFragment = { __typename?: 'UpdateBranch', id: string, name: string };
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { FingerprintSource } from '@expo/eas-build-job';
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../client';
|
||||
import { CreateFingeprintMutation, FingerprintFragment } from '../generated';
|
||||
import { FingerprintFragmentNode } from '../types/Fingerprint';
|
||||
|
||||
export const FingerprintMutation = {
|
||||
async createFingerprintAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
appId: string,
|
||||
fingerprintData: { hash: string; source?: FingerprintSource }
|
||||
): Promise<FingerprintFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateFingeprintMutation>(
|
||||
gql`
|
||||
mutation CreateFingeprintMutation(
|
||||
$fingerprintData: CreateFingerprintInput!
|
||||
$appId: ID!
|
||||
) {
|
||||
fingerprint {
|
||||
createOrGetExistingFingerprint(fingerprintData: $fingerprintData, appId: $appId) {
|
||||
id
|
||||
...FingerprintFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(FingerprintFragmentNode)}
|
||||
`,
|
||||
{ appId, fingerprintData }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.fingerprint.createOrGetExistingFingerprint;
|
||||
},
|
||||
};
|
||||
@@ -7,6 +7,8 @@ import { withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
UpdateBranchBasicInfoFragment,
|
||||
UpdateFragment,
|
||||
ViewUpdateChannelBasicInfoOnAppQuery,
|
||||
ViewUpdateChannelBasicInfoOnAppQueryVariables,
|
||||
ViewUpdateChannelOnAppQuery,
|
||||
ViewUpdateChannelOnAppQueryVariables,
|
||||
ViewUpdateChannelsOnAppQuery,
|
||||
@@ -41,6 +43,42 @@ export function composeUpdateBranchObject(
|
||||
}
|
||||
|
||||
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(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, channelName, filter }: ViewUpdateChannelOnAppQueryVariables
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
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,6 +4,8 @@ import gql from 'graphql-tag';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
UpdateByIdQuery,
|
||||
UpdateByIdQueryVariables,
|
||||
UpdateFragment,
|
||||
ViewUpdateGroupsOnAppQuery,
|
||||
ViewUpdateGroupsOnAppQueryVariables,
|
||||
@@ -137,4 +139,30 @@ export const UpdateQuery = {
|
||||
|
||||
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;
|
||||
},
|
||||
};
|
||||
@@ -57,6 +57,11 @@ export const BuildFragmentNode = gql`
|
||||
completedAt
|
||||
expirationDate
|
||||
isForIosSimulator
|
||||
metrics {
|
||||
buildWaitTime
|
||||
buildQueueTime
|
||||
buildDuration
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -5,5 +5,23 @@ export const FingerprintFragmentNode = gql`
|
||||
id
|
||||
hash
|
||||
debugInfoUrl
|
||||
builds(first: 1) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
... on Build {
|
||||
platform
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
updates(first: 1) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
platform
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -38,6 +38,7 @@ export const UpdateFragmentNode = gql`
|
||||
fingerprint {
|
||||
id
|
||||
hash
|
||||
debugInfoUrl
|
||||
source {
|
||||
type
|
||||
bucketKey
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function validateCustomBuildConfigAsync({
|
||||
throw new Error(
|
||||
`Custom build configuration file ${chalk.bold(
|
||||
relativeConfigPath
|
||||
)} is ignored by your version control system. Remove it from the ignore list to successfully create custom build.`
|
||||
)} is ignored by your version control system or .easignore. Remove it from the ignore list to successfully create custom build.`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -84,13 +84,13 @@ export async function fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsyn
|
||||
const projectDashboardUrl = getProjectDashboardUrl(accountName, projectName);
|
||||
const projectLink = link(projectDashboardUrl, { text: projectFullName });
|
||||
|
||||
const spinner = ora(`Creating ${chalk.bold(projectFullName)} on Expo`).start();
|
||||
const spinner = ora(`Creating ${chalk.bold(projectFullName)} on EAS`).start();
|
||||
try {
|
||||
const id = await AppMutation.createAppAsync(graphqlClient, {
|
||||
accountId: account.id,
|
||||
projectName,
|
||||
});
|
||||
spinner.succeed(`Created ${chalk.bold(projectLink)} on Expo`);
|
||||
spinner.succeed(`Created ${chalk.bold(projectLink)} on EAS`);
|
||||
return id;
|
||||
} catch (err) {
|
||||
spinner.fail();
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -29,7 +29,7 @@ describe(getApplePlatformFromSdkRoot, () => {
|
||||
// Update `getApplePlatformFromSdkRoot` to be compatible with new Apple Platforms
|
||||
test('all enumerations work with the function', () => {
|
||||
expect(Object.values(ApplePlatform).sort()).toEqual(
|
||||
[ApplePlatform.IOS, ApplePlatform.TV_OS, ApplePlatform.MAC_OS].sort()
|
||||
[ApplePlatform.IOS, ApplePlatform.TV_OS, ApplePlatform.MAC_OS, ApplePlatform.VISION_OS].sort()
|
||||
);
|
||||
});
|
||||
test('existing SDKs work with the function', () => {
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -733,11 +733,11 @@ export const defaultPublishPlatforms: UpdatePublishPlatform[] = ['android', 'ios
|
||||
|
||||
export type RuntimeVersionInfo = {
|
||||
runtimeVersion: string;
|
||||
fingerprint: {
|
||||
expoUpdatesRuntimeFingerprint: {
|
||||
fingerprintSources: object[];
|
||||
isDebugFingerprintSource: boolean;
|
||||
} | null;
|
||||
fingerprintHash: string | null;
|
||||
expoUpdatesRuntimeFingerprintHash: string | null;
|
||||
};
|
||||
|
||||
type FingerprintInfoGroup = {
|
||||
@@ -797,11 +797,11 @@ async function getRuntimeVersionInfoForPlatformAsync({
|
||||
env: Env | undefined;
|
||||
}): Promise<{
|
||||
runtimeVersion: string;
|
||||
fingerprint: {
|
||||
expoUpdatesRuntimeFingerprint: {
|
||||
fingerprintSources: object[];
|
||||
isDebugFingerprintSource: boolean;
|
||||
} | null;
|
||||
fingerprintHash: string | null;
|
||||
expoUpdatesRuntimeFingerprintHash: string | null;
|
||||
}> {
|
||||
if (await isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync(projectDir)) {
|
||||
try {
|
||||
@@ -852,8 +852,8 @@ async function getRuntimeVersionInfoForPlatformAsync({
|
||||
|
||||
return {
|
||||
runtimeVersion: resolvedRuntimeVersion,
|
||||
fingerprint: null,
|
||||
fingerprintHash: null,
|
||||
expoUpdatesRuntimeFingerprint: null,
|
||||
expoUpdatesRuntimeFingerprintHash: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -875,13 +875,15 @@ export function getRuntimeToPlatformsAndFingerprintInfoMappingFromRuntimeVersion
|
||||
platforms: runtimeVersionInfoObjects.map(
|
||||
runtimeVersionInfoObject => runtimeVersionInfoObject.platform
|
||||
),
|
||||
fingerprint:
|
||||
expoUpdatesRuntimeFingerprint:
|
||||
runtimeVersionInfoObjects.map(
|
||||
runtimeVersionInfoObject => runtimeVersionInfoObject.runtimeVersionInfo.fingerprint
|
||||
runtimeVersionInfoObject =>
|
||||
runtimeVersionInfoObject.runtimeVersionInfo.expoUpdatesRuntimeFingerprint
|
||||
)[0] ?? null,
|
||||
fingerprintHash:
|
||||
expoUpdatesRuntimeFingerprintHash:
|
||||
runtimeVersionInfoObjects.map(
|
||||
runtimeVersionInfoObject => runtimeVersionInfoObject.runtimeVersionInfo.fingerprintHash
|
||||
runtimeVersionInfoObject =>
|
||||
runtimeVersionInfoObject.runtimeVersionInfo.expoUpdatesRuntimeFingerprintHash
|
||||
)[0] ?? null,
|
||||
};
|
||||
}
|
||||
@@ -899,20 +901,20 @@ export async function maybeCalculateFingerprintForRuntimeVersionInfoObjectsWitho
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping: (RuntimeVersionInfo & {
|
||||
platforms: UpdatePublishPlatform[];
|
||||
fingerprintSource: FingerprintSource | null;
|
||||
expoUpdatesRuntimeFingerprintSource: FingerprintSource | null;
|
||||
})[];
|
||||
workflowsByPlatform: Record<Platform, Workflow>;
|
||||
env: Env | undefined;
|
||||
}): Promise<
|
||||
(RuntimeVersionInfo & {
|
||||
platforms: UpdatePublishPlatform[];
|
||||
fingerprintSource: FingerprintSource | null;
|
||||
expoUpdatesRuntimeFingerprintSource: FingerprintSource | null;
|
||||
fingerprintInfoGroup: FingerprintInfoGroup;
|
||||
})[]
|
||||
> {
|
||||
const runtimesToComputeFingerprintsFor =
|
||||
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping.filter(
|
||||
infoGroup => !infoGroup.fingerprintHash
|
||||
infoGroup => !infoGroup.expoUpdatesRuntimeFingerprintHash
|
||||
);
|
||||
const fingerprintOptionsByRuntimeAndPlatform = new Map<string, FingerprintOptions>();
|
||||
for (const infoGroup of runtimesToComputeFingerprintsFor) {
|
||||
@@ -975,9 +977,11 @@ export async function maybeCalculateFingerprintForRuntimeVersionInfoObjectsWitho
|
||||
infoGroup
|
||||
): infoGroup is RuntimeVersionInfo & {
|
||||
platforms: UpdatePublishPlatform[];
|
||||
fingerprintSource: FingerprintSource;
|
||||
fingerprintHash: string;
|
||||
} => !!infoGroup.fingerprintHash && !!infoGroup.fingerprintSource
|
||||
expoUpdatesRuntimeFingerprintSource: FingerprintSource;
|
||||
expoUpdatesRuntimeFingerprintHash: string;
|
||||
} =>
|
||||
!!infoGroup.expoUpdatesRuntimeFingerprintHash &&
|
||||
!!infoGroup.expoUpdatesRuntimeFingerprintSource
|
||||
)
|
||||
.map(infoGroup => {
|
||||
const platform = infoGroup.platforms[0];
|
||||
@@ -985,8 +989,8 @@ export async function maybeCalculateFingerprintForRuntimeVersionInfoObjectsWitho
|
||||
...infoGroup,
|
||||
fingerprintInfoGroup: {
|
||||
[platform]: {
|
||||
fingerprintHash: infoGroup.fingerprintHash,
|
||||
fingerprintSource: infoGroup.fingerprintSource,
|
||||
fingerprintHash: infoGroup.expoUpdatesRuntimeFingerprintHash,
|
||||
fingerprintSource: infoGroup.expoUpdatesRuntimeFingerprintSource,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,11 +23,11 @@ export async function resolveRuntimeVersionUsingCLIAsync({
|
||||
cwd?: string;
|
||||
}): Promise<{
|
||||
runtimeVersion: string | null;
|
||||
fingerprint: {
|
||||
expoUpdatesRuntimeFingerprint: {
|
||||
fingerprintSources: object[];
|
||||
isDebugFingerprintSource: boolean;
|
||||
} | null;
|
||||
fingerprintHash: string | null;
|
||||
expoUpdatesRuntimeFingerprintHash: string | null;
|
||||
}> {
|
||||
Log.debug('Using expo-updates runtimeversion:resolve CLI for runtime version resolution');
|
||||
|
||||
@@ -47,13 +47,13 @@ export async function resolveRuntimeVersionUsingCLIAsync({
|
||||
|
||||
return {
|
||||
runtimeVersion: runtimeVersionResult.runtimeVersion ?? null,
|
||||
fingerprint: runtimeVersionResult.fingerprintSources
|
||||
expoUpdatesRuntimeFingerprint: runtimeVersionResult.fingerprintSources
|
||||
? {
|
||||
fingerprintSources: runtimeVersionResult.fingerprintSources,
|
||||
isDebugFingerprintSource: useDebugFingerprintSource,
|
||||
}
|
||||
: null,
|
||||
fingerprintHash: runtimeVersionResult.fingerprintSources
|
||||
expoUpdatesRuntimeFingerprintHash: runtimeVersionResult.fingerprintSources
|
||||
? runtimeVersionResult.runtimeVersion
|
||||
: null,
|
||||
};
|
||||
@@ -75,19 +75,19 @@ export async function resolveRuntimeVersionAsync({
|
||||
cwd?: string;
|
||||
}): Promise<{
|
||||
runtimeVersion: string | null;
|
||||
fingerprint: {
|
||||
expoUpdatesRuntimeFingerprint: {
|
||||
fingerprintSources: object[];
|
||||
isDebugFingerprintSource: boolean;
|
||||
} | null;
|
||||
fingerprintHash: string | null;
|
||||
expoUpdatesRuntimeFingerprintHash: string | null;
|
||||
} | null> {
|
||||
if (!(await isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync(projectDir))) {
|
||||
// fall back to the previous behavior (using the @expo/config-plugins eas-cli dependency rather
|
||||
// than the versioned @expo/config-plugins dependency in the project)
|
||||
return {
|
||||
runtimeVersion: await Updates.getRuntimeVersionNullableAsync(projectDir, exp, platform),
|
||||
fingerprint: null,
|
||||
fingerprintHash: null,
|
||||
expoUpdatesRuntimeFingerprint: null,
|
||||
expoUpdatesRuntimeFingerprintHash: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { AccountUploadSessionType } from '../graphql/generated';
|
||||
import { uploadAccountScopedFileAtPathToGCSAsync } from '../uploads';
|
||||
import { formatBytes } from '../utils/files';
|
||||
import { createProgressTracker } from '../utils/progress';
|
||||
|
||||
/**
|
||||
* Uploads the `eas.json` file to GCS as account-scoped object.
|
||||
* Used in workflows. Takes care of logging progress.
|
||||
*/
|
||||
export async function uploadAccountScopedEasJsonAsync({
|
||||
graphqlClient,
|
||||
accountId,
|
||||
projectDir,
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
accountId: string;
|
||||
projectDir: string;
|
||||
}): Promise<{ easJsonBucketKey: string }> {
|
||||
const easJsonFilePath = path.join(projectDir, 'eas.json');
|
||||
|
||||
const easJsonFileStat = await fs.promises.stat(easJsonFilePath);
|
||||
|
||||
if (easJsonFileStat.size > 1024 * 1024) {
|
||||
throw new Error('eas.json is too big. Maximum allowed size is 1MB.');
|
||||
}
|
||||
|
||||
const easJsonBucketKey = await uploadAccountScopedFileAtPathToGCSAsync(graphqlClient, {
|
||||
accountId,
|
||||
type: AccountUploadSessionType.WorkflowsProjectSources,
|
||||
path: easJsonFilePath,
|
||||
handleProgressEvent: createProgressTracker({
|
||||
total: easJsonFileStat.size,
|
||||
message: ratio =>
|
||||
`Uploading eas.json to EAS (${formatBytes(easJsonFileStat.size * ratio)} / ${formatBytes(
|
||||
easJsonFileStat.size
|
||||
)})`,
|
||||
completedMessage: (duration: string) => `Uploaded eas.json to EAS ${chalk.dim(duration)}`,
|
||||
}),
|
||||
});
|
||||
|
||||
return { easJsonBucketKey };
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import chalk from 'chalk';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { AccountUploadSessionType } from '../graphql/generated';
|
||||
import { uploadAccountScopedFileAtPathToGCSAsync } from '../uploads';
|
||||
import { formatBytes } from '../utils/files';
|
||||
import { createProgressTracker } from '../utils/progress';
|
||||
|
||||
/**
|
||||
* Uploads a file to GCS as account-scoped object.
|
||||
* Used in workflows. Takes care of logging progress.
|
||||
* (Uses file name when mentioning file in logs.)
|
||||
*/
|
||||
export async function uploadAccountScopedFileAsync({
|
||||
graphqlClient,
|
||||
accountId,
|
||||
filePath,
|
||||
maxSizeBytes,
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
accountId: string;
|
||||
filePath: string;
|
||||
maxSizeBytes: number;
|
||||
}): Promise<{ fileBucketKey: string }> {
|
||||
const fileName = path.basename(filePath);
|
||||
const fileStat = await fs.promises.stat(filePath);
|
||||
|
||||
if (fileStat.size > maxSizeBytes) {
|
||||
throw new Error(`File is too big. Maximum allowed size is ${formatBytes(maxSizeBytes)}.`);
|
||||
}
|
||||
|
||||
const fileBucketKey = await uploadAccountScopedFileAtPathToGCSAsync(graphqlClient, {
|
||||
accountId,
|
||||
type: AccountUploadSessionType.WorkflowsProjectSources,
|
||||
path: filePath,
|
||||
handleProgressEvent: createProgressTracker({
|
||||
total: fileStat.size,
|
||||
message: ratio =>
|
||||
`Uploading ${fileName} to EAS (${formatBytes(fileStat.size * ratio)} / ${formatBytes(
|
||||
fileStat.size
|
||||
)})`,
|
||||
completedMessage: (duration: string) => `Uploaded ${fileName} to EAS ${chalk.dim(duration)}`,
|
||||
}),
|
||||
});
|
||||
|
||||
return { fileBucketKey };
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
import chalk from 'chalk';
|
||||
import fs from 'node:fs';
|
||||
|
||||
import { makeProjectTarballAsync } from '../build/utils/repository';
|
||||
import {
|
||||
assertProjectTarballSizeDoesNotExceedLimit,
|
||||
makeProjectTarballAsync,
|
||||
maybeWarnAboutProjectTarballSize,
|
||||
} from '../build/utils/repository';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { AccountUploadSessionType } from '../graphql/generated';
|
||||
import Log, { learnMore } from '../log';
|
||||
@@ -36,19 +40,8 @@ export async function uploadAccountScopedProjectSourceAsync({
|
||||
const projectTarball = await makeProjectTarballAsync(vcsClient);
|
||||
projectTarballPath = projectTarball.path;
|
||||
|
||||
if (projectTarball.size > 1024 * 1024 * 100) {
|
||||
Log.warn(
|
||||
`Your project archive is ${formatBytes(
|
||||
projectTarball.size
|
||||
)}. You can reduce its size and the time it takes to upload by excluding files that are unnecessary for the build process in ${chalk.bold(
|
||||
'.easignore'
|
||||
)} file. ${learnMore('https://expo.fyi/eas-build-archive')}`
|
||||
);
|
||||
}
|
||||
|
||||
if (projectTarball.size > 2 * 1024 * 1024 * 1024) {
|
||||
throw new Error('Project archive is too big. Maximum allowed size is 2GB.');
|
||||
}
|
||||
maybeWarnAboutProjectTarballSize(projectTarball.size);
|
||||
assertProjectTarballSizeDoesNotExceedLimit(projectTarball.size);
|
||||
|
||||
const projectArchiveBucketKey = await uploadAccountScopedFileAtPathToGCSAsync(graphqlClient, {
|
||||
accountId,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import path from 'path';
|
||||
|
||||
import { runAppOnAndroidEmulatorAsync } from './android/run';
|
||||
import { runAppOnIosSimulatorAsync } from './ios/run';
|
||||
import { AppPlatform } from '../graphql/generated';
|
||||
import { getEasBuildRunCacheDirectoryPath } from '../utils/paths';
|
||||
|
||||
export interface RunArchiveFlags {
|
||||
latest?: boolean;
|
||||
@@ -19,3 +22,14 @@ export async function runAsync(
|
||||
await runAppOnAndroidEmulatorAsync(simulatorBuildPath);
|
||||
}
|
||||
}
|
||||
|
||||
export function getEasBuildRunCachedAppPath(
|
||||
projectId: string,
|
||||
buildId: string,
|
||||
platform: AppPlatform
|
||||
): string {
|
||||
return path.join(
|
||||
getEasBuildRunCacheDirectoryPath(),
|
||||
`${projectId}_${buildId}.${platform === AppPlatform.Ios ? 'app' : 'apk'}`
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { App, RequestContext, Session, User } from '@expo/apple-utils';
|
||||
import { RequestContext, Session, User } from '@expo/apple-utils';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { sanitizeLanguage } from './utils/language';
|
||||
import { getRequestContext } from '../../credentials/ios/appstore/authenticate';
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
ensureAppExistsAsync,
|
||||
ensureBundleIdExistsWithNameAsync,
|
||||
} from '../../credentials/ios/appstore/ensureAppExists';
|
||||
import { ensureTestFlightGroupExistsAsync } from '../../credentials/ios/appstore/ensureTestFlightGroup';
|
||||
import Log from '../../log';
|
||||
import { getBundleIdentifierAsync } from '../../project/ios/bundleIdentifier';
|
||||
import { promptAsync } from '../../prompts';
|
||||
@@ -85,37 +85,22 @@ async function createAppStoreConnectAppAsync(
|
||||
);
|
||||
}
|
||||
|
||||
let app: App | null = null;
|
||||
const app = await ensureAppExistsAsync(userAuthCtx, {
|
||||
name: appName,
|
||||
language,
|
||||
companyName,
|
||||
bundleIdentifier: bundleId,
|
||||
sku,
|
||||
});
|
||||
|
||||
try {
|
||||
app = await ensureAppExistsAsync(userAuthCtx, {
|
||||
name: appName,
|
||||
language,
|
||||
companyName,
|
||||
bundleIdentifier: bundleId,
|
||||
sku,
|
||||
});
|
||||
await ensureTestFlightGroupExistsAsync(app);
|
||||
} catch (error: any) {
|
||||
if (
|
||||
// Name is invalid
|
||||
error.message.match(
|
||||
/App Name contains certain Unicode(.*)characters that are not permitted/
|
||||
) ||
|
||||
// UnexpectedAppleResponse: An attribute value has invalid characters. - App Name contains certain Unicode symbols, emoticons, diacritics, special characters, or private use characters that are not permitted.
|
||||
// Name is taken
|
||||
error.message.match(/The App Name you entered is already being used/)
|
||||
// UnexpectedAppleResponse: The provided entity includes an attribute with a value that has already been used on a different account. - The App Name you entered is already being used. If you have trademark rights to
|
||||
// this name and would like it released for your use, submit a claim.
|
||||
) {
|
||||
Log.addNewLineIfNone();
|
||||
Log.warn(
|
||||
`Change the name in your app config, or use a custom name with the ${chalk.bold(
|
||||
'--app-name'
|
||||
)} flag`
|
||||
);
|
||||
Log.newLine();
|
||||
}
|
||||
throw error;
|
||||
// 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 {
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('abridgedDiff', () => {
|
||||
abridgedDiff(str1, str2, 1);
|
||||
|
||||
const expectedOutput = [
|
||||
'cyan(@@ -2,1 +2,1 @@)',
|
||||
'cyan(@@ -1,3 +1,3 @@)',
|
||||
' gray(Line1)',
|
||||
'red(-Line2)',
|
||||
'green(+LineX)',
|
||||
@@ -106,7 +106,7 @@ describe('abridgedDiff', () => {
|
||||
abridgedDiff(str1, str2, 1);
|
||||
|
||||
const expectedOutput = [
|
||||
'cyan(@@ -3,1 +3,1 @@)',
|
||||
'cyan(@@ -2,3 +2,3 @@)',
|
||||
' gray(Line1)',
|
||||
'red(-Line2)',
|
||||
'green(+LineX)',
|
||||
|
||||
@@ -7,11 +7,12 @@ import Log from '../log';
|
||||
import { ora } from '../ora';
|
||||
|
||||
export type FingerprintOptions = {
|
||||
workflow: Workflow;
|
||||
workflow?: Workflow;
|
||||
platforms: string[];
|
||||
debug?: boolean;
|
||||
env: Env | undefined;
|
||||
cwd?: string;
|
||||
ignorePaths?: string[];
|
||||
};
|
||||
|
||||
export function diffFingerprint(
|
||||
@@ -85,12 +86,20 @@ async function createFingerprintWithoutLoggingAsync(
|
||||
> {
|
||||
const Fingerprint = require(fingerprintPath);
|
||||
const fingerprintOptions: Record<string, any> = {};
|
||||
const ignorePaths = [];
|
||||
if (options.workflow === Workflow.MANAGED) {
|
||||
ignorePaths.push('android/**/*');
|
||||
ignorePaths.push('ios/**/*');
|
||||
}
|
||||
if (options.ignorePaths) {
|
||||
ignorePaths.push(...options.ignorePaths);
|
||||
}
|
||||
if (ignorePaths.length > 0) {
|
||||
fingerprintOptions.ignorePaths = ignorePaths;
|
||||
}
|
||||
if (options.platforms) {
|
||||
fingerprintOptions.platforms = [...options.platforms];
|
||||
}
|
||||
if (options.workflow === Workflow.MANAGED) {
|
||||
fingerprintOptions.ignorePaths = ['android/**/*', 'ios/**/*'];
|
||||
}
|
||||
if (options.debug) {
|
||||
fingerprintOptions.debug = true;
|
||||
}
|
||||
|
||||
@@ -52,8 +52,13 @@ export function abridgedDiff(str1: string, str2: string, contextLines: number =
|
||||
|
||||
const flushChunk = (): void => {
|
||||
if (currentChunk.length > 0) {
|
||||
const originalRange = `${startOriginal},${removedLines || 0}`;
|
||||
const modifiedRange = `${startModified},${addedLines || 0}`;
|
||||
const contextLines = currentChunkPriorContext.length + currentChunkAfterContext.length;
|
||||
const originalRange = `${(startOriginal ?? 1) - currentChunkPriorContext.length},${
|
||||
contextLines + removedLines
|
||||
}`;
|
||||
const modifiedRange = `${(startModified ?? 1) - currentChunkPriorContext.length},${
|
||||
contextLines + addedLines
|
||||
}`;
|
||||
// `git diff` style header
|
||||
output.push(chalk.cyan(`@@ -${originalRange} +${modifiedRange} @@`));
|
||||
output.push(...currentChunkPriorContext);
|
||||
|
||||
@@ -10,6 +10,10 @@ export default function formatFields(
|
||||
fields: FormatFieldsItem[],
|
||||
options: FormatFieldsOptions = { labelFormat: chalk.dim }
|
||||
): string {
|
||||
if (fields.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const columnWidth = fields.reduce((a, b) => (a.label.length > b.label.length ? a : b)).label
|
||||
.length;
|
||||
|
||||
|
||||
@@ -42,13 +42,13 @@ function warnAboutServiceOutage(service: StatuspageServiceFragment): void {
|
||||
|
||||
if (service.incidents.length > 0) {
|
||||
const [currentIncident] = service.incidents;
|
||||
Log.warn(`Reason: ${currentIncident.name}`);
|
||||
Log.warn(`Reason: ${currentIncident.name}.`);
|
||||
}
|
||||
|
||||
Log.warn(
|
||||
`All information on service status and incidents available at ${link(
|
||||
'https://status.expo.dev/'
|
||||
)}.`
|
||||
)}`
|
||||
);
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
@@ -18,8 +18,7 @@ describe(Ignore, () => {
|
||||
'/root'
|
||||
);
|
||||
|
||||
const ignore = new Ignore('/root');
|
||||
await ignore.initIgnoreAsync();
|
||||
const ignore = await Ignore.createAsync('/root');
|
||||
expect(ignore.ignores('aaa')).toBe(true);
|
||||
expect(ignore.ignores('bbb')).toBe(false);
|
||||
expect(ignore.ignores('dir/aaa')).toBe(true);
|
||||
@@ -36,8 +35,7 @@ describe(Ignore, () => {
|
||||
'/root'
|
||||
);
|
||||
|
||||
const ignore = new Ignore('/root');
|
||||
await ignore.initIgnoreAsync();
|
||||
const ignore = await Ignore.createAsync('/root');
|
||||
expect(ignore.ignores('aaa')).toBe(false);
|
||||
expect(ignore.ignores('bbb')).toBe(false);
|
||||
expect(ignore.ignores('ccc')).toBe(true);
|
||||
@@ -54,8 +52,7 @@ describe(Ignore, () => {
|
||||
'/root'
|
||||
);
|
||||
|
||||
const ignore = new Ignore('/root');
|
||||
await ignore.initIgnoreAsync();
|
||||
const ignore = await Ignore.createAsync('/root');
|
||||
expect(ignore.ignores('aaa')).toBe(true);
|
||||
expect(ignore.ignores('bbb')).toBe(false);
|
||||
expect(ignore.ignores('node_modules/aaa')).toBe(true);
|
||||
@@ -72,16 +69,14 @@ describe(Ignore, () => {
|
||||
'/root'
|
||||
);
|
||||
|
||||
const ignore = new Ignore('/root');
|
||||
await ignore.initIgnoreAsync();
|
||||
const ignore = await Ignore.createAsync('/root');
|
||||
expect(ignore.ignores('dir/ccc')).toBe(true);
|
||||
});
|
||||
|
||||
it('ignores .git', async () => {
|
||||
vol.fromJSON({}, '/root');
|
||||
|
||||
const ignore = new Ignore('/root');
|
||||
await ignore.initIgnoreAsync();
|
||||
const ignore = await Ignore.createAsync('/root');
|
||||
expect(ignore.ignores('.git')).toBe(true);
|
||||
});
|
||||
|
||||
@@ -93,8 +88,7 @@ describe(Ignore, () => {
|
||||
'/root'
|
||||
);
|
||||
|
||||
const ignore = new Ignore('/root');
|
||||
await ignore.initIgnoreAsync();
|
||||
const ignore = await Ignore.createAsync('/root');
|
||||
expect(() => ignore.ignores('dir/test')).not.toThrowError();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,208 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import fs from 'fs/promises';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import GitClient from '../git';
|
||||
|
||||
describe('git', () => {
|
||||
describe('GitClient that does not require a commit', () => {
|
||||
let vcs: GitClient;
|
||||
let repoRoot: string;
|
||||
|
||||
afterAll(async () => {
|
||||
await fs.rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
|
||||
await spawnAsync('git', ['init'], { cwd: repoRoot });
|
||||
vcs = new GitClient({
|
||||
requireCommit: false,
|
||||
maybeCwdOverride: repoRoot,
|
||||
});
|
||||
});
|
||||
|
||||
describe('isFileIgnoredAsync', () => {
|
||||
const testFiles = [
|
||||
{ commit: true, gitignore: true, easignore: true },
|
||||
{ commit: true, gitignore: true, easignore: false },
|
||||
{ commit: true, gitignore: false, easignore: true },
|
||||
{ commit: true, gitignore: false, easignore: false },
|
||||
{ commit: false, gitignore: true, easignore: true },
|
||||
{ commit: false, gitignore: true, easignore: false },
|
||||
{ commit: false, gitignore: false, easignore: true },
|
||||
{ commit: false, gitignore: false, easignore: false },
|
||||
].map(combo => {
|
||||
const filename = `${combo.commit ? 'tracked' : 'new'}-${
|
||||
combo.gitignore ? 'gitignored' : 'gitnonignored'
|
||||
}-${combo.easignore ? 'easignored' : 'easnonignored'}.txt`;
|
||||
return { ...combo, filename };
|
||||
});
|
||||
|
||||
async function setupTestFiles(): Promise<void> {
|
||||
await Promise.all(
|
||||
testFiles.map(async file => {
|
||||
const content = `File that is ${Object.values(file).join(', ')}`;
|
||||
await fs.writeFile(`${repoRoot}/${file.filename}`, content);
|
||||
})
|
||||
);
|
||||
|
||||
// Commit the "committed" files
|
||||
await spawnAsync('git', ['add', '*tracked*.txt'], { cwd: repoRoot });
|
||||
await spawnAsync('git', ['commit', '-m', 'test setup'], { cwd: repoRoot });
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupTestFiles();
|
||||
});
|
||||
|
||||
describe('with only .easignore', () => {
|
||||
beforeAll(async () => {
|
||||
await fs.writeFile(`${repoRoot}/.easignore`, '*easignored*\n');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await fs.rm(`${repoRoot}/.easignore`);
|
||||
});
|
||||
|
||||
it.each(testFiles.filter(file => file.easignore))(
|
||||
'$filename should be ignored',
|
||||
async file => {
|
||||
expect(await vcs.isFileIgnoredAsync(file.filename)).toBe(true);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(testFiles.filter(file => !file.easignore))(
|
||||
'$filename should not be ignored',
|
||||
async file => {
|
||||
expect(await vcs.isFileIgnoredAsync(file.filename)).toBe(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('with only .gitignore', () => {
|
||||
beforeAll(async () => {
|
||||
await fs.writeFile(`${repoRoot}/.gitignore`, '*gitignored*\n');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await fs.rm(`${repoRoot}/.gitignore`);
|
||||
});
|
||||
|
||||
it.each(testFiles.filter(file => file.gitignore && !file.commit))(
|
||||
'$filename should be ignored',
|
||||
async file => {
|
||||
expect(await vcs.isFileIgnoredAsync(file.filename)).toBe(true);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(testFiles.filter(file => !file.gitignore || file.commit))(
|
||||
'$filename should not be ignored',
|
||||
async file => {
|
||||
expect(await vcs.isFileIgnoredAsync(file.filename)).toBe(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('with both .gitignore and .easignore', () => {
|
||||
beforeAll(async () => {
|
||||
await fs.writeFile(`${repoRoot}/.gitignore`, '*gitignored*\n');
|
||||
await fs.writeFile(`${repoRoot}/.easignore`, '*easignored*\n');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await fs.rm(`${repoRoot}/.gitignore`);
|
||||
await fs.rm(`${repoRoot}/.easignore`);
|
||||
});
|
||||
|
||||
it.each(testFiles.filter(file => file.easignore))(
|
||||
'$filename should be ignored',
|
||||
async file => {
|
||||
expect(await vcs.isFileIgnoredAsync(file.filename)).toBe(true);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(testFiles.filter(file => !file.easignore))(
|
||||
'$filename should not be ignored',
|
||||
async file => {
|
||||
expect(await vcs.isFileIgnoredAsync(file.filename)).toBe(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('is able to delete a submodule ignored by .easignore', async () => {
|
||||
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
|
||||
await spawnAsync('git', ['init'], { cwd: repoRoot });
|
||||
const vcs = new GitClient({
|
||||
requireCommit: false,
|
||||
maybeCwdOverride: repoRoot,
|
||||
});
|
||||
|
||||
await spawnAsync(
|
||||
'git',
|
||||
['submodule', 'add', 'https://github.com/expo/results.git', 'results'],
|
||||
{ cwd: repoRoot }
|
||||
);
|
||||
await spawnAsync('git', ['add', 'results'], { cwd: repoRoot });
|
||||
await spawnAsync('git', ['commit', '-m', 'add submodule'], { cwd: repoRoot });
|
||||
|
||||
const repoCloneNonIgnored = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
|
||||
await expect(vcs.makeShallowCopyAsync(repoCloneNonIgnored)).resolves.not.toThrow();
|
||||
await expect(fs.stat(path.join(repoCloneNonIgnored, 'results'))).resolves.not.toThrow();
|
||||
|
||||
await fs.writeFile(`${repoRoot}/.easignore`, 'results');
|
||||
const repoCloneIgnored = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
|
||||
await expect(vcs.makeShallowCopyAsync(repoCloneIgnored)).resolves.not.toThrow();
|
||||
await expect(fs.stat(path.join(repoCloneIgnored, 'results'))).rejects.toThrow('ENOENT');
|
||||
});
|
||||
});
|
||||
|
||||
it('does not include files that have been removed in the working directory', async () => {
|
||||
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
|
||||
await spawnAsync('git', ['init'], { cwd: repoRoot });
|
||||
const vcs = new GitClient({
|
||||
requireCommit: false,
|
||||
maybeCwdOverride: repoRoot,
|
||||
});
|
||||
|
||||
await fs.writeFile(`${repoRoot}/committed-file.txt`, 'file');
|
||||
await fs.writeFile(`${repoRoot}/file-to-remove.txt`, 'file');
|
||||
await spawnAsync('git', ['add', 'committed-file.txt', 'file-to-remove.txt'], {
|
||||
cwd: repoRoot,
|
||||
});
|
||||
await spawnAsync('git', ['commit', '-m', 'add files'], { cwd: repoRoot });
|
||||
|
||||
await fs.rm(`${repoRoot}/file-to-remove.txt`);
|
||||
await spawnAsync('git', ['add', 'file-to-remove.txt'], { cwd: repoRoot });
|
||||
await spawnAsync('git', ['commit', '-m', 'remove file'], { cwd: repoRoot });
|
||||
|
||||
await fs.writeFile(`${repoRoot}/new-file.txt`, 'file');
|
||||
await fs.writeFile(`${repoRoot}/new-tracked-file.txt`, 'file');
|
||||
|
||||
const repoClone = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
|
||||
await expect(vcs.makeShallowCopyAsync(repoClone)).resolves.not.toThrow();
|
||||
await expect(fs.stat(path.join(repoClone, 'file-to-remove.txt'))).rejects.toThrow('ENOENT');
|
||||
await expect(fs.stat(path.join(repoClone, 'committed-file.txt'))).resolves.not.toThrow();
|
||||
await expect(fs.stat(path.join(repoClone, 'new-file.txt'))).resolves.not.toThrow();
|
||||
await expect(fs.stat(path.join(repoClone, 'new-tracked-file.txt'))).resolves.not.toThrow();
|
||||
|
||||
vcs.requireCommit = true;
|
||||
await spawnAsync('git', ['add', '.'], { cwd: repoRoot });
|
||||
await spawnAsync('git', ['commit', '-m', 'tmp commit'], { cwd: repoRoot });
|
||||
|
||||
const requireCommitClone = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
|
||||
await expect(vcs.makeShallowCopyAsync(requireCommitClone)).resolves.not.toThrow();
|
||||
await expect(fs.stat(path.join(requireCommitClone, 'file-to-remove.txt'))).rejects.toThrow(
|
||||
'ENOENT'
|
||||
);
|
||||
await expect(
|
||||
fs.stat(path.join(requireCommitClone, 'committed-file.txt'))
|
||||
).resolves.not.toThrow();
|
||||
await expect(fs.stat(path.join(requireCommitClone, 'new-file.txt'))).resolves.not.toThrow();
|
||||
await expect(
|
||||
fs.stat(path.join(requireCommitClone, 'new-tracked-file.txt'))
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import * as PackageManagerUtils from '@expo/package-manager';
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import { Errors } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import Log, { learnMore } from '../../log';
|
||||
@@ -14,11 +15,17 @@ import {
|
||||
gitStatusAsync,
|
||||
isGitInstalledAsync,
|
||||
} from '../git';
|
||||
import { EASIGNORE_FILENAME, Ignore, makeShallowCopyAsync } from '../local';
|
||||
import { Client } from '../vcs';
|
||||
|
||||
export default class GitClient extends Client {
|
||||
constructor(private readonly maybeCwdOverride?: string) {
|
||||
private readonly maybeCwdOverride?: string;
|
||||
public requireCommit: boolean;
|
||||
|
||||
constructor(options: { maybeCwdOverride?: string; requireCommit: boolean }) {
|
||||
super();
|
||||
this.maybeCwdOverride = options.maybeCwdOverride;
|
||||
this.requireCommit = options.requireCommit;
|
||||
}
|
||||
|
||||
public override async ensureRepoExistsAsync(): Promise<void> {
|
||||
@@ -119,10 +126,6 @@ export default class GitClient extends Client {
|
||||
}
|
||||
}
|
||||
|
||||
public override async isCommitRequiredAsync(): Promise<boolean> {
|
||||
return await this.hasUncommittedChangesAsync();
|
||||
}
|
||||
|
||||
public override async showChangedFilesAsync(): Promise<void> {
|
||||
const gitStatusOutput = await gitStatusAsync({
|
||||
showUntracked: true,
|
||||
@@ -144,49 +147,120 @@ export default class GitClient extends Client {
|
||||
).stdout.trim();
|
||||
}
|
||||
|
||||
public override async isCommitRequiredAsync(): Promise<boolean> {
|
||||
if (!this.requireCommit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await this.hasUncommittedChangesAsync();
|
||||
}
|
||||
|
||||
public async makeShallowCopyAsync(destinationPath: string): Promise<void> {
|
||||
if (await this.hasUncommittedChangesAsync()) {
|
||||
if (await this.isCommitRequiredAsync()) {
|
||||
// it should already be checked before this function is called, but in case it wasn't
|
||||
// we want to ensure that any changes were introduced by call to `setGitCaseSensitivityAsync`
|
||||
throw new Error('You have some uncommitted changes in your repository.');
|
||||
}
|
||||
|
||||
const rootPath = await this.getRootPathAsync();
|
||||
|
||||
let gitRepoUri;
|
||||
if (process.platform === 'win32') {
|
||||
// getRootDirectoryAsync() will return C:/path/to/repo on Windows and path
|
||||
// prefix should be file:///
|
||||
gitRepoUri = `file:///${await this.getRootPathAsync()}`;
|
||||
gitRepoUri = `file:///${rootPath}`;
|
||||
} else {
|
||||
// getRootDirectoryAsync() will /path/to/repo, and path prefix should be
|
||||
// file:/// so only file:// needs to be prepended
|
||||
gitRepoUri = `file://${await this.getRootPathAsync()}`;
|
||||
gitRepoUri = `file://${rootPath}`;
|
||||
}
|
||||
const isCaseSensitive = await isGitCaseSensitiveAsync(this.maybeCwdOverride);
|
||||
await setGitCaseSensitivityAsync(true, this.maybeCwdOverride);
|
||||
|
||||
await assertEnablingGitCaseSensitivityDoesNotCauseNewUncommittedChangesAsync(rootPath);
|
||||
|
||||
const isCaseSensitive = await isGitCaseSensitiveAsync(rootPath);
|
||||
try {
|
||||
if (await this.hasUncommittedChangesAsync()) {
|
||||
Log.error('Detected inconsistent filename casing between your local filesystem and git.');
|
||||
Log.error('This will likely cause your build to fail. Impacted files:');
|
||||
await spawnAsync('git', ['status', '--short'], {
|
||||
stdio: 'inherit',
|
||||
cwd: this.maybeCwdOverride,
|
||||
});
|
||||
Log.newLine();
|
||||
Log.error(
|
||||
`Error: Resolve filename casing inconsistencies before proceeding. ${learnMore(
|
||||
'https://expo.fyi/macos-ignorecase'
|
||||
)}`
|
||||
);
|
||||
throw new Error('You have some uncommitted changes in your repository.');
|
||||
}
|
||||
await setGitCaseSensitivityAsync(true, rootPath);
|
||||
await spawnAsync(
|
||||
'git',
|
||||
['clone', '--no-hardlinks', '--depth', '1', gitRepoUri, destinationPath],
|
||||
{
|
||||
cwd: this.maybeCwdOverride,
|
||||
}
|
||||
[
|
||||
'clone',
|
||||
// If we do not require a commit, we are going to later
|
||||
// copy the working directory into the destination path,
|
||||
// so we can skip the checkout step (which also adds files
|
||||
// that have been removed in the working directory).
|
||||
this.requireCommit ? null : '--no-checkout',
|
||||
'--no-hardlinks',
|
||||
'--depth',
|
||||
'1',
|
||||
gitRepoUri,
|
||||
destinationPath,
|
||||
].flatMap(e => e ?? []),
|
||||
{ cwd: rootPath }
|
||||
);
|
||||
|
||||
const sourceEasignorePath = path.join(rootPath, EASIGNORE_FILENAME);
|
||||
if (await fs.exists(sourceEasignorePath)) {
|
||||
Log.debug('.easignore exists, deleting files that should be ignored', {
|
||||
sourceEasignorePath,
|
||||
});
|
||||
|
||||
const cachedFilesWeShouldHaveIgnored = (
|
||||
await spawnAsync(
|
||||
'git',
|
||||
[
|
||||
'ls-files',
|
||||
'--exclude-from',
|
||||
sourceEasignorePath,
|
||||
// `--ignored --cached` makes git print files that should be
|
||||
// ignored by rules from `--exclude-from`, but instead are currently cached.
|
||||
'--ignored',
|
||||
'--cached',
|
||||
// separates file names with null characters
|
||||
'-z',
|
||||
],
|
||||
{ cwd: destinationPath }
|
||||
)
|
||||
).stdout
|
||||
.split('\0')
|
||||
// ls-files' output is terminated by a null character
|
||||
.filter(file => file !== '');
|
||||
|
||||
Log.debug('cachedFilesWeShouldHaveIgnored', {
|
||||
cachedFilesWeShouldHaveIgnored,
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
cachedFilesWeShouldHaveIgnored.map(file =>
|
||||
// `ls-files` does not go over files within submodules. If submodule is
|
||||
// ignored, it is listed as a single path, so we need to `rm -rf` it.
|
||||
fs.rm(path.join(destinationPath, file), { recursive: true, force: true })
|
||||
)
|
||||
);
|
||||
|
||||
// Special-case `.git` which `git ls-files` will never consider ignored.
|
||||
const ignore = await Ignore.createAsync(rootPath);
|
||||
if (ignore.ignores('.git')) {
|
||||
await fs.rm(path.join(destinationPath, '.git'), { recursive: true, force: true });
|
||||
Log.debug('deleted .git', {
|
||||
destinationPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await setGitCaseSensitivityAsync(isCaseSensitive, this.maybeCwdOverride);
|
||||
await setGitCaseSensitivityAsync(isCaseSensitive, rootPath);
|
||||
}
|
||||
|
||||
if (!this.requireCommit) {
|
||||
Log.debug('making shallow copy', { requireCommit: this.requireCommit });
|
||||
// After we create the shallow Git copy, we copy the files
|
||||
// again. This way we include the changed and untracked files
|
||||
// (`git clone` only copies the committed changes).
|
||||
//
|
||||
// We only do this if `requireCommit` is false because `requireCommit: true`
|
||||
// setups expect no changes in files (e.g. locked files should remain locked).
|
||||
await makeShallowCopyAsync(rootPath, destinationPath);
|
||||
} else {
|
||||
Log.debug('not making shallow copy', { requireCommit: this.requireCommit });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,11 +325,47 @@ export default class GitClient extends Client {
|
||||
);
|
||||
}
|
||||
|
||||
/** NOTE: This method does not support checking whether `.git` is ignored by `.easignore` rules. */
|
||||
public override async isFileIgnoredAsync(filePath: string): Promise<boolean> {
|
||||
const rootPath = await this.getRootPathAsync();
|
||||
|
||||
let isTracked: boolean;
|
||||
try {
|
||||
await spawnAsync('git', ['check-ignore', '-q', filePath], {
|
||||
cwd: this.maybeCwdOverride ?? path.normalize(await this.getRootPathAsync()),
|
||||
await spawnAsync('git', ['ls-files', '--error-unmatch', filePath], {
|
||||
cwd: rootPath,
|
||||
});
|
||||
isTracked = true;
|
||||
} catch {
|
||||
isTracked = false;
|
||||
}
|
||||
|
||||
const easIgnorePath = path.join(rootPath, EASIGNORE_FILENAME);
|
||||
if (await fs.exists(easIgnorePath)) {
|
||||
const ignore = await Ignore.createAsync(rootPath);
|
||||
const wouldNotBeCopiedToClone = ignore.ignores(filePath);
|
||||
const wouldBeDeletedFromClone =
|
||||
(
|
||||
await spawnAsync(
|
||||
'git',
|
||||
['ls-files', '--exclude-from', easIgnorePath, '--ignored', '--cached', filePath],
|
||||
{ cwd: rootPath }
|
||||
)
|
||||
).stdout.trim() !== '';
|
||||
|
||||
// File is considered ignored if:
|
||||
// - makeShallowCopyAsync() will not copy it to the clone
|
||||
// AND
|
||||
// - it will not be copied to the clone because it's not tracked
|
||||
// - or it will get copied to the clone, but then will be deleted by .easignore rules
|
||||
return wouldNotBeCopiedToClone && (!isTracked || wouldBeDeletedFromClone);
|
||||
}
|
||||
|
||||
if (isTracked) {
|
||||
return false; // Tracked files aren't ignored even if they match ignore patterns
|
||||
}
|
||||
|
||||
try {
|
||||
await spawnAsync('git', ['check-ignore', '-q', filePath], { cwd: rootPath });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
@@ -386,3 +496,51 @@ async function setGitCaseSensitivityAsync(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function assertEnablingGitCaseSensitivityDoesNotCauseNewUncommittedChangesAsync(
|
||||
cwd: string
|
||||
): Promise<void> {
|
||||
// Remember uncommited changes before case sensitivity change
|
||||
// for later comparison so we log to the user only the files
|
||||
// that were marked as changed after the case sensitivity change.
|
||||
const uncommittedChangesBeforeCaseSensitivityChange = await gitStatusAsync({
|
||||
showUntracked: true,
|
||||
cwd,
|
||||
});
|
||||
|
||||
const isCaseSensitive = await isGitCaseSensitiveAsync(cwd);
|
||||
await setGitCaseSensitivityAsync(true, cwd);
|
||||
try {
|
||||
const uncommitedChangesAfterCaseSensitivityChange = await gitStatusAsync({
|
||||
showUntracked: true,
|
||||
cwd,
|
||||
});
|
||||
|
||||
if (
|
||||
uncommitedChangesAfterCaseSensitivityChange !== uncommittedChangesBeforeCaseSensitivityChange
|
||||
) {
|
||||
const baseUncommitedChangesSet = new Set(
|
||||
uncommittedChangesBeforeCaseSensitivityChange.split('\n')
|
||||
);
|
||||
|
||||
const errorMessage = [
|
||||
'Detected inconsistent filename casing between your local filesystem and git.',
|
||||
'This will likely cause your job to fail. Impacted files:',
|
||||
...uncommitedChangesAfterCaseSensitivityChange.split('\n').flatMap(changedFile => {
|
||||
// This file was changed before the case sensitivity change too.
|
||||
if (baseUncommitedChangesSet.has(changedFile)) {
|
||||
return [];
|
||||
}
|
||||
return [changedFile];
|
||||
}),
|
||||
`Resolve filename casing inconsistencies before proceeding. ${learnMore(
|
||||
'https://expo.fyi/macos-ignorecase'
|
||||
)}`,
|
||||
];
|
||||
|
||||
throw new Error(errorMessage.join('\n'));
|
||||
}
|
||||
} finally {
|
||||
await setGitCaseSensitivityAsync(isCaseSensitive, cwd);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import chalk from 'chalk';
|
||||
import path from 'path';
|
||||
|
||||
import GitClient from './git';
|
||||
import Log from '../../log';
|
||||
import { Ignore, makeShallowCopyAsync } from '../local';
|
||||
|
||||
export default class GitNoCommitClient extends GitClient {
|
||||
public override async isCommitRequiredAsync(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
public override async getRootPathAsync(): Promise<string> {
|
||||
return (await spawnAsync('git', ['rev-parse', '--show-toplevel'])).stdout.trim();
|
||||
}
|
||||
|
||||
public override async makeShallowCopyAsync(destinationPath: string): Promise<void> {
|
||||
// normalize converts C:/some/path to C:\some\path on windows
|
||||
const srcPath = path.normalize(await this.getRootPathAsync());
|
||||
await makeShallowCopyAsync(srcPath, destinationPath);
|
||||
}
|
||||
|
||||
public override async isFileIgnoredAsync(filePath: string): Promise<boolean> {
|
||||
// normalize converts C:/some/path to C:\some\path on windows
|
||||
const rootPath = path.normalize(await this.getRootPathAsync());
|
||||
const ignore = new Ignore(rootPath);
|
||||
await ignore.initIgnoreAsync();
|
||||
return ignore.ignores(filePath);
|
||||
}
|
||||
|
||||
public override async trackFileAsync(file: string): Promise<void> {
|
||||
try {
|
||||
await super.trackFileAsync(file);
|
||||
} catch {
|
||||
// In the no commit workflow it doesn't matter if we fail to track changes,
|
||||
// so we can ignore if this throws an exception
|
||||
Log.warn(
|
||||
`Unable to track ${chalk.bold(path.basename(file))} in Git. Proceeding without tracking.`
|
||||
);
|
||||
Log.warn(` Reason: the command ${chalk.bold(`"git add ${file}"`)} exited with an error.`);
|
||||
Log.newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,7 @@ export default class NoVcsClient extends Client {
|
||||
}
|
||||
|
||||
public override async isFileIgnoredAsync(filePath: string): Promise<boolean> {
|
||||
const ignore = new Ignore(getRootPath());
|
||||
await ignore.initIgnoreAsync();
|
||||
const ignore = await Ignore.createAsync(getRootPath());
|
||||
return ignore.ignores(filePath);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import GitClient from './clients/git';
|
||||
import GitNoCommitClient from './clients/gitNoCommit';
|
||||
import NoVcsClient from './clients/noVcs';
|
||||
import { Client } from './vcs';
|
||||
|
||||
const NO_VCS_WARNING = `Using EAS CLI without version control system is not recommended, use this mode only if you know what you are doing.`;
|
||||
|
||||
let wasNoVcsWarningPrinted = false;
|
||||
|
||||
export function resolveVcsClient(requireCommit: boolean = false): Client {
|
||||
if (process.env.EAS_NO_VCS) {
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
// This log might be printed before cli arguments are evaluated,
|
||||
// so it needs to go to stderr in case command is run in JSON
|
||||
// only mode.
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(chalk.yellow(NO_VCS_WARNING));
|
||||
if (!wasNoVcsWarningPrinted) {
|
||||
// This log might be printed before cli arguments are evaluated,
|
||||
// so it needs to go to stderr in case command is run in JSON
|
||||
// only mode.
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(chalk.yellow(NO_VCS_WARNING));
|
||||
wasNoVcsWarningPrinted = true;
|
||||
}
|
||||
}
|
||||
return new NoVcsClient();
|
||||
}
|
||||
if (requireCommit) {
|
||||
return new GitClient();
|
||||
}
|
||||
return new GitNoCommitClient();
|
||||
return new GitClient({ requireCommit });
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import fg from 'fast-glob';
|
||||
import fs from 'fs-extra';
|
||||
import fs from 'fs/promises';
|
||||
import fsExtra from 'fs-extra';
|
||||
import createIgnore, { Ignore as SingleFileIgnore } from 'ignore';
|
||||
import path from 'path';
|
||||
|
||||
const EASIGNORE_FILENAME = '.easignore';
|
||||
import Log from '../log';
|
||||
|
||||
export const EASIGNORE_FILENAME = '.easignore';
|
||||
const GITIGNORE_FILENAME = '.gitignore';
|
||||
|
||||
const DEFAULT_IGNORE = `
|
||||
@@ -30,17 +33,27 @@ export function getRootPath(): string {
|
||||
* - if .easignore exists, .gitignore files are not used.
|
||||
*/
|
||||
export class Ignore {
|
||||
private ignoreMapping: (readonly [string, SingleFileIgnore])[] = [];
|
||||
public ignoreMapping: (readonly [string, SingleFileIgnore])[] = [];
|
||||
|
||||
constructor(private readonly rootDir: string) {}
|
||||
private constructor(private readonly rootDir: string) {}
|
||||
|
||||
static async createAsync(rootDir: string): Promise<Ignore> {
|
||||
const ignore = new Ignore(rootDir);
|
||||
await ignore.initIgnoreAsync();
|
||||
return ignore;
|
||||
}
|
||||
|
||||
public async initIgnoreAsync(): Promise<void> {
|
||||
const easIgnorePath = path.join(this.rootDir, EASIGNORE_FILENAME);
|
||||
if (await fs.pathExists(easIgnorePath)) {
|
||||
if (await fsExtra.pathExists(easIgnorePath)) {
|
||||
this.ignoreMapping = [
|
||||
['', createIgnore().add(DEFAULT_IGNORE)],
|
||||
['', createIgnore().add(await fs.readFile(easIgnorePath, 'utf-8'))],
|
||||
['', createIgnore().add(await fsExtra.readFile(easIgnorePath, 'utf-8'))],
|
||||
];
|
||||
|
||||
Log.debug('initializing ignore mapping with .easignore', {
|
||||
ignoreMapping: this.ignoreMapping,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const ignoreFilePaths = (
|
||||
@@ -57,11 +70,16 @@ export class Ignore {
|
||||
ignoreFilePaths.map(async filePath => {
|
||||
return [
|
||||
filePath.slice(0, filePath.length - GITIGNORE_FILENAME.length),
|
||||
createIgnore().add(await fs.readFile(path.join(this.rootDir, filePath), 'utf-8')),
|
||||
createIgnore().add(await fsExtra.readFile(path.join(this.rootDir, filePath), 'utf-8')),
|
||||
] as const;
|
||||
})
|
||||
);
|
||||
this.ignoreMapping = [['', createIgnore().add(DEFAULT_IGNORE)], ...ignoreMapping];
|
||||
|
||||
Log.debug('initializing ignore mapping with .gitignore files', {
|
||||
ignoreFilePaths,
|
||||
ignoreMapping: this.ignoreMapping,
|
||||
});
|
||||
}
|
||||
|
||||
public ignores(relativePath: string): boolean {
|
||||
@@ -74,15 +92,37 @@ export class Ignore {
|
||||
}
|
||||
}
|
||||
|
||||
export async function makeShallowCopyAsync(src: string, dst: string): Promise<void> {
|
||||
const ignore = new Ignore(src);
|
||||
await ignore.initIgnoreAsync();
|
||||
await fs.copy(src, dst, {
|
||||
filter: (srcFilePath: string) => {
|
||||
export async function makeShallowCopyAsync(_src: string, dst: string): Promise<void> {
|
||||
// `node:fs` on Windows adds a namespace prefix (e.g. `\\?\`) to the path provided
|
||||
// to the `filter` function in `fs.cp`. We need to ensure that we compare the right paths
|
||||
// (both with prefix), otherwise the `relativePath` ends up being wrong and causes no files
|
||||
// to be ignored.
|
||||
const src = path.toNamespacedPath(path.normalize(_src));
|
||||
|
||||
Log.debug('makeShallowCopyAsync', { src, dst });
|
||||
const ignore = await Ignore.createAsync(src);
|
||||
Log.debug('makeShallowCopyAsync ignoreMapping', { ignoreMapping: ignore.ignoreMapping });
|
||||
|
||||
await fs.cp(src, dst, {
|
||||
recursive: true,
|
||||
// Preserve symlinks without re-resolving them to their original targets
|
||||
verbatimSymlinks: true,
|
||||
filter: (_srcFilePath: string) => {
|
||||
const srcFilePath = path.toNamespacedPath(_srcFilePath);
|
||||
|
||||
if (srcFilePath === src) {
|
||||
return true;
|
||||
}
|
||||
return !ignore.ignores(path.relative(src, srcFilePath));
|
||||
const relativePath = path.relative(src, srcFilePath);
|
||||
const shouldCopyTheItem = !ignore.ignores(relativePath);
|
||||
|
||||
Log.debug(shouldCopyTheItem ? 'copying' : 'skipping', {
|
||||
src,
|
||||
srcFilePath,
|
||||
relativePath,
|
||||
});
|
||||
|
||||
return shouldCopyTheItem;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { get as getEnv } from '@expo/env';
|
||||
import { parseProjectEnv } from '@expo/env';
|
||||
import { Gzip, GzipOptions } from 'minizlib';
|
||||
import { HashOptions, createHash, randomBytes } from 'node:crypto';
|
||||
import fs, { createWriteStream } from 'node:fs';
|
||||
@@ -98,6 +98,11 @@ export interface Manifest {
|
||||
env: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
export interface CreateManifestResult {
|
||||
conflictingVariableNames: string[] | undefined;
|
||||
manifest: Manifest;
|
||||
}
|
||||
|
||||
interface CreateManifestParams {
|
||||
projectId: string;
|
||||
projectDir: string;
|
||||
@@ -108,23 +113,32 @@ interface CreateManifestParams {
|
||||
export async function createManifestAsync(
|
||||
params: CreateManifestParams,
|
||||
graphqlClient: ExpoGraphqlClient
|
||||
): Promise<Manifest> {
|
||||
let env: Record<string, string | undefined>;
|
||||
): Promise<CreateManifestResult> {
|
||||
// Resolve .env file variables
|
||||
const { env } = parseProjectEnv(params.projectDir, { mode: 'production' });
|
||||
// Maybe load EAS Environment Variables (based on `--environment` arg)
|
||||
let conflictingVariableNames: string[] | undefined;
|
||||
if (params.environment) {
|
||||
env = Object.fromEntries(
|
||||
(
|
||||
await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(graphqlClient, {
|
||||
appId: params.projectId,
|
||||
environment: params.environment,
|
||||
})
|
||||
).map(variable => [variable.name, variable.value ?? undefined])
|
||||
const loadedVariables = await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
appId: params.projectId,
|
||||
environment: params.environment,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// NOTE: This is required for the .env resolution
|
||||
process.env.NODE_ENV = 'production';
|
||||
env = getEnv(params.projectDir).env;
|
||||
// Load EAS Env vars into `env` object, keeping track of conflicts
|
||||
conflictingVariableNames = [];
|
||||
for (const variable of loadedVariables) {
|
||||
if (variable.value != null) {
|
||||
if (env[variable.name] != null) {
|
||||
conflictingVariableNames.push(variable.name);
|
||||
}
|
||||
env[variable.name] = variable.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { env };
|
||||
const manifest: Manifest = { env };
|
||||
return { conflictingVariableNames, manifest };
|
||||
}
|
||||
|
||||
interface WorkerFileEntry {
|
||||
|
||||
@@ -4,6 +4,7 @@ import mime from 'mime';
|
||||
import { Gzip } from 'minizlib';
|
||||
import fetch, { Headers, HeadersInit, RequestInit, Response } from 'node-fetch';
|
||||
import fs, { createReadStream } from 'node:fs';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import promiseRetry from 'promise-retry';
|
||||
|
||||
@@ -28,6 +29,24 @@ const isCompressible = (contentType: string | null, size: number): boolean => {
|
||||
}
|
||||
};
|
||||
|
||||
const getContentTypeAsync = async (filePath: string): Promise<string | null> => {
|
||||
let contentType = mime.getType(path.basename(filePath));
|
||||
|
||||
if (!contentType) {
|
||||
const fileContent = await readFile(filePath, 'utf-8');
|
||||
try {
|
||||
// check if file is valid JSON without an extension, e.g. for the apple app site association file
|
||||
const parsedData = JSON.parse(fileContent);
|
||||
|
||||
if (parsedData) {
|
||||
contentType = 'application/json';
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return contentType;
|
||||
};
|
||||
|
||||
export interface UploadParams extends Omit<RequestInit, 'signal' | 'body'> {
|
||||
filePath: string;
|
||||
compress?: boolean;
|
||||
@@ -70,14 +89,15 @@ export async function uploadAsync(params: UploadParams): Promise<UploadResult> {
|
||||
headers: headersInit,
|
||||
...requestInit
|
||||
} = params;
|
||||
const stat = await fs.promises.stat(params.filePath);
|
||||
const stat = await fs.promises.stat(filePath);
|
||||
if (stat.size > MAX_UPLOAD_SIZE) {
|
||||
throw new Error(
|
||||
`Upload of "${params.filePath}" aborted: File size is greater than the upload limit (>500MB)`
|
||||
`Upload of "${filePath}" aborted: File size is greater than the upload limit (>500MB)`
|
||||
);
|
||||
}
|
||||
|
||||
const contentType = mime.getType(path.basename(params.filePath));
|
||||
const contentType = await getContentTypeAsync(filePath);
|
||||
|
||||
return await promiseRetry(
|
||||
async retry => {
|
||||
const headers = new Headers(headersInit);
|
||||
@@ -109,20 +129,23 @@ export async function uploadAsync(params: UploadParams): Promise<UploadResult> {
|
||||
return retry(error);
|
||||
}
|
||||
|
||||
const getErrorMessageAsync = async (): Promise<string> => {
|
||||
const body = await response.json().catch(() => null);
|
||||
return body?.error ?? `Upload of "${filePath}" failed: ${response.statusText}`;
|
||||
};
|
||||
|
||||
if (
|
||||
response.status === 408 ||
|
||||
response.status === 409 ||
|
||||
response.status === 429 ||
|
||||
(response.status >= 500 && response.status <= 599)
|
||||
) {
|
||||
const message = `Upload of "${filePath}" failed: ${response.statusText}`;
|
||||
const text = await response.text().catch(() => null);
|
||||
return retry(new Error(text ? `${message}\n${text}` : message));
|
||||
return retry(new Error(await getErrorMessageAsync()));
|
||||
} else if (response.status === 413) {
|
||||
const message = `Upload of "${filePath}" failed: File size exceeded the upload limit`;
|
||||
throw new Error(message);
|
||||
} else if (!response.ok) {
|
||||
throw new Error(`Upload of "${filePath}" failed: ${response.statusText}`);
|
||||
throw new Error(await getErrorMessageAsync());
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
{
|
||||
"extends": "@tsconfig/node18/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"importHelpers": true,
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"strict": true,
|
||||
"target": "ES2022",
|
||||
"outDir": "build",
|
||||
"rootDir": "src",
|
||||
"typeRoots": ["../../ts-declarations", "../../node_modules/@types", "./node_modules/@types"]
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"description": "A library for interacting with eas.json",
|
||||
"version": "14.2.0",
|
||||
"version": "15.0.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "7.23.5",
|
||||
"@expo/eas-build-job": "1.0.154",
|
||||
"@expo/eas-build-job": "1.0.165",
|
||||
"chalk": "4.1.2",
|
||||
"env-string": "1.0.1",
|
||||
"fs-extra": "11.2.0",
|
||||
@@ -18,6 +18,7 @@
|
||||
"tslib": "2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node18": "18.2.4",
|
||||
"@types/babel__code-frame": "7.0.3",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"memfs": "3.4.13",
|
||||
|
||||
@@ -129,6 +129,33 @@ test('valid eas.json with specified environment', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('valid eas.json with specified keystoreName', async () => {
|
||||
const keystoreName = 'this-is-a-keystore-name';
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
development: {
|
||||
android: {
|
||||
credentialsSource: 'remote',
|
||||
keystoreName,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const accessor = EasJsonAccessor.fromProjectPath('/project');
|
||||
const androidProfile = await EasJsonUtils.getBuildProfileAsync(
|
||||
accessor,
|
||||
Platform.ANDROID,
|
||||
'development'
|
||||
);
|
||||
|
||||
expect(androidProfile).toEqual(
|
||||
expect.objectContaining({
|
||||
keystoreName,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('valid eas.json with top level withoutCredentials property', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
@@ -274,37 +301,124 @@ test('valid profile for internal distribution on Android', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('valid profile extending other profile', async () => {
|
||||
test('valid profile with extension chain not exceeding 5', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
base: {
|
||||
node: '12.0.0',
|
||||
},
|
||||
extension: {
|
||||
extension1: {
|
||||
extends: 'base',
|
||||
distribution: 'internal',
|
||||
node: '13.0.0',
|
||||
},
|
||||
extension2: {
|
||||
extends: 'extension1',
|
||||
distribution: 'internal',
|
||||
node: '14.0.0',
|
||||
},
|
||||
extension3: {
|
||||
extends: 'extension2',
|
||||
distribution: 'internal',
|
||||
node: '15.0.0',
|
||||
},
|
||||
extension4: {
|
||||
extends: 'extension3',
|
||||
distribution: 'internal',
|
||||
node: '16.0.0',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const accessor = EasJsonAccessor.fromProjectPath('/project');
|
||||
const baseProfile = await EasJsonUtils.getBuildProfileAsync(accessor, Platform.ANDROID, 'base');
|
||||
const extendedProfile = await EasJsonUtils.getBuildProfileAsync(
|
||||
const extendedProfile1 = await EasJsonUtils.getBuildProfileAsync(
|
||||
accessor,
|
||||
Platform.ANDROID,
|
||||
'extension'
|
||||
'extension1'
|
||||
);
|
||||
const extendedProfile2 = await EasJsonUtils.getBuildProfileAsync(
|
||||
accessor,
|
||||
Platform.ANDROID,
|
||||
'extension2'
|
||||
);
|
||||
const extendedProfile3 = await EasJsonUtils.getBuildProfileAsync(
|
||||
accessor,
|
||||
Platform.ANDROID,
|
||||
'extension3'
|
||||
);
|
||||
const extendedProfile4 = await EasJsonUtils.getBuildProfileAsync(
|
||||
accessor,
|
||||
Platform.ANDROID,
|
||||
'extension4'
|
||||
);
|
||||
expect(baseProfile).toEqual({
|
||||
distribution: 'store',
|
||||
credentialsSource: 'remote',
|
||||
node: '12.0.0',
|
||||
});
|
||||
expect(extendedProfile).toEqual({
|
||||
expect(extendedProfile1).toEqual({
|
||||
distribution: 'internal',
|
||||
credentialsSource: 'remote',
|
||||
node: '13.0.0',
|
||||
});
|
||||
expect(extendedProfile2).toEqual({
|
||||
distribution: 'internal',
|
||||
credentialsSource: 'remote',
|
||||
node: '14.0.0',
|
||||
});
|
||||
expect(extendedProfile3).toEqual({
|
||||
distribution: 'internal',
|
||||
credentialsSource: 'remote',
|
||||
node: '15.0.0',
|
||||
});
|
||||
expect(extendedProfile4).toEqual({
|
||||
distribution: 'internal',
|
||||
credentialsSource: 'remote',
|
||||
node: '16.0.0',
|
||||
});
|
||||
});
|
||||
|
||||
test('valid profile with extension chain exceeding 5 - too long', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
base: {
|
||||
node: '12.0.0',
|
||||
},
|
||||
extension1: {
|
||||
extends: 'base',
|
||||
distribution: 'internal',
|
||||
node: '13.0.0',
|
||||
},
|
||||
extension2: {
|
||||
extends: 'extension1',
|
||||
distribution: 'internal',
|
||||
node: '14.0.0',
|
||||
},
|
||||
extension3: {
|
||||
extends: 'extension2',
|
||||
distribution: 'internal',
|
||||
node: '15.0.0',
|
||||
},
|
||||
extension4: {
|
||||
extends: 'extension3',
|
||||
distribution: 'internal',
|
||||
node: '16.0.0',
|
||||
},
|
||||
extension5: {
|
||||
extends: 'extension4',
|
||||
distribution: 'internal',
|
||||
node: '17.0.0',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const accessor = EasJsonAccessor.fromProjectPath('/project');
|
||||
await expect(
|
||||
EasJsonUtils.getBuildProfileAsync(accessor, Platform.ANDROID, 'extension5')
|
||||
).rejects.toThrow(
|
||||
'Too long chain of profile extensions, make sure "extends" keys do not make a cycle'
|
||||
);
|
||||
});
|
||||
|
||||
test('valid profile extending other profile with platform specific envs', async () => {
|
||||
|
||||
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user