mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b36ae0ef8 | ||
|
|
1a4cb107af |
No files matched your search
@@ -11,6 +11,7 @@ module.exports = {
|
||||
ignoreDeclarationSort: true,
|
||||
},
|
||||
],
|
||||
'flowtype/no-types-missing-file-annotation': 'off',
|
||||
curly: 'warn',
|
||||
'import/no-cycle': 'error',
|
||||
'import/no-extraneous-dependencies': [
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
@@ -3,6 +3,7 @@
|
||||
# Checklist
|
||||
|
||||
- [ ] I've added an entry to [CHANGELOG.md](https://github.com/expo/eas-cli/blob/main/CHANGELOG.md#main) if necessary. You can comment this pull request with `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]` and CHANGELOG.md will be updated automatically.
|
||||
- [ ] I've tagged the changelog entry with `[EAS BUILD API]` if it's a part of a breaking change to EAS Build API (only possible when updating `@expo/eas-build-job` package).
|
||||
|
||||
# Why
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
if: ${{ github.event.issue.pull_request && github.event.issue.user.login == github.event.comment.user.login && startsWith(github.event.comment.body, '/changelog-entry ') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Checkout Pull Request
|
||||
@@ -28,8 +28,8 @@ jobs:
|
||||
echo "Invalid /changelog-entry command"
|
||||
exit 1
|
||||
fi
|
||||
CATEGORY=$( cut -d ' ' -f 1 <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
|
||||
MESSAGE=$( cut -d ' ' -f 2- <<< "$PARAMS" | sed -e 's/^[[:space:]]*//' )
|
||||
CATEGORY=$( cut -d ' ' -f 1 <<< "$PARAMS" | xargs )
|
||||
MESSAGE=$( cut -d ' ' -f 2- <<< "$PARAMS" | xargs )
|
||||
yarn changelog-entry "$CATEGORY" "$MESSAGE"
|
||||
env:
|
||||
GITHUB_PR_AUTHOR: ${{ github.event.issue.user.login }}
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
needs: release
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16'
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
needs: release
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16'
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
needs: release
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16'
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
needs: [build-linux, build-mac, build-windows]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
scope: 'expo'
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
needs: publish-to-npm
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16'
|
||||
- uses: preactjs/compressed-size-action@v2
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
|
||||
@@ -12,4 +12,3 @@ node_modules
|
||||
coverage
|
||||
.history
|
||||
.DS_Store
|
||||
.idea
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"bracketSameLine": true,
|
||||
"jsxBracketSameLine": true,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
+4
-142
@@ -6,161 +6,23 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Require explicitly defined applicationId/bundleIdentifier for EAS Submit in case of bare projects consisting of multiple product flavors on Android or multiple schemes/targets on iOS. ([#765](https://github.com/expo/eas-cli/pull/765) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Auto create channel on publish. ([#766](https://github.com/expo/eas-cli/pull/766) by [@jkhales](https://github.com/jkhales))
|
||||
- Interactively configure Git `user.name` and `user.email`. ([#772](https://github.com/expo/eas-cli/pull/772) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Validate release channel in eas.json. ([#764](https://github.com/expo/eas-cli/pull/764) by [@dsokal](https://github.com/dsokal))
|
||||
- Make the missing profile error message more descriptive. ([#761](https://github.com/expo/eas-cli/pull/761) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Better error message when missing eas.json. ([#770](https://github.com/expo/eas-cli/pull/770) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.36.1](https://github.com/expo/eas-cli/releases/tag/v0.36.1) - 2021-11-15
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix `dateformat` import issue when building with `--local` flag. ([a520d](https://github.com/expo/eas-cli/commit/a520d1750736d61d6489da6487879f35ed3deb23) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.36.0](https://github.com/expo/eas-cli/releases/tag/v0.36.0) - 2021-11-15
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Do not require manual `eas-cli-local-build-plugin` installation. An existing global installation (either with `yarn global add` or `npm install -g`) will not be used anymore. ([#753](https://github.com/expo/eas-cli/pull/753) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Use Keystore Service when `keytool` is not installed. ([#754](https://github.com/expo/eas-cli/pull/754) by [@dsokal](https://github.com/dsokal))
|
||||
- Add applicationId/bundleIdentifier fields to the submit profile. ([#765](https://github.com/expo/eas-cli/pull/765) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix auto-submitting with `--auto-submit-with-profile`. ([#748](https://github.com/expo/eas-cli/pull/748) by [@dsokal](https://github.com/dsokal))
|
||||
- Pass env from build profile when resolving entitlements. ([#751](https://github.com/expo/eas-cli/pull/751) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Only prompt for Apple Id username if authenticating with an App Specific Password. ([#745](https://github.com/expo/eas-cli/pull/745) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Use same URL paths for local development of EAS CLI as production. ([#750](https://github.com/expo/eas-cli/pull/750) by [@ide](https://github.com/ide))
|
||||
- Grammar: Api -> API. ([#755](https://github.com/expo/eas-cli/pull/755) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add analytics for EAS Submit. ([#752](https://github.com/expo/eas-cli/pull/752) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add an 'attempt' event to build and submit analytics. ([#757](https://github.com/expo/eas-cli/pull/757) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.35.0](https://github.com/expo/eas-cli/releases/tag/v0.35.0) - 2021-11-08
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add ASC API Key generation workflow. ([#718](https://github.com/expo/eas-cli/pull/718) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add support for removal of ASC API Keys. ([#721](https://github.com/expo/eas-cli/pull/721) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Allow users to assign an ASC API Key to their project. ([#719](https://github.com/expo/eas-cli/pull/719) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add setup support for ASC API Keys. ([#733](https://github.com/expo/eas-cli/pull/733) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Show initiating user display name when selecting a build to submit. ([#730](https://github.com/expo/eas-cli/pull/730) by [@barthap](https://github.com/barthap))
|
||||
- Handle Apple servers maintenance error in `eas submit`. ([#738](https://github.com/expo/eas-cli/pull/738) by [@barthap](https://github.com/barthap))
|
||||
- Integrate ASC API Key with submissions workflow. ([#737](https://github.com/expo/eas-cli/pull/737) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Change EAS API server domain. ([#744](https://github.com/expo/eas-cli/pull/744) by [@ide](https://github.com/ide))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Clean up credentials prompt method. ([#728](https://github.com/expo/eas-cli/pull/728) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Bump @expo/apple-utils to 0.0.0-alpha.26. ([#723](https://github.com/expo/eas-cli/pull/723) by [@brentvatne](https://github.com/brentvatne))
|
||||
- Grammar: replace setup with set up. ([#735](https://github.com/expo/eas-cli/pull/735) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Improve VCS workflow migration experience. ([#732](https://github.com/expo/eas-cli/pull/732) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
## [0.34.1](https://github.com/expo/eas-cli/releases/tag/v0.34.1) - 2021-11-02
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Don't show commit prompt in no-commit workflow after installing `expo-dev-client`. ([#722](https://github.com/expo/eas-cli/pull/722) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add App Store Connect API Key graphql type and print support. ([#717](https://github.com/expo/eas-cli/pull/717) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.34.0](https://github.com/expo/eas-cli/releases/tag/v0.34.0) - 2021-11-01
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Use new build job format. ([#701](https://github.com/expo/eas-cli/pull/701) and [#711](https://github.com/expo/eas-cli/pull/711) by [@dsokal](https://github.com/dsokal))
|
||||
- Don't print logs to `stderr`. ([#708](https://github.com/expo/eas-cli/pull/708) by [@dsokal](https://github.com/dsokal))
|
||||
- Remove automatic migration for legacy `eas.json` format introduced in [v0.22.2](https://github.com/expo/eas-cli/releases/tag/v0.22.2). ([#695](https://github.com/expo/eas-cli/pull/695) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Implement no-commit build workflow. Add required `cli` field in root of `eas.json`. ([#695](https://github.com/expo/eas-cli/pull/695) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Better error message when eas.json is invalid. ([#707](https://github.com/expo/eas-cli/pull/707) by [@dsokal](https://github.com/dsokal))
|
||||
- Fix credentials workflow for new users: add additionalTypenames for credentials. ([#703](https://github.com/expo/eas-cli/pull/703) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add additionalTypenames for other queries. ([#704](https://github.com/expo/eas-cli/pull/704) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Fix errors after pulling down most recent graphql schema changes. ([#713](https://github.com/expo/eas-cli/pull/713) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Refactor credentials manager. ([#712](https://github.com/expo/eas-cli/pull/712) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Remove unneeded parameters. ([#710](https://github.com/expo/eas-cli/pull/710) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Enforce additionalTypenames for graphql queries. ([#709](https://github.com/expo/eas-cli/pull/709) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.33.1](https://github.com/expo/eas-cli/releases/tag/v0.33.1) - 2021-10-22
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Compute runtime version using config-plugins. ([#697](https://github.com/expo/eas-cli/pull/697) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- [eas-cli] Skip validating updates scripts integration, which no longer exist in SDK 43+ ([#706](https://github.com/expo/eas-cli/pull/706) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.33.0](https://github.com/expo/eas-cli/releases/tag/v0.33.0) - 2021-10-20
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Make "production" the default profile for building and submitting. ([#677](https://github.com/expo/eas-cli/pull/677)) by [@jonsamp](https://github.com/jonsamp)
|
||||
- Previously, the default profile when running `eas build` or `eas submit` was "release". We're changing it to a more recognizable name that is consistent with our docs, which is now "production". You can always specify a profile manually with the `--profile` flag. For this major version, if you do not have a profile named "production", and still have a profile named "release", we will fall back to the "release" profile as the default, however you'll see a warning as we're going to remove that behavior in the next major release of EAS CLI.
|
||||
- To upgrade, update **eas.json** to have a "production" profile in both the `build` and `submit` objects. If you already have a project set up, this will replace the existing "release" profile. After the change, eas.json should have the following profiles:
|
||||
```json
|
||||
{
|
||||
"build": {
|
||||
"production": { ... }
|
||||
},
|
||||
"submit": {
|
||||
"production": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Skip the second prompt for Apple ID if the user is already signed in with Apple. ([#691](https://github.com/expo/eas-cli/pull/691) by [@dsokal](https://github.com/dsokal))
|
||||
- Unify reading `app.json`. ([#692](https://github.com/expo/eas-cli/pull/692) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.32.0](https://github.com/expo/eas-cli/releases/tag/v0.32.0) - 2021-10-15
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add App Store Connect API Key support to iOS submissions. ([#678](https://github.com/expo/eas-cli/pull/678) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Create/list/revoke App Store Connect Api keys via Apple apis. ([#687](https://github.com/expo/eas-cli/pull/687) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add ability to select a build from a list in `eas submit` interactive mode. ([#688](https://github.com/expo/eas-cli/pull/688) by [@barthap](https://github.com/barthap))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix printing App Store Connect URL after submission. ([#683](https://github.com/expo/eas-cli/pull/683) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add App Store Connect API Key fields to `eas.json`. ([#684](https://github.com/expo/eas-cli/pull/684) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Enable no-underscore-dangle eslint rule. ([#686](https://github.com/expo/eas-cli/pull/686) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.31.1](https://github.com/expo/eas-cli/releases/tag/v0.31.1) - 2021-10-08
|
||||
## [0.31.1](https://github.com/expo/eas-cli/releases/tag/0.31.1) - 2021-10-08
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Google Service Account Keys: Fix non-interactive bug in credentials service workflow ([#682](https://github.com/expo/eas-cli/pull/682) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.31.0](https://github.com/expo/eas-cli/releases/tag/v0.31.0) - 2021-10-08
|
||||
## [0.31.0](https://github.com/expo/eas-cli/releases/tag/0.31.0) - 2021-10-08
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
@@ -171,7 +33,7 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
- Integrate credentials service with Android submissions. ([#664](https://github.com/expo/eas-cli/pull/664) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add option to review ad-hoc devices when reusing provisioning profile. ([#673](https://github.com/expo/eas-cli/pull/673) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.30.1](https://github.com/expo/eas-cli/releases/tag/v0.30.1) - 2021-10-06
|
||||
## [0.30.1](https://github.com/expo/eas-cli/releases/tag/0.30.1) - 2021-10-06
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
@@ -550,7 +412,7 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Remove "Auto" option for `schemeBuildConfiguration` and make the old "Auto" behaviour the default. ([#394](https://github.com/expo/eas-cli/pull/394) by [@randomhajile](https://github.com/randomhajile))
|
||||
- `[EAS BUILD API]` Remove "Auto" option for `schemeBuildConfiguration` and make the old "Auto" behaviour the default. ([#394](https://github.com/expo/eas-cli/pull/394) by [@randomhajile](https://github.com/randomhajile))
|
||||
- Remove `experimental.disableIosBundleIdentifierValidation` flag from eas.json. ([#407](https://github.com/expo/eas-cli/pull/407) by [@dsokal](https://github.com/dsokal))
|
||||
- Deprecate `android.package` and `ios.bundleIdentifier` in app config for generic projects. EAS CLI depends on the values in native code now. ([#407](https://github.com/expo/eas-cli/pull/407) by [@dsokal](https://github.com/dsokal))
|
||||
- Remove application id synchronization (`android.package` and `ios.bundleIdentifier`) between app.json and native code when running builds. ([#407](https://github.com/expo/eas-cli/pull/407) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
+7
-1
@@ -1,4 +1,10 @@
|
||||
## Releasing a new version
|
||||
|
||||
1. Run `yarn release` in the repository root folder.
|
||||
2. That's it! GitHub Actions is going to take care of the rest. Watch the #eas-cli Slack channel for a successful release notification.
|
||||
2. Wait, the `release` workflow on GitHub Actions is going to take care of the rest (including publishing packages to npm and updating the changelog).
|
||||
3. Check the changelog. If any of the entries have been tagged with `[EAS BUILD API]`
|
||||
- Open `package.json` and note the version of `@expo/eas-build-job` used in the eas-cli.
|
||||
- Find the `Publish` commit in [expo/eas-build](https://github.com/expo/eas-build/commit/) for that version. In most cases, it will be the latest `Publish` commit.
|
||||
- Find the `eas-cli-local-build-plugin` version published in that commit.
|
||||

|
||||
- Run `npm dist-tags add eas-cli-local-build-plugin@VERSION latest` with the version from the previous step.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.37.0"
|
||||
"version": "0.31.1"
|
||||
}
|
||||
+4
-4
@@ -8,10 +8,10 @@
|
||||
"start": "lerna run watch --parallel",
|
||||
"watch": "yarn start",
|
||||
"eas": "packages/eas-cli/bin/run",
|
||||
"lint": "eslint .",
|
||||
"release": "./scripts/bin/run update-local-plugin && lerna version",
|
||||
"lint": "eslint . --ext .ts --ext .js",
|
||||
"release": "lerna version",
|
||||
"test": "jest",
|
||||
"clean": "lerna run clean && rm -rf node_modules coverage"
|
||||
"clean": "lerna run clean"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/oclif-dev-cli": "1.24.0-expo",
|
||||
@@ -24,7 +24,7 @@
|
||||
"jest": "27.1.0",
|
||||
"jest-watch-typeahead": "0.6.4",
|
||||
"lerna": "4.0.0",
|
||||
"prettier": "2.4.1",
|
||||
"prettier": "2.3.2",
|
||||
"ts-jest": "27.0.5",
|
||||
"typescript": "4.4.2"
|
||||
},
|
||||
|
||||
+29
-30
@@ -73,7 +73,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/login.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/account/login.js)_
|
||||
_See code: [src/commands/account/login.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/account/login.js)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -87,7 +87,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/logout.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/account/logout.js)_
|
||||
_See code: [src/commands/account/logout.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/account/logout.js)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -101,7 +101,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/view.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/account/view.js)_
|
||||
_See code: [src/commands/account/view.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/account/view.js)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -112,7 +112,7 @@ USAGE
|
||||
$ eas analytics [STATUS]
|
||||
```
|
||||
|
||||
_See code: [src/commands/analytics.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/analytics.js)_
|
||||
_See code: [src/commands/analytics.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/analytics.js)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -138,15 +138,14 @@ OPTIONS
|
||||
|
||||
--non-interactive Run command in non-interactive mode
|
||||
|
||||
--profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined
|
||||
in eas.json.
|
||||
--profile=PROFILE_NAME [default: release] Name of the build profile from eas.json
|
||||
|
||||
--skip-project-configuration Skip project configuration
|
||||
|
||||
--[no-]wait Wait for build(s) to complete
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/index.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/build/index.js)_
|
||||
_See code: [src/commands/build/index.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/build/index.js)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -157,7 +156,7 @@ USAGE
|
||||
$ eas build:cancel [BUILD_ID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/cancel.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/build/cancel.js)_
|
||||
_See code: [src/commands/build/cancel.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/build/cancel.js)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -171,7 +170,7 @@ OPTIONS
|
||||
-p, --platform=(android|ios|all) Platform to configure
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/configure.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/build/configure.js)_
|
||||
_See code: [src/commands/build/configure.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/build/configure.js)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -204,7 +203,7 @@ OPTIONS
|
||||
--status=(new|in-queue|in-progress|errored|finished|canceled)
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/list.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/build/list.js)_
|
||||
_See code: [src/commands/build/list.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/build/list.js)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -218,7 +217,7 @@ OPTIONS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/view.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/build/view.js)_
|
||||
_See code: [src/commands/build/view.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/build/view.js)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -233,7 +232,7 @@ OPTIONS
|
||||
--profile=profile
|
||||
```
|
||||
|
||||
_See code: [src/commands/config.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/config.js)_
|
||||
_See code: [src/commands/config.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/config.js)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -244,7 +243,7 @@ USAGE
|
||||
$ eas credentials
|
||||
```
|
||||
|
||||
_See code: [src/commands/credentials.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/credentials.js)_
|
||||
_See code: [src/commands/credentials.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/credentials.js)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -255,7 +254,7 @@ USAGE
|
||||
$ eas device:create
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/create.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/device/create.js)_
|
||||
_See code: [src/commands/device/create.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/device/create.js)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -269,7 +268,7 @@ OPTIONS
|
||||
--apple-team-id=apple-team-id
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/list.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/device/list.js)_
|
||||
_See code: [src/commands/device/list.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/device/list.js)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -280,7 +279,7 @@ USAGE
|
||||
$ eas device:view [UDID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/view.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/device/view.js)_
|
||||
_See code: [src/commands/device/view.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/device/view.js)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -291,7 +290,7 @@ USAGE
|
||||
$ eas diagnostics
|
||||
```
|
||||
|
||||
_See code: [src/commands/diagnostics.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/diagnostics.js)_
|
||||
_See code: [src/commands/diagnostics.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/diagnostics.js)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -319,7 +318,7 @@ USAGE
|
||||
$ eas project:info
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/info.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/project/info.js)_
|
||||
_See code: [src/commands/project/info.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/project/info.js)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -333,7 +332,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/init.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/project/init.js)_
|
||||
_See code: [src/commands/project/init.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/project/init.js)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
@@ -350,7 +349,7 @@ OPTIONS
|
||||
--value=value Value of the secret
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/create.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/secret/create.js)_
|
||||
_See code: [src/commands/secret/create.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/secret/create.js)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -364,10 +363,10 @@ OPTIONS
|
||||
--id=id ID of the secret to delete
|
||||
|
||||
DESCRIPTION
|
||||
Unsure where to find the secret's ID? Run eas secret:list
|
||||
Unsure where to find the secret's ID? Run eas secrets:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/delete.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/secret/delete.js)_
|
||||
_See code: [src/commands/secret/delete.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/secret/delete.js)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -378,7 +377,7 @@ USAGE
|
||||
$ eas secret:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/list.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/secret/list.js)_
|
||||
_See code: [src/commands/secret/list.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/secret/list.js)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -395,7 +394,7 @@ OPTIONS
|
||||
--non-interactive Run command in non-interactive mode
|
||||
--path=path Path to the .apk/.aab/.ipa file
|
||||
|
||||
--profile=profile Name of the submit profile from eas.json. Defaults to "production" if defined in
|
||||
--profile=profile Name of the submit profile from eas.json. Defaults to "release" if defined in
|
||||
eas.json.
|
||||
|
||||
--url=url App archive url
|
||||
@@ -411,7 +410,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [src/commands/submit.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/submit.js)_
|
||||
_See code: [src/commands/submit.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/submit.js)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -430,7 +429,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/create.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/webhook/create.js)_
|
||||
_See code: [src/commands/webhook/create.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/webhook/create.js)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -444,7 +443,7 @@ ARGUMENTS
|
||||
ID ID of the webhook to delete
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/delete.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/webhook/delete.js)_
|
||||
_See code: [src/commands/webhook/delete.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/webhook/delete.js)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -458,7 +457,7 @@ OPTIONS
|
||||
--event=(BUILD) Event type that triggers the webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/list.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/webhook/list.js)_
|
||||
_See code: [src/commands/webhook/list.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/webhook/list.js)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -478,7 +477,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/update.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/webhook/update.js)_
|
||||
_See code: [src/commands/webhook/update.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/webhook/update.js)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -492,5 +491,5 @@ ARGUMENTS
|
||||
ID ID of the webhook to view
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/view.js](https://github.com/expo/eas-cli/blob/v0.37.0/packages/eas-cli/src/commands/webhook/view.js)_
|
||||
_See code: [src/commands/webhook/view.js](https://github.com/expo/eas-cli/blob/v0.31.1/packages/eas-cli/src/commands/webhook/view.js)_
|
||||
<!-- commandsstop -->
|
||||
@@ -1,5 +1,5 @@
|
||||
overwrite: true
|
||||
schema: 'https://staging-api.expo.dev/graphql'
|
||||
schema: 'https://staging.exp.host/--/graphql'
|
||||
documents:
|
||||
- 'src/graphql/**/!(*.d).{ts,tsx}'
|
||||
- 'src/credentials/ios/api/graphql/**/!(*.d).{ts,tsx}'
|
||||
|
||||
Generated
+1325
-1844
File diff suppressed because it is too large.
Load diff
@@ -1,23 +1,24 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"type": "module",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.37.0",
|
||||
"version": "0.31.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
},
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/apple-utils": "0.0.0-alpha.26",
|
||||
"@expo/config": "6.0.3",
|
||||
"@expo/config-plugins": "4.0.3",
|
||||
"@expo/eas-build-job": "0.2.57",
|
||||
"@expo/eas-json": "^0.37.0",
|
||||
"@expo/apple-utils": "0.0.0-alpha.25",
|
||||
"@expo/config": "6.0.2",
|
||||
"@expo/config-plugins": "4.0.2",
|
||||
"@expo/eas-build-job": "0.2.52",
|
||||
"@expo/eas-json": "^0.31.0",
|
||||
"@expo/json-file": "8.2.33",
|
||||
"@expo/pkcs12": "0.0.4",
|
||||
"@expo/plist": "0.0.14",
|
||||
"@expo/plugin-warn-if-update-available": "1.7.0",
|
||||
"@expo/prebuild-config": "3.0.3",
|
||||
"@expo/prebuild-config": "3.0.2",
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.0",
|
||||
"@expo/sdk-runtime-versions": "1.0.0",
|
||||
@@ -177,6 +178,6 @@
|
||||
"test": "jest",
|
||||
"version": "yarn oclif-dev readme && node patch-readme && git add README.md",
|
||||
"generate-graphql-code": "graphql-codegen --config graphql-codegen.yml",
|
||||
"clean": "rm -rf dist build node_modules yarn-error.log"
|
||||
"clean": "rm -rf build node_modules"
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { apiClient, getExpoApiBaseUrl } from '../api';
|
||||
describe('apiClient', () => {
|
||||
it('converts Expo APIv2 error to ApiV2Error', async () => {
|
||||
nock(getExpoApiBaseUrl())
|
||||
.post('/v2/test')
|
||||
.post('/--/api/v2/test')
|
||||
.reply(400, {
|
||||
errors: [
|
||||
{
|
||||
@@ -39,7 +39,7 @@ describe('apiClient', () => {
|
||||
});
|
||||
|
||||
it('does not convert non-APIv2 error to ApiV2Error', async () => {
|
||||
nock(getExpoApiBaseUrl()).post('/v2/test').reply(500, 'Something went wrong');
|
||||
nock(getExpoApiBaseUrl()).post('/--/api/v2/test').reply(500, 'Something went wrong');
|
||||
|
||||
let error: Error | null = null;
|
||||
try {
|
||||
|
||||
+10
-4
@@ -3,8 +3,14 @@ import os from 'os';
|
||||
import { URL } from 'url';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import UserSettings from '../user/UserSettings';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import UserSettings from './user/UserSettings';
|
||||
|
||||
/**
|
||||
* We use require() to exclude package.json from TypeScript's analysis since it lives outside
|
||||
* the src directory and would change the directory structure of the emitted files
|
||||
* under the build directory
|
||||
*/
|
||||
const packageJSON = require('../package.json');
|
||||
|
||||
const PLATFORM_TO_ANALYTICS_PLATFORM: { [platform: string]: string } = {
|
||||
darwin: 'Mac',
|
||||
@@ -91,7 +97,7 @@ export function logEvent(name: string, properties: Record<string, any> = {}): vo
|
||||
ensureUserIdentified();
|
||||
|
||||
const { userId, deviceId } = identifyData ?? {};
|
||||
const commonEventProperties = { source_version: easCliVersion, source: 'eas cli' };
|
||||
const commonEventProperties = { source_version: packageJSON?.version, source: 'eas cli' };
|
||||
|
||||
const identity = { userId: userId ?? undefined, anonymousId: deviceId ?? uuidv4() };
|
||||
rudderstackClient.track({
|
||||
@@ -120,7 +126,7 @@ function getRudderStackContext(): Record<string, any> {
|
||||
return {
|
||||
os: { name: platform, version: os.release() },
|
||||
device: { type: platform, model: platform },
|
||||
app: { name: 'eas cli', version: easCliVersion ?? undefined },
|
||||
app: { name: 'eas cli', version: packageJSON?.version ?? undefined },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Analytics, Event } from './events';
|
||||
|
||||
export type TrackingContext = Record<string, string | number | boolean>;
|
||||
|
||||
export async function withAnalyticsAsync<Result>(
|
||||
fn: () => Promise<Result>,
|
||||
analytics: {
|
||||
attemptEvent: Event;
|
||||
successEvent: Event;
|
||||
failureEvent: Event;
|
||||
trackingCtx: TrackingContext;
|
||||
}
|
||||
): Promise<Result> {
|
||||
try {
|
||||
const result = await fn();
|
||||
Analytics.logEvent(analytics.successEvent, analytics.trackingCtx);
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
Analytics.logEvent(analytics.failureEvent, {
|
||||
...analytics.trackingCtx,
|
||||
reason: error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { logEvent } from './rudderstackClient';
|
||||
export type Event = BuildEvent | SubmissionEvent;
|
||||
|
||||
export enum SubmissionEvent {
|
||||
SUBMIT_COMMAND = 'submit cli submit command',
|
||||
SUBMIT_COMMAND_ATTEMPT = 'submit cli attempt',
|
||||
SUBMIT_COMMAND_SUCCESS = 'submit cli success',
|
||||
SUBMIT_COMMAND_FAIL = 'submit cli fail',
|
||||
GATHER_CREDENTIALS_ATTEMPT = 'submit cli gather credentials attempt',
|
||||
GATHER_CREDENTIALS_SUCCESS = 'submit cli gather credentials success',
|
||||
GATHER_CREDENTIALS_FAIL = 'submit cli gather credentials fail',
|
||||
GATHER_ARCHIVE_ATTEMPT = 'submit cli gather archive attempt',
|
||||
GATHER_ARCHIVE_SUCCESS = 'submit cli gather archive success',
|
||||
GATHER_ARCHIVE_FAIL = 'submit cli gather archive fail',
|
||||
SUBMIT_REQUEST_ATTEMPT = 'submit cli request attempt',
|
||||
SUBMIT_REQUEST_SUCCESS = 'submit cli request success',
|
||||
SUBMIT_REQUEST_FAIL = 'submit cli request fail',
|
||||
}
|
||||
|
||||
export enum BuildEvent {
|
||||
BUILD_COMMAND = 'build cli build command',
|
||||
PROJECT_UPLOAD_ATTEMPT = 'build cli project upload attempt',
|
||||
PROJECT_UPLOAD_SUCCESS = 'build cli project upload success',
|
||||
PROJECT_UPLOAD_FAIL = 'build cli project upload fail',
|
||||
GATHER_CREDENTIALS_ATTEMPT = 'build cli gather credentials attempt',
|
||||
GATHER_CREDENTIALS_SUCCESS = 'build cli gather credentials success',
|
||||
GATHER_CREDENTIALS_FAIL = 'build cli gather credentials fail',
|
||||
CONFIGURE_PROJECT_ATTEMPT = 'build cli configure project attempt',
|
||||
CONFIGURE_PROJECT_SUCCESS = 'build cli configure project success',
|
||||
CONFIGURE_PROJECT_FAIL = 'build cli configure project fail',
|
||||
BUILD_REQUEST_ATTEMPT = 'build cli build request attempt',
|
||||
BUILD_REQUEST_SUCCESS = 'build cli build request success',
|
||||
BUILD_REQUEST_FAIL = 'build cli build request fail',
|
||||
|
||||
BUILD_STATUS_COMMAND = 'build cli build status',
|
||||
|
||||
CREDENTIALS_SYNC_COMMAND = 'build cli credentials sync command',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_ATTEMPT = 'build cli credentials sync update local attempt',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_SUCCESS = 'build cli credentials sync update local success',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_FAIL = 'build cli credentials sync update local fail',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_ATTEMPT = 'build cli credentials sync update remote attempt',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_SUCCESS = 'build cli credentials sync update remote success',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_FAIL = 'build cli credentials sync update remote fail',
|
||||
|
||||
ANDROID_KEYSTORE_CREATE = 'build cli credentials keystore create',
|
||||
}
|
||||
|
||||
export class Analytics {
|
||||
static logEvent(name: Event, properties: Record<string, any>): void {
|
||||
logEvent(name, properties);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import ApiV2Error from './ApiV2Error';
|
||||
import { getAccessToken, getSessionSecret } from './user/sessionStorage';
|
||||
|
||||
export const apiClient = got.extend({
|
||||
prefixUrl: getExpoApiBaseUrl() + '/v2/',
|
||||
prefixUrl: getExpoApiBaseUrl() + '/--/api/v2/',
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
(options: NormalizedOptions) => {
|
||||
@@ -40,11 +40,11 @@ export const apiClient = got.extend({
|
||||
|
||||
export function getExpoApiBaseUrl(): string {
|
||||
if (process.env.EXPO_STAGING) {
|
||||
return `https://staging-api.expo.dev`;
|
||||
return `https://staging.exp.host`;
|
||||
} else if (process.env.EXPO_LOCAL) {
|
||||
return `http://127.0.0.1:3000`;
|
||||
} else {
|
||||
return `https://api.expo.dev`;
|
||||
return `https://exp.host`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { AndroidConfig, AndroidManifest } from '@expo/config-plugins';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import Log from '../../log';
|
||||
import { getProjectAccountName } from '../../project/projectUtils';
|
||||
@@ -9,6 +10,17 @@ import { ensureValidVersions } from '../utils/updates';
|
||||
export async function configureUpdatesAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
|
||||
ensureValidVersions(exp);
|
||||
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
|
||||
const buildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(projectDir);
|
||||
const buildGradleContents = await fs.readFile(buildGradlePath, 'utf8');
|
||||
|
||||
if (!AndroidConfig.Updates.isBuildGradleConfigured(projectDir, buildGradleContents)) {
|
||||
const updatedBuildGradleContents =
|
||||
AndroidConfig.Updates.ensureBuildGradleContainsConfigurationScript(
|
||||
projectDir,
|
||||
buildGradleContents
|
||||
);
|
||||
await fs.writeFile(buildGradlePath, updatedBuildGradleContents);
|
||||
}
|
||||
|
||||
const androidManifestPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectDir);
|
||||
const androidManifest = await getAndroidManifestAsync(projectDir);
|
||||
@@ -27,7 +39,7 @@ export async function syncUpdatesConfigurationAsync(
|
||||
ensureValidVersions(exp);
|
||||
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
|
||||
try {
|
||||
await ensureUpdatesConfiguredAsync(projectDir);
|
||||
await ensureUpdatesConfiguredAsync(projectDir, exp);
|
||||
} catch (error) {
|
||||
Log.error(
|
||||
'expo-updates module is not configured. Please run "eas build:configure" first to configure the project'
|
||||
@@ -50,9 +62,15 @@ export async function syncUpdatesConfigurationAsync(
|
||||
}
|
||||
}
|
||||
|
||||
// Note: we assume here that Expo modules are properly configured in the project. Aside from that,
|
||||
// all that is needed on Expo SDK 43+ to configure expo-updates configuration in AndroidManifest.xml
|
||||
async function ensureUpdatesConfiguredAsync(projectDir: string): Promise<void> {
|
||||
async function ensureUpdatesConfiguredAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
|
||||
const buildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(projectDir);
|
||||
const buildGradleContents = await fs.readFile(buildGradlePath, 'utf8');
|
||||
|
||||
if (!AndroidConfig.Updates.isBuildGradleConfigured(projectDir, buildGradleContents)) {
|
||||
const gradleScriptApply = AndroidConfig.Updates.formatApplyLineForBuildGradle(projectDir);
|
||||
throw new Error(`Missing ${gradleScriptApply} in ${buildGradlePath}`);
|
||||
}
|
||||
|
||||
const androidManifest = await getAndroidManifestAsync(projectDir);
|
||||
|
||||
if (!AndroidConfig.Updates.isMainApplicationMetaDataSet(androidManifest)) {
|
||||
|
||||
@@ -31,7 +31,7 @@ beforeEach(async () => {
|
||||
await fs.mkdirp(os.tmpdir());
|
||||
});
|
||||
|
||||
// bare workflow
|
||||
// generic workflow
|
||||
describe(bumpVersionAsync, () => {
|
||||
it('throws an informative error when multiple flavors are detected in the android project', async () => {
|
||||
await initProjectWithGradleFileAsync(
|
||||
@@ -69,7 +69,7 @@ describe(bumpVersionAsync, () => {
|
||||
});
|
||||
it('bumps expo.android.versionCode and buildGradle versionCode when strategy = BumpStrategy.VERSION_CODE', async () => {
|
||||
const nativeVersionCode = 100; // this should be overwritten by bumpVersionAsync
|
||||
const fakeExp = initBareWorkflowProject({ versionCode: nativeVersionCode });
|
||||
const fakeExp = initGenericProject({ versionCode: nativeVersionCode });
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.VERSION_CODE,
|
||||
@@ -91,7 +91,7 @@ describe(bumpVersionAsync, () => {
|
||||
|
||||
it('bumps expo.version and gradle versionCode when strategy = BumpStrategy.SHORT_VERSION', async () => {
|
||||
const nativeVersionName = '1.0.0'; // this should be overwritten by bumpVersionAsync
|
||||
const fakeExp = initBareWorkflowProject({ versionName: nativeVersionName });
|
||||
const fakeExp = initGenericProject({ versionName: nativeVersionName });
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.APP_VERSION,
|
||||
@@ -112,7 +112,7 @@ describe(bumpVersionAsync, () => {
|
||||
});
|
||||
|
||||
it('does not bump any version when strategy = BumpStrategy.NOOP', async () => {
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
const fakeExp = initGenericProject();
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.NOOP,
|
||||
@@ -185,9 +185,9 @@ describe(bumpVersionInAppJsonAsync, () => {
|
||||
});
|
||||
|
||||
describe(maybeResolveVersionsAsync, () => {
|
||||
describe('bare project', () => {
|
||||
describe('generic project', () => {
|
||||
it('reads the versions from native code', async () => {
|
||||
const exp = initBareWorkflowProject();
|
||||
const exp = initGenericProject();
|
||||
const { appVersion, appBuildVersion } = await maybeResolveVersionsAsync(
|
||||
'/repo',
|
||||
exp,
|
||||
@@ -211,7 +211,7 @@ describe(maybeResolveVersionsAsync, () => {
|
||||
});
|
||||
});
|
||||
|
||||
function initBareWorkflowProject({
|
||||
function initGenericProject({
|
||||
versionCode = 123,
|
||||
versionName = '3.0.0',
|
||||
}: {
|
||||
|
||||
@@ -6,10 +6,10 @@ import { AndroidBuildProfile, AndroidVersionAutoIncrement } from '@expo/eas-json
|
||||
import Log from '../../log';
|
||||
import {
|
||||
ensureApplicationIdIsDefinedForManagedProjectAsync,
|
||||
warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject,
|
||||
warnIfAndroidPackageDefinedInAppConfigForGenericProject,
|
||||
} from '../../project/android/applicationId';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import vcs from '../../vcs';
|
||||
import { ConfigureContext } from '../context';
|
||||
import { isExpoUpdatesInstalled } from '../utils/updates';
|
||||
import { configureUpdatesAsync, syncUpdatesConfigurationAsync } from './UpdatesModule';
|
||||
@@ -21,12 +21,12 @@ export async function configureAndroidAsync(ctx: ConfigureContext): Promise<void
|
||||
return;
|
||||
}
|
||||
|
||||
warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject(ctx.projectDir, ctx.exp);
|
||||
warnIfAndroidPackageDefinedInAppConfigForGenericProject(ctx.projectDir, ctx.exp);
|
||||
|
||||
await AndroidConfig.EasBuild.configureEasBuildAsync(ctx.projectDir);
|
||||
|
||||
const easGradlePath = AndroidConfig.EasBuild.getEasBuildGradlePath(ctx.projectDir);
|
||||
await getVcsClient().trackFileAsync(easGradlePath);
|
||||
await vcs.trackFileAsync(easGradlePath);
|
||||
|
||||
if (isExpoUpdatesInstalled(ctx.projectDir)) {
|
||||
await configureUpdatesAsync(ctx.projectDir, ctx.exp);
|
||||
|
||||
@@ -17,14 +17,15 @@ export function transformJob(job: Android.Job): AndroidJobInput {
|
||||
artifactPath: job.artifactPath,
|
||||
username: job.username,
|
||||
buildType: job.buildType && transformBuildType(job.buildType),
|
||||
developmentClient: job.developmentClient,
|
||||
};
|
||||
}
|
||||
|
||||
function transformBuildType(buildType: Android.BuildType): AndroidBuildType {
|
||||
if (buildType === Android.BuildType.APK) {
|
||||
return AndroidBuildType.Apk;
|
||||
} else {
|
||||
} else if (buildType === Android.BuildType.APP_BUNDLE) {
|
||||
return AndroidBuildType.AppBundle;
|
||||
} else {
|
||||
return AndroidBuildType.DevelopmentClient;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import slash from 'slash';
|
||||
import { AndroidCredentials } from '../../credentials/android/AndroidCredentialsProvider';
|
||||
import { getUsername } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import vcs from '../../vcs';
|
||||
import { BuildContext } from '../context';
|
||||
|
||||
interface JobData {
|
||||
@@ -27,7 +27,7 @@ export async function prepareJobAsync(
|
||||
const username = getUsername(ctx.exp, await ensureLoggedInAsync());
|
||||
const buildProfile: AndroidBuildProfile = ctx.buildProfile;
|
||||
const projectRootDirectory =
|
||||
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
slash(path.relative(await vcs.getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
const { credentials } = jobData;
|
||||
const buildCredentials = credentials
|
||||
? {
|
||||
@@ -70,11 +70,12 @@ export async function prepareJobAsync(
|
||||
},
|
||||
releaseChannel: ctx.buildProfile.releaseChannel,
|
||||
updates: { channel: ctx.buildProfile.channel },
|
||||
developmentClient: buildProfile.developmentClient,
|
||||
|
||||
gradleCommand: buildProfile.gradleCommand,
|
||||
artifactPath: buildProfile.artifactPath,
|
||||
buildType,
|
||||
|
||||
username,
|
||||
buildType: buildProfile.developmentClient ? Android.BuildType.DEVELOPMENT_CLIENT : buildType,
|
||||
};
|
||||
|
||||
return sanitizeJob(job);
|
||||
|
||||
@@ -3,8 +3,6 @@ import { CredentialsSource } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { withAnalyticsAsync } from '../analytics/common';
|
||||
import { BuildEvent } from '../analytics/events';
|
||||
import { BuildFragment, BuildStatus, UploadSessionType } from '../graphql/generated';
|
||||
import { BuildResult } from '../graphql/mutations/BuildMutation';
|
||||
import { BuildQuery } from '../graphql/queries/BuildQuery';
|
||||
@@ -15,10 +13,12 @@ import { uploadAsync } from '../uploads';
|
||||
import { formatBytes } from '../utils/files';
|
||||
import { createProgressTracker } from '../utils/progress';
|
||||
import { sleepAsync } from '../utils/promise';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import vcs from '../vcs';
|
||||
import { BuildContext } from './context';
|
||||
import { runLocalBuildAsync } from './local';
|
||||
import { MetadataContext, collectMetadataAsync } from './metadata';
|
||||
import { TrackingContext } from './types';
|
||||
import Analytics, { Event } from './utils/analytics';
|
||||
import { printDeprecationWarnings } from './utils/printBuildInfo';
|
||||
import { makeProjectTarballAsync, reviewAndCommitChangesAsync } from './utils/repository';
|
||||
|
||||
@@ -55,22 +55,20 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
const credentialsResult = await withAnalyticsAsync(
|
||||
async () => await builder.ensureCredentialsAsync(ctx),
|
||||
{
|
||||
attemptEvent: BuildEvent.GATHER_CREDENTIALS_ATTEMPT,
|
||||
successEvent: BuildEvent.GATHER_CREDENTIALS_SUCCESS,
|
||||
failureEvent: BuildEvent.GATHER_CREDENTIALS_FAIL,
|
||||
successEvent: Event.GATHER_CREDENTIALS_SUCCESS,
|
||||
failureEvent: Event.GATHER_CREDENTIALS_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
}
|
||||
);
|
||||
if (!ctx.skipProjectConfiguration) {
|
||||
await withAnalyticsAsync(async () => await builder.ensureProjectConfiguredAsync(ctx), {
|
||||
attemptEvent: BuildEvent.CONFIGURE_PROJECT_ATTEMPT,
|
||||
successEvent: BuildEvent.CONFIGURE_PROJECT_SUCCESS,
|
||||
failureEvent: BuildEvent.CONFIGURE_PROJECT_FAIL,
|
||||
successEvent: Event.CONFIGURE_PROJECT_SUCCESS,
|
||||
failureEvent: Event.CONFIGURE_PROJECT_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
});
|
||||
}
|
||||
|
||||
if (await getVcsClient().isCommitRequiredAsync()) {
|
||||
if (await vcs.hasUncommittedChangesAsync()) {
|
||||
Log.addNewLineIfNone();
|
||||
await reviewAndCommitChangesAsync(
|
||||
`[EAS Build] Run EAS Build for ${requestedPlatformDisplayNames[ctx.platform as Platform]}`,
|
||||
@@ -168,9 +166,8 @@ async function uploadProjectAsync<TPlatform extends Platform>(
|
||||
return bucketKey;
|
||||
},
|
||||
{
|
||||
attemptEvent: BuildEvent.PROJECT_UPLOAD_ATTEMPT,
|
||||
successEvent: BuildEvent.PROJECT_UPLOAD_SUCCESS,
|
||||
failureEvent: BuildEvent.PROJECT_UPLOAD_FAIL,
|
||||
successEvent: Event.PROJECT_UPLOAD_SUCCESS,
|
||||
failureEvent: Event.PROJECT_UPLOAD_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
}
|
||||
);
|
||||
@@ -203,14 +200,34 @@ async function sendBuildRequestAsync<TPlatform extends Platform, Credentials, TJ
|
||||
return build;
|
||||
},
|
||||
{
|
||||
attemptEvent: BuildEvent.BUILD_REQUEST_ATTEMPT,
|
||||
successEvent: BuildEvent.BUILD_REQUEST_SUCCESS,
|
||||
failureEvent: BuildEvent.BUILD_REQUEST_FAIL,
|
||||
successEvent: Event.BUILD_REQUEST_SUCCESS,
|
||||
failureEvent: Event.BUILD_REQUEST_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function withAnalyticsAsync<Result>(
|
||||
fn: () => Promise<Result>,
|
||||
analytics: {
|
||||
successEvent: Event;
|
||||
failureEvent: Event;
|
||||
trackingCtx: TrackingContext;
|
||||
}
|
||||
): Promise<Result> {
|
||||
try {
|
||||
const result = await fn();
|
||||
Analytics.logEvent(analytics.successEvent, analytics.trackingCtx);
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
Analytics.logEvent(analytics.failureEvent, {
|
||||
...analytics.trackingCtx,
|
||||
reason: error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function waitForBuildEndAsync(
|
||||
buildIds: string[],
|
||||
{ timeoutSec = 3600, intervalSec = 30 } = {}
|
||||
|
||||
@@ -10,8 +10,7 @@ import { RequestedPlatform } from '../platform';
|
||||
import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { confirmAsync, promptAsync } from '../prompts';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import vcs from '../vcs';
|
||||
import { configureAndroidAsync } from './android/configure';
|
||||
import { ConfigureContext } from './context';
|
||||
import { configureIosAsync } from './ios/configure';
|
||||
@@ -38,7 +37,7 @@ export async function ensureProjectConfiguredAsync(
|
||||
projectDir,
|
||||
platform: requestedPlatform,
|
||||
});
|
||||
if (await getVcsClient().isCommitRequiredAsync()) {
|
||||
if (await vcs.hasUncommittedChangesAsync()) {
|
||||
error(
|
||||
'Build process requires clean working tree, please commit all your changes and run `eas build` again',
|
||||
{ exit: 1 }
|
||||
@@ -58,7 +57,7 @@ export async function configureAsync(options: {
|
||||
platform: RequestedPlatform;
|
||||
projectDir: string;
|
||||
}): Promise<void> {
|
||||
await getVcsClient().ensureRepoExistsAsync();
|
||||
await vcs.ensureRepoExistsAsync();
|
||||
await maybeBailOnRepoStatusAsync();
|
||||
|
||||
const { exp } = getConfig(options.projectDir, { skipSDKVersionRequirement: true });
|
||||
@@ -87,39 +86,31 @@ export async function configureAsync(options: {
|
||||
await configureIosAsync(ctx);
|
||||
}
|
||||
|
||||
if (await getVcsClient().isCommitRequiredAsync()) {
|
||||
if (await vcs.hasUncommittedChangesAsync()) {
|
||||
Log.newLine();
|
||||
await reviewAndCommitChangesAsync(configureCommitMessage[options.platform]);
|
||||
} else if (!(await getVcsClient().hasUncommittedChangesAsync())) {
|
||||
} else {
|
||||
Log.newLine();
|
||||
Log.withTick('No changes were necessary, the project is already configured correctly.');
|
||||
}
|
||||
}
|
||||
|
||||
const EAS_JSON_MANAGED_DEFAULT: EasJson = {
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
},
|
||||
build: {
|
||||
release: {},
|
||||
development: {
|
||||
developmentClient: true,
|
||||
distribution: 'internal',
|
||||
},
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
},
|
||||
production: {},
|
||||
},
|
||||
submit: {
|
||||
production: {},
|
||||
release: {},
|
||||
},
|
||||
};
|
||||
|
||||
const EAS_JSON_BARE_DEFAULT: EasJson = {
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
},
|
||||
const EAS_JSON_GENERIC_DEFAULT: EasJson = {
|
||||
build: {
|
||||
release: {},
|
||||
development: {
|
||||
distribution: 'internal',
|
||||
android: {
|
||||
@@ -129,13 +120,9 @@ const EAS_JSON_BARE_DEFAULT: EasJson = {
|
||||
buildConfiguration: 'Debug',
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
},
|
||||
production: {},
|
||||
},
|
||||
submit: {
|
||||
production: {},
|
||||
release: {},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -152,11 +139,11 @@ export async function ensureEasJsonExistsAsync(ctx: ConfigureContext): Promise<v
|
||||
|
||||
const easJson =
|
||||
ctx.hasAndroidNativeProject && ctx.hasIosNativeProject
|
||||
? EAS_JSON_BARE_DEFAULT
|
||||
? EAS_JSON_GENERIC_DEFAULT
|
||||
: EAS_JSON_MANAGED_DEFAULT;
|
||||
|
||||
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
|
||||
await getVcsClient().trackFileAsync(easJsonPath);
|
||||
await vcs.trackFileAsync(easJsonPath);
|
||||
Log.withTick('Generated eas.json');
|
||||
}
|
||||
|
||||
@@ -190,7 +177,7 @@ async function reviewAndCommitChangesAsync(
|
||||
await commitPromptAsync({ initialCommitMessage });
|
||||
Log.withTick('Committed changes');
|
||||
} else if (selected === ShouldCommitChanges.ShowDiffFirst) {
|
||||
await getVcsClient().showDiffAsync();
|
||||
await vcs.showDiffAsync();
|
||||
await reviewAndCommitChangesAsync(initialCommitMessage, false);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@ import JsonFile from '@expo/json-file';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { TrackingContext } from '../analytics/common';
|
||||
import { Analytics, BuildEvent } from '../analytics/events';
|
||||
import { CredentialsContext } from '../credentials/context';
|
||||
import { RequestedPlatform } from '../platform';
|
||||
import { getExpoConfig } from '../project/expoConfig';
|
||||
@@ -15,6 +13,8 @@ import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { findAccountByName } from '../user/Account';
|
||||
import { Actor } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { TrackingContext } from './types';
|
||||
import Analytics, { Event } from './utils/analytics';
|
||||
|
||||
export interface ConfigureContext {
|
||||
user: Actor;
|
||||
@@ -95,7 +95,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
project_type: workflow,
|
||||
...devClientProperties,
|
||||
};
|
||||
Analytics.logEvent(BuildEvent.BUILD_COMMAND, trackingCtx);
|
||||
Analytics.logEvent(Event.BUILD_COMMAND, trackingCtx);
|
||||
|
||||
return {
|
||||
accountName,
|
||||
|
||||
@@ -1,16 +1,30 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { IOSConfig } from '@expo/config-plugins';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import Log from '../../log';
|
||||
import { getProjectAccountName } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import vcs from '../../vcs';
|
||||
import { ensureValidVersions } from '../utils/updates';
|
||||
import { readPlistAsync, writePlistAsync } from './plist';
|
||||
|
||||
export async function configureUpdatesAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
|
||||
ensureValidVersions(exp);
|
||||
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
|
||||
let xcodeProject = IOSConfig.XcodeUtils.getPbxproj(projectDir);
|
||||
|
||||
if (!IOSConfig.Updates.isShellScriptBuildPhaseConfigured(projectDir, xcodeProject)) {
|
||||
xcodeProject = IOSConfig.Updates.ensureBundleReactNativePhaseContainsConfigurationScript(
|
||||
projectDir,
|
||||
xcodeProject
|
||||
);
|
||||
await fs.writeFile(
|
||||
IOSConfig.Paths.getPBXProjectPath(projectDir),
|
||||
// eslint-disable-next-line node/no-sync
|
||||
xcodeProject.writeSync()
|
||||
);
|
||||
}
|
||||
|
||||
let expoPlist = await readExpoPlistAsync(projectDir);
|
||||
if (!IOSConfig.Updates.isPlistConfigurationSynced(exp, expoPlist, accountName)) {
|
||||
@@ -27,7 +41,7 @@ export async function syncUpdatesConfigurationAsync(
|
||||
ensureValidVersions(exp);
|
||||
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
|
||||
try {
|
||||
await ensureUpdatesConfiguredAsync(projectDir);
|
||||
await ensureUpdatesConfiguredAsync(projectDir, exp);
|
||||
} catch (error) {
|
||||
Log.error(
|
||||
'expo-updates module is not configured. Please run "eas build:configure" first to configure the project'
|
||||
@@ -48,9 +62,15 @@ export async function syncUpdatesConfigurationAsync(
|
||||
}
|
||||
}
|
||||
|
||||
// Note: we assume here that Expo modules are properly configured in the project. Aside from that,
|
||||
// all that is needed on Expo SDK 43+ to configure expo-updates configuration in Expo.plist
|
||||
async function ensureUpdatesConfiguredAsync(projectDir: string): Promise<void> {
|
||||
async function ensureUpdatesConfiguredAsync(projectDir: string, exp: ExpoConfig): Promise<void> {
|
||||
const xcodeProject = IOSConfig.XcodeUtils.getPbxproj(projectDir);
|
||||
|
||||
if (!IOSConfig.Updates.isShellScriptBuildPhaseConfigured(projectDir, xcodeProject)) {
|
||||
const script = 'expo-updates/scripts/create-manifest-ios.sh';
|
||||
const buildPhase = '"Bundle React Native code and images"';
|
||||
throw new Error(`Path to ${script} is missing in a ${buildPhase} build phase.`);
|
||||
}
|
||||
|
||||
const expoPlist = await readExpoPlistAsync(projectDir);
|
||||
if (!IOSConfig.Updates.isPlistConfigurationSet(expoPlist)) {
|
||||
throw new Error('Missing values in Expo.plist');
|
||||
@@ -59,7 +79,7 @@ async function ensureUpdatesConfiguredAsync(projectDir: string): Promise<void> {
|
||||
|
||||
async function readExpoPlistAsync(projectDir: string): Promise<IOSConfig.ExpoPlist> {
|
||||
const expoPlistPath = IOSConfig.Paths.getExpoPlistPath(projectDir);
|
||||
return ((await readPlistAsync(expoPlistPath)) ?? {}) as IOSConfig.ExpoPlist;
|
||||
return (await readPlistAsync(expoPlistPath)) as IOSConfig.ExpoPlist;
|
||||
}
|
||||
|
||||
async function writeExpoPlistAsync(
|
||||
@@ -68,7 +88,7 @@ async function writeExpoPlistAsync(
|
||||
): Promise<void> {
|
||||
const expoPlistPath = IOSConfig.Paths.getExpoPlistPath(projectDir);
|
||||
await writePlistAsync(expoPlistPath, expoPlist);
|
||||
await getVcsClient().trackFileAsync(expoPlistPath);
|
||||
await vcs.trackFileAsync(expoPlistPath);
|
||||
}
|
||||
|
||||
export async function readReleaseChannelSafelyAsync(projectDir: string): Promise<string | null> {
|
||||
|
||||
@@ -4,7 +4,7 @@ import fs from 'fs-extra';
|
||||
import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
|
||||
import { readPlistAsync } from '../../../utils/plist';
|
||||
import { readPlistAsync } from '../plist';
|
||||
import {
|
||||
BumpStrategy,
|
||||
bumpVersionAsync,
|
||||
@@ -49,10 +49,10 @@ describe(evaluateTemplateString, () => {
|
||||
});
|
||||
});
|
||||
|
||||
// bare workflow
|
||||
// generic workflow
|
||||
describe(bumpVersionAsync, () => {
|
||||
it('bumps expo.ios.buildNumber and CFBundleVersion when strategy = BumpStrategy.BUILD_NUMBER', async () => {
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
const fakeExp = initGenericProject();
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.BUILD_NUMBER,
|
||||
@@ -74,7 +74,7 @@ describe(bumpVersionAsync, () => {
|
||||
});
|
||||
|
||||
it('bumps expo.ios.buildNumber and CFBundleVersion for non default Info.plist location', async () => {
|
||||
const fakeExp = initBareWorkflowProject({ infoPlistName: 'Info2.plist' });
|
||||
const fakeExp = initGenericProject({ infoPlistName: 'Info2.plist' });
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.BUILD_NUMBER,
|
||||
@@ -98,7 +98,7 @@ describe(bumpVersionAsync, () => {
|
||||
});
|
||||
|
||||
it('bumps expo.version and CFBundleShortVersionString when strategy = BumpStrategy.SHORT_VERSION', async () => {
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
const fakeExp = initGenericProject();
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.APP_VERSION,
|
||||
@@ -120,7 +120,7 @@ describe(bumpVersionAsync, () => {
|
||||
});
|
||||
|
||||
it('does not bump any version when strategy = BumpStrategy.NOOP', async () => {
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
const fakeExp = initGenericProject();
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.NOOP,
|
||||
@@ -194,9 +194,9 @@ describe(bumpVersionInAppJsonAsync, () => {
|
||||
});
|
||||
|
||||
describe(readBuildNumberAsync, () => {
|
||||
describe('bare project', () => {
|
||||
describe('generic project', () => {
|
||||
it('reads the build number from native code', async () => {
|
||||
const exp = initBareWorkflowProject();
|
||||
const exp = initGenericProject();
|
||||
const buildNumber = await readBuildNumberAsync('/repo', exp, {});
|
||||
expect(buildNumber).toBe('1');
|
||||
});
|
||||
@@ -212,14 +212,14 @@ describe(readBuildNumberAsync, () => {
|
||||
});
|
||||
|
||||
describe(readShortVersionAsync, () => {
|
||||
describe('bare project', () => {
|
||||
describe('generic project', () => {
|
||||
it('reads the short version from native code', async () => {
|
||||
const exp = initBareWorkflowProject();
|
||||
const exp = initGenericProject();
|
||||
const appVersion = await readShortVersionAsync('/repo', exp, {});
|
||||
expect(appVersion).toBe('1.0.0');
|
||||
});
|
||||
it('evaluates interpolated build number', async () => {
|
||||
const exp = initBareWorkflowProject({
|
||||
const exp = initGenericProject({
|
||||
appVersion: '$(CURRENT_PROJECT_VERSION)',
|
||||
});
|
||||
const buildNumber = await readShortVersionAsync('/repo', exp, {
|
||||
@@ -231,7 +231,7 @@ describe(readShortVersionAsync, () => {
|
||||
|
||||
describe('managed project', () => {
|
||||
it('reads the version from app config', async () => {
|
||||
const exp = initBareWorkflowProject();
|
||||
const exp = initGenericProject();
|
||||
const appVersion = await readShortVersionAsync('/repo', exp, {});
|
||||
expect(appVersion).toBe('1.0.0');
|
||||
});
|
||||
@@ -286,7 +286,7 @@ describe(getInfoPlistPath, () => {
|
||||
});
|
||||
});
|
||||
|
||||
function initBareWorkflowProject({
|
||||
function initGenericProject({
|
||||
appVersion = '1.0.0',
|
||||
version = '1',
|
||||
infoPlistName = 'Info.plist',
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { XCBuildConfiguration } from 'xcode';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
ensureBundleIdentifierIsDefinedForManagedProjectAsync,
|
||||
warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject,
|
||||
warnIfBundleIdentifierDefinedInAppConfigForGenericProject,
|
||||
} from '../../project/ios/bundleIdentifier';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { ConfigureContext } from '../context';
|
||||
@@ -20,7 +20,7 @@ export async function configureIosAsync(ctx: ConfigureContext): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject(ctx.projectDir, ctx.exp);
|
||||
warnIfBundleIdentifierDefinedInAppConfigForGenericProject(ctx.projectDir, ctx.exp);
|
||||
|
||||
if (isExpoUpdatesInstalled(ctx.projectDir)) {
|
||||
await configureUpdatesAsync(ctx.projectDir, ctx.exp);
|
||||
|
||||
@@ -20,11 +20,7 @@ export async function ensureIosCredentialsAsync(
|
||||
app: getAppFromContext(buildCtx.credentialsCtx),
|
||||
targets,
|
||||
iosCapabilitiesOptions: {
|
||||
entitlements: await resolveEntitlementsJsonAsync(
|
||||
buildCtx.projectDir,
|
||||
buildCtx.workflow,
|
||||
buildCtx.buildProfile.env ?? {}
|
||||
),
|
||||
entitlements: await resolveEntitlementsJsonAsync(buildCtx.projectDir, buildCtx.workflow),
|
||||
},
|
||||
distribution: buildCtx.buildProfile.distribution ?? 'store',
|
||||
enterpriseProvisioning: buildCtx.buildProfile.enterpriseProvisioning,
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { Env, Ios } from '@expo/eas-build-job';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { IosJobInput, IosJobSecretsInput } from '../../graphql/generated';
|
||||
import {
|
||||
DistributionType,
|
||||
IosBuildType,
|
||||
IosJobInput,
|
||||
IosJobSecretsInput,
|
||||
} from '../../graphql/generated';
|
||||
import { transformProjectArchive, transformWorkflow } from '../graphql';
|
||||
|
||||
export function transformJob(job: Ios.Job): IosJobInput {
|
||||
@@ -11,18 +16,30 @@ export function transformJob(job: Ios.Job): IosJobInput {
|
||||
projectRootDirectory: job.projectRootDirectory,
|
||||
releaseChannel: job.releaseChannel,
|
||||
updates: job.updates,
|
||||
distribution: job.distribution && transformDistributionType(job.distribution),
|
||||
secrets: transformIosSecrets(job.secrets),
|
||||
builderEnvironment: job.builderEnvironment,
|
||||
cache: job.cache,
|
||||
|
||||
scheme: job.scheme,
|
||||
buildConfiguration: job.buildConfiguration,
|
||||
artifactPath: job.artifactPath,
|
||||
|
||||
buildType: job.buildType && transformBuildType(job.buildType),
|
||||
username: job.username,
|
||||
developmentClient: job.developmentClient,
|
||||
simulator: job.simulator,
|
||||
};
|
||||
}
|
||||
|
||||
function transformDistributionType(distributionType: Ios.DistributionType): DistributionType {
|
||||
if (distributionType === 'store') {
|
||||
return DistributionType.Store;
|
||||
} else if (distributionType === 'internal') {
|
||||
return DistributionType.Internal;
|
||||
} else {
|
||||
return DistributionType.Simulator;
|
||||
}
|
||||
}
|
||||
|
||||
function transformIosSecrets(secrets: {
|
||||
buildCredentials?: Ios.BuildCredentials;
|
||||
env?: Env;
|
||||
@@ -44,3 +61,11 @@ function transformIosSecrets(secrets: {
|
||||
environmentSecrets: secrets.env,
|
||||
};
|
||||
}
|
||||
|
||||
function transformBuildType(buildType: Ios.BuildType): IosBuildType {
|
||||
if (buildType === Ios.BuildType.DEVELOPMENT_CLIENT) {
|
||||
return IosBuildType.DevelopmentClient;
|
||||
} else {
|
||||
return IosBuildType.Release;
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,12 @@ import plist from '@expo/plist';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
export async function readPlistAsync(plistPath: string): Promise<object | null> {
|
||||
export async function readPlistAsync(plistPath: string): Promise<object> {
|
||||
if (await fs.pathExists(plistPath)) {
|
||||
const expoPlistContent = await fs.readFile(plistPath, 'utf8');
|
||||
try {
|
||||
return plist.parse(expoPlistContent);
|
||||
} catch (err: any) {
|
||||
err.message = `Failed to parse ${plistPath}. ${err.message}`;
|
||||
throw err;
|
||||
}
|
||||
return plist.parse(expoPlistContent);
|
||||
} else {
|
||||
return null;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import slash from 'slash';
|
||||
import { IosCredentials, TargetCredentials } from '../../credentials/ios/types';
|
||||
import { getUsername } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import vcs from '../../vcs';
|
||||
import { BuildContext } from '../context';
|
||||
|
||||
interface JobData {
|
||||
@@ -25,7 +25,7 @@ export async function prepareJobAsync(
|
||||
jobData: JobData
|
||||
): Promise<Job> {
|
||||
const projectRootDirectory =
|
||||
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
slash(path.relative(await vcs.getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
const username = getUsername(ctx.exp, await ensureLoggedInAsync());
|
||||
const buildCredentials: Ios.Job['secrets']['buildCredentials'] = {};
|
||||
if (jobData.credentials) {
|
||||
@@ -40,6 +40,7 @@ export async function prepareJobAsync(
|
||||
platform: Platform.IOS,
|
||||
projectArchive: jobData.projectArchive,
|
||||
projectRootDirectory,
|
||||
distribution: ctx.buildProfile.simulator ? 'simulator' : ctx.buildProfile.distribution,
|
||||
builderEnvironment: {
|
||||
image: ctx.buildProfile.image,
|
||||
node: ctx.buildProfile.node,
|
||||
@@ -60,11 +61,14 @@ export async function prepareJobAsync(
|
||||
},
|
||||
releaseChannel: ctx.buildProfile.releaseChannel,
|
||||
updates: { channel: ctx.buildProfile.channel },
|
||||
developmentClient: ctx.buildProfile.developmentClient,
|
||||
simulator: ctx.buildProfile.simulator,
|
||||
|
||||
scheme: jobData.buildScheme,
|
||||
buildConfiguration: ctx.buildProfile.buildConfiguration,
|
||||
artifactPath: ctx.buildProfile.artifactPath,
|
||||
|
||||
buildType: ctx.buildProfile.developmentClient
|
||||
? Ios.BuildType.DEVELOPMENT_CLIENT
|
||||
: Ios.BuildType.RELEASE,
|
||||
username,
|
||||
};
|
||||
return sanitizeJob(job);
|
||||
|
||||
@@ -8,9 +8,9 @@ import type { XCBuildConfiguration } from 'xcode';
|
||||
import Log from '../../log';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
|
||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||
import { bumpAppVersionAsync } from '../utils/version';
|
||||
import { readPlistAsync, writePlistAsync } from './plist';
|
||||
|
||||
export enum BumpStrategy {
|
||||
APP_VERSION,
|
||||
@@ -128,10 +128,7 @@ export async function maybeResolveVersionsAsync(
|
||||
appBuildVersion: await readBuildNumberAsync(projectDir, exp, buildSettings),
|
||||
appVersion: await readShortVersionAsync(projectDir, exp, buildSettings),
|
||||
};
|
||||
} catch (err: any) {
|
||||
Log.warn('Failed to read app versions.');
|
||||
Log.warn(err.message);
|
||||
Log.warn('Proceeding anyway...');
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -177,7 +174,7 @@ async function readInfoPlistAsync(
|
||||
buildSettings: XCBuildConfiguration['buildSettings']
|
||||
): Promise<IOSConfig.InfoPlist> {
|
||||
const infoPlistPath = getInfoPlistPath(projectDir, buildSettings);
|
||||
return ((await readPlistAsync(infoPlistPath)) ?? {}) as IOSConfig.InfoPlist;
|
||||
return (await readPlistAsync(infoPlistPath)) as IOSConfig.InfoPlist;
|
||||
}
|
||||
|
||||
async function writeInfoPlistAsync({
|
||||
|
||||
@@ -1,27 +1,45 @@
|
||||
import { Job } from '@expo/eas-build-job';
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import Log from '../log';
|
||||
|
||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||
const PLUGIN_PACKAGE_VERSION = '0.0.50';
|
||||
|
||||
export async function runLocalBuildAsync(job: Job): Promise<void> {
|
||||
const { command, args } = getCommandAndArgs(job);
|
||||
await spawnAsync(command, args, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
|
||||
function getCommandAndArgs(job: Job): { command: string; args: string[] } {
|
||||
const jobBase64 = Buffer.from(JSON.stringify({ job })).toString('base64');
|
||||
if (process.env.EAS_LOCAL_BUILD_PLUGIN_PATH) {
|
||||
return {
|
||||
command: process.env.EAS_LOCAL_BUILD_PLUGIN_PATH,
|
||||
args: [jobBase64],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
command: 'npx',
|
||||
args: ['-y', `${PLUGIN_PACKAGE_NAME}@${PLUGIN_PACKAGE_VERSION}`, jobBase64],
|
||||
};
|
||||
const execNameOrPath =
|
||||
process.env.EAS_LOCAL_BUILD_PLUGIN_PATH ??
|
||||
(await findWithNodeResolutionAsync()) ??
|
||||
PLUGIN_PACKAGE_NAME;
|
||||
try {
|
||||
const arg = Buffer.from(JSON.stringify({ job })).toString('base64');
|
||||
await spawnAsync(execNameOrPath, [arg], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (err.code === 'ENOENT') {
|
||||
Log.warn(`Could not resolve executable ${execNameOrPath}.`);
|
||||
Log.warn(
|
||||
`Install ${PLUGIN_PACKAGE_NAME} package from npm e.g. ${chalk.bold(
|
||||
`npm install -g ${PLUGIN_PACKAGE_NAME}`
|
||||
)} and make sure it's in PATH.`
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function findWithNodeResolutionAsync(): Promise<string | undefined> {
|
||||
try {
|
||||
const resolvedPath = path.resolve(
|
||||
path.dirname(require.resolve(PLUGIN_PACKAGE_NAME)),
|
||||
'../bin/run'
|
||||
);
|
||||
if (await fs.pathExists(resolvedPath)) {
|
||||
return resolvedPath;
|
||||
}
|
||||
} catch {}
|
||||
return undefined;
|
||||
}
|
||||
@@ -7,8 +7,7 @@ import { GradleBuildContext } from '../project/android/gradle';
|
||||
import { getBundleIdentifierAsync } from '../project/ios/bundleIdentifier';
|
||||
import { getUsername } from '../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import vcs from '../vcs';
|
||||
import {
|
||||
readChannelSafelyAsync as readAndroidChannelSafelyAsync,
|
||||
readReleaseChannelSafelyAsync as readAndroidReleaseChannelSafelyAsync,
|
||||
@@ -22,6 +21,13 @@ import {
|
||||
import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios/version';
|
||||
import { isExpoUpdatesInstalled } from './utils/updates';
|
||||
|
||||
/**
|
||||
* We use require() to exclude package.json from TypeScript's analysis since it lives outside
|
||||
* the src directory and would change the directory structure of the emitted files
|
||||
* under the build directory
|
||||
*/
|
||||
const packageJSON = require('../../package.json');
|
||||
|
||||
export type MetadataContext<T extends Platform> = T extends Platform.ANDROID
|
||||
? AndroidMetadataContext
|
||||
: IosMetadataContext;
|
||||
@@ -47,7 +53,7 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
const metadata = {
|
||||
trackingContext: ctx.trackingCtx,
|
||||
...(await maybeResolveVersionsAsync(ctx, platformContext)),
|
||||
cliVersion: easCliVersion,
|
||||
cliVersion: packageJSON.version,
|
||||
workflow: ctx.workflow,
|
||||
credentialsSource: ctx.buildProfile.credentialsSource,
|
||||
sdkVersion: ctx.exp.sdkVersion,
|
||||
@@ -57,8 +63,7 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
appName: ctx.exp.name,
|
||||
appIdentifier: await resolveAppIdentifierAsync(ctx, platformContext),
|
||||
buildProfile: ctx.buildProfileName,
|
||||
gitCommitHash: await getVcsClient().getCommitHashAsync(),
|
||||
isGitWorkingTreeDirty: await getVcsClient().hasUncommittedChangesAsync(),
|
||||
gitCommitHash: await vcs.getCommitHashAsync(),
|
||||
username: getUsername(ctx.exp, await ensureLoggedInAsync()),
|
||||
...(ctx.platform === Platform.IOS && {
|
||||
iosEnterpriseProvisioning: resolveIosEnterpriseProvisioning(
|
||||
|
||||
@@ -12,3 +12,5 @@ export enum BuildDistributionType {
|
||||
INTERNAL = 'internal',
|
||||
SIMULATOR = 'simulator',
|
||||
}
|
||||
|
||||
export type TrackingContext = Record<string, string | number | boolean>;
|
||||
@@ -0,0 +1,29 @@
|
||||
import { logEvent } from '../../analytics';
|
||||
|
||||
export enum Event {
|
||||
BUILD_COMMAND = 'build cli build command',
|
||||
PROJECT_UPLOAD_SUCCESS = 'build cli project upload success',
|
||||
PROJECT_UPLOAD_FAIL = 'build cli project upload fail',
|
||||
GATHER_CREDENTIALS_SUCCESS = 'build cli gather credentials success',
|
||||
GATHER_CREDENTIALS_FAIL = 'build cli gather credentials fail',
|
||||
CONFIGURE_PROJECT_SUCCESS = 'build cli configure project success',
|
||||
CONFIGURE_PROJECT_FAIL = 'build cli configure project fail',
|
||||
BUILD_REQUEST_SUCCESS = 'build cli build request success',
|
||||
BUILD_REQUEST_FAIL = 'build cli build request fail',
|
||||
|
||||
BUILD_STATUS_COMMAND = 'build cli build status',
|
||||
|
||||
CREDENTIALS_SYNC_COMMAND = 'build cli credentials sync command',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_SUCCESS = 'build cli credentials sync update local success',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_FAIL = 'build cli credentials sync update local fail',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_SUCCESS = 'build cli credentials sync update remote success',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_FAIL = 'build cli credentials sync update remote fail',
|
||||
|
||||
ANDROID_KEYSTORE_CREATE = 'build cli credentials keystore create',
|
||||
}
|
||||
|
||||
export default {
|
||||
logEvent(name: Event, properties: Record<string, any>) {
|
||||
logEvent(name, properties);
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as ExpoConfig from '@expo/config';
|
||||
import JsonFile from '@expo/json-file';
|
||||
import assert from 'assert';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
export async function updateAppJsonConfigAsync(
|
||||
{
|
||||
@@ -15,19 +15,10 @@ export async function updateAppJsonConfigAsync(
|
||||
const paths = ExpoConfig.getConfigFilePaths(projectDir);
|
||||
assert(paths.staticConfigPath, "can't update dynamic config");
|
||||
|
||||
const rawStaticConfig = readAppJson(paths.staticConfigPath);
|
||||
const rawStaticConfig = await fs.readJSON(paths.staticConfigPath);
|
||||
rawStaticConfig.expo = rawStaticConfig.expo ?? {};
|
||||
modifyConfig(rawStaticConfig.expo);
|
||||
await JsonFile.writeAsync(paths.staticConfigPath, rawStaticConfig, { json5: false });
|
||||
await fs.writeJson(paths.staticConfigPath, rawStaticConfig, { spaces: 2 });
|
||||
|
||||
modifyConfig(exp);
|
||||
}
|
||||
|
||||
// TODO: remove this once @expo/config exports getStaticConfig
|
||||
export function readAppJson(appJsonPath: string): any {
|
||||
const config = JsonFile.read(appJsonPath, { json5: true });
|
||||
if (config) {
|
||||
return config as any;
|
||||
}
|
||||
throw new Error(`Failed to read app.json`);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Workflow } from '@expo/eas-build-job';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import { error } from '@oclif/errors';
|
||||
import chalk from 'chalk';
|
||||
import resolveFrom from 'resolve-from';
|
||||
@@ -8,38 +9,40 @@ import Log, { learnMore } from '../../log';
|
||||
import { appPlatformDisplayNames } from '../../platform';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { expoCommandAsync } from '../../utils/expoCli';
|
||||
import { ProfileData } from '../../utils/profiles';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { expoCommandAsync } from '../../utils/expoCommand';
|
||||
import zipObject from '../../utils/expodash/zipObject';
|
||||
import { reviewAndCommitChangesAsync } from './repository';
|
||||
|
||||
export async function ensureExpoDevClientInstalledForDevClientBuildsAsync({
|
||||
projectDir,
|
||||
platforms,
|
||||
profile,
|
||||
nonInteractive = false,
|
||||
buildProfiles = [],
|
||||
}: {
|
||||
projectDir: string;
|
||||
platforms: Platform[];
|
||||
profile: string;
|
||||
nonInteractive?: boolean;
|
||||
buildProfiles?: ProfileData<'build'>[];
|
||||
}): Promise<void> {
|
||||
if (await isExpoDevClientInstalledAsync(projectDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buildProfilesWithDevelopmentClientRequired = buildProfiles.filter(
|
||||
buildProfile => buildProfile.profile.developmentClient
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const devClientPerPlatformList = await Promise.all(
|
||||
platforms.map(async platform => {
|
||||
const buildProfile = await easJsonReader.readBuildProfileAsync(platform, profile);
|
||||
return buildProfile.developmentClient ?? false;
|
||||
})
|
||||
);
|
||||
|
||||
const isDevelopmentClientRequired = buildProfilesWithDevelopmentClientRequired.some(Boolean);
|
||||
|
||||
if (!isDevelopmentClientRequired) {
|
||||
const isDevClientRequired = devClientPerPlatformList.some(i => i);
|
||||
if (!isDevClientRequired) {
|
||||
return;
|
||||
}
|
||||
|
||||
const platformsToCheck = buildProfilesWithDevelopmentClientRequired.map(
|
||||
({ platform }) => platform
|
||||
);
|
||||
|
||||
const devClientPerPlatform = zipObject(platforms, devClientPerPlatformList);
|
||||
const platformsToCheck = platforms.filter(platform => devClientPerPlatform[platform]);
|
||||
const workflowPerPlatformList = await Promise.all(
|
||||
platformsToCheck.map(platform => resolveWorkflowAsync(projectDir, platform))
|
||||
);
|
||||
@@ -64,15 +67,15 @@ export async function ensureExpoDevClientInstalledForDevClientBuildsAsync({
|
||||
if (install) {
|
||||
await installExpoDevClientAsync(projectDir, { nonInteractive });
|
||||
} else {
|
||||
error(`Install ${chalk.bold('expo-dev-client')} manually and come back later.`, {
|
||||
error(`Install ${chalk.bold('expo-dev-client')} on your own and come back later.`, {
|
||||
exit: 1,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Log.warn(`You'll need to install ${chalk.bold('expo-dev-client')} manually.`);
|
||||
Log.warn(`Unfortunately, you need to install ${chalk.bold('expo-dev-client')} on your own.`);
|
||||
Log.warn(
|
||||
learnMore('https://docs.expo.dev/clients/installation/', {
|
||||
learnMoreMessage: 'See installation instructions on how to do it.',
|
||||
learnMoreMessage: 'See installation instructions on how to do it',
|
||||
dim: false,
|
||||
})
|
||||
);
|
||||
@@ -107,9 +110,7 @@ async function installExpoDevClientAsync(
|
||||
Log.newLine();
|
||||
await expoCommandAsync(projectDir, ['install', 'expo-dev-client']);
|
||||
Log.newLine();
|
||||
if (await getVcsClient().isCommitRequiredAsync()) {
|
||||
await reviewAndCommitChangesAsync('Install expo-dev-client', {
|
||||
nonInteractive,
|
||||
});
|
||||
}
|
||||
await reviewAndCommitChangesAsync('Install expo-dev-client', {
|
||||
nonInteractive,
|
||||
});
|
||||
}
|
||||
@@ -120,5 +120,11 @@ export function formatGraphQLBuild(build: BuildFragment): string {
|
||||
}
|
||||
|
||||
const getActorName = (build: BuildFragment): string => {
|
||||
return build.initiatingActor?.displayName || 'unknown';
|
||||
if (!build.initiatingActor) {
|
||||
return 'unknown';
|
||||
} else if (build.initiatingActor.__typename === 'User') {
|
||||
return build.initiatingActor.username;
|
||||
} else {
|
||||
return build.initiatingActor.firstName ?? 'unknown';
|
||||
}
|
||||
};
|
||||
@@ -10,10 +10,10 @@ import { confirmAsync, promptAsync } from '../../prompts';
|
||||
import { formatBytes } from '../../utils/files';
|
||||
import { getTmpDirectory } from '../../utils/paths';
|
||||
import { endTimer, formatMilliseconds, startTimer } from '../../utils/timer';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import vcs from '../../vcs';
|
||||
|
||||
export async function maybeBailOnRepoStatusAsync(): Promise<void> {
|
||||
if (!(await getVcsClient().isCommitRequiredAsync())) {
|
||||
if (!(await vcs.hasUncommittedChangesAsync())) {
|
||||
return;
|
||||
}
|
||||
Log.addNewLineIfNone();
|
||||
@@ -33,7 +33,7 @@ export async function maybeBailOnRepoStatusAsync(): Promise<void> {
|
||||
}
|
||||
|
||||
export async function ensureRepoIsCleanAsync(nonInteractive = false): Promise<void> {
|
||||
if (!(await getVcsClient().isCommitRequiredAsync())) {
|
||||
if (!(await vcs.hasUncommittedChangesAsync())) {
|
||||
return;
|
||||
}
|
||||
Log.addNewLineIfNone();
|
||||
@@ -70,7 +70,7 @@ export async function commitPromptAsync({
|
||||
initial: initialCommitMessage,
|
||||
validate: (input: string) => input !== '',
|
||||
});
|
||||
await getVcsClient().commitAsync({ commitAllFiles, commitMessage: message });
|
||||
await vcs.commitAsync({ commitAllFiles, commitMessage: message });
|
||||
}
|
||||
|
||||
export async function makeProjectTarballAsync(): Promise<{ path: string; size: number }> {
|
||||
@@ -95,7 +95,7 @@ export async function makeProjectTarballAsync(): Promise<{ path: string; size: n
|
||||
startTimer(compressTimerLabel);
|
||||
|
||||
try {
|
||||
await getVcsClient().makeShallowCopyAsync(shallowClonePath);
|
||||
await vcs.makeShallowCopyAsync(shallowClonePath);
|
||||
await tar.create({ cwd: shallowClonePath, file: tarPath, prefix: 'project', gzip: true }, [
|
||||
'.',
|
||||
]);
|
||||
@@ -133,11 +133,7 @@ export async function reviewAndCommitChangesAsync(
|
||||
{ nonInteractive, askedFirstTime = true }: { nonInteractive: boolean; askedFirstTime?: boolean }
|
||||
): Promise<void> {
|
||||
if (process.env.EAS_BUILD_AUTOCOMMIT) {
|
||||
await getVcsClient().commitAsync({
|
||||
commitMessage: initialCommitMessage,
|
||||
commitAllFiles: false,
|
||||
nonInteractive,
|
||||
});
|
||||
await vcs.commitAsync({ commitMessage: initialCommitMessage, commitAllFiles: false });
|
||||
Log.withTick('Committed changes.');
|
||||
return;
|
||||
}
|
||||
@@ -170,7 +166,7 @@ export async function reviewAndCommitChangesAsync(
|
||||
await commitPromptAsync({ initialCommitMessage });
|
||||
Log.withTick('Committed changes.');
|
||||
} else if (selected === ShouldCommitChanges.ShowDiffFirst) {
|
||||
await getVcsClient().showDiffAsync();
|
||||
await vcs.showDiffAsync();
|
||||
await reviewAndCommitChangesAsync(initialCommitMessage, {
|
||||
nonInteractive,
|
||||
askedFirstTime: false,
|
||||
|
||||
@@ -22,7 +22,7 @@ export function getArtifactUrl(artifactId: string): string {
|
||||
|
||||
export function getInternalDistributionInstallUrl(build: BuildFragment): string {
|
||||
if (build.platform === AppPlatform.Ios) {
|
||||
return `itms-services://?action=download-manifest;url=${getExpoApiBaseUrl()}/v2/projects/${
|
||||
return `itms-services://?action=download-manifest;url=${getExpoApiBaseUrl()}/--/api/v2/projects/${
|
||||
build.project.id
|
||||
}/builds/${build.id}/manifest.plist`;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import Log, { learnMore } from '../log';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import vcs from '../vcs';
|
||||
import { BuildContext } from './context';
|
||||
|
||||
export function checkNodeEnvVariable(ctx: BuildContext<Platform>): void {
|
||||
@@ -25,12 +25,12 @@ export async function checkGoogleServicesFileAsync<T extends Platform>(
|
||||
if (!googleServicesFilePath) {
|
||||
return;
|
||||
}
|
||||
const rootDir = path.normalize(await getVcsClient().getRootPathAsync());
|
||||
const rootDir = path.normalize(await vcs.getRootPathAsync());
|
||||
const absGoogleServicesFilePath = path.resolve(ctx.projectDir, googleServicesFilePath);
|
||||
if (
|
||||
(await fs.pathExists(absGoogleServicesFilePath)) &&
|
||||
(!isInsideDirectory(absGoogleServicesFilePath, rootDir) ||
|
||||
(await getVcsClient().isFileIgnoredAsync(path.relative(rootDir, absGoogleServicesFilePath))))
|
||||
(await vcs.isFileIgnoredAsync(path.relative(rootDir, absGoogleServicesFilePath))))
|
||||
) {
|
||||
Log.warn(
|
||||
`File specified via "${ctx.platform}.googleServicesFile" field in your app.json is not checked in to your repository and won't be uploaded to the builder.`
|
||||
@@ -41,7 +41,7 @@ export async function checkGoogleServicesFileAsync<T extends Platform>(
|
||||
)}`
|
||||
);
|
||||
Log.warn(
|
||||
`If you are using that file for compatibility with the classic build service (expo build) you can silence this warning by setting your build profile's env.GOOGLE_SERVICES_FILE in eas.json to any non-empty string.`
|
||||
'If you are using that file for compatibility with the classic build service (expo build) you can silence this warning by setting GOOGLE_SERVICES_FILE in your build profile in eas.json to any non-falsy value.'
|
||||
);
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import { Command } from '@oclif/command';
|
||||
import semver from 'semver';
|
||||
|
||||
import {
|
||||
AnalyticsEvent,
|
||||
flushAsync as flushAnalyticsAsync,
|
||||
initAsync as initAnalyticsAsync,
|
||||
logEvent,
|
||||
} from '../analytics/rudderstackClient';
|
||||
import Log from '../log';
|
||||
import { findProjectRootAsync } from '../project/projectUtils';
|
||||
} from '../analytics';
|
||||
import { getUserAsync } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { setVcsClient } from '../vcs';
|
||||
import GitClient from '../vcs/clients/git';
|
||||
|
||||
export default abstract class EasCommand extends Command {
|
||||
/**
|
||||
@@ -27,10 +20,7 @@ export default abstract class EasCommand extends Command {
|
||||
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
async run(): Promise<any> {
|
||||
EasJsonReader.setLog(Log);
|
||||
|
||||
await initAnalyticsAsync();
|
||||
await this.applyCliConfigAsync();
|
||||
|
||||
if (this.requiresAuthentication) {
|
||||
const { flags } = this.parse();
|
||||
@@ -52,18 +42,4 @@ export default abstract class EasCommand extends Command {
|
||||
await flushAnalyticsAsync();
|
||||
return super.finally(err);
|
||||
}
|
||||
|
||||
private async applyCliConfigAsync(): Promise<void> {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const config = await easJsonReader.getCliConfigAsync();
|
||||
if (config?.version && !semver.satisfies(easCliVersion, config.version)) {
|
||||
throw new Error(
|
||||
`You are on eas-cli@${easCliVersion} which does not satisfy the CLI version constraint in eas.json (${config.version})`
|
||||
);
|
||||
}
|
||||
if (config?.requireCommit) {
|
||||
setVcsClient(new GitClient());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { asMock } from '../../__tests__/utils';
|
||||
import { flushAsync, initAsync, logEvent } from '../../analytics/rudderstackClient';
|
||||
import { flushAsync, initAsync, logEvent } from '../../analytics';
|
||||
import { jester as mockJester } from '../../credentials/__tests__/fixtures-constants';
|
||||
import { getUserAsync } from '../../user/User';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
@@ -10,8 +10,8 @@ TestEasCommand.prototype.authValue = jest.fn().mockImplementation(() => true);
|
||||
|
||||
jest.mock('../../user/User');
|
||||
jest.mock('../../user/actions', () => ({ ensureLoggedInAsync: jest.fn() }));
|
||||
jest.mock('../../analytics/rudderstackClient', () => {
|
||||
const { AnalyticsEvent } = jest.requireActual('../../analytics/rudderstackClient');
|
||||
jest.mock('../../analytics', () => {
|
||||
const { AnalyticsEvent } = jest.requireActual('../../analytics');
|
||||
return {
|
||||
AnalyticsEvent,
|
||||
logEvent: jest.fn(),
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import vcs from '../../vcs';
|
||||
|
||||
export async function createUpdateBranchOnAppAsync({
|
||||
appId,
|
||||
@@ -90,8 +90,7 @@ export default class BranchCreate extends EasCommand {
|
||||
name: 'name',
|
||||
message: 'Please name the branch:',
|
||||
initial:
|
||||
(await getVcsClient().getBranchNameAsync()) ||
|
||||
`branch-${Math.random().toString(36).substr(2, 4)}`,
|
||||
(await vcs.getBranchNameAsync()) || `branch-${Math.random().toString(36).substr(2, 4)}`,
|
||||
validate: value => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@ async function getBranchInfoAsync({
|
||||
{
|
||||
appId,
|
||||
name,
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch'] }
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -44,8 +44,7 @@ export async function listBranchesAsync({
|
||||
{
|
||||
appId: projectId,
|
||||
limit: BRANCHES_LIMIT,
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch'] }
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ExpoConfig, getConfig } from '@expo/config';
|
||||
import { Updates } from '@expo/config-plugins';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { ExpoConfig, getConfig, getDefaultTarget } from '@expo/config';
|
||||
import { getRuntimeVersionForSDKVersion } from '@expo/sdk-runtime-versions';
|
||||
import { flags } from '@oclif/command';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
@@ -29,13 +28,11 @@ import {
|
||||
collectAssetsAsync,
|
||||
uploadAssetsAsync,
|
||||
} from '../../project/publish';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { promptAsync, selectAsync } from '../../prompts';
|
||||
import { formatUpdate } from '../../update/utils';
|
||||
import uniqBy from '../../utils/expodash/uniqBy';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { createUpdateChannelOnAppAsync } from '../channel/create';
|
||||
import vcs from '../../vcs';
|
||||
import { createUpdateBranchOnAppAsync } from './create';
|
||||
import { listBranchesAsync } from './list';
|
||||
import { viewUpdateBranchAsync } from './view';
|
||||
@@ -63,42 +60,13 @@ async function getUpdateGroupAsync({
|
||||
`,
|
||||
{
|
||||
group,
|
||||
},
|
||||
{ additionalTypenames: ['Update'] }
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return updatesByGroup;
|
||||
}
|
||||
|
||||
async function ensureChannelExistsAsync({
|
||||
appId,
|
||||
branchId,
|
||||
channelName,
|
||||
}: {
|
||||
appId: string;
|
||||
branchId: string;
|
||||
channelName: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
await createUpdateChannelOnAppAsync({
|
||||
appId,
|
||||
channelName,
|
||||
branchId,
|
||||
});
|
||||
Log.withTick(
|
||||
`Created a channel: ${chalk.bold(channelName)} pointed at branch: ${chalk.bold(channelName)}.`
|
||||
);
|
||||
} catch (e: any) {
|
||||
const isIgnorableError =
|
||||
e.graphQLErrors?.length === 1 &&
|
||||
e.graphQLErrors[0].extensions.errorCode === 'CHANNEL_ALREADY_EXISTS';
|
||||
if (!isIgnorableError) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureBranchExistsAsync({
|
||||
appId,
|
||||
name: branchName,
|
||||
@@ -119,13 +87,10 @@ async function ensureBranchExistsAsync({
|
||||
const updateBranch = app?.byId.updateBranchByName;
|
||||
if (updateBranch) {
|
||||
const { id, updates } = updateBranch;
|
||||
await ensureChannelExistsAsync({ appId, branchId: id, channelName: branchName });
|
||||
return { id, updates };
|
||||
}
|
||||
|
||||
const newUpdateBranch = await createUpdateBranchOnAppAsync({ appId, name: branchName });
|
||||
Log.withTick(`Created branch: ${chalk.bold(branchName)}`);
|
||||
await ensureChannelExistsAsync({ appId, branchId: newUpdateBranch.id, channelName: branchName });
|
||||
return { id: newUpdateBranch.id, updates: [] };
|
||||
}
|
||||
|
||||
@@ -203,14 +168,13 @@ export default class BranchPublish extends EasCommand {
|
||||
isPublicConfig: true,
|
||||
});
|
||||
|
||||
const runtimeVersions = await getRuntimeVersionObjectAsync(exp, platformFlag, projectDir);
|
||||
const runtimeVersions = getRuntimeVersionObject(exp, platformFlag, projectDir);
|
||||
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
if (!branchName && autoFlag) {
|
||||
branchName =
|
||||
(await getVcsClient().getBranchNameAsync()) ||
|
||||
`branch-${Math.random().toString(36).substr(2, 4)}`;
|
||||
(await vcs.getBranchNameAsync()) || `branch-${Math.random().toString(36).substr(2, 4)}`;
|
||||
}
|
||||
|
||||
if (!branchName) {
|
||||
@@ -333,7 +297,7 @@ export default class BranchPublish extends EasCommand {
|
||||
}
|
||||
|
||||
if (!message && autoFlag) {
|
||||
message = (await getVcsClient().getLastCommitMessageAsync())?.trim();
|
||||
message = (await vcs.getLastCommitMessageAsync())?.trim();
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
@@ -347,7 +311,7 @@ export default class BranchPublish extends EasCommand {
|
||||
message: `Please enter a publication message.`,
|
||||
initial: republish
|
||||
? `Republish "${oldMessage!}" - group: ${group}`
|
||||
: (await getVcsClient().getLastCommitMessageAsync())?.trim(),
|
||||
: (await vcs.getLastCommitMessageAsync())?.trim(),
|
||||
validate: value => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
@@ -424,26 +388,59 @@ export default class BranchPublish extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
async function getRuntimeVersionObjectAsync(
|
||||
function getRuntimeVersionObject(
|
||||
exp: ExpoConfig,
|
||||
platformFlag: PlatformFlag,
|
||||
platformFlag: string,
|
||||
projectDir: string
|
||||
): Promise<Record<string, string>> {
|
||||
const platforms = (platformFlag === 'all' ? ['android', 'ios'] : [platformFlag]) as Platform[];
|
||||
): Record<string, string> {
|
||||
let { runtimeVersion: defaultRuntimeVersion, sdkVersion } = exp;
|
||||
|
||||
for (const platform of platforms) {
|
||||
const isPolicy = typeof (exp[platform]?.runtimeVersion ?? exp.runtimeVersion) === 'object';
|
||||
if (isPolicy) {
|
||||
const isManaged = (await resolveWorkflowAsync(projectDir, platform)) === Workflow.MANAGED;
|
||||
if (!isManaged) {
|
||||
throw new Error('Runtime version policies are only supported in the managed workflow.');
|
||||
}
|
||||
// When a SDK version is supplied instead of a runtime version and we're in the managed workflow
|
||||
// construct the runtimeVersion with special meaning indicating that the runtime is an
|
||||
// Expo SDK preset runtime that can be launched in Expo Go.
|
||||
const isManagedProject = getDefaultTarget(projectDir) === 'managed';
|
||||
if (!defaultRuntimeVersion && sdkVersion && isManagedProject) {
|
||||
Log.withTick('Generating runtime version from sdk version');
|
||||
defaultRuntimeVersion = getRuntimeVersionForSDKVersion(sdkVersion);
|
||||
}
|
||||
const iOSRuntimeVersion = (exp as any).ios?.runtimeVersion; // TODO-JJ remove cast to any
|
||||
const androidRuntimeVersion = (exp as any).android?.runtimeVersion; // TODO-JJ remove cast to any
|
||||
let runtimeVersions: Record<string, string>;
|
||||
switch (platformFlag) {
|
||||
case 'ios': {
|
||||
runtimeVersions = {
|
||||
ios: iOSRuntimeVersion ?? defaultRuntimeVersion,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'android': {
|
||||
runtimeVersions = {
|
||||
android: androidRuntimeVersion ?? defaultRuntimeVersion,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'all': {
|
||||
runtimeVersions = {
|
||||
ios: iOSRuntimeVersion ?? defaultRuntimeVersion,
|
||||
android: androidRuntimeVersion ?? defaultRuntimeVersion,
|
||||
};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error('Platform flag must be "ios", "android", or "all"');
|
||||
}
|
||||
if (Object.values(runtimeVersions).some(runtime => !runtime)) {
|
||||
throw new Error(
|
||||
"Couldn't find a 'runtimeVersion' for every platform. Please specify it under the 'expo' key in 'app.json'"
|
||||
);
|
||||
}
|
||||
if (Object.values(runtimeVersions).some(runtime => typeof runtime !== 'string')) {
|
||||
throw new Error(
|
||||
`Please ensure that all of the runtime versions defined in the app.json are strings.`
|
||||
);
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
platforms.map(platform => [platform, Updates.getRuntimeVersion(exp, platform)])
|
||||
);
|
||||
return runtimeVersions;
|
||||
}
|
||||
|
||||
function formatUpdateTitle(
|
||||
|
||||
@@ -58,8 +58,7 @@ export async function viewUpdateBranchAsync({
|
||||
appId,
|
||||
name,
|
||||
limit: PAGE_LIMIT,
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch', 'Update'] }
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import {
|
||||
EasJsonReader,
|
||||
hasMismatchedExtendsAsync,
|
||||
isUsingDeprecatedFormatAsync,
|
||||
migrateAsync,
|
||||
} from '@expo/eas-json';
|
||||
import { flags } from '@oclif/command';
|
||||
import { error } from '@oclif/errors';
|
||||
import chalk from 'chalk';
|
||||
import figures from 'figures';
|
||||
import fs from 'fs-extra';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { prepareAndroidBuildAsync } from '../../build/android/build';
|
||||
@@ -14,7 +17,7 @@ import { BuildContext, createBuildContextAsync } from '../../build/context';
|
||||
import { prepareIosBuildAsync } from '../../build/ios/build';
|
||||
import { ensureExpoDevClientInstalledForDevClientBuildsAsync } from '../../build/utils/devClient';
|
||||
import { printBuildResults, printLogsUrls } from '../../build/utils/printBuildInfo';
|
||||
import { ensureRepoIsCleanAsync, reviewAndCommitChangesAsync } from '../../build/utils/repository';
|
||||
import { ensureRepoIsCleanAsync } from '../../build/utils/repository';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { CredentialsContext } from '../../credentials/context';
|
||||
import {
|
||||
@@ -24,7 +27,7 @@ import {
|
||||
SubmissionFragment,
|
||||
} from '../../graphql/generated';
|
||||
import { toAppPlatform, toPlatform } from '../../graphql/types/AppPlatform';
|
||||
import Log, { learnMore, link } from '../../log';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
RequestedPlatform,
|
||||
appPlatformDisplayNames,
|
||||
@@ -32,26 +35,27 @@ import {
|
||||
selectRequestedPlatformAsync,
|
||||
toPlatforms,
|
||||
} from '../../platform';
|
||||
import {
|
||||
EAS_UNAVAILABLE_MESSAGE,
|
||||
isEasEnabledForProjectAsync,
|
||||
} from '../../project/isEasEnabledForProject';
|
||||
import { validateMetroConfigForManagedWorkflowAsync } from '../../project/metroConfig';
|
||||
import { findProjectRootAsync } from '../../project/projectUtils';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { createSubmissionContextAsync } from '../../submit/context';
|
||||
import {
|
||||
submitAsync,
|
||||
waitToCompleteAsync as waitForSubmissionsToCompleteAsync,
|
||||
} from '../../submit/submit';
|
||||
import { printSubmissionDetailsUrls } from '../../submit/utils/urls';
|
||||
import { easCliVersion } from '../../utils/easCli';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
import { ProfileData, getProfilesAsync } from '../../utils/profiles';
|
||||
import { getVcsClient, setVcsClient } from '../../vcs';
|
||||
import GitClient from '../../vcs/clients/git';
|
||||
import vcs from '../../vcs';
|
||||
|
||||
interface RawBuildFlags {
|
||||
platform?: string;
|
||||
'skip-credentials-check': boolean;
|
||||
'skip-project-configuration': boolean;
|
||||
profile?: string;
|
||||
profile: string;
|
||||
'non-interactive': boolean;
|
||||
local: boolean;
|
||||
wait: boolean;
|
||||
@@ -64,7 +68,7 @@ interface RawBuildFlags {
|
||||
interface BuildFlags {
|
||||
requestedPlatform: RequestedPlatform;
|
||||
skipProjectConfiguration: boolean;
|
||||
profile: string | null;
|
||||
profile: string;
|
||||
nonInteractive: boolean;
|
||||
local: boolean;
|
||||
wait: boolean;
|
||||
@@ -95,8 +99,8 @@ export default class Build extends EasCommand {
|
||||
description: 'Skip project configuration',
|
||||
}),
|
||||
profile: flags.string({
|
||||
description:
|
||||
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
default: 'release',
|
||||
description: 'Name of the build profile from eas.json',
|
||||
helpValue: 'PROFILE_NAME',
|
||||
}),
|
||||
'non-interactive': flags.boolean({
|
||||
@@ -142,64 +146,50 @@ export default class Build extends EasCommand {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||
|
||||
await getVcsClient().ensureRepoExistsAsync();
|
||||
await vcs.ensureRepoExistsAsync();
|
||||
await ensureRepoIsCleanAsync(flags.nonInteractive);
|
||||
|
||||
await ensureProjectConfiguredAsync(projectDir, requestedPlatform);
|
||||
|
||||
const platforms = toPlatforms(requestedPlatform);
|
||||
const buildProfiles = await getProfilesAsync({
|
||||
type: 'build',
|
||||
projectDir,
|
||||
platforms,
|
||||
profileName: flags.profile,
|
||||
});
|
||||
|
||||
await ensureExpoDevClientInstalledForDevClientBuildsAsync({
|
||||
platforms,
|
||||
projectDir,
|
||||
profile: flags.profile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
buildProfiles,
|
||||
});
|
||||
|
||||
const startedBuilds: {
|
||||
build: BuildFragment;
|
||||
buildProfile: ProfileData<'build'>;
|
||||
}[] = [];
|
||||
const startedBuilds: BuildFragment[] = [];
|
||||
const buildCtxByPlatform: { [p in AppPlatform]?: BuildContext<Platform> } = {};
|
||||
|
||||
for (const buildProfile of buildProfiles) {
|
||||
for (const platform of platforms) {
|
||||
const { build: maybeBuild, buildCtx } = await this.prepareAndStartBuildAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
flags,
|
||||
moreBuilds: platforms.length > 1,
|
||||
buildProfile,
|
||||
});
|
||||
if (maybeBuild) {
|
||||
startedBuilds.push({ build: maybeBuild, buildProfile });
|
||||
startedBuilds.push(maybeBuild);
|
||||
}
|
||||
buildCtxByPlatform[toAppPlatform(buildProfile.platform)] = buildCtx;
|
||||
buildCtxByPlatform[toAppPlatform(platform)] = buildCtx;
|
||||
}
|
||||
|
||||
if (flags.local) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.newLine();
|
||||
printLogsUrls(startedBuilds.map(startedBuild => startedBuild.build));
|
||||
printLogsUrls(startedBuilds);
|
||||
Log.newLine();
|
||||
|
||||
const submissions: SubmissionFragment[] = [];
|
||||
if (flags.autoSubmit) {
|
||||
for (const startedBuild of startedBuilds) {
|
||||
for (const build of startedBuilds) {
|
||||
const submission = await this.prepareAndStartSubmissionAsync({
|
||||
build: startedBuild.build,
|
||||
credentialsCtx: nullthrows(
|
||||
buildCtxByPlatform[startedBuild.build.platform]?.credentialsCtx
|
||||
),
|
||||
build,
|
||||
credentialsCtx: nullthrows(buildCtxByPlatform[build.platform]?.credentialsCtx),
|
||||
flags,
|
||||
moreBuilds: startedBuilds.length > 1,
|
||||
projectDir,
|
||||
buildProfile: startedBuild.buildProfile,
|
||||
});
|
||||
submissions.push(submission);
|
||||
}
|
||||
@@ -213,7 +203,7 @@ export default class Build extends EasCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
const builds = await waitForBuildEndAsync(startedBuilds.map(({ build }) => build.id));
|
||||
const builds = await waitForBuildEndAsync(startedBuilds.map(build => build.id));
|
||||
printBuildResults(builds, flags.json);
|
||||
|
||||
const haveAllBuildsFailedOrCanceled = builds.every(
|
||||
@@ -262,42 +252,45 @@ export default class Build extends EasCommand {
|
||||
return {
|
||||
requestedPlatform,
|
||||
skipProjectConfiguration: flags['skip-project-configuration'],
|
||||
profile: profile ?? null,
|
||||
profile,
|
||||
nonInteractive,
|
||||
local: flags['local'],
|
||||
wait: flags['wait'],
|
||||
clearCache: flags['clear-cache'],
|
||||
json: flags['json'],
|
||||
autoSubmit: flags['auto-submit'] || flags['auto-submit-with-profile'] !== undefined,
|
||||
autoSubmit: flags['auto-submit'],
|
||||
submitProfile: flags['auto-submit-with-profile'] ?? profile,
|
||||
};
|
||||
}
|
||||
|
||||
private async prepareAndStartBuildAsync({
|
||||
private async prepareAndStartBuildAsync<T extends Platform>({
|
||||
projectDir,
|
||||
platform,
|
||||
flags,
|
||||
moreBuilds,
|
||||
buildProfile,
|
||||
}: {
|
||||
projectDir: string;
|
||||
platform: T;
|
||||
flags: BuildFlags;
|
||||
moreBuilds: boolean;
|
||||
buildProfile: ProfileData<'build'>;
|
||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<T> }> {
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const buildProfile = await easJsonReader.readBuildProfileAsync(platform, flags.profile);
|
||||
|
||||
const buildCtx = await createBuildContextAsync({
|
||||
buildProfileName: buildProfile.profileName,
|
||||
buildProfileName: flags.profile,
|
||||
clearCache: flags.clearCache,
|
||||
buildProfile: buildProfile.profile,
|
||||
buildProfile,
|
||||
local: flags.local,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
platform: buildProfile.platform,
|
||||
platform,
|
||||
projectDir,
|
||||
skipProjectConfiguration: flags.skipProjectConfiguration,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
Log.newLine();
|
||||
const appPlatform = toAppPlatform(buildProfile.platform);
|
||||
const appPlatform = toAppPlatform(platform);
|
||||
Log.log(
|
||||
`${appPlatformEmojis[appPlatform]} ${chalk.bold(
|
||||
`${appPlatformDisplayNames[appPlatform]} build`
|
||||
@@ -310,6 +303,10 @@ export default class Build extends EasCommand {
|
||||
this.metroConfigValidated = true;
|
||||
}
|
||||
|
||||
if (!buildCtx.local && !(await isEasEnabledForProjectAsync(buildCtx.projectId))) {
|
||||
error(EAS_UNAVAILABLE_MESSAGE, { exit: 1 });
|
||||
}
|
||||
|
||||
const build = await this.startBuildAsync(buildCtx);
|
||||
return {
|
||||
build,
|
||||
@@ -333,17 +330,16 @@ export default class Build extends EasCommand {
|
||||
flags,
|
||||
moreBuilds,
|
||||
projectDir,
|
||||
buildProfile,
|
||||
}: {
|
||||
build: BuildFragment;
|
||||
credentialsCtx: CredentialsContext;
|
||||
flags: BuildFlags;
|
||||
moreBuilds: boolean;
|
||||
projectDir: string;
|
||||
buildProfile: ProfileData<'build'>;
|
||||
}): Promise<SubmissionFragment> {
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const platform = toPlatform(build.platform);
|
||||
const buildProfile = await easJsonReader.readBuildProfileAsync(platform, flags.profile);
|
||||
const submitProfile = await easJsonReader.readSubmitProfileAsync(platform, flags.submitProfile);
|
||||
const submissionCtx = await createSubmissionContextAsync({
|
||||
platform,
|
||||
@@ -352,7 +348,7 @@ export default class Build extends EasCommand {
|
||||
profile: submitProfile,
|
||||
archiveFlags: { id: build.id },
|
||||
nonInteractive: flags.nonInteractive,
|
||||
env: buildProfile.profile.env,
|
||||
env: buildProfile.env,
|
||||
credentialsCtx,
|
||||
});
|
||||
|
||||
@@ -382,62 +378,36 @@ export async function handleDeprecatedEasJsonAsync(
|
||||
projectDir: string,
|
||||
nonInteractive: boolean
|
||||
): Promise<void> {
|
||||
const easJsonPath = EasJsonReader.formatEasJsonPath(projectDir);
|
||||
if (!(await fs.pathExists(easJsonPath))) {
|
||||
return;
|
||||
}
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const rawEasJson = await easJsonReader.readRawAsync();
|
||||
if (rawEasJson?.cli) {
|
||||
return;
|
||||
}
|
||||
if (await isUsingDeprecatedFormatAsync(projectDir)) {
|
||||
const hasMismatchedExtendsKeys = await hasMismatchedExtendsAsync(projectDir);
|
||||
if (nonInteractive) {
|
||||
Log.error('We detected that your eas.json is using a deprecated format.');
|
||||
Log.error(
|
||||
'We will convert it automatically if run this command without --non-interactive flag. Alternatively, you can update eas.json manually according to https://docs.expo.dev/build/eas-json'
|
||||
);
|
||||
error('Unsupported eas.json format', { exit: 1 });
|
||||
}
|
||||
|
||||
if (nonInteractive) {
|
||||
Log.warn(
|
||||
`${
|
||||
figures.warning
|
||||
} Action required: the default behavior of EAS CLI has changed and your eas.json must be updated to remove ambiguity around which Git integration workflow to use. Refer to ${link(
|
||||
'https://expo.fyi/eas-vcs-workflow'
|
||||
)} for more information.`
|
||||
);
|
||||
Log.warn(
|
||||
'This warning will become an error in an upcoming EAS CLI release. For now, we will proceed with the old default behavior to avoid disruption of your builds.'
|
||||
);
|
||||
setVcsClient(new GitClient());
|
||||
return;
|
||||
}
|
||||
Log.log(
|
||||
`${chalk.bold(
|
||||
'eas-cli@>=0.34.0 no longer requires that you commit changes to Git before starting a build.'
|
||||
)} ${learnMore('https://expo.fyi/eas-vcs-workflow')}`
|
||||
);
|
||||
Log.log(
|
||||
`If you want to continue using the Git integration, you can opt in with ${chalk.bold(
|
||||
'cli.requireCommit'
|
||||
)} in ${chalk.bold('eas.json')} or with the following prompt.`
|
||||
);
|
||||
Log.newLine();
|
||||
|
||||
const mode = await selectAsync('Select your preferred Git integration', [
|
||||
{ title: 'Require changes to be committed in Git (old default)', value: 'requireCommit' },
|
||||
{ title: 'Allow builds with dirty Git working tree (new default)', value: 'noCommit' },
|
||||
]);
|
||||
|
||||
if (mode === 'requireCommit') {
|
||||
setVcsClient(new GitClient());
|
||||
await ensureRepoIsCleanAsync(nonInteractive);
|
||||
}
|
||||
|
||||
rawEasJson.cli =
|
||||
mode === 'requireCommit'
|
||||
? { version: `>= ${easCliVersion}`, requireCommit: true }
|
||||
: { version: `>= ${easCliVersion}` };
|
||||
await fs.writeJSON(easJsonPath, rawEasJson, { spaces: 2 });
|
||||
Log.withTick('Updated eas.json');
|
||||
if (mode === 'requireCommit') {
|
||||
await getVcsClient().trackFileAsync(easJsonPath);
|
||||
await reviewAndCommitChangesAsync('Set cli.requireCommit to true in eas.json', {
|
||||
nonInteractive,
|
||||
const confirm = await confirmAsync({
|
||||
message:
|
||||
'We detected that your eas.json is using a deprecated format, do you want to migrate to the new format?',
|
||||
});
|
||||
if (confirm) {
|
||||
await migrateAsync(projectDir);
|
||||
if (hasMismatchedExtendsKeys) {
|
||||
Log.warn(
|
||||
'"extends" keyword can only be migrated automatically to the new format if both Android and iOS profiles extend base profiles with the same names for both platforms'
|
||||
);
|
||||
Log.warn(
|
||||
'Migration was successful, but you need to manually adjust the extend rules for your profiles'
|
||||
);
|
||||
error('Fix eas.json manually', { exit: 1 });
|
||||
}
|
||||
} else {
|
||||
Log.error(
|
||||
"Aborting, update your eas.json according to https://docs.expo.dev/build/eas-json and run 'eas build' again"
|
||||
);
|
||||
error('Unsupported eas.json format', { exit: 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import { promptAsync } from '../../prompts';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { createUpdateBranchOnAppAsync } from '../branch/create';
|
||||
|
||||
export async function createUpdateChannelOnAppAsync({
|
||||
async function createUpdateChannelOnAppAsync({
|
||||
appId,
|
||||
channelName,
|
||||
branchId,
|
||||
|
||||
@@ -49,8 +49,7 @@ async function getChannelByNameForAppAsync({
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ appId, channelName },
|
||||
{ additionalTypenames: ['UpdateChannel', 'UpdateBranch'] }
|
||||
{ appId, channelName }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -58,8 +58,7 @@ async function getAllUpdateChannelForAppAsync({
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ appId, offset: 0, limit: CHANNEL_LIMIT },
|
||||
{ additionalTypenames: ['UpdateChannel', 'UpdateBranch', 'Update'] }
|
||||
{ appId, offset: 0, limit: CHANNEL_LIMIT }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -133,8 +133,7 @@ export async function getUpdateChannelByNameForAppAsync({
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ appId, channelName },
|
||||
{ additionalTypenames: ['UpdateChannel', 'UpdateBranch', 'Update'] }
|
||||
{ appId, channelName }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -2,7 +2,8 @@ import envinfo from 'envinfo';
|
||||
|
||||
import EasCommand from '../commandUtils/EasCommand';
|
||||
import Log from '../log';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
|
||||
const packageJSON = require('../../package.json');
|
||||
|
||||
export default class Diagnostics extends EasCommand {
|
||||
static description = 'log environment info to the console';
|
||||
@@ -29,7 +30,7 @@ export default class Diagnostics extends EasCommand {
|
||||
npmGlobalPackages: ['eas-cli', 'expo-cli'],
|
||||
},
|
||||
{
|
||||
title: `EAS CLI ${easCliVersion} environment info`,
|
||||
title: `EAS CLI ${packageJSON.version} environment info`,
|
||||
}
|
||||
);
|
||||
Log.log(info);
|
||||
|
||||
@@ -22,8 +22,7 @@ async function projectInfoByIdAsync(appId: string): Promise<AppInfoQuery> {
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ appId },
|
||||
{ additionalTypenames: ['App'] }
|
||||
{ appId }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -9,6 +9,10 @@ import {
|
||||
EnvironmentSecretsQuery,
|
||||
} from '../../graphql/queries/EnvironmentSecretsQuery';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
isEasEnabledForProjectAsync,
|
||||
warnEasUnavailable,
|
||||
} from '../../project/isEasEnabledForProject';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectAccountNameAsync,
|
||||
@@ -53,6 +57,12 @@ export default class EnvironmentSecretCreate extends EasCommand {
|
||||
const { slug } = exp;
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
if (!(await isEasEnabledForProjectAsync(projectId))) {
|
||||
warnEasUnavailable();
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!scope) {
|
||||
const validationMessage = 'Secret scope may not be empty.';
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ import {
|
||||
EnvironmentSecretsQuery,
|
||||
} from '../../graphql/queries/EnvironmentSecretsQuery';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
isEasEnabledForProjectAsync,
|
||||
warnEasUnavailable,
|
||||
} from '../../project/isEasEnabledForProject';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectAccountNameAsync,
|
||||
@@ -19,7 +23,7 @@ import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
|
||||
export default class EnvironmentSecretDelete extends EasCommand {
|
||||
static description = `Delete an environment secret by ID.
|
||||
Unsure where to find the secret's ID? Run ${chalk.bold('eas secret:list')}`;
|
||||
Unsure where to find the secret's ID? Run ${chalk.bold('eas secrets:list')}`;
|
||||
|
||||
static flags = {
|
||||
id: flags.string({
|
||||
@@ -33,6 +37,12 @@ Unsure where to find the secret's ID? Run ${chalk.bold('eas secret:list')}`;
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectAccountName = await getProjectAccountNameAsync(exp);
|
||||
|
||||
if (!(await isEasEnabledForProjectAsync(projectId))) {
|
||||
warnEasUnavailable();
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
let {
|
||||
flags: { id },
|
||||
} = this.parse(EnvironmentSecretDelete);
|
||||
|
||||
@@ -6,6 +6,10 @@ import dateFormat from 'dateformat';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EnvironmentSecretsQuery } from '../../graphql/queries/EnvironmentSecretsQuery';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
isEasEnabledForProjectAsync,
|
||||
warnEasUnavailable,
|
||||
} from '../../project/isEasEnabledForProject';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectAccountNameAsync,
|
||||
@@ -21,6 +25,12 @@ export default class EnvironmentSecretList extends EasCommand {
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectAccountName = await getProjectAccountNameAsync(exp);
|
||||
|
||||
if (!(await isEasEnabledForProjectAsync(projectId))) {
|
||||
warnEasUnavailable();
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!projectDir) {
|
||||
throw new Error("Please run this command inside your project's directory");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import { flags } from '@oclif/command';
|
||||
import { error } from '@oclif/errors';
|
||||
import chalk from 'chalk';
|
||||
@@ -14,11 +15,11 @@ import {
|
||||
selectRequestedPlatformAsync,
|
||||
toPlatforms,
|
||||
} from '../platform';
|
||||
import { isEasEnabledForProjectAsync, warnEasUnavailable } from '../project/isEasEnabledForProject';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../project/projectUtils';
|
||||
import { SubmitArchiveFlags, createSubmissionContextAsync } from '../submit/context';
|
||||
import { submitAsync, waitToCompleteAsync } from '../submit/submit';
|
||||
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
|
||||
import { getProfilesAsync } from '../utils/profiles';
|
||||
|
||||
interface RawFlags {
|
||||
platform?: string;
|
||||
@@ -53,7 +54,7 @@ See how to configure submits with eas.json: ${link('https://docs.expo.dev/submit
|
||||
}),
|
||||
profile: flags.string({
|
||||
description:
|
||||
'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
'Name of the submit profile from eas.json. Defaults to "release" if defined in eas.json.',
|
||||
}),
|
||||
latest: flags.boolean({
|
||||
description: 'Submit the latest build for specified platform',
|
||||
@@ -94,28 +95,29 @@ See how to configure submits with eas.json: ${link('https://docs.expo.dev/submit
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
const platforms = toPlatforms(flags.requestedPlatform);
|
||||
const submissionProfiles = await getProfilesAsync({
|
||||
type: 'submit',
|
||||
projectDir,
|
||||
platforms,
|
||||
profileName: flags.profile,
|
||||
});
|
||||
if (!(await isEasEnabledForProjectAsync(projectId))) {
|
||||
warnEasUnavailable();
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const platforms = toPlatforms(flags.requestedPlatform);
|
||||
const submissions: SubmissionFragment[] = [];
|
||||
for (const submissionProfile of submissionProfiles) {
|
||||
for (const platform of platforms) {
|
||||
const profile = await easJsonReader.readSubmitProfileAsync(platform, flags.profile);
|
||||
const ctx = await createSubmissionContextAsync({
|
||||
platform: submissionProfile.platform,
|
||||
platform,
|
||||
projectDir,
|
||||
projectId,
|
||||
profile: submissionProfile.profile,
|
||||
profile,
|
||||
archiveFlags: flags.archiveFlags,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
});
|
||||
|
||||
if (submissionProfiles.length > 1) {
|
||||
if (platforms.length > 1) {
|
||||
Log.newLine();
|
||||
const appPlatform = toAppPlatform(submissionProfile.platform);
|
||||
const appPlatform = toAppPlatform(platform);
|
||||
Log.log(
|
||||
`${appPlatformEmojis[appPlatform]} ${chalk.bold(
|
||||
`${appPlatformDisplayNames[appPlatform]} submission`
|
||||
|
||||
@@ -39,8 +39,7 @@ export async function viewUpdateAsync({
|
||||
`,
|
||||
{
|
||||
groupId,
|
||||
},
|
||||
{ additionalTypenames: ['Update'] }
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AuthCtx } from '../ios/appstore/authenticate';
|
||||
export const testAuthCtx: AuthCtx = {
|
||||
appleId: 'test-apple-id',
|
||||
appleIdPassword: 'test-apple-password',
|
||||
team: { id: 'test-apple-team-identifier', name: 'test-team-name', inHouse: false },
|
||||
team: { id: 'test-team-id', name: 'test-team-name', inHouse: false },
|
||||
};
|
||||
|
||||
export function getAppstoreMock(): AppStoreApi {
|
||||
@@ -22,9 +22,5 @@ export function getAppstoreMock(): AppStoreApi {
|
||||
createProvisioningProfileAsync: jest.fn(),
|
||||
revokeProvisioningProfileAsync: jest.fn(),
|
||||
createOrReuseAdhocProvisioningProfileAsync: jest.fn(),
|
||||
createAscApiKeyAsync: jest.fn(),
|
||||
getAscApiKeyAsync: jest.fn(),
|
||||
revokeAscApiKeyAsync: jest.fn(),
|
||||
listAscApiKeysAsync: jest.fn(),
|
||||
} as any;
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
import { UserRole } from '@expo/apple-utils';
|
||||
|
||||
import {
|
||||
AppFragment,
|
||||
AppStoreConnectApiKeyFragment,
|
||||
AppStoreConnectUserRole,
|
||||
AppleAppIdentifierFragment,
|
||||
AppleDistributionCertificateFragment,
|
||||
AppleProvisioningProfileFragment,
|
||||
@@ -14,27 +10,12 @@ import {
|
||||
IosDistributionType,
|
||||
} from '../../graphql/generated';
|
||||
import * as IosGraphqlClient from '../ios/api/GraphqlClient';
|
||||
import {
|
||||
AscApiKey,
|
||||
DistributionCertificate,
|
||||
ProvisioningProfile,
|
||||
} from '../ios/appstore/Credentials.types';
|
||||
import { DistributionCertificate, ProvisioningProfile } from '../ios/appstore/Credentials.types';
|
||||
import { Target } from '../ios/types';
|
||||
import { testProvisioningProfileBase64 } from './fixtures-base64-data';
|
||||
|
||||
const now = new Date();
|
||||
|
||||
export const testAscApiKey: AscApiKey = {
|
||||
keyId: 'test-keyIdentifier',
|
||||
issuerId: 'test-issuer-id-from-apple',
|
||||
teamId: 'test-team-id',
|
||||
teamName: 'test-team-name',
|
||||
name: 'test-name',
|
||||
roles: [UserRole.ADMIN],
|
||||
isRevoked: false,
|
||||
keyP8: 'test-key-p8',
|
||||
};
|
||||
|
||||
export const testProvisioningProfile: ProvisioningProfile = {
|
||||
provisioningProfileId: 'test-id',
|
||||
provisioningProfile: testProvisioningProfileBase64,
|
||||
@@ -57,17 +38,6 @@ export const testAppFragment: AppFragment = {
|
||||
slug: 'testapp',
|
||||
};
|
||||
|
||||
export const testAscApiKeyFragment: AppStoreConnectApiKeyFragment = {
|
||||
id: 'test-asc-api-key-id',
|
||||
appleTeam: { ...testAppleTeamFragment },
|
||||
issuerIdentifier: 'test-issuerIdentifier',
|
||||
keyIdentifier: 'test-keyIdentifier',
|
||||
name: 'test-name',
|
||||
roles: [AppStoreConnectUserRole.Admin],
|
||||
updatedAt: now,
|
||||
createdAt: now,
|
||||
};
|
||||
|
||||
export const testPushKey: ApplePushKeyFragment = {
|
||||
id: 'test-push-key-id',
|
||||
keyIdentifier: 'test-key-identifier',
|
||||
@@ -142,7 +112,6 @@ export const testCommonIosAppCredentialsFragment: CommonIosAppCredentialsFragmen
|
||||
appleAppIdentifier: testAppleAppIdentifierFragment,
|
||||
pushKey: testPushKey,
|
||||
iosAppBuildCredentialsList: [testIosAppBuildCredentialsFragment],
|
||||
appStoreConnectApiKeyForSubmissions: testAscApiKeyFragment,
|
||||
};
|
||||
|
||||
export const testAppleTeam = {
|
||||
@@ -170,9 +139,6 @@ export function getNewIosApiMock(): { [key in keyof typeof IosGraphqlClient]?: a
|
||||
createPushKeyAsync: jest.fn(),
|
||||
getPushKeyForAppAsync: jest.fn(),
|
||||
deletePushKeyAsync: jest.fn(),
|
||||
createAscApiKeyAsync: jest.fn(),
|
||||
deleteAscApiKeyAsync: jest.fn(),
|
||||
getAscApiKeysForAccountAsync: jest.fn(() => []),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import nullthrows from 'nullthrows';
|
||||
import { AndroidAppBuildCredentialsFragment } from '../../graphql/generated';
|
||||
import { CredentialsContext } from '../context';
|
||||
import * as credentialsJsonReader from '../credentialsJson/read';
|
||||
import { SetUpBuildCredentials } from './actions/SetUpBuildCredentials';
|
||||
import { SetupBuildCredentials } from './actions/SetupBuildCredentials';
|
||||
import { AppLookupParams } from './api/GraphqlClient';
|
||||
import { Keystore } from './credentials';
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class AndroidCredentialsProvider {
|
||||
}
|
||||
|
||||
private async getRemoteAsync(): Promise<AndroidCredentials> {
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentials({ app: this.options.app });
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentials({ app: this.options.app });
|
||||
const buildCredentials = await setupBuildCredentialsAction.runAsync(this.ctx);
|
||||
return this.toAndroidCredentials(buildCredentials);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export class RemoveFcm {
|
||||
async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(
|
||||
"Deleting an FCM API Key is a destructive operation. Start the CLI without the '--non-interactive' flag to delete the credentials."
|
||||
"Deleting an FCM Api Key is a destructive operation. Start the CLI without the '--non-interactive' flag to delete the credentials."
|
||||
);
|
||||
}
|
||||
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(
|
||||
@@ -18,7 +18,7 @@ export class RemoveFcm {
|
||||
const fcm = appCredentials?.androidFcm;
|
||||
if (!fcm) {
|
||||
Log.warn(
|
||||
`There is no valid FCM API Key defined for ${formatProjectFullName(this.app)}, ${
|
||||
`There is no valid FCM Api Key defined for ${formatProjectFullName(this.app)}, ${
|
||||
this.app.androidApplicationIdentifier
|
||||
}`
|
||||
);
|
||||
@@ -26,7 +26,7 @@ export class RemoveFcm {
|
||||
}
|
||||
|
||||
const confirm = await confirmAsync({
|
||||
message: 'Permanently delete the FCM API Key from Expo servers?',
|
||||
message: 'Permanently delete the FCM Api Key from Expo servers?',
|
||||
initial: false,
|
||||
});
|
||||
if (!confirm) {
|
||||
@@ -34,6 +34,6 @@ export class RemoveFcm {
|
||||
}
|
||||
|
||||
await ctx.android.deleteFcmAsync(fcm);
|
||||
Log.succeed('FCM API Key removed');
|
||||
Log.succeed('FCM Api Key removed');
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -25,7 +25,7 @@ interface Options {
|
||||
* Sets up Build Credentials for Android
|
||||
* @name: sets up build credentials for the specified configuration. If no name is specified, the default configuration is setup
|
||||
*/
|
||||
export class SetUpBuildCredentials {
|
||||
export class SetupBuildCredentials {
|
||||
constructor(private options: Options) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<AndroidAppBuildCredentialsFragment> {
|
||||
+1
-1
@@ -14,7 +14,7 @@ import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { getKeystoreWithType } from '../utils/keystoreNew';
|
||||
import { BackupKeystore } from './DownloadKeystore';
|
||||
|
||||
export class SetUpBuildCredentialsFromCredentialsJson {
|
||||
export class SetupBuildCredentialsFromCredentialsJson {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<AndroidAppBuildCredentialsFragment | null> {
|
||||
+1
-1
@@ -13,7 +13,7 @@ import { AssignGoogleServiceAccountKey } from './AssignGoogleServiceAccountKey';
|
||||
import { CreateGoogleServiceAccountKey } from './CreateGoogleServiceAccountKey';
|
||||
import { UseExistingGoogleServiceAccountKey } from './UseExistingGoogleServiceAccountKey';
|
||||
|
||||
export class SetUpGoogleServiceAccountKey {
|
||||
export class SetupGoogleServiceAccountKey {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
public async runAsync(ctx: CredentialsContext): Promise<CommonAndroidAppCredentialsFragment> {
|
||||
@@ -12,7 +12,7 @@ jest.mock('../../../../prompts');
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe(RemoveFcm, () => {
|
||||
it('removes an FCM API Key', async () => {
|
||||
it('removes an FCM Api Key', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
android: {
|
||||
|
||||
+5
-5
@@ -9,7 +9,7 @@ import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import { generateRandomKeystoreAsync } from '../../utils/keystore';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { SetUpBuildCredentials } from '../SetUpBuildCredentials';
|
||||
import { SetupBuildCredentials } from '../SetupBuildCredentials';
|
||||
|
||||
jest.mock('../../../../ora');
|
||||
jest.mock('../../../../project/ensureProjectExists');
|
||||
@@ -17,7 +17,7 @@ jest.mock('../../../../user/actions', () => ({ ensureLoggedInAsync: jest.fn(() =
|
||||
jest.mock('../../../../prompts', () => ({ confirmAsync: jest.fn(() => true) }));
|
||||
jest.mock('../../utils/keystore', () => ({ generateRandomKeystoreAsync: jest.fn() }));
|
||||
|
||||
describe('SetUpBuildCredentials', () => {
|
||||
describe('SetupBuildCredentials', () => {
|
||||
beforeEach(() => {
|
||||
asMock(generateRandomKeystoreAsync).mockReset();
|
||||
});
|
||||
@@ -33,7 +33,7 @@ describe('SetUpBuildCredentials', () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentials({ app: appLookupParams });
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentials({ app: appLookupParams });
|
||||
await setupBuildCredentialsAction.runAsync(ctx);
|
||||
|
||||
// expect keystore not to be created
|
||||
@@ -48,7 +48,7 @@ describe('SetUpBuildCredentials', () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentials({ app: appLookupParams });
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentials({ app: appLookupParams });
|
||||
await setupBuildCredentialsAction.runAsync(ctx);
|
||||
|
||||
// expect keystore to be created
|
||||
@@ -63,7 +63,7 @@ describe('SetUpBuildCredentials', () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentials({ app: appLookupParams });
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentials({ app: appLookupParams });
|
||||
await expect(setupBuildCredentialsAction.runAsync(ctx)).rejects.toThrowError(
|
||||
MissingCredentialsNonInteractiveError
|
||||
);
|
||||
+5
-5
@@ -14,7 +14,7 @@ import {
|
||||
SelectAndroidBuildCredentialsResultType,
|
||||
} from '../../../manager/SelectAndroidBuildCredentials';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { SetUpBuildCredentialsFromCredentialsJson } from '../SetUpBuildCredentialsFromCredentialsJson';
|
||||
import { SetupBuildCredentialsFromCredentialsJson } from '../SetupBuildCredentialsFromCredentialsJson';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
@@ -26,7 +26,7 @@ beforeEach(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
describe(SetUpBuildCredentialsFromCredentialsJson, () => {
|
||||
describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
it('sets up a new build configuration from credentials.json upon user request', async () => {
|
||||
asMock(SelectAndroidBuildCredentials).mockImplementation(() => {
|
||||
return {
|
||||
@@ -59,7 +59,7 @@ describe(SetUpBuildCredentialsFromCredentialsJson, () => {
|
||||
'keystore.jks': 'some-binary-content',
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentialsFromCredentialsJson(
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams
|
||||
);
|
||||
await setupBuildCredentialsAction.runAsync(ctx);
|
||||
@@ -109,7 +109,7 @@ describe(SetUpBuildCredentialsFromCredentialsJson, () => {
|
||||
'keystore.jks': 'some-binary-content',
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentialsFromCredentialsJson(
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams
|
||||
);
|
||||
await setupBuildCredentialsAction.runAsync(ctx);
|
||||
@@ -132,7 +132,7 @@ describe(SetUpBuildCredentialsFromCredentialsJson, () => {
|
||||
nonInteractive: true,
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentialsFromCredentialsJson(
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams
|
||||
);
|
||||
await expect(setupBuildCredentialsAction.runAsync(ctx)).rejects.toThrowError();
|
||||
+5
-5
@@ -10,7 +10,7 @@ import {
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { SetUpGoogleServiceAccountKey } from '../SetUpGoogleServiceAccountKey';
|
||||
import { SetupGoogleServiceAccountKey } from '../SetupGoogleServiceAccountKey';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('fs');
|
||||
@@ -22,7 +22,7 @@ beforeEach(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
describe(SetUpGoogleServiceAccountKey, () => {
|
||||
describe(SetupGoogleServiceAccountKey, () => {
|
||||
it('skips setup when there is a Google Service Account Key already assigned to the project', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
@@ -34,7 +34,7 @@ describe(SetUpGoogleServiceAccountKey, () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKey(appLookupParams);
|
||||
const setupGoogleServiceAccountKeyAction = new SetupGoogleServiceAccountKey(appLookupParams);
|
||||
await setupGoogleServiceAccountKeyAction.runAsync(ctx);
|
||||
|
||||
expect(ctx.android.createGoogleServiceAccountKeyAsync).not.toHaveBeenCalled();
|
||||
@@ -57,7 +57,7 @@ describe(SetUpGoogleServiceAccountKey, () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKey(appLookupParams);
|
||||
const setupGoogleServiceAccountKeyAction = new SetupGoogleServiceAccountKey(appLookupParams);
|
||||
await setupGoogleServiceAccountKeyAction.runAsync(ctx);
|
||||
|
||||
expect(ctx.android.createGoogleServiceAccountKeyAsync).toHaveBeenCalledTimes(1);
|
||||
@@ -68,7 +68,7 @@ describe(SetUpGoogleServiceAccountKey, () => {
|
||||
nonInteractive: true,
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKey(appLookupParams);
|
||||
const setupGoogleServiceAccountKeyAction = new SetupGoogleServiceAccountKey(appLookupParams);
|
||||
await expect(setupGoogleServiceAccountKeyAction.runAsync(ctx)).rejects.toThrowError(
|
||||
MissingCredentialsNonInteractiveError
|
||||
);
|
||||
-3
@@ -29,9 +29,6 @@ export const GoogleServiceAccountKeyQuery = {
|
||||
`,
|
||||
{
|
||||
accountName,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['GoogleServiceAccountKey'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
|
||||
@@ -1,35 +1,31 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import chalk from 'chalk';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs-extra';
|
||||
// (dsokal) We actually want to use node-fetch but the change is in progress.
|
||||
// See https://github.com/expo/eas-cli/issues/32 for context.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import fetch from 'node-fetch';
|
||||
import path from 'path';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { Analytics, BuildEvent } from '../../../analytics/events';
|
||||
import Analytics, { Event } from '../../../build/utils/analytics';
|
||||
import { AndroidKeystoreType } from '../../../graphql/generated';
|
||||
import { KeystoreGenerationUrlMutation } from '../../../graphql/mutations/KeystoreGenerationUrlMutation';
|
||||
import Log from '../../../log';
|
||||
import { ora } from '../../../ora';
|
||||
import { getTmpDirectory } from '../../../utils/paths';
|
||||
import { KeystoreWithType } from '../credentials';
|
||||
|
||||
interface KeystoreParams {
|
||||
keystorePassword: string;
|
||||
keyAlias: string;
|
||||
keyPassword: string;
|
||||
}
|
||||
|
||||
export async function keytoolCommandExistsAsync(): Promise<boolean> {
|
||||
try {
|
||||
await spawnAsync('keytool');
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function ensureKeytoolCommandExistsAsync(): Promise<void> {
|
||||
if (!(await keytoolCommandExistsAsync())) {
|
||||
Log.error('keytool is required to run this command, make sure you have it installed?');
|
||||
Log.warn('keytool is a part of OpenJDK: https://openjdk.java.net/');
|
||||
Log.warn('Also make sure that keytool is in your PATH after installation.');
|
||||
throw new Error('keytool not found');
|
||||
}
|
||||
}
|
||||
|
||||
enum KeystoreCreateStep {
|
||||
@@ -39,7 +35,7 @@ enum KeystoreCreateStep {
|
||||
}
|
||||
|
||||
export async function generateRandomKeystoreAsync(projectId: string): Promise<KeystoreWithType> {
|
||||
const keystoreData: KeystoreParams = {
|
||||
const keystoreData = {
|
||||
keystorePassword: crypto.randomBytes(16).toString('hex'),
|
||||
keyPassword: crypto.randomBytes(16).toString('hex'),
|
||||
keyAlias: crypto.randomBytes(16).toString('hex'),
|
||||
@@ -48,38 +44,23 @@ export async function generateRandomKeystoreAsync(projectId: string): Promise<Ke
|
||||
}
|
||||
|
||||
async function createKeystoreAsync(
|
||||
keystoreParams: KeystoreParams,
|
||||
credentials: {
|
||||
keystorePassword: string;
|
||||
keyAlias: string;
|
||||
keyPassword: string;
|
||||
},
|
||||
projectId: string
|
||||
): Promise<KeystoreWithType> {
|
||||
Analytics.logEvent(BuildEvent.ANDROID_KEYSTORE_CREATE, {
|
||||
Analytics.logEvent(Event.ANDROID_KEYSTORE_CREATE, {
|
||||
project_id: projectId,
|
||||
step: KeystoreCreateStep.Attempt,
|
||||
type: AndroidKeystoreType.Jks,
|
||||
});
|
||||
|
||||
try {
|
||||
let keystore: KeystoreWithType | undefined;
|
||||
let localAttempt = false;
|
||||
if (await keytoolCommandExistsAsync()) {
|
||||
try {
|
||||
localAttempt = true;
|
||||
keystore = await createKeystoreLocallyAsync(keystoreParams);
|
||||
} catch {
|
||||
Log.error('Failed to generate keystore locally. Falling back to cloud generation.');
|
||||
}
|
||||
}
|
||||
if (!keystore) {
|
||||
keystore = await createKeystoreInCloudAsync(keystoreParams, {
|
||||
showKeytoolDetectionMsg: !localAttempt,
|
||||
});
|
||||
}
|
||||
Analytics.logEvent(BuildEvent.ANDROID_KEYSTORE_CREATE, {
|
||||
project_id: projectId,
|
||||
step: KeystoreCreateStep.Success,
|
||||
type: AndroidKeystoreType.Jks,
|
||||
});
|
||||
return keystore;
|
||||
await ensureKeytoolCommandExistsAsync();
|
||||
} catch (error: any) {
|
||||
Analytics.logEvent(BuildEvent.ANDROID_KEYSTORE_CREATE, {
|
||||
Analytics.logEvent(Event.ANDROID_KEYSTORE_CREATE, {
|
||||
project_id: projectId,
|
||||
step: KeystoreCreateStep.Fail,
|
||||
reason: error.message,
|
||||
@@ -87,11 +68,7 @@ async function createKeystoreAsync(
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function createKeystoreLocallyAsync(
|
||||
keystoreParams: KeystoreParams
|
||||
): Promise<KeystoreWithType> {
|
||||
await fs.mkdirp(getTmpDirectory());
|
||||
const keystorePath = path.join(getTmpDirectory(), `${uuidv4()}-keystore.jks`);
|
||||
try {
|
||||
@@ -101,13 +78,13 @@ async function createKeystoreLocallyAsync(
|
||||
'-storetype',
|
||||
'JKS',
|
||||
'-storepass',
|
||||
keystoreParams.keystorePassword,
|
||||
credentials.keystorePassword,
|
||||
'-keypass',
|
||||
keystoreParams.keyPassword,
|
||||
credentials.keyPassword,
|
||||
'-keystore',
|
||||
keystorePath,
|
||||
'-alias',
|
||||
keystoreParams.keyAlias,
|
||||
credentials.keyAlias,
|
||||
'-keyalg',
|
||||
'RSA',
|
||||
'-keysize',
|
||||
@@ -117,49 +94,27 @@ async function createKeystoreLocallyAsync(
|
||||
'-dname',
|
||||
`CN=,OU=,O=,L=,S=,C=US`,
|
||||
]);
|
||||
|
||||
Analytics.logEvent(Event.ANDROID_KEYSTORE_CREATE, {
|
||||
project_id: projectId,
|
||||
step: KeystoreCreateStep.Success,
|
||||
type: AndroidKeystoreType.Jks,
|
||||
});
|
||||
|
||||
return {
|
||||
...keystoreParams,
|
||||
...credentials,
|
||||
keystore: await fs.readFile(keystorePath, 'base64'),
|
||||
type: AndroidKeystoreType.Jks,
|
||||
};
|
||||
} catch (error: any) {
|
||||
Analytics.logEvent(Event.ANDROID_KEYSTORE_CREATE, {
|
||||
project_id: projectId,
|
||||
step: KeystoreCreateStep.Fail,
|
||||
reason: error.message,
|
||||
type: AndroidKeystoreType.Jks,
|
||||
});
|
||||
throw error;
|
||||
} finally {
|
||||
await fs.remove(keystorePath);
|
||||
}
|
||||
}
|
||||
|
||||
interface KeystoreServiceResult {
|
||||
keystoreBase64: string;
|
||||
keystorePassword: string;
|
||||
keyPassword: string;
|
||||
keyAlias: string;
|
||||
}
|
||||
|
||||
async function createKeystoreInCloudAsync(
|
||||
keystoreParams: KeystoreParams,
|
||||
{ showKeytoolDetectionMsg = true } = {}
|
||||
): Promise<KeystoreWithType> {
|
||||
if (showKeytoolDetectionMsg) {
|
||||
Log.log(`Detected that you do not have ${chalk.bold('keytool')} installed locally.`);
|
||||
}
|
||||
const spinner = ora('Generating keystore in the cloud...').start();
|
||||
try {
|
||||
const url = await KeystoreGenerationUrlMutation.createKeystoreGenerationUrlAsync();
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(keystoreParams),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
const result: KeystoreServiceResult = await response.json();
|
||||
spinner.succeed();
|
||||
return {
|
||||
type: AndroidKeystoreType.Jks,
|
||||
keystore: result.keystoreBase64,
|
||||
keystorePassword: result.keystorePassword,
|
||||
keyAlias: result.keyAlias,
|
||||
keyPassword: result.keyPassword,
|
||||
};
|
||||
} catch (err: any) {
|
||||
spinner.fail();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ import { AndroidAppBuildCredentialsFragment, IosDistributionType } from '../../g
|
||||
import Log from '../../log';
|
||||
import { findApplicationTarget, findTargetByName } from '../../project/ios/target';
|
||||
import zipObject from '../../utils/expodash/zipObject';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import GitClient from '../../vcs/clients/git';
|
||||
import vcs from '../../vcs';
|
||||
import GitClient from '../../vcs/git';
|
||||
import { CredentialsContext } from '../context';
|
||||
import { App, Target, TargetCredentials } from '../ios/types';
|
||||
import { readRawAsync } from './read';
|
||||
@@ -294,9 +294,8 @@ async function updateFileAsync(
|
||||
}
|
||||
|
||||
async function isFileUntrackedAsync(path: string): Promise<boolean> {
|
||||
const vcsClient = getVcsClient();
|
||||
if (vcsClient instanceof GitClient) {
|
||||
return await vcsClient.isFileUntrackedAsync(path);
|
||||
if (vcs instanceof GitClient) {
|
||||
return await vcs.isFileUntrackedAsync(path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5,15 +5,3 @@ export class MissingCredentialsNonInteractiveError extends Error {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingCredentialsError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message ?? 'Credentials are not set up.');
|
||||
}
|
||||
}
|
||||
|
||||
export class UnsupportedCredentialsChoiceError extends Error {
|
||||
constructor(message: string, public choice: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ import { CredentialsContext } from '../context';
|
||||
import * as credentialsJsonReader from '../credentialsJson/read';
|
||||
import { ensureAllTargetsAreConfigured } from '../credentialsJson/utils';
|
||||
import { getAppFromContext } from './actions/BuildCredentialsUtils';
|
||||
import { SetUpBuildCredentials } from './actions/SetUpBuildCredentials';
|
||||
import { SetUpPushKey } from './actions/SetUpPushKey';
|
||||
import { SetupBuildCredentials } from './actions/SetupBuildCredentials';
|
||||
import { SetupPushKey } from './actions/SetupPushKey';
|
||||
import { IosCapabilitiesOptions } from './appstore/ensureAppExists';
|
||||
import { App, IosCredentials, Target } from './types';
|
||||
import { isAdHocProfile } from './utils/provisioningProfile';
|
||||
@@ -58,7 +58,7 @@ export default class IosCredentialsProvider {
|
||||
}
|
||||
|
||||
private async getRemoteAsync(): Promise<IosCredentials> {
|
||||
return await new SetUpBuildCredentials({
|
||||
return await new SetupBuildCredentials({
|
||||
app: this.options.app,
|
||||
targets: this.options.targets,
|
||||
distribution: this.options.distribution,
|
||||
@@ -83,7 +83,7 @@ export default class IosCredentialsProvider {
|
||||
parentBundleIdentifier: applicationTarget.parentBundleIdentifier,
|
||||
};
|
||||
|
||||
const setupPushKeyAction = new SetUpPushKey(appLookupParams);
|
||||
const setupPushKeyAction = new SetupPushKey(appLookupParams);
|
||||
const isPushKeySetup = await setupPushKeyAction.isPushKeySetupAsync(ctx);
|
||||
if (isPushKeySetup) {
|
||||
Log.succeed(
|
||||
@@ -93,7 +93,7 @@ export default class IosCredentialsProvider {
|
||||
}
|
||||
|
||||
const confirmSetup = await confirmAsync({
|
||||
message: `Would you like to set up Push Notifications for your project?`,
|
||||
message: `Would you like to setup Push Notifications for your project?`,
|
||||
});
|
||||
if (!confirmSetup) {
|
||||
return null;
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import path from 'path';
|
||||
|
||||
import { AppStoreConnectApiKeyFragment } from '../../../graphql/generated';
|
||||
import Log, { learnMore } from '../../../log';
|
||||
import { confirmAsync, promptAsync } from '../../../prompts';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { fromNow } from '../../../utils/date';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import {
|
||||
getCredentialsFromUserAsync,
|
||||
shouldAutoGenerateCredentialsAsync,
|
||||
} from '../../utils/promptForCredentials';
|
||||
import { AscApiKey } from '../appstore/Credentials.types';
|
||||
import {
|
||||
AscApiKeyPath,
|
||||
MinimalAscApiKey,
|
||||
ascApiKeyIdSchema,
|
||||
ascApiKeyIssuerIdSchema,
|
||||
} from '../credentials';
|
||||
import { isAscApiKeyValidAndTrackedAsync } from '../validators/validateAscApiKey';
|
||||
import { formatAppleTeam } from './AppleTeamUtils';
|
||||
|
||||
export enum AppStoreApiKeyPurpose {
|
||||
SUBMISSION_SERVICE = 'EAS Submit',
|
||||
}
|
||||
|
||||
export async function promptForAscApiKeyPathAsync(ctx: CredentialsContext): Promise<AscApiKeyPath> {
|
||||
const { keyId, keyP8Path } = await promptForKeyP8AndIdAsync();
|
||||
|
||||
const bestEffortIssuerId = await getBestEffortIssuerIdAsync(ctx, keyId);
|
||||
if (bestEffortIssuerId) {
|
||||
Log.log(`Detected Issuer ID: ${bestEffortIssuerId}`);
|
||||
return { keyId, issuerId: bestEffortIssuerId, keyP8Path };
|
||||
}
|
||||
const issuerId = await promptForIssuerIdAsync();
|
||||
return { keyId, issuerId, keyP8Path };
|
||||
}
|
||||
|
||||
export async function promptForIssuerIdAsync(): Promise<string> {
|
||||
Log.log(chalk.bold('An App Store Connect Issuer ID is required'));
|
||||
Log.log(
|
||||
`If you're not sure what this is or how to find yours, ${learnMore(
|
||||
'https://expo.fyi/asc-issuer-id'
|
||||
)}`
|
||||
);
|
||||
|
||||
// Do not perform uuid validation - Apple's issuerIds are not RFC4122 compliant
|
||||
const { issuerId } = await getCredentialsFromUserAsync(ascApiKeyIssuerIdSchema);
|
||||
return issuerId;
|
||||
}
|
||||
|
||||
export async function getMinimalAscApiKeyAsync(ascApiKey: AscApiKey): Promise<MinimalAscApiKey> {
|
||||
return {
|
||||
...ascApiKey,
|
||||
issuerId: ascApiKey.issuerId ?? (await promptForIssuerIdAsync()),
|
||||
};
|
||||
}
|
||||
|
||||
export async function provideOrGenerateAscApiKeyAsync(
|
||||
ctx: CredentialsContext,
|
||||
purpose: AppStoreApiKeyPurpose
|
||||
): Promise<MinimalAscApiKey> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`A new App Store Connect API Key cannot be created in non-interactive mode.`);
|
||||
}
|
||||
|
||||
const userProvided = await promptForAscApiKeyAsync(ctx);
|
||||
if (!userProvided) {
|
||||
return await generateAscApiKeyAsync(ctx, purpose);
|
||||
}
|
||||
|
||||
if (!ctx.appStore.authCtx) {
|
||||
Log.warn('Unable to validate App Store Connect API Key, you are not authenticated with Apple.');
|
||||
return userProvided;
|
||||
}
|
||||
|
||||
const isValidAndTracked = await isAscApiKeyValidAndTrackedAsync(ctx, userProvided);
|
||||
if (isValidAndTracked) {
|
||||
return userProvided;
|
||||
}
|
||||
const useUserProvided = await confirmAsync({
|
||||
message: `App Store Connect API Key with ID ${userProvided.keyId} is not valid on Apple's servers. Proceed anyway?`,
|
||||
});
|
||||
if (useUserProvided) {
|
||||
return userProvided;
|
||||
}
|
||||
return await provideOrGenerateAscApiKeyAsync(ctx, purpose);
|
||||
}
|
||||
|
||||
async function generateAscApiKeyAsync(
|
||||
ctx: CredentialsContext,
|
||||
purpose: AppStoreApiKeyPurpose
|
||||
): Promise<MinimalAscApiKey> {
|
||||
//console.log('debug');
|
||||
await ctx.appStore.ensureAuthenticatedAsync();
|
||||
const ascApiKey = await ctx.appStore.createAscApiKeyAsync({
|
||||
nickname: getAscApiKeyName(purpose),
|
||||
});
|
||||
return await getMinimalAscApiKeyAsync(ascApiKey);
|
||||
}
|
||||
|
||||
export function getAscApiKeyName(purpose: AppStoreApiKeyPurpose): string {
|
||||
const nameParts = ['[Expo]', purpose, nanoid(10)];
|
||||
return nameParts.join(' ');
|
||||
}
|
||||
|
||||
async function promptForAscApiKeyAsync(ctx: CredentialsContext): Promise<MinimalAscApiKey | null> {
|
||||
const shouldAutoGenerateCredentials = await shouldAutoGenerateCredentialsAsync(ascApiKeyIdSchema);
|
||||
if (shouldAutoGenerateCredentials) {
|
||||
return null;
|
||||
}
|
||||
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx);
|
||||
const { keyP8Path, keyId, issuerId } = ascApiKeyPath;
|
||||
return { keyP8: await fs.readFile(keyP8Path, 'utf-8'), keyId, issuerId };
|
||||
}
|
||||
|
||||
async function promptForKeyP8AndIdAsync(): Promise<Pick<AscApiKeyPath, 'keyP8Path' | 'keyId'>> {
|
||||
Log.log(
|
||||
chalk.bold('An App Store Connect Api key is required to upload your app to the Apple App Store')
|
||||
);
|
||||
Log.log(
|
||||
`If you're not sure what this is or how to create one, ${learnMore(
|
||||
'https://expo.fyi/creating-asc-api-key'
|
||||
)}`
|
||||
);
|
||||
|
||||
const { keyP8Path } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'keyP8Path',
|
||||
message: 'Path to App Store Connect API Key:',
|
||||
initial: 'AuthKey_ABCD.p8',
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
validate: async (filePath: string) => {
|
||||
try {
|
||||
const stats = await fs.stat(filePath);
|
||||
if (stats.isFile()) {
|
||||
return true;
|
||||
}
|
||||
return 'Input is not a file.';
|
||||
} catch {
|
||||
return 'File does not exist.';
|
||||
}
|
||||
},
|
||||
});
|
||||
const regex = /^AuthKey_(?<keyId>\w+)\.p8$/; // Common ASC Api file name downloaded from Apple
|
||||
const bestEffortKeyId = path.basename(keyP8Path).match(regex)?.groups?.keyId;
|
||||
const { keyId } = await getCredentialsFromUserAsync(ascApiKeyIdSchema, {
|
||||
keyId: bestEffortKeyId,
|
||||
});
|
||||
return { keyId, keyP8Path };
|
||||
}
|
||||
|
||||
async function getBestEffortIssuerIdAsync(
|
||||
ctx: CredentialsContext,
|
||||
ascApiKeyId: string
|
||||
): Promise<string | null> {
|
||||
if (!ctx.appStore.authCtx) {
|
||||
return null;
|
||||
}
|
||||
const ascApiKeyInfo = await ctx.appStore.getAscApiKeyAsync(ascApiKeyId);
|
||||
return ascApiKeyInfo?.issuerId ?? null;
|
||||
}
|
||||
|
||||
export async function getAscApiKeysFromAccountAsync(
|
||||
ctx: CredentialsContext,
|
||||
account: Account,
|
||||
{ filterDifferentAppleTeam }: { filterDifferentAppleTeam?: boolean } = {}
|
||||
): Promise<AppStoreConnectApiKeyFragment[]> {
|
||||
const ascApiKeysForAccount = await ctx.ios.getAscApiKeysForAccountAsync(account);
|
||||
|
||||
if (!filterDifferentAppleTeam) {
|
||||
return ascApiKeysForAccount;
|
||||
} else {
|
||||
return filterKeysFromDifferentAppleTeam(ctx, ascApiKeysForAccount);
|
||||
}
|
||||
}
|
||||
|
||||
export async function selectAscApiKeysFromAccountAsync(
|
||||
ctx: CredentialsContext,
|
||||
account: Account,
|
||||
{ filterDifferentAppleTeam }: { filterDifferentAppleTeam?: boolean } = {}
|
||||
): Promise<AppStoreConnectApiKeyFragment | null> {
|
||||
const ascApiKeysForAccount = await getAscApiKeysFromAccountAsync(ctx, account, {
|
||||
filterDifferentAppleTeam,
|
||||
});
|
||||
if (ascApiKeysForAccount.length === 0) {
|
||||
if (filterDifferentAppleTeam) {
|
||||
const maybeAppleTeamId = ctx.appStore.authCtx?.team.id;
|
||||
Log.warn(
|
||||
`There are no App Store Connect API Keys in your EAS account${
|
||||
maybeAppleTeamId ? ` matching Apple Team ID: ${maybeAppleTeamId}.` : '.'
|
||||
}`
|
||||
);
|
||||
} else {
|
||||
Log.warn(`There are no App Store Connect API Keys available in your EAS account.`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return selectAscApiKeysAsync(ascApiKeysForAccount);
|
||||
}
|
||||
|
||||
async function selectAscApiKeysAsync(
|
||||
ascApiKeys: AppStoreConnectApiKeyFragment[]
|
||||
): Promise<AppStoreConnectApiKeyFragment | null> {
|
||||
const sortedAscApiKeys = sortAscApiKeysByUpdatedAtDesc(ascApiKeys);
|
||||
const { chosenAscApiKey } = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'chosenAscApiKey',
|
||||
message: 'Select an API Key from the list:',
|
||||
choices: sortedAscApiKeys.map(ascApiKey => ({
|
||||
title: formatAscApiKey(ascApiKey),
|
||||
value: ascApiKey,
|
||||
})),
|
||||
});
|
||||
return chosenAscApiKey;
|
||||
}
|
||||
|
||||
function filterKeysFromDifferentAppleTeam(
|
||||
ctx: CredentialsContext,
|
||||
keys: AppStoreConnectApiKeyFragment[]
|
||||
): AppStoreConnectApiKeyFragment[] {
|
||||
if (!ctx.appStore.authCtx) {
|
||||
return keys;
|
||||
}
|
||||
const teamId = ctx.appStore.authCtx.team.id;
|
||||
return keys.filter(key => !key.appleTeam || key.appleTeam?.appleTeamIdentifier === teamId);
|
||||
}
|
||||
|
||||
export function sortAscApiKeysByUpdatedAtDesc(
|
||||
keys: AppStoreConnectApiKeyFragment[]
|
||||
): AppStoreConnectApiKeyFragment[] {
|
||||
return keys.sort(
|
||||
(keyA, keyB) => new Date(keyB.updatedAt).getTime() - new Date(keyA.updatedAt).getTime()
|
||||
);
|
||||
}
|
||||
|
||||
export function formatAscApiKey(ascApiKey: AppStoreConnectApiKeyFragment): string {
|
||||
const { keyIdentifier, appleTeam, name, updatedAt } = ascApiKey;
|
||||
let line: string = '';
|
||||
line += `Key ID: ${keyIdentifier}`;
|
||||
|
||||
if (name) {
|
||||
line += chalk.gray(`\n Name: ${name}`);
|
||||
}
|
||||
if (appleTeam) {
|
||||
line += chalk.gray(`\n ${formatAppleTeam(appleTeam)}`);
|
||||
}
|
||||
|
||||
line += chalk.gray(`\n Updated: ${fromNow(new Date(updatedAt))} ago`);
|
||||
return line;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import {
|
||||
AppStoreConnectApiKeyFragment,
|
||||
CommonIosAppCredentialsFragment,
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
|
||||
import { AppStoreApiKeyPurpose } from './AscApiKeyUtils';
|
||||
|
||||
export class AssignAscApiKey {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
public async runAsync(
|
||||
ctx: CredentialsContext,
|
||||
ascApiKey: AppStoreConnectApiKeyFragment,
|
||||
purpose: AppStoreApiKeyPurpose
|
||||
): Promise<CommonIosAppCredentialsFragment> {
|
||||
const appleTeam =
|
||||
(await resolveAppleTeamIfAuthenticatedAsync(ctx, this.app)) ?? ascApiKey.appleTeam ?? null;
|
||||
const appCredentials = await ctx.ios.createOrGetIosAppCredentialsWithCommonFieldsAsync(
|
||||
this.app,
|
||||
{ appleTeam: appleTeam ?? undefined }
|
||||
);
|
||||
let updatedAppCredentials;
|
||||
if (purpose === AppStoreApiKeyPurpose.SUBMISSION_SERVICE) {
|
||||
updatedAppCredentials = await ctx.ios.updateIosAppCredentialsAsync(appCredentials, {
|
||||
ascApiKeyIdForSubmissions: ascApiKey.id,
|
||||
});
|
||||
} else {
|
||||
throw new Error(`${purpose} is not yet supported.`);
|
||||
}
|
||||
Log.succeed(
|
||||
`App Store Connect API Key assigned to ${this.app.projectName}: ${this.app.bundleIdentifier} for ${purpose}.`
|
||||
);
|
||||
return updatedAppCredentials;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { AppStoreConnectApiKeyFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppStoreApiKeyPurpose, provideOrGenerateAscApiKeyAsync } from './AscApiKeyUtils';
|
||||
|
||||
export class CreateAscApiKey {
|
||||
constructor(private account: Account) {}
|
||||
|
||||
public async runAsync(
|
||||
ctx: CredentialsContext,
|
||||
purpose: AppStoreApiKeyPurpose
|
||||
): Promise<AppStoreConnectApiKeyFragment> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`A new App Store Connect API Key cannot be created in non-interactive mode.`);
|
||||
}
|
||||
|
||||
const ascApiKey = await provideOrGenerateAscApiKeyAsync(ctx, purpose);
|
||||
const result = await ctx.ios.createAscApiKeyAsync(this.account, ascApiKey);
|
||||
Log.succeed('Created App Store Connect API Key');
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export class CreateDistributionCertificate {
|
||||
public async runAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<AppleDistributionCertificateMutationResult> {
|
||||
const distCert = await provideOrGenerateDistributionCertificateAsync(ctx);
|
||||
const distCert = await provideOrGenerateDistributionCertificateAsync(ctx, this.account.name);
|
||||
const result = await ctx.ios.createDistributionCertificateAsync(this.account, distCert);
|
||||
Log.succeed('Created distribution certificate');
|
||||
return result;
|
||||
|
||||
@@ -12,7 +12,7 @@ export class CreatePushKey {
|
||||
throw new Error(`A new push key cannot be created in non-interactive mode.`);
|
||||
}
|
||||
|
||||
const pushKey = await provideOrGeneratePushKeyAsync(ctx);
|
||||
const pushKey = await provideOrGeneratePushKeyAsync(ctx, this.account.name);
|
||||
const result = await ctx.ios.createPushKeyAsync(this.account, pushKey);
|
||||
Log.succeed('Created push key');
|
||||
return result;
|
||||
|
||||
@@ -152,14 +152,15 @@ Please remember that Apple Distribution Certificates are not application specifi
|
||||
`;
|
||||
|
||||
export async function provideOrGenerateDistributionCertificateAsync(
|
||||
ctx: CredentialsContext
|
||||
ctx: CredentialsContext,
|
||||
accountName: string
|
||||
): Promise<DistributionCertificate> {
|
||||
if (!ctx.nonInteractive) {
|
||||
const userProvided = await promptForDistCertAsync(ctx);
|
||||
if (userProvided) {
|
||||
if (!ctx.appStore.authCtx) {
|
||||
Log.warn(
|
||||
'Unable to validate distribution certificate, you are not authenticated with Apple.'
|
||||
'Unable to validate distribution certificate due to insufficient Apple Credentials'
|
||||
);
|
||||
return userProvided;
|
||||
} else {
|
||||
@@ -167,11 +168,13 @@ export async function provideOrGenerateDistributionCertificateAsync(
|
||||
if (!isValid) {
|
||||
Log.warn("Provided Distribution Certificate is no longer valid on Apple's server");
|
||||
}
|
||||
return isValid ? userProvided : await provideOrGenerateDistributionCertificateAsync(ctx);
|
||||
return isValid
|
||||
? userProvided
|
||||
: await provideOrGenerateDistributionCertificateAsync(ctx, accountName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return await generateDistributionCertificateAsync(ctx);
|
||||
return await generateDistributionCertificateAsync(ctx, accountName);
|
||||
}
|
||||
|
||||
async function promptForDistCertAsync(
|
||||
@@ -197,7 +200,8 @@ async function promptForDistCertAsync(
|
||||
}
|
||||
|
||||
async function generateDistributionCertificateAsync(
|
||||
ctx: CredentialsContext
|
||||
ctx: CredentialsContext,
|
||||
accountName: string
|
||||
): Promise<DistributionCertificate> {
|
||||
await ctx.appStore.ensureAuthenticatedAsync();
|
||||
try {
|
||||
@@ -242,7 +246,7 @@ async function generateDistributionCertificateAsync(
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return await generateDistributionCertificateAsync(ctx);
|
||||
return await generateDistributionCertificateAsync(ctx, accountName);
|
||||
}
|
||||
|
||||
function formatDistributionCertificateFromApple(
|
||||
|
||||
@@ -14,12 +14,15 @@ import { pushKeySchema } from '../credentials';
|
||||
import { isPushKeyValidAndTrackedAsync } from '../validators/validatePushKey';
|
||||
import { formatAppleTeam } from './AppleTeamUtils';
|
||||
|
||||
export async function provideOrGeneratePushKeyAsync(ctx: CredentialsContext): Promise<PushKey> {
|
||||
export async function provideOrGeneratePushKeyAsync(
|
||||
ctx: CredentialsContext,
|
||||
accountName: string
|
||||
): Promise<PushKey> {
|
||||
if (!ctx.nonInteractive) {
|
||||
const userProvided = await promptForPushKeyAsync(ctx);
|
||||
if (userProvided) {
|
||||
if (!ctx.appStore.authCtx) {
|
||||
Log.warn('Unable to validate push key, you are not authenticated with Apple.');
|
||||
Log.warn('Unable to validate push key due to insufficient Apple Credentials');
|
||||
return userProvided;
|
||||
} else {
|
||||
const isValidAndTracked = await isPushKeyValidAndTrackedAsync(ctx, userProvided);
|
||||
@@ -33,11 +36,11 @@ export async function provideOrGeneratePushKeyAsync(ctx: CredentialsContext): Pr
|
||||
if (useUserProvided) {
|
||||
return userProvided;
|
||||
}
|
||||
return await provideOrGeneratePushKeyAsync(ctx);
|
||||
return await provideOrGeneratePushKeyAsync(ctx, accountName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return await generatePushKeyAsync(ctx);
|
||||
return await generatePushKeyAsync(ctx, accountName);
|
||||
}
|
||||
|
||||
async function promptForPushKeyAsync(ctx: CredentialsContext): Promise<PushKey | null> {
|
||||
@@ -60,7 +63,10 @@ async function promptForPushKeyAsync(ctx: CredentialsContext): Promise<PushKey |
|
||||
return userProvided;
|
||||
}
|
||||
|
||||
async function generatePushKeyAsync(ctx: CredentialsContext): Promise<PushKey> {
|
||||
async function generatePushKeyAsync(
|
||||
ctx: CredentialsContext,
|
||||
accountName: string
|
||||
): Promise<PushKey> {
|
||||
await ctx.appStore.ensureAuthenticatedAsync();
|
||||
try {
|
||||
return await ctx.appStore.createPushKeyAsync();
|
||||
@@ -100,7 +106,7 @@ async function generatePushKeyAsync(ctx: CredentialsContext): Promise<PushKey> {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return await generatePushKeyAsync(ctx);
|
||||
return await generatePushKeyAsync(ctx, accountName);
|
||||
}
|
||||
|
||||
function formatPushKeyFromApple(pushKey: PushKeyStoreInfo): string {
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { AppStoreConnectApiKeyFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { selectAscApiKeysFromAccountAsync } from './AscApiKeyUtils';
|
||||
|
||||
export class SelectAndRemoveAscApiKey {
|
||||
constructor(private account: Account) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
const selected = await selectAscApiKeysFromAccountAsync(ctx, this.account);
|
||||
if (selected) {
|
||||
await new RemoveAscApiKey(this.account, selected).runAsync(ctx);
|
||||
Log.succeed('Removed App Store Connect API Key');
|
||||
Log.newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveAscApiKey {
|
||||
constructor(private account: Account, private ascApiKey: AppStoreConnectApiKeyFragment) {}
|
||||
|
||||
public async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`Cannot remove App Store Connect API Keys in non-interactive mode.`);
|
||||
}
|
||||
|
||||
// TODO(quin): add an extra edge on AppStoreConnectApiKey to find the apps using it
|
||||
const confirm = await confirmAsync({
|
||||
message: `Deleting this API Key may affect your projects that rely on it. Do you want to continue?`,
|
||||
});
|
||||
if (!confirm) {
|
||||
Log.log('Aborting');
|
||||
return;
|
||||
}
|
||||
|
||||
Log.log('Removing API Key');
|
||||
await ctx.ios.deleteAscApiKeyAsync(this.ascApiKey.id);
|
||||
|
||||
let shouldRevoke = false;
|
||||
if (!ctx.nonInteractive) {
|
||||
shouldRevoke = await confirmAsync({
|
||||
message: `Do you also want to revoke this API Key on the Apple Developer Portal?`,
|
||||
});
|
||||
} else if (ctx.nonInteractive) {
|
||||
Log.log('Skipping API Key revocation on the Apple Developer Portal.');
|
||||
}
|
||||
if (shouldRevoke) {
|
||||
await ctx.appStore.revokeAscApiKeyAsync(this.ascApiKey.keyIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import assert from 'assert';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import {
|
||||
AppStoreConnectApiKeyFragment,
|
||||
CommonIosAppCredentialsFragment,
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync, promptAsync } from '../../../prompts';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import {
|
||||
MissingCredentialsNonInteractiveError,
|
||||
UnsupportedCredentialsChoiceError,
|
||||
} from '../../errors';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { getValidAndTrackedAscApiKeysAsync } from '../validators/validateAscApiKey';
|
||||
import {
|
||||
AppStoreApiKeyPurpose,
|
||||
formatAscApiKey,
|
||||
getAscApiKeysFromAccountAsync,
|
||||
selectAscApiKeysFromAccountAsync,
|
||||
sortAscApiKeysByUpdatedAtDesc,
|
||||
} from './AscApiKeyUtils';
|
||||
import { AssignAscApiKey } from './AssignAscApiKey';
|
||||
import { CreateAscApiKey } from './CreateAscApiKey';
|
||||
|
||||
export enum SetupAscApiKeyChoice {
|
||||
GENERATE = 'GENERATE',
|
||||
USE_EXISTING = 'USE_EXISTING',
|
||||
}
|
||||
export class SetUpAscApiKey {
|
||||
public choices: { title: string; value: string }[] = [
|
||||
{
|
||||
title: '[Choose an existing ASC API Key]',
|
||||
value: SetupAscApiKeyChoice.USE_EXISTING,
|
||||
},
|
||||
{ title: '[Add a new ASC API Key]', value: SetupAscApiKeyChoice.GENERATE },
|
||||
];
|
||||
|
||||
constructor(private app: AppLookupParams, private purpose: AppStoreApiKeyPurpose) {}
|
||||
|
||||
public async runAsync(ctx: CredentialsContext): Promise<CommonIosAppCredentialsFragment> {
|
||||
const isKeySetup = await this.isAscApiKeySetupAsync(ctx, this.purpose);
|
||||
if (isKeySetup) {
|
||||
Log.succeed('App Store Connect API Key already set up.');
|
||||
return nullthrows(
|
||||
await ctx.ios.getIosAppCredentialsWithCommonFieldsAsync(this.app),
|
||||
'iosAppCredentials cannot be null if App Store Connect API Key is already set up'
|
||||
);
|
||||
}
|
||||
if (ctx.nonInteractive) {
|
||||
throw new MissingCredentialsNonInteractiveError(
|
||||
'App Store Connect API Keys cannot be set up in --non-interactive mode.'
|
||||
);
|
||||
}
|
||||
|
||||
const keysForAccount = await getAscApiKeysFromAccountAsync(ctx, this.app.account, {
|
||||
filterDifferentAppleTeam: true,
|
||||
});
|
||||
const maybeAutoselectedKey = await this.doBestEffortAutoselectAsync(ctx, keysForAccount);
|
||||
if (maybeAutoselectedKey) {
|
||||
return await new AssignAscApiKey(this.app).runAsync(ctx, maybeAutoselectedKey, this.purpose);
|
||||
}
|
||||
|
||||
const availableChoices =
|
||||
keysForAccount.length === 0
|
||||
? this.choices.filter(choice => choice.value !== SetupAscApiKeyChoice.USE_EXISTING)
|
||||
: this.choices;
|
||||
const ascApiKey = await this.processChoicesAsync(ctx, this.purpose, availableChoices);
|
||||
return await new AssignAscApiKey(this.app).runAsync(ctx, ascApiKey, this.purpose);
|
||||
}
|
||||
|
||||
private async doBestEffortAutoselectAsync(
|
||||
ctx: CredentialsContext,
|
||||
keysForAccount: AppStoreConnectApiKeyFragment[]
|
||||
): Promise<AppStoreConnectApiKeyFragment | null> {
|
||||
if (!ctx.appStore.authCtx) {
|
||||
return null;
|
||||
}
|
||||
if (keysForAccount.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// only provide autoselect if we can find a key that is certainly valid
|
||||
const validKeys = await getValidAndTrackedAscApiKeysAsync(ctx, keysForAccount);
|
||||
if (validKeys.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const [autoselectedKey] = sortAscApiKeysByUpdatedAtDesc(validKeys);
|
||||
const useAutoselected = await confirmAsync({
|
||||
message: `Reuse this App Store Connect API Key?\n${formatAscApiKey(autoselectedKey)}`,
|
||||
});
|
||||
|
||||
if (useAutoselected) {
|
||||
Log.log(`Using App Store Connect API Key with ID ${autoselectedKey.keyIdentifier}`);
|
||||
return autoselectedKey;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async isAscApiKeySetupAsync(
|
||||
ctx: CredentialsContext,
|
||||
purpose: AppStoreApiKeyPurpose
|
||||
): Promise<boolean> {
|
||||
const appCredentials = await ctx.ios.getIosAppCredentialsWithCommonFieldsAsync(this.app);
|
||||
if (purpose !== AppStoreApiKeyPurpose.SUBMISSION_SERVICE) {
|
||||
throw new Error(`App Store Connect API Key setup is not yet supported for ${purpose}.`);
|
||||
}
|
||||
return !!appCredentials?.appStoreConnectApiKeyForSubmissions;
|
||||
}
|
||||
|
||||
private async processChoicesAsync(
|
||||
ctx: CredentialsContext,
|
||||
purpose: AppStoreApiKeyPurpose,
|
||||
choices: { title: string; value: string }[]
|
||||
): Promise<AppStoreConnectApiKeyFragment> {
|
||||
assert(choices.length > 0, 'SetupAscApiKey: There must be at least one choice');
|
||||
let choice: string;
|
||||
if (choices.length === 1) {
|
||||
choice = choices[0].value;
|
||||
} else {
|
||||
const result = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'choice',
|
||||
message: 'Select what to use for your project:',
|
||||
choices,
|
||||
});
|
||||
choice = result.choice;
|
||||
}
|
||||
|
||||
if (choice === SetupAscApiKeyChoice.GENERATE) {
|
||||
return await new CreateAscApiKey(this.app.account).runAsync(ctx, purpose);
|
||||
} else if (choice === SetupAscApiKeyChoice.USE_EXISTING) {
|
||||
const selectedAscApiKey = await selectAscApiKeysFromAccountAsync(ctx, this.app.account, {
|
||||
filterDifferentAppleTeam: true,
|
||||
});
|
||||
if (!selectedAscApiKey) {
|
||||
return await this.processChoicesAsync(ctx, purpose, choices);
|
||||
}
|
||||
return selectedAscApiKey;
|
||||
}
|
||||
throw new UnsupportedCredentialsChoiceError(
|
||||
`AscApiKey Setup does not support choice:${choice}`,
|
||||
choice
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
import wrapAnsi from 'wrap-ansi';
|
||||
|
||||
import { CommonIosAppCredentialsFragment } from '../../../graphql/generated';
|
||||
import Log, { learnMore } from '../../../log';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { UnsupportedCredentialsChoiceError } from '../../errors';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { AppStoreApiKeyPurpose } from './AscApiKeyUtils';
|
||||
import { SetUpAscApiKey } from './SetUpAscApiKey';
|
||||
|
||||
export const PROMPT_FOR_APP_SPECIFIC_PASSWORD = 'PROMPT_FOR_APP_SPECIFIC_PASSWORD';
|
||||
|
||||
export class SetUpSubmissionCredentials {
|
||||
private setupAscApiKeyAction;
|
||||
|
||||
constructor(app: AppLookupParams) {
|
||||
this.setupAscApiKeyAction = new SetUpAscApiKey(app, AppStoreApiKeyPurpose.SUBMISSION_SERVICE);
|
||||
// Add this unrelated choice to ASC API Key setup for legacy purposes -- we will deprecate it soon
|
||||
this.setupAscApiKeyAction.choices = this.setupAscApiKeyAction.choices.concat({
|
||||
title: '[Enter an App Specific Password]',
|
||||
value: PROMPT_FOR_APP_SPECIFIC_PASSWORD,
|
||||
});
|
||||
}
|
||||
|
||||
public async runAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<CommonIosAppCredentialsFragment | string> {
|
||||
try {
|
||||
const iosAppCredentials = await this.setupAscApiKeyAction.runAsync(ctx);
|
||||
const { keyIdentifier, name } = nullthrows(
|
||||
iosAppCredentials.appStoreConnectApiKeyForSubmissions,
|
||||
'ASC API Key must be defined for EAS Submit'
|
||||
);
|
||||
Log.log(`Using API Key ID: ${keyIdentifier}${name ? ` (${name})` : ''}`);
|
||||
return iosAppCredentials;
|
||||
} catch (e) {
|
||||
if (e instanceof UnsupportedCredentialsChoiceError) {
|
||||
if (e.choice === PROMPT_FOR_APP_SPECIFIC_PASSWORD) {
|
||||
return await this.promptForAppSpecificPasswordAsync();
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async promptForAppSpecificPasswordAsync(): Promise<string> {
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(`Please enter your Apple app-specific password.`);
|
||||
Log.log(learnMore('https://expo.fyi/apple-app-specific-password'));
|
||||
Log.warn(
|
||||
wrapAnsi(
|
||||
`This option will be deprecated soon. You will still be able to provide your password with the ${chalk.bold(
|
||||
'EXPO_APPLE_APP_SPECIFIC_PASSWORD'
|
||||
)} environment variable.`,
|
||||
process.stdout.columns || 80
|
||||
)
|
||||
);
|
||||
|
||||
const { appSpecificPassword } = await promptAsync({
|
||||
name: 'appSpecificPassword',
|
||||
message: 'Your Apple app-specific password:',
|
||||
type: 'password',
|
||||
validate: (val: string) => val !== '' || 'Apple app-specific password cannot be empty!',
|
||||
});
|
||||
return appSpecificPassword;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -21,7 +21,7 @@ import { validateProvisioningProfileAsync } from '../validators/validateProvisio
|
||||
import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
|
||||
import { assignBuildCredentialsAsync, getBuildCredentialsAsync } from './BuildCredentialsUtils';
|
||||
import { chooseDevicesAsync, formatDeviceLabel } from './DeviceUtils';
|
||||
import { SetUpDistributionCertificate } from './SetUpDistributionCertificate';
|
||||
import { SetupDistributionCertificate } from './SetupDistributionCertificate';
|
||||
|
||||
enum ReuseAction {
|
||||
Yes,
|
||||
@@ -29,11 +29,11 @@ enum ReuseAction {
|
||||
No,
|
||||
}
|
||||
|
||||
export class SetUpAdhocProvisioningProfile {
|
||||
export class SetupAdhocProvisioningProfile {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosAppBuildCredentialsFragment> {
|
||||
const distCert = await new SetUpDistributionCertificate(
|
||||
const distCert = await new SetupDistributionCertificate(
|
||||
this.app,
|
||||
IosDistributionType.AdHoc
|
||||
).runAsync(ctx);
|
||||
+3
-3
@@ -7,7 +7,7 @@ import { CredentialsContext } from '../../context';
|
||||
import { IosCapabilitiesOptions } from '../appstore/ensureAppExists';
|
||||
import { App, IosAppBuildCredentialsMap, IosCredentials, Target } from '../types';
|
||||
import { displayProjectCredentials } from '../utils/printCredentials';
|
||||
import { SetUpTargetBuildCredentials } from './SetUpTargetBuildCredentials';
|
||||
import { SetupTargetBuildCredentials } from './SetupTargetBuildCredentials';
|
||||
|
||||
interface Options {
|
||||
app: App;
|
||||
@@ -17,7 +17,7 @@ interface Options {
|
||||
iosCapabilitiesOptions?: IosCapabilitiesOptions;
|
||||
}
|
||||
|
||||
export class SetUpBuildCredentials {
|
||||
export class SetupBuildCredentials {
|
||||
constructor(private options: Options) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosCredentials> {
|
||||
@@ -49,7 +49,7 @@ export class SetUpBuildCredentials {
|
||||
} else {
|
||||
Log.newLine();
|
||||
}
|
||||
const action = new SetUpTargetBuildCredentials({
|
||||
const action = new SetupTargetBuildCredentials({
|
||||
...this.options,
|
||||
app: {
|
||||
...this.options.app,
|
||||
+3
-3
@@ -8,9 +8,9 @@ import { readIosCredentialsAsync } from '../../credentialsJson/read';
|
||||
import { IosCredentials } from '../../credentialsJson/types';
|
||||
import { ensureAllTargetsAreConfigured } from '../../credentialsJson/utils';
|
||||
import { App, Target } from '../types';
|
||||
import { SetUpTargetBuildCredentialsFromCredentialsJson } from './SetUpTargetBuildCredentialsFromCredentialsJson';
|
||||
import { SetupTargetBuildCredentialsFromCredentialsJson } from './SetupTargetBuildCredentialsFromCredentialsJson';
|
||||
|
||||
export class SetUpBuildCredentialsFromCredentialsJson {
|
||||
export class SetupBuildCredentialsFromCredentialsJson {
|
||||
constructor(
|
||||
private app: App,
|
||||
private targets: Target[],
|
||||
@@ -32,7 +32,7 @@ export class SetUpBuildCredentialsFromCredentialsJson {
|
||||
);
|
||||
Log.newLine();
|
||||
}
|
||||
await new SetUpTargetBuildCredentialsFromCredentialsJson(
|
||||
await new SetupTargetBuildCredentialsFromCredentialsJson(
|
||||
{
|
||||
account: this.app.account,
|
||||
projectName: this.app.projectName,
|
||||
+1
-1
@@ -18,7 +18,7 @@ import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
|
||||
import { CreateDistributionCertificate } from './CreateDistributionCertificate';
|
||||
import { formatDistributionCertificate } from './DistributionCertificateUtils';
|
||||
|
||||
export class SetUpDistributionCertificate {
|
||||
export class SetupDistributionCertificate {
|
||||
private validDistCerts?: AppleDistributionCertificateFragment[];
|
||||
|
||||
constructor(private app: AppLookupParams, private distributionType: IosDistributionType) {}
|
||||
+5
-5
@@ -4,8 +4,8 @@ import { promptAsync } from '../../../prompts';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { getAllBuildCredentialsAsync } from './BuildCredentialsUtils';
|
||||
import { SetUpAdhocProvisioningProfile } from './SetUpAdhocProvisioningProfile';
|
||||
import { SetUpProvisioningProfile } from './SetUpProvisioningProfile';
|
||||
import { SetupAdhocProvisioningProfile } from './SetupAdhocProvisioningProfile';
|
||||
import { SetupProvisioningProfile } from './SetupProvisioningProfile';
|
||||
|
||||
/**
|
||||
* It's used when setting up credentials for internal distribution but `enterpriseProvisioning` is not set.
|
||||
@@ -14,7 +14,7 @@ import { SetUpProvisioningProfile } from './SetUpProvisioningProfile';
|
||||
* to choose if they want to set up an adhoc or universal distribution provisioning profile. Otherwise, always
|
||||
* set up an adhoc provisioning profile.
|
||||
*/
|
||||
export class SetUpInternalProvisioningProfile {
|
||||
export class SetupInternalProvisioningProfile {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosAppBuildCredentialsFragment> {
|
||||
@@ -82,13 +82,13 @@ export class SetUpInternalProvisioningProfile {
|
||||
private async setupAdhocProvisioningProfileAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
return await new SetUpAdhocProvisioningProfile(this.app).runAsync(ctx);
|
||||
return await new SetupAdhocProvisioningProfile(this.app).runAsync(ctx);
|
||||
}
|
||||
|
||||
private async setupUniversalProvisioningProfileAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
return await new SetUpProvisioningProfile(this.app, IosDistributionType.Enterprise).runAsync(
|
||||
return await new SetupProvisioningProfile(this.app, IosDistributionType.Enterprise).runAsync(
|
||||
ctx
|
||||
);
|
||||
}
|
||||
+3
-3
@@ -20,12 +20,12 @@ import {
|
||||
import { ConfigureProvisioningProfile } from './ConfigureProvisioningProfile';
|
||||
import { CreateProvisioningProfile } from './CreateProvisioningProfile';
|
||||
import { formatProvisioningProfileFromApple } from './ProvisioningProfileUtils';
|
||||
import { SetUpDistributionCertificate } from './SetUpDistributionCertificate';
|
||||
import { SetupDistributionCertificate } from './SetupDistributionCertificate';
|
||||
|
||||
/**
|
||||
* Sets up either APP_STORE or ENTERPRISE provisioning profiles
|
||||
*/
|
||||
export class SetUpProvisioningProfile {
|
||||
export class SetupProvisioningProfile {
|
||||
constructor(private app: AppLookupParams, private distributionType: IosDistributionType) {}
|
||||
|
||||
async areBuildCredentialsSetupAsync(ctx: CredentialsContext): Promise<boolean> {
|
||||
@@ -84,7 +84,7 @@ export class SetUpProvisioningProfile {
|
||||
}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosAppBuildCredentialsFragment> {
|
||||
const distCert = await new SetUpDistributionCertificate(
|
||||
const distCert = await new SetupDistributionCertificate(
|
||||
this.app,
|
||||
this.distributionType
|
||||
).runAsync(ctx);
|
||||
+1
-1
@@ -14,7 +14,7 @@ import {
|
||||
selectPushKeyAsync,
|
||||
} from './PushKeyUtils';
|
||||
|
||||
export class SetUpPushKey {
|
||||
export class SetupPushKey {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async isPushKeySetupAsync(ctx: CredentialsContext): Promise<boolean> {
|
||||
Loaded 100 of 221 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user