mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-14 10:08:06 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b36ae0ef8 | ||
|
|
1a4cb107af |
No files matched your search
@@ -8,42 +8,12 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 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))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [0.32.0](https://github.com/expo/eas-cli/releases/tag/0.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/0.31.1) - 2021-10-08
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.33.0"
|
||||
"version": "0.31.1"
|
||||
}
|
||||
+2
-1
@@ -10,7 +10,8 @@
|
||||
"eas": "packages/eas-cli/bin/run",
|
||||
"lint": "eslint . --ext .ts --ext .js",
|
||||
"release": "lerna version",
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"clean": "lerna run clean"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/oclif-dev-cli": "1.24.0-expo",
|
||||
|
||||
+29
-31
@@ -73,7 +73,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/login.js](https://github.com/expo/eas-cli/blob/v0.33.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.33.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.33.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.33.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.33.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.33.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.33.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.33.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.33.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.33.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.33.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.33.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.33.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.33.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.33.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.33.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.33.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.33.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`
|
||||
|
||||
@@ -367,7 +366,7 @@ DESCRIPTION
|
||||
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.33.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.33.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
|
||||
@@ -405,14 +404,13 @@ OPTIONS
|
||||
--[no-]wait Wait for submission to complete
|
||||
|
||||
DESCRIPTION
|
||||
See how to configure submits with eas.json:
|
||||
https://docs.expo.dev/submit/eas-json/
|
||||
See how to configure submits with eas.json: https://docs.expo.dev/submit/eas-json/
|
||||
|
||||
ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [src/commands/submit.js](https://github.com/expo/eas-cli/blob/v0.33.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`
|
||||
|
||||
@@ -431,7 +429,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/create.js](https://github.com/expo/eas-cli/blob/v0.33.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]`
|
||||
|
||||
@@ -445,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.33.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`
|
||||
|
||||
@@ -459,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.33.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`
|
||||
|
||||
@@ -479,7 +477,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/update.js](https://github.com/expo/eas-cli/blob/v0.33.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`
|
||||
|
||||
@@ -493,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.33.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 -->
|
||||
Generated
+999
-417
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"type": "module",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.33.0",
|
||||
"version": "0.31.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
@@ -12,7 +13,7 @@
|
||||
"@expo/config": "6.0.2",
|
||||
"@expo/config-plugins": "4.0.2",
|
||||
"@expo/eas-build-job": "0.2.52",
|
||||
"@expo/eas-json": "^0.33.0",
|
||||
"@expo/eas-json": "^0.31.0",
|
||||
"@expo/json-file": "8.2.33",
|
||||
"@expo/pkcs12": "0.0.4",
|
||||
"@expo/plist": "0.0.14",
|
||||
@@ -113,7 +114,6 @@
|
||||
"cli"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "build/index.js",
|
||||
"oclif": {
|
||||
"bin": "eas",
|
||||
"commands": "./build/commands",
|
||||
@@ -177,6 +177,7 @@
|
||||
"typecheck": "tsc",
|
||||
"test": "jest",
|
||||
"version": "yarn oclif-dev readme && node patch-readme && git add README.md",
|
||||
"generate-graphql-code": "graphql-codegen --config graphql-codegen.yml"
|
||||
"generate-graphql-code": "graphql-codegen --config graphql-codegen.yml",
|
||||
"clean": "rm -rf build node_modules"
|
||||
}
|
||||
}
|
||||
@@ -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,7 +6,7 @@ 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 vcs from '../../vcs';
|
||||
@@ -21,7 +21,7 @@ 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);
|
||||
|
||||
|
||||
@@ -97,22 +97,20 @@ export async function configureAsync(options: {
|
||||
|
||||
const EAS_JSON_MANAGED_DEFAULT: EasJson = {
|
||||
build: {
|
||||
release: {},
|
||||
development: {
|
||||
developmentClient: true,
|
||||
distribution: 'internal',
|
||||
},
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
},
|
||||
production: {},
|
||||
},
|
||||
submit: {
|
||||
production: {},
|
||||
release: {},
|
||||
},
|
||||
};
|
||||
|
||||
const EAS_JSON_BARE_DEFAULT: EasJson = {
|
||||
const EAS_JSON_GENERIC_DEFAULT: EasJson = {
|
||||
build: {
|
||||
release: {},
|
||||
development: {
|
||||
distribution: 'internal',
|
||||
android: {
|
||||
@@ -122,13 +120,9 @@ const EAS_JSON_BARE_DEFAULT: EasJson = {
|
||||
buildConfiguration: 'Debug',
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
},
|
||||
production: {},
|
||||
},
|
||||
submit: {
|
||||
production: {},
|
||||
release: {},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -145,7 +139,7 @@ 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`);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
import { Workflow } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
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';
|
||||
@@ -10,36 +10,39 @@ import { appPlatformDisplayNames } from '../../platform';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { expoCommandAsync } from '../../utils/expoCommand';
|
||||
import { ProfileData } from '../../utils/profiles';
|
||||
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<BuildProfile>[];
|
||||
}): 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,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import {
|
||||
BuildProfile,
|
||||
EasJsonReader,
|
||||
hasMismatchedExtendsAsync,
|
||||
isUsingDeprecatedFormatAsync,
|
||||
@@ -50,14 +49,13 @@ import {
|
||||
} from '../../submit/submit';
|
||||
import { printSubmissionDetailsUrls } from '../../submit/utils/urls';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
import { ProfileData, getProfilesAsync } from '../../utils/profiles';
|
||||
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;
|
||||
@@ -70,7 +68,7 @@ interface RawBuildFlags {
|
||||
interface BuildFlags {
|
||||
requestedPlatform: RequestedPlatform;
|
||||
skipProjectConfiguration: boolean;
|
||||
profile: string | null;
|
||||
profile: string;
|
||||
nonInteractive: boolean;
|
||||
local: boolean;
|
||||
wait: boolean;
|
||||
@@ -101,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({
|
||||
@@ -154,60 +152,44 @@ export default class Build extends EasCommand {
|
||||
await ensureProjectConfiguredAsync(projectDir, requestedPlatform);
|
||||
|
||||
const platforms = toPlatforms(requestedPlatform);
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const buildProfiles = await getProfilesAsync({
|
||||
platforms,
|
||||
profileName: flags.profile,
|
||||
async readProfileAsync(platform, profileName) {
|
||||
return await easJsonReader.readBuildProfileAsync(platform, profileName);
|
||||
},
|
||||
});
|
||||
|
||||
await ensureExpoDevClientInstalledForDevClientBuildsAsync({
|
||||
platforms,
|
||||
projectDir,
|
||||
profile: flags.profile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
buildProfiles,
|
||||
});
|
||||
|
||||
const startedBuilds: {
|
||||
build: BuildFragment;
|
||||
buildProfile: ProfileData<BuildProfile>;
|
||||
}[] = [];
|
||||
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);
|
||||
}
|
||||
@@ -221,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(
|
||||
@@ -270,7 +252,7 @@ export default class Build extends EasCommand {
|
||||
return {
|
||||
requestedPlatform,
|
||||
skipProjectConfiguration: flags['skip-project-configuration'],
|
||||
profile: profile ?? null,
|
||||
profile,
|
||||
nonInteractive,
|
||||
local: flags['local'],
|
||||
wait: flags['wait'],
|
||||
@@ -281,31 +263,34 @@ export default class Build extends EasCommand {
|
||||
};
|
||||
}
|
||||
|
||||
private async prepareAndStartBuildAsync({
|
||||
private async prepareAndStartBuildAsync<T extends Platform>({
|
||||
projectDir,
|
||||
platform,
|
||||
flags,
|
||||
moreBuilds,
|
||||
buildProfile,
|
||||
}: {
|
||||
projectDir: string;
|
||||
platform: T;
|
||||
flags: BuildFlags;
|
||||
moreBuilds: boolean;
|
||||
buildProfile: ProfileData<BuildProfile>;
|
||||
}): 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`
|
||||
@@ -345,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<BuildProfile>;
|
||||
}): 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,
|
||||
@@ -364,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,
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ 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;
|
||||
@@ -55,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',
|
||||
@@ -102,30 +101,23 @@ See how to configure submits with eas.json: ${link('https://docs.expo.dev/submit
|
||||
return;
|
||||
}
|
||||
|
||||
const platforms = toPlatforms(flags.requestedPlatform);
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const submissionProfiles = await getProfilesAsync({
|
||||
platforms,
|
||||
profileName: flags.profile,
|
||||
async readProfileAsync(platform, profileName) {
|
||||
return await easJsonReader.readSubmitProfileAsync(platform, profileName);
|
||||
},
|
||||
});
|
||||
|
||||
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`
|
||||
|
||||
@@ -5,9 +5,3 @@ export class MissingCredentialsNonInteractiveError extends Error {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingCredentialsError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message ?? 'Credentials are not set up.');
|
||||
}
|
||||
}
|
||||
@@ -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,71 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
import Log, { learnMore } from '../../../log';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { getCredentialsFromUserAsync } from '../../utils/promptForCredentials';
|
||||
import { AscApiKey } from '../appstore/Credentials.types';
|
||||
import { AscApiKeyPath, MinimalAscApiKey, ascApiKeyMetadataSchema } from '../credentials';
|
||||
|
||||
export async function promptForAscApiKeyAsync(): Promise<AscApiKeyPath> {
|
||||
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 ascApiKeyMetadata = await getCredentialsFromUserAsync(ascApiKeyMetadataSchema, {
|
||||
keyId: bestEffortKeyId,
|
||||
});
|
||||
return { ...ascApiKeyMetadata, 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'
|
||||
)}`
|
||||
);
|
||||
|
||||
const { issuerId } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'issuerId',
|
||||
message: 'App Store Connect Issuer ID:',
|
||||
validate: (input: string) => uuid.validate(input),
|
||||
});
|
||||
|
||||
return issuerId;
|
||||
}
|
||||
|
||||
export async function getMinimalAscApiKeyAsync(ascApiKey: AscApiKey): Promise<MinimalAscApiKey> {
|
||||
return {
|
||||
...ascApiKey,
|
||||
issuerId: ascApiKey.issuerId ?? (await promptForIssuerIdAsync()),
|
||||
};
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export class SetupTargetBuildCredentials {
|
||||
try {
|
||||
return await this.setupBuildCredentialsAsync(ctx);
|
||||
} catch (error) {
|
||||
Log.error('Failed to set up credentials.');
|
||||
Log.error('Failed to setup credentials.');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import {
|
||||
AscApiKey,
|
||||
AscApiKeyInfo,
|
||||
DistributionCertificate,
|
||||
DistributionCertificateStoreInfo,
|
||||
ProvisioningProfile,
|
||||
@@ -8,12 +6,6 @@ import {
|
||||
PushKey,
|
||||
PushKeyStoreInfo,
|
||||
} from './Credentials.types';
|
||||
import {
|
||||
createAscApiKeyAsync,
|
||||
getAscApiKeyAsync,
|
||||
listAscApiKeysAsync,
|
||||
revokeAscApiKeyAsync,
|
||||
} from './ascApiKey';
|
||||
import { AuthCtx, Options as AuthenticateOptions, authenticateAsync } from './authenticate';
|
||||
import {
|
||||
createDistributionCertificateAsync,
|
||||
@@ -144,24 +136,4 @@ export default class AppStoreApi {
|
||||
distCertSerialNumber
|
||||
);
|
||||
}
|
||||
|
||||
public async listAscApiKeysAsync(): Promise<AscApiKeyInfo[]> {
|
||||
const ctx = await this.ensureAuthenticatedAsync();
|
||||
return await listAscApiKeysAsync(ctx);
|
||||
}
|
||||
|
||||
public async getAscApiKeyAsync(keyId: string): Promise<AscApiKeyInfo> {
|
||||
const ctx = await this.ensureAuthenticatedAsync();
|
||||
return await getAscApiKeyAsync(ctx, keyId);
|
||||
}
|
||||
|
||||
public async createAscApiKeyAsync({ nickname }: { nickname: string }): Promise<AscApiKey> {
|
||||
const ctx = await this.ensureAuthenticatedAsync();
|
||||
return await createAscApiKeyAsync(ctx, { nickname });
|
||||
}
|
||||
|
||||
public async revokeAscApiKeyAsync(keyId: string): Promise<AscApiKeyInfo> {
|
||||
const ctx = await this.ensureAuthenticatedAsync();
|
||||
return await revokeAscApiKeyAsync(ctx, keyId);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { UserRole } from '@expo/apple-utils';
|
||||
|
||||
export interface Device {
|
||||
id: string;
|
||||
teamId: string;
|
||||
@@ -68,16 +66,3 @@ export interface PushKey {
|
||||
teamId: string;
|
||||
teamName?: string;
|
||||
}
|
||||
|
||||
export type AscApiKeyInfo = {
|
||||
keyId: string;
|
||||
issuerId?: string;
|
||||
teamId: string;
|
||||
name: string;
|
||||
teamName?: string;
|
||||
roles: UserRole[];
|
||||
};
|
||||
|
||||
export type AscApiKey = AscApiKeyInfo & {
|
||||
keyP8: string;
|
||||
};
|
||||
@@ -1,98 +0,0 @@
|
||||
import { ApiKey, ApiKeyType, UserRole } from '@expo/apple-utils';
|
||||
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import {
|
||||
createAscApiKeyAsync,
|
||||
getAscApiKeyAsync,
|
||||
listAscApiKeysAsync,
|
||||
revokeAscApiKeyAsync,
|
||||
} from '../ascApiKey';
|
||||
import { getRequestContext } from '../authenticate';
|
||||
|
||||
jest.mock('@expo/apple-utils');
|
||||
jest.mock('../authenticate');
|
||||
jest.mock('../../../../ora');
|
||||
|
||||
const mockTeam = {
|
||||
id: 'test-id',
|
||||
name: 'test-name',
|
||||
};
|
||||
const mockAuthCtx = {
|
||||
appleId: 'test-appleId',
|
||||
team: mockTeam,
|
||||
};
|
||||
|
||||
const mockApiKey = {
|
||||
type: 'apiKeys',
|
||||
id: 'NL67AN9Q6Q',
|
||||
attributes: {
|
||||
nickname: 'ejb-apple-utils-admin',
|
||||
lastUsed: '2021-06-28T12:30:41-07:00',
|
||||
revokingDate: null,
|
||||
isActive: true,
|
||||
canDownload: false,
|
||||
privateKey: null,
|
||||
roles: ['ADMIN'],
|
||||
allAppsVisible: true,
|
||||
keyType: 'PUBLIC_API',
|
||||
},
|
||||
downloadAsync: jest.fn(() => 'super secret'),
|
||||
revokeAsync: jest.fn(() => mockApiKey),
|
||||
} as unknown as ApiKey;
|
||||
|
||||
const mockAscApiKeyInfo = {
|
||||
issuerId: undefined,
|
||||
keyId: 'NL67AN9Q6Q',
|
||||
name: 'ejb-apple-utils-admin',
|
||||
roles: ['ADMIN'],
|
||||
teamId: 'test-id',
|
||||
teamName: 'test-name',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test(`listAscApiKeysAsync`, async () => {
|
||||
const mockRequestContext = {};
|
||||
jest.spyOn(ApiKey, 'getAsync').mockImplementation(async () => []);
|
||||
asMock(getRequestContext).mockImplementation(() => mockRequestContext);
|
||||
const result = await listAscApiKeysAsync(mockAuthCtx);
|
||||
expect(ApiKey.getAsync).toHaveBeenLastCalledWith(mockRequestContext);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
test(`getAscApiKeyAsync`, async () => {
|
||||
const mockRequestContext = {};
|
||||
jest.spyOn(ApiKey, 'infoAsync').mockImplementation(async () => mockApiKey);
|
||||
asMock(getRequestContext).mockImplementation(() => mockRequestContext);
|
||||
const result = await getAscApiKeyAsync(mockAuthCtx, 'test-key-id');
|
||||
expect(ApiKey.infoAsync).toHaveBeenLastCalledWith(mockRequestContext, { id: 'test-key-id' });
|
||||
expect(result).toEqual(mockAscApiKeyInfo);
|
||||
});
|
||||
|
||||
test(`createAscApiKeyAsync`, async () => {
|
||||
const mockRequestContext = {};
|
||||
jest.spyOn(ApiKey, 'createAsync').mockImplementation(async () => mockApiKey);
|
||||
asMock(getRequestContext).mockImplementation(() => mockRequestContext);
|
||||
const result = await createAscApiKeyAsync(mockAuthCtx, { nickname: 'test-name' });
|
||||
expect(ApiKey.createAsync).toHaveBeenLastCalledWith(mockRequestContext, {
|
||||
nickname: 'test-name',
|
||||
allAppsVisible: true,
|
||||
roles: [UserRole.ADMIN],
|
||||
keyType: ApiKeyType.PUBLIC_API,
|
||||
});
|
||||
expect(result).toEqual({ ...mockAscApiKeyInfo, keyP8: 'super secret' });
|
||||
});
|
||||
|
||||
test(`revokeAscApiKeyAsync`, async () => {
|
||||
const mockRequestContext = {};
|
||||
jest.spyOn(ApiKey, 'infoAsync').mockImplementation(async () => mockApiKey);
|
||||
asMock(getRequestContext).mockImplementation(() => mockRequestContext);
|
||||
const result = await revokeAscApiKeyAsync(mockAuthCtx, 'test-key-id');
|
||||
expect(ApiKey.infoAsync).toHaveBeenLastCalledWith(mockRequestContext, {
|
||||
id: 'test-key-id',
|
||||
});
|
||||
expect(mockApiKey.revokeAsync).toBeCalled();
|
||||
expect(result).toEqual(mockAscApiKeyInfo);
|
||||
});
|
||||
@@ -1,104 +0,0 @@
|
||||
import { ApiKey, ApiKeyProps, ApiKeyType, UserRole } from '@expo/apple-utils';
|
||||
|
||||
import Log from '../../../log';
|
||||
import { ora } from '../../../ora';
|
||||
import { AscApiKey, AscApiKeyInfo } from './Credentials.types';
|
||||
import { AuthCtx, getRequestContext } from './authenticate';
|
||||
|
||||
export async function listAscApiKeysAsync(authCtx: AuthCtx): Promise<AscApiKeyInfo[]> {
|
||||
const spinner = ora(`Fetching App Store Connect API Keys.`).start();
|
||||
try {
|
||||
const context = getRequestContext(authCtx);
|
||||
const keys = await ApiKey.getAsync(context);
|
||||
spinner.succeed(`Fetched App Store Connect API Keys.`);
|
||||
return keys.map(key => getAscApiKeyInfo(key, authCtx));
|
||||
} catch (error) {
|
||||
spinner.fail(`Failed to fetch App Store Connect API Keys.`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAscApiKeyAsync(authCtx: AuthCtx, keyId: string): Promise<AscApiKeyInfo> {
|
||||
const spinner = ora(`Fetching App Store Connect API Key.`).start();
|
||||
try {
|
||||
const context = getRequestContext(authCtx);
|
||||
const apiKey = await ApiKey.infoAsync(context, { id: keyId });
|
||||
spinner.succeed(`Fetched App Store Connect API Key (ID: ${keyId}).`);
|
||||
return getAscApiKeyInfo(apiKey, authCtx);
|
||||
} catch (error) {
|
||||
Log.error(error);
|
||||
spinner.fail(`Failed to fetch App Store Connect API Key.`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createAscApiKeyAsync(
|
||||
authCtx: AuthCtx,
|
||||
{
|
||||
nickname,
|
||||
allAppsVisible,
|
||||
roles,
|
||||
keyType,
|
||||
}: Partial<Pick<ApiKeyProps, 'nickname' | 'roles' | 'allAppsVisible' | 'keyType'>>
|
||||
): Promise<AscApiKey> {
|
||||
const spinner = ora(`Creating App Store Connect API Key.`).start();
|
||||
try {
|
||||
const context = getRequestContext(authCtx);
|
||||
const key = await ApiKey.createAsync(context, {
|
||||
nickname: nickname ?? `[expo] ${new Date().getTime()}`,
|
||||
allAppsVisible: allAppsVisible ?? true,
|
||||
roles: roles ?? [UserRole.ADMIN],
|
||||
keyType: keyType ?? ApiKeyType.PUBLIC_API,
|
||||
});
|
||||
const keyP8 = await key.downloadAsync();
|
||||
if (!keyP8) {
|
||||
const { nickname, roles } = key.attributes;
|
||||
const humanReadableKey = `App Store Connect Key '${nickname}' (${
|
||||
key.id
|
||||
}) with roles {${roles.join(',')}}`;
|
||||
if (!key.attributes.canDownload) {
|
||||
// this case would be unexpected because we just created the key
|
||||
throw new Error(`${humanReadableKey} is not available for download from Apple.`);
|
||||
} else if (!key.attributes.isActive) {
|
||||
throw new Error(`${humanReadableKey} is inactive and could not be downloaded.`);
|
||||
}
|
||||
throw new Error(`Failed to download .p8 file of ${humanReadableKey}.`);
|
||||
}
|
||||
return {
|
||||
...getAscApiKeyInfo(key, authCtx),
|
||||
keyP8,
|
||||
};
|
||||
} catch (err: any) {
|
||||
spinner.fail('Failed to create App Store Connect API Key.');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function revokeAscApiKeyAsync(
|
||||
authCtx: AuthCtx,
|
||||
keyId: string
|
||||
): Promise<AscApiKeyInfo> {
|
||||
const spinner = ora(`Revoking App Store Connect API Key.`).start();
|
||||
try {
|
||||
const context = getRequestContext(authCtx);
|
||||
const apiKey = await ApiKey.infoAsync(context, { id: keyId });
|
||||
const revokedKey = await apiKey.revokeAsync();
|
||||
spinner.succeed(`Revoked App Store Connect API Key.`);
|
||||
return getAscApiKeyInfo(revokedKey, authCtx);
|
||||
} catch (error) {
|
||||
Log.error(error);
|
||||
spinner.fail(`Failed to revoke App Store Connect API Key.`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function getAscApiKeyInfo(apiKey: ApiKey, authCtx: AuthCtx): AscApiKeyInfo {
|
||||
return {
|
||||
name: apiKey.attributes.nickname,
|
||||
keyId: apiKey.id,
|
||||
issuerId: apiKey.attributes.provider?.id,
|
||||
teamId: authCtx.team.id,
|
||||
teamName: authCtx.team.name,
|
||||
roles: apiKey.attributes.roles,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { UserRole } from '@expo/apple-utils';
|
||||
|
||||
import { AppleDevice } from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import { CredentialSchema } from '../utils/promptForCredentials';
|
||||
@@ -102,38 +100,6 @@ export const distributionCertificateSchema: CredentialSchema<DistributionCertifi
|
||||
},
|
||||
};
|
||||
|
||||
export type MinimalAscApiKey = {
|
||||
keyP8: string;
|
||||
keyId: string;
|
||||
issuerId: string;
|
||||
teamId?: string;
|
||||
teamName?: string;
|
||||
roles?: UserRole[];
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export interface AscApiKeyPath {
|
||||
keyP8Path: string;
|
||||
keyId: string;
|
||||
issuerId: string;
|
||||
}
|
||||
|
||||
export const ascApiKeyMetadataSchema: CredentialSchema<Omit<MinimalAscApiKey, 'keyP8'>> = {
|
||||
name: 'App Store Connect API Key',
|
||||
questions: [
|
||||
{
|
||||
field: 'keyId',
|
||||
type: 'string',
|
||||
question: 'Key ID:',
|
||||
},
|
||||
{
|
||||
field: 'issuerId',
|
||||
type: 'string',
|
||||
question: 'Issuer ID:',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const pushKeySchema: CredentialSchema<PushKey> = {
|
||||
name: 'Apple Push Notifications service key',
|
||||
questions: [
|
||||
|
||||
@@ -87,7 +87,7 @@ const highLevelActions: ActionInfo[] = [
|
||||
},
|
||||
{
|
||||
value: ActionType.ManageCredentialsJson,
|
||||
title: 'credentials.json: Upload/Download credentials between EAS servers and your local json ',
|
||||
title: 'Credentials.json: Upload/Download credentials between EAS servers and your local json ',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -82,7 +82,7 @@ const highLevelActions: ActionInfo[] = [
|
||||
},
|
||||
{
|
||||
value: ActionType.ManageCredentialsJson,
|
||||
title: 'credentials.json: Upload/Download credentials between EAS servers and your local json ',
|
||||
title: 'Credentials.json: Upload/Download credentials between EAS servers and your local json ',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
|
||||
+130
-56
@@ -350,16 +350,13 @@ export type Offer = {
|
||||
trialLength?: Maybe<Scalars['Int']>;
|
||||
type: OfferType;
|
||||
features?: Maybe<Array<Maybe<Feature>>>;
|
||||
prerequisite?: Maybe<OfferPrerequisite>;
|
||||
};
|
||||
|
||||
export enum OfferType {
|
||||
/** Term subscription */
|
||||
Subscription = 'SUBSCRIPTION',
|
||||
/** Advanced Purchase of Paid Resource */
|
||||
Prepaid = 'PREPAID',
|
||||
/** Addon, or supplementary subscription */
|
||||
Addon = 'ADDON'
|
||||
Prepaid = 'PREPAID'
|
||||
}
|
||||
|
||||
export enum Feature {
|
||||
@@ -373,12 +370,6 @@ export enum Feature {
|
||||
OpenSource = 'OPEN_SOURCE'
|
||||
}
|
||||
|
||||
export type OfferPrerequisite = {
|
||||
__typename?: 'OfferPrerequisite';
|
||||
type: Scalars['String'];
|
||||
stripeIds: Array<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type Snack = Project & {
|
||||
__typename?: 'Snack';
|
||||
id: Scalars['ID'];
|
||||
@@ -1388,7 +1379,6 @@ export type UserPermission = {
|
||||
|
||||
export type Billing = {
|
||||
__typename?: 'Billing';
|
||||
id: Scalars['ID'];
|
||||
payment?: Maybe<PaymentDetails>;
|
||||
subscription?: Maybe<SubscriptionDetails>;
|
||||
/** History of invoices */
|
||||
@@ -1423,8 +1413,6 @@ export type Address = {
|
||||
export type SubscriptionDetails = {
|
||||
__typename?: 'SubscriptionDetails';
|
||||
id: Scalars['ID'];
|
||||
planId?: Maybe<Scalars['String']>;
|
||||
addons: Array<AddonDetails>;
|
||||
name?: Maybe<Scalars['String']>;
|
||||
nextInvoice?: Maybe<Scalars['DateTime']>;
|
||||
cancelledAt?: Maybe<Scalars['DateTime']>;
|
||||
@@ -1434,13 +1422,6 @@ export type SubscriptionDetails = {
|
||||
status?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type AddonDetails = {
|
||||
__typename?: 'AddonDetails';
|
||||
id: Scalars['ID'];
|
||||
planId: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Charge = {
|
||||
__typename?: 'Charge';
|
||||
id: Scalars['ID'];
|
||||
@@ -2490,8 +2471,22 @@ export type BuildMutation = {
|
||||
* @deprecated Use cancelBuild instead
|
||||
*/
|
||||
cancel: Build;
|
||||
/**
|
||||
* Create an Android generic build
|
||||
* @deprecated Use createAndroidBuild instead
|
||||
*/
|
||||
createAndroidGenericBuild: CreateBuildResult;
|
||||
/**
|
||||
* Create an Android managed build
|
||||
* @deprecated Use createAndroidBuild instead
|
||||
*/
|
||||
createAndroidManagedBuild: CreateBuildResult;
|
||||
/** Create an Android build */
|
||||
createAndroidBuild: CreateBuildResult;
|
||||
/** Create an iOS generic build */
|
||||
createIosGenericBuild: CreateBuildResult;
|
||||
/** Create an iOS managed build */
|
||||
createIosManagedBuild: CreateBuildResult;
|
||||
/** Create an iOS build */
|
||||
createIosBuild: CreateBuildResult;
|
||||
};
|
||||
@@ -2507,6 +2502,20 @@ export type BuildMutationDeleteBuildArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateAndroidGenericBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: AndroidGenericJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateAndroidManagedBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: AndroidManagedJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateAndroidBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: AndroidJobInput;
|
||||
@@ -2514,14 +2523,27 @@ export type BuildMutationCreateAndroidBuildArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateIosGenericBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: IosGenericJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateIosManagedBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: IosManagedJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateIosBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: IosJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
export type AndroidJobInput = {
|
||||
type: BuildWorkflow;
|
||||
export type AndroidGenericJobInput = {
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
@@ -2531,15 +2553,8 @@ export type AndroidJobInput = {
|
||||
cache?: Maybe<BuildCacheInput>;
|
||||
gradleCommand?: Maybe<Scalars['String']>;
|
||||
artifactPath?: Maybe<Scalars['String']>;
|
||||
buildType?: Maybe<AndroidBuildType>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum BuildWorkflow {
|
||||
Generic = 'GENERIC',
|
||||
Managed = 'MANAGED'
|
||||
}
|
||||
|
||||
export type ProjectArchiveSourceInput = {
|
||||
type: ProjectArchiveSourceType;
|
||||
bucketKey?: Maybe<Scalars['String']>;
|
||||
@@ -2588,12 +2603,6 @@ export type BuildCacheInput = {
|
||||
customPaths?: Maybe<Array<Maybe<Scalars['String']>>>;
|
||||
};
|
||||
|
||||
export enum AndroidBuildType {
|
||||
Apk = 'APK',
|
||||
AppBundle = 'APP_BUNDLE',
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
}
|
||||
|
||||
export type BuildMetadataInput = {
|
||||
trackingContext?: Maybe<Scalars['JSONObject']>;
|
||||
appVersion?: Maybe<Scalars['String']>;
|
||||
@@ -2614,6 +2623,11 @@ export type BuildMetadataInput = {
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum BuildWorkflow {
|
||||
Generic = 'GENERIC',
|
||||
Managed = 'MANAGED'
|
||||
}
|
||||
|
||||
export enum BuildCredentialsSource {
|
||||
Local = 'LOCAL',
|
||||
Remote = 'REMOTE'
|
||||
@@ -2636,8 +2650,46 @@ export enum EasBuildDeprecationInfoType {
|
||||
Internal = 'INTERNAL'
|
||||
}
|
||||
|
||||
export type IosJobInput = {
|
||||
export type AndroidManagedJobInput = {
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
updates?: Maybe<BuildUpdatesInput>;
|
||||
secrets?: Maybe<AndroidJobSecretsInput>;
|
||||
builderEnvironment?: Maybe<AndroidBuilderEnvironmentInput>;
|
||||
cache?: Maybe<BuildCacheInput>;
|
||||
buildType?: Maybe<AndroidManagedBuildType>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum AndroidManagedBuildType {
|
||||
Apk = 'APK',
|
||||
AppBundle = 'APP_BUNDLE',
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
}
|
||||
|
||||
export type AndroidJobInput = {
|
||||
type: BuildWorkflow;
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
updates?: Maybe<BuildUpdatesInput>;
|
||||
secrets?: Maybe<AndroidJobSecretsInput>;
|
||||
builderEnvironment?: Maybe<AndroidBuilderEnvironmentInput>;
|
||||
cache?: Maybe<BuildCacheInput>;
|
||||
gradleCommand?: Maybe<Scalars['String']>;
|
||||
artifactPath?: Maybe<Scalars['String']>;
|
||||
buildType?: Maybe<AndroidBuildType>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum AndroidBuildType {
|
||||
Apk = 'APK',
|
||||
AppBundle = 'APP_BUNDLE',
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
}
|
||||
|
||||
export type IosGenericJobInput = {
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
@@ -2646,11 +2698,10 @@ export type IosJobInput = {
|
||||
secrets?: Maybe<IosJobSecretsInput>;
|
||||
builderEnvironment?: Maybe<IosBuilderEnvironmentInput>;
|
||||
cache?: Maybe<BuildCacheInput>;
|
||||
scheme?: Maybe<Scalars['String']>;
|
||||
scheme: Scalars['String'];
|
||||
schemeBuildConfiguration?: Maybe<IosSchemeBuildConfiguration>;
|
||||
buildConfiguration?: Maybe<Scalars['String']>;
|
||||
artifactPath?: Maybe<Scalars['String']>;
|
||||
buildType?: Maybe<IosBuildType>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type IosJobSecretsInput = {
|
||||
@@ -2680,6 +2731,46 @@ export type IosBuilderEnvironmentInput = {
|
||||
env?: Maybe<Scalars['JSONObject']>;
|
||||
};
|
||||
|
||||
export enum IosSchemeBuildConfiguration {
|
||||
Release = 'RELEASE',
|
||||
Debug = 'DEBUG'
|
||||
}
|
||||
|
||||
export type IosManagedJobInput = {
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
updates?: Maybe<BuildUpdatesInput>;
|
||||
distribution?: Maybe<DistributionType>;
|
||||
secrets?: Maybe<IosJobSecretsInput>;
|
||||
builderEnvironment?: Maybe<IosBuilderEnvironmentInput>;
|
||||
cache?: Maybe<BuildCacheInput>;
|
||||
buildType?: Maybe<IosManagedBuildType>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum IosManagedBuildType {
|
||||
Release = 'RELEASE',
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
}
|
||||
|
||||
export type IosJobInput = {
|
||||
type: BuildWorkflow;
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
updates?: Maybe<BuildUpdatesInput>;
|
||||
distribution?: Maybe<DistributionType>;
|
||||
secrets?: Maybe<IosJobSecretsInput>;
|
||||
builderEnvironment?: Maybe<IosBuilderEnvironmentInput>;
|
||||
cache?: Maybe<BuildCacheInput>;
|
||||
scheme?: Maybe<Scalars['String']>;
|
||||
buildConfiguration?: Maybe<Scalars['String']>;
|
||||
artifactPath?: Maybe<Scalars['String']>;
|
||||
buildType?: Maybe<IosBuildType>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum IosBuildType {
|
||||
Release = 'RELEASE',
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
@@ -2865,18 +2956,11 @@ export type CreateIosSubmissionInput = {
|
||||
export type IosSubmissionConfigInput = {
|
||||
appleAppSpecificPasswordId?: Maybe<Scalars['String']>;
|
||||
appleAppSpecificPassword?: Maybe<Scalars['String']>;
|
||||
ascApiKey?: Maybe<AscApiKeyInput>;
|
||||
archiveUrl?: Maybe<Scalars['String']>;
|
||||
appleIdUsername: Scalars['String'];
|
||||
ascAppIdentifier: Scalars['String'];
|
||||
};
|
||||
|
||||
export type AscApiKeyInput = {
|
||||
keyP8: Scalars['String'];
|
||||
keyIdentifier: Scalars['String'];
|
||||
issuerIdentifier: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CreateAndroidSubmissionInput = {
|
||||
appId: Scalars['ID'];
|
||||
config: AndroidSubmissionConfigInput;
|
||||
@@ -3428,16 +3512,6 @@ export enum StandardOffer {
|
||||
Support = 'SUPPORT'
|
||||
}
|
||||
|
||||
export enum IosSchemeBuildConfiguration {
|
||||
Release = 'RELEASE',
|
||||
Debug = 'DEBUG'
|
||||
}
|
||||
|
||||
export enum IosManagedBuildType {
|
||||
Release = 'RELEASE',
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
}
|
||||
|
||||
export enum CacheControlScope {
|
||||
Public = 'PUBLIC',
|
||||
Private = 'PRIVATE'
|
||||
|
||||
@@ -12,7 +12,7 @@ describe(resolveWorkflowAsync, () => {
|
||||
|
||||
const projectDir = '/app';
|
||||
|
||||
test('bare workflow for both platforms', async () => {
|
||||
test('generic workflow for both platforms', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/helloworld.xcodeproj/project.pbxproj': 'fake',
|
||||
@@ -27,7 +27,7 @@ describe(resolveWorkflowAsync, () => {
|
||||
await expect(resolveWorkflowAsync(projectDir, Platform.IOS)).resolves.toBe(Workflow.GENERIC);
|
||||
});
|
||||
|
||||
test('bare workflow for single platform', async () => {
|
||||
test('generic workflow for single platform', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/helloworld.xcodeproj/project.pbxproj': 'fake',
|
||||
|
||||
@@ -25,7 +25,7 @@ beforeEach(async () => {
|
||||
});
|
||||
|
||||
describe(getApplicationIdAsync, () => {
|
||||
describe('bare projects', () => {
|
||||
describe('generic projects', () => {
|
||||
it('reads applicationId from build.gradle', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ beforeEach(async () => {
|
||||
});
|
||||
|
||||
describe(resolveGradleBuildContextAsync, () => {
|
||||
describe('bare projects', () => {
|
||||
describe('generic projects', () => {
|
||||
it('resolves to default config (no flavor)', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@ import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { readAppJson } from '../../build/utils/appJson';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { getProjectConfigDescription, getUsername } from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
@@ -40,7 +39,7 @@ export async function getApplicationIdAsync(
|
||||
): Promise<string> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject(projectDir, exp);
|
||||
warnIfAndroidPackageDefinedInAppConfigForGenericProject(projectDir, exp);
|
||||
|
||||
const errorMessage = 'Could not read application id from Android project.';
|
||||
if (gradleContext) {
|
||||
@@ -113,7 +112,7 @@ async function configureApplicationIdAsync(projectDir: string, exp: ExpoConfig):
|
||||
validate: value => (isApplicationIdValid(value) ? true : INVALID_APPLICATION_ID_MESSAGE),
|
||||
});
|
||||
|
||||
const rawStaticConfig = readAppJson(paths.staticConfigPath);
|
||||
const rawStaticConfig = await fs.readJSON(paths.staticConfigPath);
|
||||
rawStaticConfig.expo = {
|
||||
...rawStaticConfig.expo,
|
||||
android: { ...rawStaticConfig.expo?.android, package: packageName },
|
||||
@@ -130,7 +129,7 @@ function isApplicationIdValid(applicationId: string): boolean {
|
||||
}
|
||||
|
||||
let warnPrinted = false;
|
||||
export function warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject(
|
||||
export function warnIfAndroidPackageDefinedInAppConfigForGenericProject(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
): void {
|
||||
@@ -138,7 +137,7 @@ export function warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject(
|
||||
Log.warn(
|
||||
`Specifying "android.package" in ${getProjectConfigDescription(
|
||||
projectDir
|
||||
)} is deprecated for bare workflow projects.\n` +
|
||||
)} is deprecated for generic projects.\n` +
|
||||
'EAS Build depends only on the value in the native code. Please remove the deprecated configuration.'
|
||||
);
|
||||
warnPrinted = true;
|
||||
|
||||
@@ -29,7 +29,7 @@ beforeEach(async () => {
|
||||
const originalFs = jest.requireActual('fs') as typeof fs;
|
||||
|
||||
describe(getBundleIdentifierAsync, () => {
|
||||
describe('bare projects', () => {
|
||||
describe('generic projects', () => {
|
||||
it('reads bundle identifier from project.', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { readAppJson } from '../../build/utils/appJson';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
@@ -35,7 +34,7 @@ export async function getBundleIdentifierAsync(
|
||||
): Promise<string> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject(projectDir, exp);
|
||||
warnIfBundleIdentifierDefinedInAppConfigForGenericProject(projectDir, exp);
|
||||
|
||||
const bundleIdentifier = IOSConfig.BundleIdentifier.getBundleIdentifierFromPbxproj(projectDir, {
|
||||
targetName,
|
||||
@@ -108,7 +107,7 @@ async function configureBundleIdentifierAsync(
|
||||
validate: value => (isBundleIdentifierValid(value) ? true : INVALID_BUNDLE_IDENTIFIER_MESSAGE),
|
||||
});
|
||||
|
||||
const rawStaticConfig = readAppJson(paths.staticConfigPath);
|
||||
const rawStaticConfig = await fs.readJSON(paths.staticConfigPath);
|
||||
rawStaticConfig.expo = {
|
||||
...rawStaticConfig.expo,
|
||||
ios: { ...rawStaticConfig.expo?.ios, bundleIdentifier },
|
||||
@@ -125,7 +124,7 @@ function isBundleIdentifierValid(bundleIdentifier: string): boolean {
|
||||
}
|
||||
|
||||
let warnPrinted = false;
|
||||
export function warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject(
|
||||
export function warnIfBundleIdentifierDefinedInAppConfigForGenericProject(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
): void {
|
||||
@@ -133,7 +132,7 @@ export function warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject(
|
||||
Log.warn(
|
||||
`Specifying "ios.bundleIdentifier" in ${getProjectConfigDescription(
|
||||
projectDir
|
||||
)} is deprecated for bare workflow projects.\n` +
|
||||
)} is deprecated for generic projects.\n` +
|
||||
'EAS Build depends only on the value in the native code. Please remove the deprecated configuration.'
|
||||
);
|
||||
warnPrinted = true;
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
import prompts from 'prompts';
|
||||
import { URL } from 'url';
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
import { AppPlatform, BuildFragment } from '../graphql/generated';
|
||||
import { BuildFragment } from '../graphql/generated';
|
||||
import { BuildQuery } from '../graphql/queries/BuildQuery';
|
||||
import { toAppPlatform } from '../graphql/types/AppPlatform';
|
||||
import Log, { learnMore } from '../log';
|
||||
import { appPlatformDisplayNames } from '../platform';
|
||||
import { confirmAsync, promptAsync } from '../prompts';
|
||||
import { getRecentBuildsForSubmissionAsync } from './utils/builds';
|
||||
import { getLatestBuildForSubmissionAsync } from './utils/builds';
|
||||
import { isExistingFileAsync, uploadAppArchiveAsync } from './utils/files';
|
||||
|
||||
export const BUILD_LIST_ITEM_COUNT = 4;
|
||||
|
||||
export enum ArchiveSourceType {
|
||||
url,
|
||||
latest,
|
||||
path,
|
||||
buildId,
|
||||
buildList,
|
||||
prompt,
|
||||
}
|
||||
|
||||
@@ -50,10 +46,6 @@ interface ArchiveBuildIdSource extends ArchiveSourceBase {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface ArchiveBuildListSource extends ArchiveSourceBase {
|
||||
sourceType: ArchiveSourceType.buildList;
|
||||
}
|
||||
|
||||
interface ArchivePromptSource extends ArchiveSourceBase {
|
||||
sourceType: ArchiveSourceType.prompt;
|
||||
}
|
||||
@@ -69,7 +61,6 @@ export type ArchiveSource =
|
||||
| ArchiveLatestSource
|
||||
| ArchivePathSource
|
||||
| ArchiveBuildIdSource
|
||||
| ArchiveBuildListSource
|
||||
| ArchivePromptSource;
|
||||
|
||||
export async function getArchiveAsync(source: ArchiveSource): Promise<Archive> {
|
||||
@@ -89,9 +80,6 @@ export async function getArchiveAsync(source: ArchiveSource): Promise<Archive> {
|
||||
case ArchiveSourceType.buildId: {
|
||||
return await handleBuildIdSourceAsync(source);
|
||||
}
|
||||
case ArchiveSourceType.buildList: {
|
||||
return await handleBuildListSourceAsync(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +113,7 @@ async function handleUrlSourceAsync(source: ArchiveUrlSource): Promise<Archive>
|
||||
|
||||
async function handleLatestSourceAsync(source: ArchiveLatestSource): Promise<Archive> {
|
||||
try {
|
||||
const [latestBuild] = await getRecentBuildsForSubmissionAsync(
|
||||
const latestBuild = await getLatestBuildForSubmissionAsync(
|
||||
toAppPlatform(source.platform),
|
||||
source.projectId
|
||||
);
|
||||
@@ -209,116 +197,6 @@ async function handleBuildIdSourceAsync(source: ArchiveBuildIdSource): Promise<A
|
||||
}
|
||||
}
|
||||
|
||||
async function handleBuildListSourceAsync(source: ArchiveBuildListSource): Promise<Archive> {
|
||||
try {
|
||||
const appPlatform = toAppPlatform(source.platform);
|
||||
const expiryDate = new Date(); // artifacts expire after 30 days
|
||||
expiryDate.setDate(expiryDate.getDate() - 30);
|
||||
|
||||
const recentBuilds = await getRecentBuildsForSubmissionAsync(appPlatform, source.projectId, {
|
||||
limit: BUILD_LIST_ITEM_COUNT,
|
||||
});
|
||||
|
||||
if (recentBuilds.length < 1) {
|
||||
Log.error(
|
||||
chalk.bold(
|
||||
`Couldn't find any ${appPlatformDisplayNames[appPlatform]} builds for this project on EAS servers. ` +
|
||||
"It looks like you haven't run 'eas build' yet."
|
||||
)
|
||||
);
|
||||
return getArchiveAsync({
|
||||
...source,
|
||||
sourceType: ArchiveSourceType.prompt,
|
||||
});
|
||||
}
|
||||
|
||||
if (recentBuilds.every(it => new Date(it.updatedAt) < expiryDate)) {
|
||||
Log.error(
|
||||
chalk.bold(
|
||||
'It looks like all of your build artifacts have expired. ' +
|
||||
'EAS keeps your build artifacts only for 30 days.'
|
||||
)
|
||||
);
|
||||
return getArchiveAsync({
|
||||
...source,
|
||||
sourceType: ArchiveSourceType.prompt,
|
||||
});
|
||||
}
|
||||
|
||||
const choices = recentBuilds.map(build => formatBuildChoice(build, expiryDate));
|
||||
choices.push({
|
||||
title: 'None of the above (select another option)',
|
||||
value: null,
|
||||
});
|
||||
|
||||
const { selectedBuild } = await promptAsync({
|
||||
name: 'selectedBuild',
|
||||
type: 'select',
|
||||
message: 'Which build would you like to submit?',
|
||||
choices: choices.map(choice => ({ ...choice, title: `- ${choice.title}` })),
|
||||
// @ts-expect-error field documented in npm, but not defined in typescript
|
||||
warn: 'This artifact has expired',
|
||||
});
|
||||
|
||||
if (selectedBuild == null) {
|
||||
return getArchiveAsync({
|
||||
...source,
|
||||
sourceType: ArchiveSourceType.prompt,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
build: selectedBuild,
|
||||
source,
|
||||
};
|
||||
} catch (err) {
|
||||
Log.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function formatBuildChoice(build: BuildFragment, expiryDate: Date): prompts.Choice {
|
||||
const {
|
||||
id,
|
||||
platform,
|
||||
updatedAt,
|
||||
appVersion,
|
||||
sdkVersion,
|
||||
runtimeVersion,
|
||||
buildProfile,
|
||||
appBuildVersion,
|
||||
releaseChannel,
|
||||
} = build;
|
||||
|
||||
const formatValue = (field?: string | null): string =>
|
||||
field ? chalk.bold(field) : chalk.dim('Unknown');
|
||||
|
||||
const buildDate = new Date(updatedAt);
|
||||
const maybeRuntimeVersion = runtimeVersion ? `Runtime: ${formatValue(runtimeVersion)}` : null;
|
||||
const maybeSdkVersion = sdkVersion ? `SDK: ${formatValue(sdkVersion)}` : null;
|
||||
const appBuildVersionString = `${
|
||||
platform === AppPlatform.Android ? 'Version code' : 'Build number'
|
||||
}: ${formatValue(appBuildVersion)}`;
|
||||
|
||||
const title = [
|
||||
`ID: ${chalk.dim(id)}, Finished at: ${chalk.bold(buildDate.toLocaleString())}`,
|
||||
[
|
||||
`\tApp version: ${formatValue(appVersion)}, ${appBuildVersionString}`,
|
||||
maybeRuntimeVersion,
|
||||
maybeSdkVersion,
|
||||
]
|
||||
.filter(it => it != null)
|
||||
.join(', '),
|
||||
`\tProfile: ${formatValue(buildProfile)}, Release channel: ${formatValue(releaseChannel)}`,
|
||||
].join('\n');
|
||||
|
||||
return {
|
||||
title,
|
||||
value: build,
|
||||
disabled: buildDate < expiryDate,
|
||||
};
|
||||
}
|
||||
|
||||
async function handlePromptSourceAsync(source: ArchivePromptSource): Promise<Archive> {
|
||||
const { sourceType: sourceTypeRaw } = await promptAsync({
|
||||
name: 'sourceType',
|
||||
@@ -326,8 +204,8 @@ async function handlePromptSourceAsync(source: ArchivePromptSource): Promise<Arc
|
||||
message: 'What would you like to submit?',
|
||||
choices: [
|
||||
{
|
||||
title: 'Selected build from EAS',
|
||||
value: ArchiveSourceType.buildList,
|
||||
title: 'Latest finished build from EAS',
|
||||
value: ArchiveSourceType.latest,
|
||||
},
|
||||
{ title: 'I have a url to the app archive', value: ArchiveSourceType.url },
|
||||
{
|
||||
@@ -358,10 +236,10 @@ async function handlePromptSourceAsync(source: ArchivePromptSource): Promise<Arc
|
||||
path,
|
||||
});
|
||||
}
|
||||
case ArchiveSourceType.buildList: {
|
||||
case ArchiveSourceType.latest: {
|
||||
return getArchiveAsync({
|
||||
...source,
|
||||
sourceType: ArchiveSourceType.buildList,
|
||||
sourceType: ArchiveSourceType.latest,
|
||||
});
|
||||
}
|
||||
case ArchiveSourceType.buildId: {
|
||||
@@ -372,7 +250,7 @@ async function handlePromptSourceAsync(source: ArchivePromptSource): Promise<Arc
|
||||
id,
|
||||
});
|
||||
}
|
||||
default:
|
||||
case ArchiveSourceType.prompt:
|
||||
throw new Error('This should never happen');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,8 @@ import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
import { toAppPlatform } from '../../graphql/types/AppPlatform';
|
||||
import { confirmAsync, promptAsync } from '../../prompts';
|
||||
import { uploadAsync } from '../../uploads';
|
||||
import {
|
||||
Archive,
|
||||
ArchiveSourceType,
|
||||
BUILD_LIST_ITEM_COUNT,
|
||||
getArchiveAsync,
|
||||
} from '../ArchiveSource';
|
||||
import { getRecentBuildsForSubmissionAsync } from '../utils/builds';
|
||||
import { Archive, ArchiveSourceType, getArchiveAsync } from '../ArchiveSource';
|
||||
import { getLatestBuildForSubmissionAsync } from '../utils/builds';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('../../log');
|
||||
@@ -37,7 +32,6 @@ const MOCK_BUILD_FRAGMENT: Partial<BuildFragment> = {
|
||||
},
|
||||
appVersion: '1.2.3',
|
||||
platform: AppPlatform.Android,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
const SOURCE_STUB_INPUT = {
|
||||
@@ -146,7 +140,7 @@ describe(getArchiveAsync, () => {
|
||||
|
||||
it('handles latest build source', async () => {
|
||||
const projectId = uuidv4();
|
||||
asMock(getRecentBuildsForSubmissionAsync).mockResolvedValueOnce([MOCK_BUILD_FRAGMENT]);
|
||||
asMock(getLatestBuildForSubmissionAsync).mockResolvedValueOnce(MOCK_BUILD_FRAGMENT);
|
||||
|
||||
const archive = await getArchiveAsync({
|
||||
...SOURCE_STUB_INPUT,
|
||||
@@ -154,7 +148,7 @@ describe(getArchiveAsync, () => {
|
||||
sourceType: ArchiveSourceType.latest,
|
||||
});
|
||||
|
||||
expect(getRecentBuildsForSubmissionAsync).toBeCalledWith(
|
||||
expect(getLatestBuildForSubmissionAsync).toBeCalledWith(
|
||||
toAppPlatform(SOURCE_STUB_INPUT.platform),
|
||||
projectId
|
||||
);
|
||||
@@ -162,7 +156,7 @@ describe(getArchiveAsync, () => {
|
||||
});
|
||||
|
||||
it('prompts again if no builds exists when selected latest', async () => {
|
||||
asMock(getRecentBuildsForSubmissionAsync).mockResolvedValueOnce([]);
|
||||
asMock(getLatestBuildForSubmissionAsync).mockResolvedValueOnce(null);
|
||||
asMock(promptAsync)
|
||||
.mockResolvedValueOnce({ sourceType: ArchiveSourceType.url })
|
||||
.mockResolvedValueOnce({ url: ARCHIVE_URL });
|
||||
@@ -175,59 +169,6 @@ describe(getArchiveAsync, () => {
|
||||
assertArchiveResult(archive, ArchiveSourceType.url);
|
||||
});
|
||||
|
||||
it('handles build-list-select source', async () => {
|
||||
const projectId = uuidv4();
|
||||
asMock(getRecentBuildsForSubmissionAsync).mockResolvedValueOnce([MOCK_BUILD_FRAGMENT]);
|
||||
asMock(promptAsync).mockResolvedValueOnce({ selectedBuild: MOCK_BUILD_FRAGMENT });
|
||||
|
||||
const archive = await getArchiveAsync({
|
||||
...SOURCE_STUB_INPUT,
|
||||
projectId,
|
||||
sourceType: ArchiveSourceType.buildList,
|
||||
});
|
||||
|
||||
expect(getRecentBuildsForSubmissionAsync).toBeCalledWith(
|
||||
toAppPlatform(SOURCE_STUB_INPUT.platform),
|
||||
projectId,
|
||||
{ limit: BUILD_LIST_ITEM_COUNT }
|
||||
);
|
||||
assertArchiveResult(archive, ArchiveSourceType.buildList);
|
||||
});
|
||||
|
||||
it('prompts again if all builds have expired', async () => {
|
||||
asMock(getRecentBuildsForSubmissionAsync).mockResolvedValueOnce([
|
||||
{
|
||||
...MOCK_BUILD_FRAGMENT,
|
||||
updatedAt: new Date(Date.now() - 31 * 24 * 3600 * 1000),
|
||||
},
|
||||
]);
|
||||
asMock(promptAsync)
|
||||
.mockResolvedValueOnce({ sourceType: ArchiveSourceType.url })
|
||||
.mockResolvedValueOnce({ url: ARCHIVE_URL });
|
||||
|
||||
const archive = await getArchiveAsync({
|
||||
...SOURCE_STUB_INPUT,
|
||||
sourceType: ArchiveSourceType.buildList,
|
||||
});
|
||||
|
||||
assertArchiveResult(archive, ArchiveSourceType.url);
|
||||
});
|
||||
|
||||
it('falls back to prompt if user selected "None of the above"', async () => {
|
||||
asMock(getRecentBuildsForSubmissionAsync).mockResolvedValueOnce([MOCK_BUILD_FRAGMENT]);
|
||||
asMock(promptAsync)
|
||||
.mockResolvedValueOnce({ selectedBuild: null })
|
||||
.mockResolvedValueOnce({ sourceType: ArchiveSourceType.url })
|
||||
.mockResolvedValueOnce({ url: ARCHIVE_URL });
|
||||
|
||||
const archive = await getArchiveAsync({
|
||||
...SOURCE_STUB_INPUT,
|
||||
sourceType: ArchiveSourceType.buildList,
|
||||
});
|
||||
|
||||
assertArchiveResult(archive, ArchiveSourceType.url);
|
||||
});
|
||||
|
||||
it('handles path source', async () => {
|
||||
const path = '/archive.apk';
|
||||
await fs.writeFile(path, 'some content');
|
||||
|
||||
@@ -15,7 +15,7 @@ import { SubmissionMutation } from '../../../graphql/mutations/SubmissionMutatio
|
||||
import { createTestProject } from '../../../project/__tests__/project-utils';
|
||||
import { getProjectIdAsync } from '../../../project/projectUtils';
|
||||
import { createSubmissionContextAsync } from '../../context';
|
||||
import { getRecentBuildsForSubmissionAsync } from '../../utils/builds';
|
||||
import { getLatestBuildForSubmissionAsync } from '../../utils/builds';
|
||||
import AndroidSubmitCommand from '../AndroidSubmitCommand';
|
||||
|
||||
jest.mock('fs');
|
||||
@@ -137,7 +137,7 @@ describe(AndroidSubmitCommand, () => {
|
||||
|
||||
it('assigns the build ID to submission', async () => {
|
||||
const projectId = uuidv4();
|
||||
asMock(getRecentBuildsForSubmissionAsync).mockResolvedValueOnce([fakeBuildFragment]);
|
||||
asMock(getLatestBuildForSubmissionAsync).mockResolvedValueOnce(fakeBuildFragment);
|
||||
|
||||
const ctx = await createSubmissionContextAsync({
|
||||
platform: Platform.ANDROID,
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { promptForAscApiKeyAsync } from '../../credentials/ios/actions/AscApiKeyUtils';
|
||||
import { AscApiKeyPath, MinimalAscApiKey } from '../../credentials/ios/credentials';
|
||||
import Log from '../../log';
|
||||
import { isExistingFileAsync } from '../utils/files';
|
||||
|
||||
export enum AscApiKeySourceType {
|
||||
path,
|
||||
prompt,
|
||||
}
|
||||
|
||||
interface AscApiKeySourceBase {
|
||||
sourceType: AscApiKeySourceType;
|
||||
}
|
||||
|
||||
interface AscApiKeyPromptSource extends AscApiKeySourceBase {
|
||||
sourceType: AscApiKeySourceType.prompt;
|
||||
}
|
||||
|
||||
interface AscApiKeyEnvVarSource extends AscApiKeySourceBase {
|
||||
sourceType: AscApiKeySourceType.path;
|
||||
path: AscApiKeyPath;
|
||||
}
|
||||
|
||||
export type AscApiKeySource = AscApiKeyEnvVarSource | AscApiKeyPromptSource;
|
||||
|
||||
type AscApiKeySummary = {
|
||||
source: 'local' | 'EAS servers';
|
||||
path?: string;
|
||||
keyId: string;
|
||||
};
|
||||
|
||||
export type AscApiKeyResult = {
|
||||
result: MinimalAscApiKey;
|
||||
summary: AscApiKeySummary;
|
||||
};
|
||||
|
||||
export async function getAscApiKeyLocallyAsync(source: AscApiKeySource): Promise<AscApiKeyResult> {
|
||||
const ascApiKeyPath = await getAscApiKeyPathAsync(source);
|
||||
const { keyP8Path, keyId, issuerId } = ascApiKeyPath;
|
||||
const keyP8 = await fs.readFile(keyP8Path, 'utf-8');
|
||||
|
||||
return {
|
||||
result: { keyP8, keyId, issuerId },
|
||||
summary: {
|
||||
source: 'local',
|
||||
path: keyP8Path,
|
||||
keyId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAscApiKeyPathAsync(source: AscApiKeySource): Promise<AscApiKeyPath> {
|
||||
switch (source.sourceType) {
|
||||
case AscApiKeySourceType.path:
|
||||
return await handlePathSourceAsync(source);
|
||||
case AscApiKeySourceType.prompt:
|
||||
return await handlePromptSourceAsync(source);
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePathSourceAsync(source: AscApiKeyEnvVarSource): Promise<AscApiKeyPath> {
|
||||
const { keyP8Path } = source.path;
|
||||
if (!(await isExistingFileAsync(keyP8Path))) {
|
||||
Log.warn(`File ${keyP8Path} doesn't exist.`);
|
||||
return await getAscApiKeyPathAsync({ sourceType: AscApiKeySourceType.prompt });
|
||||
}
|
||||
return source.path;
|
||||
}
|
||||
|
||||
async function handlePromptSourceAsync(_source: AscApiKeyPromptSource): Promise<AscApiKeyPath> {
|
||||
const ascApiKeyPath = await promptForAscApiKeyAsync();
|
||||
return await getAscApiKeyPathAsync({
|
||||
sourceType: AscApiKeySourceType.path,
|
||||
path: ascApiKeyPath,
|
||||
});
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import chalk from 'chalk';
|
||||
import getenv from 'getenv';
|
||||
import wrapAnsi from 'wrap-ansi';
|
||||
|
||||
import { MissingCredentialsError } from '../../credentials/errors';
|
||||
import { SubmissionFragment } from '../../graphql/generated';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { promptAsync } from '../../prompts';
|
||||
@@ -17,7 +16,6 @@ import {
|
||||
AppSpecificPasswordSource,
|
||||
AppSpecificPasswordSourceType,
|
||||
} from './AppSpecificPasswordSource';
|
||||
import { AscApiKeySource, AscApiKeySourceType } from './AscApiKeySource';
|
||||
import IosSubmitter, { IosSubmissionOptions } from './IosSubmitter';
|
||||
|
||||
export default class IosSubmitCommand {
|
||||
@@ -30,37 +28,15 @@ export default class IosSubmitCommand {
|
||||
return await submitter.submitAsync();
|
||||
}
|
||||
|
||||
private async resolveCredentialSubmissionOptionsAsync(): Promise<
|
||||
| { appSpecificPasswordSource: Result<AppSpecificPasswordSource> }
|
||||
| { ascApiKeySource: Result<AscApiKeySource> }
|
||||
> {
|
||||
// Fall back to app specific password if no ascApiKey defined
|
||||
const ascApiKeySource = this.resolveAscApiKeySource();
|
||||
const shouldUseAppSpecificPassword =
|
||||
!ascApiKeySource.ok && ascApiKeySource.enforceError() instanceof MissingCredentialsError;
|
||||
if (shouldUseAppSpecificPassword) {
|
||||
return { appSpecificPasswordSource: this.resolveAppSpecificPasswordSource() };
|
||||
} else {
|
||||
return { ascApiKeySource };
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveSubmissionOptionsAsync(): Promise<IosSubmissionOptions> {
|
||||
const archiveSource = this.resolveArchiveSource();
|
||||
const credentialsSource = await this.resolveCredentialSubmissionOptionsAsync();
|
||||
const maybeAppSpecificPasswordSource =
|
||||
'appSpecificPasswordSource' in credentialsSource
|
||||
? credentialsSource.appSpecificPasswordSource
|
||||
: null;
|
||||
const maybeAscApiKeySource =
|
||||
'ascApiKeySource' in credentialsSource ? credentialsSource.ascApiKeySource : null;
|
||||
const appSpecificPasswordSource = this.resolveAppSpecificPasswordSource();
|
||||
const ascAppIdentifier = await this.resolveAscAppIdentifierAsync();
|
||||
const appleIdUsername = await this.resolveAppleIdUsernameAsync();
|
||||
|
||||
const errored = [
|
||||
archiveSource,
|
||||
...(maybeAppSpecificPasswordSource ? [maybeAppSpecificPasswordSource] : []),
|
||||
...(maybeAscApiKeySource ? [maybeAscApiKeySource] : []),
|
||||
appSpecificPasswordSource,
|
||||
ascAppIdentifier,
|
||||
appleIdUsername,
|
||||
].filter(r => !r.ok);
|
||||
@@ -75,16 +51,7 @@ export default class IosSubmitCommand {
|
||||
appleIdUsername: appleIdUsername.enforceValue(),
|
||||
ascAppIdentifier: ascAppIdentifier.enforceValue(),
|
||||
archiveSource: archiveSource.enforceValue(),
|
||||
...(maybeAppSpecificPasswordSource
|
||||
? {
|
||||
appSpecificPasswordSource: maybeAppSpecificPasswordSource.enforceValue(),
|
||||
}
|
||||
: null),
|
||||
...(maybeAscApiKeySource
|
||||
? {
|
||||
ascApiKeySource: maybeAscApiKeySource.enforceValue(),
|
||||
}
|
||||
: null),
|
||||
appSpecificPasswordSource: appSpecificPasswordSource.enforceValue(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -105,35 +72,6 @@ export default class IosSubmitCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private resolveAscApiKeySource(): Result<AscApiKeySource> {
|
||||
const { ascApiKeyPath, ascApiKeyIssuerId, ascApiKeyId } = this.ctx.profile;
|
||||
|
||||
if (ascApiKeyPath && ascApiKeyIssuerId && ascApiKeyId) {
|
||||
return result({
|
||||
sourceType: AscApiKeySourceType.path,
|
||||
path: {
|
||||
keyP8Path: ascApiKeyPath,
|
||||
issuerId: ascApiKeyIssuerId,
|
||||
keyId: ascApiKeyId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// interpret this to mean the user had some intention of passing in ASC Api key
|
||||
if (ascApiKeyPath || ascApiKeyIssuerId || ascApiKeyId) {
|
||||
Log.warn(`ascApiKeyPath, ascApiKeyIssuerId and ascApiKeyId must all be defined in eas.json`);
|
||||
return result({
|
||||
sourceType: AscApiKeySourceType.prompt,
|
||||
});
|
||||
}
|
||||
|
||||
return result(
|
||||
new MissingCredentialsError(
|
||||
'Set the ascApiKeyPath, ascApiKeyIssuerId and ascApiKeyId fields in eas.json.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private resolveArchiveSource(): Result<ArchiveSource> {
|
||||
try {
|
||||
return result(resolveArchiveSource(this.ctx, Platform.IOS));
|
||||
@@ -177,10 +115,6 @@ export default class IosSubmitCommand {
|
||||
return result(envAppleId);
|
||||
}
|
||||
|
||||
if (this.ctx.credentialsCtx.appStore.authCtx?.appleId) {
|
||||
return result(this.ctx.credentialsCtx.appStore.authCtx.appleId);
|
||||
}
|
||||
|
||||
// Get the email address that was last used and set it as
|
||||
// the default value for quicker authentication.
|
||||
const lastAppleId = await UserSettings.getAsync('appleId', null);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { IosSubmissionConfigInput, SubmissionFragment } from '../../graphql/generated';
|
||||
import { SubmissionMutation } from '../../graphql/mutations/SubmissionMutation';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { Archive, ArchiveSource, getArchiveAsync } from '../ArchiveSource';
|
||||
import BaseSubmitter, { SubmissionInput } from '../BaseSubmitter';
|
||||
import {
|
||||
@@ -15,20 +13,17 @@ import {
|
||||
AppSpecificPasswordSource,
|
||||
getAppSpecificPasswordAsync,
|
||||
} from './AppSpecificPasswordSource';
|
||||
import { AscApiKeyResult, AscApiKeySource, getAscApiKeyLocallyAsync } from './AscApiKeySource';
|
||||
|
||||
export interface IosSubmissionOptions
|
||||
extends Pick<IosSubmissionConfigInput, 'appleIdUsername' | 'ascAppIdentifier'> {
|
||||
projectId: string;
|
||||
archiveSource: ArchiveSource;
|
||||
appSpecificPasswordSource?: AppSpecificPasswordSource;
|
||||
ascApiKeySource?: AscApiKeySource;
|
||||
appSpecificPasswordSource: AppSpecificPasswordSource;
|
||||
}
|
||||
|
||||
interface ResolvedSourceOptions {
|
||||
archive: Archive;
|
||||
appSpecificPassword?: string;
|
||||
ascApiKeyResult?: AscApiKeyResult;
|
||||
appSpecificPassword: string;
|
||||
}
|
||||
|
||||
export default class IosSubmitter extends BaseSubmitter<Platform.IOS, IosSubmissionOptions> {
|
||||
@@ -65,22 +60,18 @@ export default class IosSubmitter extends BaseSubmitter<Platform.IOS, IosSubmiss
|
||||
|
||||
private async resolveSourceOptionsAsync(): Promise<ResolvedSourceOptions> {
|
||||
const archive = await getArchiveAsync(this.options.archiveSource);
|
||||
const maybeAppSpecificPassword = this.options.appSpecificPasswordSource
|
||||
? await getAppSpecificPasswordAsync(this.options.appSpecificPasswordSource)
|
||||
: null;
|
||||
const maybeAppStoreConnectApiKey = this.options.ascApiKeySource
|
||||
? await getAscApiKeyLocallyAsync(this.options.ascApiKeySource)
|
||||
: null;
|
||||
const appSpecificPassword = await getAppSpecificPasswordAsync(
|
||||
this.options.appSpecificPasswordSource
|
||||
);
|
||||
return {
|
||||
archive,
|
||||
...(maybeAppSpecificPassword ? { appSpecificPassword: maybeAppSpecificPassword } : null),
|
||||
...(maybeAppStoreConnectApiKey ? { ascApiKeyResult: maybeAppStoreConnectApiKey } : null),
|
||||
appSpecificPassword,
|
||||
};
|
||||
}
|
||||
|
||||
private async formatSubmissionConfigAsync(
|
||||
options: IosSubmissionOptions,
|
||||
{ archive, appSpecificPassword, ascApiKeyResult }: ResolvedSourceOptions
|
||||
{ archive, appSpecificPassword }: ResolvedSourceOptions
|
||||
): Promise<IosSubmissionConfigInput> {
|
||||
const { appleIdUsername, ascAppIdentifier } = options;
|
||||
return {
|
||||
@@ -88,21 +79,12 @@ export default class IosSubmitter extends BaseSubmitter<Platform.IOS, IosSubmiss
|
||||
appleIdUsername,
|
||||
archiveUrl: archive.url,
|
||||
appleAppSpecificPassword: appSpecificPassword,
|
||||
...(ascApiKeyResult?.result
|
||||
? {
|
||||
ascApiKey: {
|
||||
keyP8: ascApiKeyResult?.result.keyP8,
|
||||
keyIdentifier: ascApiKeyResult?.result.keyId,
|
||||
issuerIdentifier: ascApiKeyResult?.result.issuerId,
|
||||
},
|
||||
}
|
||||
: null),
|
||||
};
|
||||
}
|
||||
|
||||
private prepareSummaryData(
|
||||
options: IosSubmissionOptions,
|
||||
{ archive, ascApiKeyResult }: ResolvedSourceOptions
|
||||
{ archive }: ResolvedSourceOptions
|
||||
): SummaryData {
|
||||
const { appleIdUsername, ascAppIdentifier, projectId } = options;
|
||||
|
||||
@@ -111,9 +93,6 @@ export default class IosSubmitter extends BaseSubmitter<Platform.IOS, IosSubmiss
|
||||
ascAppIdentifier,
|
||||
appleIdUsername,
|
||||
projectId,
|
||||
...(ascApiKeyResult
|
||||
? { formattedAscApiKey: formatServiceAccountSummary(ascApiKeyResult) }
|
||||
: null),
|
||||
...formatArchiveSourceSummary(archive),
|
||||
};
|
||||
}
|
||||
@@ -123,7 +102,6 @@ type SummaryData = {
|
||||
ascAppIdentifier: string;
|
||||
appleIdUsername: string;
|
||||
projectId: string;
|
||||
formattedAscApiKey?: string;
|
||||
} & ArchiveSourceSummaryFields;
|
||||
|
||||
const SummaryHumanReadableKeys: Record<keyof SummaryData, string> = {
|
||||
@@ -133,36 +111,4 @@ const SummaryHumanReadableKeys: Record<keyof SummaryData, string> = {
|
||||
archiveUrl: 'Archive URL',
|
||||
archivePath: 'Archive Path',
|
||||
formattedBuild: 'Build',
|
||||
formattedAscApiKey: 'App Store Connect Api Key',
|
||||
};
|
||||
|
||||
function formatServiceAccountSummary({ summary }: AscApiKeyResult): string {
|
||||
const { source, path, keyId } = summary;
|
||||
|
||||
const fields = [
|
||||
{
|
||||
label: 'Key Source',
|
||||
value: source,
|
||||
},
|
||||
{
|
||||
label: 'Key Path',
|
||||
value: path,
|
||||
},
|
||||
{
|
||||
label: 'Key ID',
|
||||
value: keyId,
|
||||
},
|
||||
];
|
||||
|
||||
const filteredFields = fields.filter(({ value }) => value !== undefined && value !== null) as {
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
|
||||
return (
|
||||
'\n' +
|
||||
formatFields(filteredFields, {
|
||||
labelFormat: label => ` ${chalk.dim(label)}:`,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
import { vol } from 'memfs';
|
||||
|
||||
import { asMock } from '../../../__tests__/utils';
|
||||
import { getCredentialsFromUserAsync } from '../../../credentials/utils/promptForCredentials';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import {
|
||||
AscApiKeySource,
|
||||
AscApiKeySourceType,
|
||||
getAscApiKeyLocallyAsync,
|
||||
getAscApiKeyPathAsync,
|
||||
} from '../AscApiKeySource';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('../../../prompts');
|
||||
jest.mock('../../../credentials/utils/promptForCredentials');
|
||||
|
||||
beforeAll(() => {
|
||||
vol.fromJSON({
|
||||
'/asc-api-key.p8': 'super secret',
|
||||
'/project_dir/subdir/asc-api-key.p8': 'super secret',
|
||||
'/project_dir/another-asc-api-key.p8': 'super secret',
|
||||
});
|
||||
});
|
||||
afterAll(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
asMock(promptAsync).mockClear();
|
||||
});
|
||||
|
||||
describe(getAscApiKeyPathAsync, () => {
|
||||
describe('when source is AscApiKeySourceType.path', () => {
|
||||
it("prompts for path if the provided file doesn't exist", async () => {
|
||||
asMock(promptAsync).mockImplementationOnce(() => ({
|
||||
keyP8Path: '/asc-api-key.p8',
|
||||
}));
|
||||
asMock(getCredentialsFromUserAsync).mockImplementationOnce(() => ({
|
||||
keyId: 'test-key-id',
|
||||
issuerId: 'test-issuer-id',
|
||||
}));
|
||||
const source: AscApiKeySource = {
|
||||
sourceType: AscApiKeySourceType.path,
|
||||
path: { keyP8Path: '/doesnt-exist.p8', keyId: 'test-key-id', issuerId: 'test-issuer-id' },
|
||||
};
|
||||
const ascApiKeyPath = await getAscApiKeyPathAsync(source);
|
||||
expect(promptAsync).toHaveBeenCalled();
|
||||
expect(ascApiKeyPath).toEqual({
|
||||
keyP8Path: '/asc-api-key.p8',
|
||||
keyId: 'test-key-id',
|
||||
issuerId: 'test-issuer-id',
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't prompt for path if the provided file exists", async () => {
|
||||
const source: AscApiKeySource = {
|
||||
sourceType: AscApiKeySourceType.path,
|
||||
path: { keyP8Path: '/asc-api-key.p8', keyId: 'test-key-id', issuerId: 'test-issuer-id' },
|
||||
};
|
||||
await getAscApiKeyPathAsync(source);
|
||||
expect(promptAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns the provided file path if the file exists', async () => {
|
||||
const source: AscApiKeySource = {
|
||||
sourceType: AscApiKeySourceType.path,
|
||||
path: { keyP8Path: '/asc-api-key.p8', keyId: 'test-key-id', issuerId: 'test-issuer-id' },
|
||||
};
|
||||
const ascApiKeyPath = await getAscApiKeyPathAsync(source);
|
||||
expect(ascApiKeyPath).toEqual({
|
||||
issuerId: 'test-issuer-id',
|
||||
keyId: 'test-key-id',
|
||||
keyP8Path: '/asc-api-key.p8',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when source is AscApiKeySourceType.prompt', () => {
|
||||
it('prompts for path', async () => {
|
||||
asMock(promptAsync).mockImplementationOnce(() => ({
|
||||
keyP8Path: '/asc-api-key.p8',
|
||||
}));
|
||||
asMock(getCredentialsFromUserAsync).mockImplementationOnce(() => ({
|
||||
keyId: 'test-key-id',
|
||||
issuerId: 'test-issuer-id',
|
||||
}));
|
||||
const source: AscApiKeySource = {
|
||||
sourceType: AscApiKeySourceType.prompt,
|
||||
};
|
||||
const ascApiKeyPath = await getAscApiKeyPathAsync(source);
|
||||
expect(promptAsync).toHaveBeenCalled();
|
||||
expect(ascApiKeyPath).toEqual({
|
||||
keyP8Path: '/asc-api-key.p8',
|
||||
keyId: 'test-key-id',
|
||||
issuerId: 'test-issuer-id',
|
||||
});
|
||||
});
|
||||
|
||||
it('prompts for path until the user provides an existing file', async () => {
|
||||
asMock(promptAsync)
|
||||
.mockImplementationOnce(() => ({
|
||||
keyP8Path: '/doesnt-exist.p8',
|
||||
}))
|
||||
.mockImplementationOnce(() => ({
|
||||
keyP8Path: '/blah.p8',
|
||||
}))
|
||||
.mockImplementationOnce(() => ({
|
||||
keyP8Path: '/asc-api-key.p8',
|
||||
}));
|
||||
asMock(getCredentialsFromUserAsync).mockImplementation(() => ({
|
||||
keyId: 'test-key-id',
|
||||
issuerId: 'test-issuer-id',
|
||||
}));
|
||||
const source: AscApiKeySource = {
|
||||
sourceType: AscApiKeySourceType.prompt,
|
||||
};
|
||||
const ascApiKeyPath = await getAscApiKeyPathAsync(source);
|
||||
expect(promptAsync).toHaveBeenCalledTimes(3);
|
||||
expect(ascApiKeyPath).toEqual({
|
||||
keyP8Path: '/asc-api-key.p8',
|
||||
keyId: 'test-key-id',
|
||||
issuerId: 'test-issuer-id',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(getAscApiKeyLocallyAsync, () => {
|
||||
it('returns a local Asc Api Key file with a AscApiKeySourceType.path source', async () => {
|
||||
const source: AscApiKeySource = {
|
||||
sourceType: AscApiKeySourceType.path,
|
||||
path: { keyP8Path: '/asc-api-key.p8', keyId: 'test-key-id', issuerId: 'test-issuer-id' },
|
||||
};
|
||||
const ascApiKeyResult = await getAscApiKeyLocallyAsync(source);
|
||||
expect(ascApiKeyResult).toMatchObject({
|
||||
result: {
|
||||
keyP8: 'super secret',
|
||||
keyId: 'test-key-id',
|
||||
issuerId: 'test-issuer-id',
|
||||
},
|
||||
summary: {
|
||||
source: 'local',
|
||||
path: '/asc-api-key.p8',
|
||||
keyId: 'test-key-id',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a local Asc Api Key file with a AscApiKeySourceType.prompt source', async () => {
|
||||
asMock(promptAsync).mockImplementationOnce(() => ({
|
||||
keyP8Path: '/asc-api-key.p8',
|
||||
}));
|
||||
asMock(getCredentialsFromUserAsync).mockImplementationOnce(() => ({
|
||||
keyId: 'test-key-id',
|
||||
issuerId: 'test-issuer-id',
|
||||
}));
|
||||
const source: AscApiKeySource = {
|
||||
sourceType: AscApiKeySourceType.prompt,
|
||||
};
|
||||
const serviceAccountResult = await getAscApiKeyLocallyAsync(source);
|
||||
expect(serviceAccountResult).toMatchObject({
|
||||
result: {
|
||||
keyP8: 'super secret',
|
||||
keyId: 'test-key-id',
|
||||
issuerId: 'test-issuer-id',
|
||||
},
|
||||
summary: {
|
||||
source: 'local',
|
||||
path: '/asc-api-key.p8',
|
||||
keyId: 'test-key-id',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,17 +1,16 @@
|
||||
import { AppPlatform, BuildFragment, BuildStatus, DistributionType } from '../../graphql/generated';
|
||||
import { AppPlatform, BuildFragment, BuildStatus } from '../../graphql/generated';
|
||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
|
||||
export async function getRecentBuildsForSubmissionAsync(
|
||||
export async function getLatestBuildForSubmissionAsync(
|
||||
platform: AppPlatform,
|
||||
appId: string,
|
||||
{ limit = 1 }: { limit?: number } = {}
|
||||
): Promise<BuildFragment[]> {
|
||||
return await BuildQuery.allForAppAsync(appId, {
|
||||
limit,
|
||||
appId: string
|
||||
): Promise<BuildFragment | null> {
|
||||
const [build] = await BuildQuery.allForAppAsync(appId, {
|
||||
limit: 1,
|
||||
filter: {
|
||||
platform,
|
||||
distribution: DistributionType.Store,
|
||||
status: BuildStatus.Finished,
|
||||
},
|
||||
});
|
||||
return build;
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
|
||||
import { getProfilesAsync } from '../profiles';
|
||||
|
||||
describe(getProfilesAsync, () => {
|
||||
test('defaults to production profile', async () => {
|
||||
const callback = jest.fn();
|
||||
const result = await getProfilesAsync({
|
||||
platforms: [Platform.ANDROID, Platform.IOS],
|
||||
profileName: undefined,
|
||||
readProfileAsync: callback,
|
||||
});
|
||||
|
||||
expect(result[0].profileName).toBe('production');
|
||||
expect(result[1].profileName).toBe('production');
|
||||
expect(callback).toBeCalledWith(Platform.ANDROID, 'production');
|
||||
expect(callback).toBeCalledWith(Platform.IOS, 'production');
|
||||
});
|
||||
|
||||
test('defaults to release profile when production profile is non-existent', async () => {
|
||||
const callback = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(() => {
|
||||
throw new Error();
|
||||
})
|
||||
.mockRejectedValueOnce(() => {
|
||||
throw new Error();
|
||||
});
|
||||
|
||||
const result = await getProfilesAsync({
|
||||
platforms: [Platform.ANDROID, Platform.IOS],
|
||||
profileName: undefined,
|
||||
readProfileAsync: callback,
|
||||
});
|
||||
|
||||
expect(result[0].profileName).toBe('release');
|
||||
expect(result[1].profileName).toBe('release');
|
||||
expect(callback).toBeCalledWith(Platform.ANDROID, 'production');
|
||||
expect(callback).toBeCalledWith(Platform.IOS, 'production');
|
||||
expect(callback).toBeCalledWith(Platform.ANDROID, 'release');
|
||||
expect(callback).toBeCalledWith(Platform.IOS, 'release');
|
||||
});
|
||||
|
||||
test('fails when neither production or release profiles are present', async () => {
|
||||
const callback = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(() => {
|
||||
throw new Error();
|
||||
})
|
||||
.mockRejectedValueOnce(() => {
|
||||
throw new Error();
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProfilesAsync({
|
||||
platforms: [Platform.ANDROID],
|
||||
profileName: undefined,
|
||||
readProfileAsync: callback,
|
||||
})
|
||||
).rejects.toThrowError(/There is no profile named "production" in eas.json/);
|
||||
});
|
||||
|
||||
test('gets a specific profile', async () => {
|
||||
const callback = jest.fn();
|
||||
const result = await getProfilesAsync({
|
||||
platforms: [Platform.ANDROID, Platform.IOS],
|
||||
profileName: 'custom-profile',
|
||||
readProfileAsync: callback,
|
||||
});
|
||||
|
||||
expect(result[0].profileName).toBe('custom-profile');
|
||||
expect(result[1].profileName).toBe('custom-profile');
|
||||
expect(callback).toBeCalledWith(Platform.ANDROID, 'custom-profile');
|
||||
expect(callback).toBeCalledWith(Platform.IOS, 'custom-profile');
|
||||
});
|
||||
});
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
|
||||
import Log from '../log';
|
||||
|
||||
export type ProfileData<T> = {
|
||||
profile: T;
|
||||
platform: Platform;
|
||||
profileName: string;
|
||||
};
|
||||
|
||||
export async function getProfilesAsync<T>({
|
||||
platforms,
|
||||
profileName: profileNameArg,
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
readProfileAsync,
|
||||
}: {
|
||||
platforms: Platform[];
|
||||
profileName?: string | null;
|
||||
readProfileAsync: (platform: Platform, profileName: string) => Promise<T>;
|
||||
}): Promise<ProfileData<T>[]> {
|
||||
const results = platforms.map(async function (platform) {
|
||||
let profile;
|
||||
let profileName = profileNameArg;
|
||||
|
||||
if (!profileName) {
|
||||
try {
|
||||
profile = await readProfileAsync(platform, 'production');
|
||||
profileName = 'production';
|
||||
} catch (error) {
|
||||
try {
|
||||
profile = await readProfileAsync(platform, 'release');
|
||||
profileName = 'release';
|
||||
Log.warn(
|
||||
'The default profile changed from "release" to "production". We detected that you still have a "release" build profile, so we are using it. Update eas.json to have a profile named "production" under the `build` key, or specify which profile you\'d like to use with the --profile flag. This fallback behavior will be removed in the next major version of EAS CLI.'
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error('There is no profile named "production" in eas.json');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
profile = await readProfileAsync(platform, profileName);
|
||||
}
|
||||
|
||||
return {
|
||||
profile,
|
||||
profileName,
|
||||
platform,
|
||||
};
|
||||
});
|
||||
|
||||
return await Promise.all(results);
|
||||
}
|
||||
@@ -2,13 +2,15 @@
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"importHelpers": true,
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"module": "ES2020",
|
||||
"noImplicitReturns": true,
|
||||
"strict": true,
|
||||
"target": "ES2019",
|
||||
"outDir": "build",
|
||||
"rootDir": "src",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"typeRoots": [
|
||||
"../../node_modules/@types",
|
||||
"../../ts-declarations",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"type": "module",
|
||||
"description": "A library for interacting with the eas.json",
|
||||
"version": "0.33.0",
|
||||
"version": "0.31.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
@@ -21,14 +22,16 @@
|
||||
},
|
||||
"homepage": "https://github.com/expo/eas-cli",
|
||||
"license": "MIT",
|
||||
"main": "build/index.js",
|
||||
"exports": "./build/index.js",
|
||||
"types": "./build/index.d.ts",
|
||||
"repository": "expo/eas-cli",
|
||||
"scripts": {
|
||||
"build": "tsc --project tsconfig.build.json",
|
||||
"watch": "yarn build --watch --preserveWatchOutput",
|
||||
"typecheck": "tsc",
|
||||
"prepack": "rm -rf build && yarn build",
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"clean": "rm -rf build node_modules"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -81,24 +81,18 @@ export class EasJsonReader {
|
||||
|
||||
public async readSubmitProfileAsync<T extends Platform>(
|
||||
platform: T,
|
||||
profileNameArg?: string
|
||||
profileName?: string
|
||||
): Promise<SubmitProfile<T>> {
|
||||
let profileName = profileNameArg;
|
||||
|
||||
if (!profileName) {
|
||||
const profileNames = await this.getSubmitProfileNamesAsync({
|
||||
throwIfEasJsonDoesNotExist: false,
|
||||
});
|
||||
|
||||
if (profileNames.includes('production')) {
|
||||
profileName = 'production';
|
||||
} else if (profileNames.includes('release')) {
|
||||
profileName = 'release';
|
||||
if (profileNames.includes('release')) {
|
||||
return await this.readSubmitProfileAsync(platform, 'release');
|
||||
} else {
|
||||
return getDefaultSubmitProfile(platform);
|
||||
}
|
||||
}
|
||||
|
||||
const easJson = await this.readAndValidateAsync();
|
||||
const profile = easJson?.submit?.[profileName];
|
||||
if (!profile) {
|
||||
|
||||
@@ -90,9 +90,6 @@ export const AndroidSubmitProfileSchema = Joi.object({
|
||||
});
|
||||
|
||||
export const IosSubmitProfileSchema = Joi.object({
|
||||
ascApiKeyPath: Joi.string(),
|
||||
ascApiKeyId: Joi.string(),
|
||||
ascApiKeyIssuerId: Joi.string(),
|
||||
appleId: Joi.string(),
|
||||
ascAppId: Joi.string(),
|
||||
appleTeamId: Joi.string(),
|
||||
|
||||
@@ -26,9 +26,6 @@ export const AndroidSubmitProfileFieldsToEvaluate: (keyof AndroidSubmitProfile)[
|
||||
];
|
||||
|
||||
export interface IosSubmitProfile {
|
||||
ascApiKeyPath?: string;
|
||||
ascApiKeyIssuerId?: string;
|
||||
ascApiKeyId?: string;
|
||||
appleId?: string;
|
||||
ascAppId?: string;
|
||||
appleTeamId?: string;
|
||||
@@ -38,11 +35,7 @@ export interface IosSubmitProfile {
|
||||
appName?: string;
|
||||
}
|
||||
|
||||
export const IosSubmitProfileFieldsToEvaluate: (keyof IosSubmitProfile)[] = [
|
||||
'ascApiKeyPath',
|
||||
'ascApiKeyIssuerId',
|
||||
'ascApiKeyId',
|
||||
];
|
||||
export const IosSubmitProfileFieldsToEvaluate: (keyof IosSubmitProfile)[] = [];
|
||||
|
||||
export type SubmitProfile<TPlatform extends Platform = Platform> =
|
||||
TPlatform extends Platform.ANDROID
|
||||
|
||||
@@ -14,13 +14,13 @@ beforeEach(async () => {
|
||||
test('minimal valid eas.json for both platforms', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
production: {},
|
||||
release: {},
|
||||
},
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const iosProfile = await reader.readBuildProfileAsync(Platform.IOS, 'production');
|
||||
const androidProfile = await reader.readBuildProfileAsync(Platform.ANDROID, 'production');
|
||||
const iosProfile = await reader.readBuildProfileAsync(Platform.IOS, 'release');
|
||||
const androidProfile = await reader.readBuildProfileAsync(Platform.ANDROID, 'release');
|
||||
|
||||
expect({
|
||||
distribution: 'store',
|
||||
@@ -36,7 +36,7 @@ test('minimal valid eas.json for both platforms', async () => {
|
||||
test('valid eas.json for development client builds', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
production: {},
|
||||
release: {},
|
||||
debug: {
|
||||
developmentClient: true,
|
||||
android: {
|
||||
@@ -219,7 +219,7 @@ test('valid profile extending other profile with platform specific caching', asy
|
||||
test('valid eas.json with missing profile', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
production: {},
|
||||
release: {},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -231,14 +231,14 @@ test('valid eas.json with missing profile', async () => {
|
||||
test('invalid eas.json when using wrong buildType', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
production: { android: { buildType: 'archive' } },
|
||||
release: { android: { buildType: 'archive' } },
|
||||
},
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'production');
|
||||
const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'release');
|
||||
await expect(promise).rejects.toThrowError(
|
||||
'eas.json is not valid [ValidationError: "build.production.android.buildType" must be one of [apk, app-bundle]]'
|
||||
'eas.json is not valid [ValidationError: "build.release.android.buildType" must be one of [apk, app-bundle]]'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -246,33 +246,33 @@ test('empty json', async () => {
|
||||
await fs.writeJson('/project/eas.json', {});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'production');
|
||||
await expect(promise).rejects.toThrowError('There is no profile named production in eas.json');
|
||||
const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'release');
|
||||
await expect(promise).rejects.toThrowError('There is no profile named release in eas.json');
|
||||
});
|
||||
|
||||
test('invalid semver value', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
production: { node: '12.0.0-alpha' },
|
||||
release: { node: '12.0.0-alpha' },
|
||||
},
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'production');
|
||||
const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'release');
|
||||
await expect(promise).rejects.toThrowError(
|
||||
'eas.json is not valid [ValidationError: "build.production.node" failed custom validation because 12.0.0-alpha is not a valid version]'
|
||||
'eas.json is not valid [ValidationError: "build.release.node" failed custom validation because 12.0.0-alpha is not a valid version]'
|
||||
);
|
||||
});
|
||||
|
||||
test('get profile names', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
production: { node: '12.0.0-alpha' },
|
||||
release: { node: '12.0.0-alpha' },
|
||||
blah: { node: '12.0.0-alpha' },
|
||||
},
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const allProfileNames = await reader.getBuildProfileNamesAsync();
|
||||
expect(allProfileNames.sort()).toEqual(['blah', 'production'].sort());
|
||||
expect(allProfileNames.sort()).toEqual(['blah', 'release'].sort());
|
||||
});
|
||||
@@ -14,13 +14,13 @@ beforeEach(async () => {
|
||||
test('minimal allowed eas.json for both platforms', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
submit: {
|
||||
production: {},
|
||||
release: {},
|
||||
},
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const iosProfile = await reader.readSubmitProfileAsync(Platform.IOS, 'production');
|
||||
const androidProfile = await reader.readSubmitProfileAsync(Platform.ANDROID, 'production');
|
||||
const iosProfile = await reader.readSubmitProfileAsync(Platform.IOS, 'release');
|
||||
const androidProfile = await reader.readSubmitProfileAsync(Platform.ANDROID, 'release');
|
||||
|
||||
expect(androidProfile).toEqual({
|
||||
changesNotSentForReview: false,
|
||||
@@ -35,7 +35,7 @@ test('minimal allowed eas.json for both platforms', async () => {
|
||||
test('android config with all required values', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
submit: {
|
||||
production: {
|
||||
release: {
|
||||
android: {
|
||||
serviceAccountKeyPath: './path.json',
|
||||
track: 'beta',
|
||||
@@ -46,7 +46,7 @@ test('android config with all required values', async () => {
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const androidProfile = await reader.readSubmitProfileAsync(Platform.ANDROID, 'production');
|
||||
const androidProfile = await reader.readSubmitProfileAsync(Platform.ANDROID, 'release');
|
||||
|
||||
expect(androidProfile).toEqual({
|
||||
serviceAccountKeyPath: './path.json',
|
||||
@@ -59,7 +59,7 @@ test('android config with all required values', async () => {
|
||||
test('android config with serviceAccountKeyPath set to env var', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
submit: {
|
||||
production: {
|
||||
release: {
|
||||
android: {
|
||||
serviceAccountKeyPath: '$GOOGLE_SERVICE_ACCOUNT',
|
||||
track: 'beta',
|
||||
@@ -72,7 +72,7 @@ test('android config with serviceAccountKeyPath set to env var', async () => {
|
||||
try {
|
||||
process.env.GOOGLE_SERVICE_ACCOUNT = './path.json';
|
||||
const reader = new EasJsonReader('/project');
|
||||
const androidProfile = await reader.readSubmitProfileAsync(Platform.ANDROID, 'production');
|
||||
const androidProfile = await reader.readSubmitProfileAsync(Platform.ANDROID, 'release');
|
||||
|
||||
expect(androidProfile).toEqual({
|
||||
serviceAccountKeyPath: './path.json',
|
||||
@@ -88,68 +88,23 @@ test('android config with serviceAccountKeyPath set to env var', async () => {
|
||||
test('ios config with all required values', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
submit: {
|
||||
production: {
|
||||
release: {
|
||||
ios: {
|
||||
appleId: '[email protected]',
|
||||
ascAppId: '1223423523',
|
||||
appleTeamId: 'QWERTY',
|
||||
ascApiKeyPath: './path-ABCD.p8',
|
||||
ascApiKeyIssuerId: 'abc-123-def-456',
|
||||
ascApiKeyId: 'ABCD',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const iosProfile = await reader.readSubmitProfileAsync(Platform.IOS, 'production');
|
||||
const iosProfile = await reader.readSubmitProfileAsync(Platform.IOS, 'release');
|
||||
|
||||
expect(iosProfile).toEqual({
|
||||
appleId: '[email protected]',
|
||||
appleTeamId: 'QWERTY',
|
||||
ascAppId: '1223423523',
|
||||
ascApiKeyPath: './path-ABCD.p8',
|
||||
ascApiKeyIssuerId: 'abc-123-def-456',
|
||||
ascApiKeyId: 'ABCD',
|
||||
language: 'en-US',
|
||||
});
|
||||
});
|
||||
|
||||
test('ios config with ascApiKey fields set to env var', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
submit: {
|
||||
release: {
|
||||
ios: {
|
||||
appleId: '[email protected]',
|
||||
ascAppId: '1223423523',
|
||||
appleTeamId: 'QWERTY',
|
||||
ascApiKeyPath: '$ASC_API_KEY_PATH',
|
||||
ascApiKeyIssuerId: '$ASC_API_KEY_ISSUER_ID',
|
||||
ascApiKeyId: '$ASC_API_KEY_ID',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
process.env.ASC_API_KEY_PATH = './path-ABCD.p8';
|
||||
process.env.ASC_API_KEY_ISSUER_ID = 'abc-123-def-456';
|
||||
process.env.ASC_API_KEY_ID = 'ABCD';
|
||||
const reader = new EasJsonReader('/project');
|
||||
const iosProfile = await reader.readSubmitProfileAsync(Platform.IOS, 'release');
|
||||
|
||||
expect(iosProfile).toEqual({
|
||||
appleId: '[email protected]',
|
||||
ascAppId: '1223423523',
|
||||
appleTeamId: 'QWERTY',
|
||||
ascApiKeyPath: './path-ABCD.p8',
|
||||
ascApiKeyIssuerId: 'abc-123-def-456',
|
||||
ascApiKeyId: 'ABCD',
|
||||
language: 'en-US',
|
||||
});
|
||||
} finally {
|
||||
process.env.ASC_API_KEY_PATH = undefined;
|
||||
process.env.ASC_API_KEY_ISSUER_ID = undefined;
|
||||
process.env.ASC_API_KEY_ID = undefined;
|
||||
}
|
||||
});
|
||||
@@ -35,7 +35,7 @@ test('migration for default manged eas.json', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('migration for default bare workflow eas.json', async () => {
|
||||
test('migration for default generic eas.json', async () => {
|
||||
const easJson = {
|
||||
builds: {
|
||||
android: {
|
||||
@@ -194,7 +194,7 @@ test('migration for example manged eas.json', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('migration for example bare workflow eas.json', async () => {
|
||||
test('migration for example generic eas.json', async () => {
|
||||
const easJson = {
|
||||
builds: {
|
||||
android: {
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"importHelpers": true,
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"module": "ES2020",
|
||||
"noImplicitReturns": true,
|
||||
"strict": true,
|
||||
"target": "ES2019",
|
||||
"outDir": "build",
|
||||
"rootDir": "src",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"typeRoots": [
|
||||
"../../node_modules/@types",
|
||||
"../../ts-declarations",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "scripts",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "0.31.0",
|
||||
"scripts": {
|
||||
"changelog-entry": "ts-node src/changelogEntry.ts",
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"importHelpers": true,
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"module": "ES2020",
|
||||
"noImplicitReturns": true,
|
||||
"strict": true,
|
||||
"target": "ES2019",
|
||||
"outDir": "build",
|
||||
"rootDir": "src",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"typeRoots": [
|
||||
"../ts-declarations",
|
||||
"./node_modules/@types"
|
||||
|
||||
Reference in new issue
Block a user