Compare commits

..
Author SHA1 Message Date
Dominik Sokal e7dbe4ee71 upgrade @expo/plugin-warn-if-update-available 2022-05-12 10:40:24 +02:00
141 changed files with 865 additions and 6800 deletions

No files matched your search

+1 -1
View File
@@ -37,7 +37,7 @@ body:
required: true
attributes:
label: Environment
description: Run `npx expo-env-info` and `expo doctor`, and paste the output here.
description: Run `npx expo-env-info` and paste the output here.
- type: textarea
validations:
required: false
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18
node-version: '16'
- name: Checkout Pull Request
run: hub pr checkout ${{ github.event.issue.number }}
env:
+5 -5
View File
@@ -27,7 +27,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18
node-version: '16'
- run: yarn install --frozen-lockfile --check-files
- run: yarn build
- name: Build tarballs
@@ -68,7 +68,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18
node-version: '16'
- run: yarn install --frozen-lockfile --check-files
- run: yarn build
- name: Build tarballs
@@ -96,7 +96,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18
node-version: '16'
- run: yarn install --frozen-lockfile --check-files
- run: sudo apt-get install nsis
- run: yarn build
@@ -138,7 +138,7 @@ jobs:
with:
registry-url: 'https://registry.npmjs.org/'
scope: 'expo'
node-version: 18
node-version: '16'
- name: Install dependencies
run: yarn install --frozen-lockfile --check-files
- name: Build
@@ -155,7 +155,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18
node-version: '16'
- name: Install dependencies
working-directory: ./scripts
run: yarn install --frozen-lockfile --check-files
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18
node-version: 16
- uses: preactjs/compressed-size-action@v2
with:
build-script: 'build:tarballs:linux'
+2 -2
View File
@@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node: ['14', '16', '18']
node: ['12', '14', '16']
include:
- node: '18'
- node: '16'
coverage: true
name: Test with Node ${{ matrix.node }}
steps:
-1
View File
@@ -6,7 +6,6 @@ packages/eas-cli/tmp
# Code editors
.idea
.history
.vscode
# macOS
.DS_Store
+1 -69
View File
@@ -8,79 +8,11 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features
- Show bundler output by default for eas update command. ([#1171](https://github.com/expo/eas-cli/pull/1171) by [@kgc00](https://github.com/kgc00/))
- Allow users to skip metadata validation. ([#1175](https://github.com/expo/eas-cli/pull/1175) by [@byCedric](https://github.com/byCedric))
- Add experimental `--resource-class` flag for Build commands. ([#1138](https://github.com/expo/eas-cli/pull/1138) by [@christopherwalter](https://github.com/christopherwalter))
- Truncate long messages for `eas update` command, rather than failing. ([#1178](https://github.com/expo/eas-cli/pull/1178) by [@kgc00](https://github.com/kgc00))
- Add review details to metadata store configuration. ([#1184](https://github.com/expo/eas-cli/pull/1184) by [@byCedric](https://github.com/byCedric))
### 🐛 Bug fixes
- Fix submission archive URLs. ([#1179](https://github.com/expo/eas-cli/pull/1179) by [@dsokal](https://github.com/dsokal))
### 🧹 Chores
- Update validation rules for metadata info locales. ([#1174](https://github.com/expo/eas-cli/pull/1174) by [@byCedric](https://github.com/byCedric))
- Improve store configuration defaults and schema documentation. ([#1183](https://github.com/expo/eas-cli/pull/1183) by [@byCedric](https://github.com/byCedric))
## [0.54.1](https://github.com/expo/eas-cli/releases/tag/v0.54.1) - 2022-06-15
### 🎉 New features
- Add App Store Connect link after `eas metadata:push`. ([#1168](https://github.com/expo/eas-cli/pull/1168) by [@byCedric](https://github.com/byCedric))
### 🐛 Bug fixes
- Surface ASC errors in metadata for normal API rejections. ([#1167](https://github.com/expo/eas-cli/pull/1167) by [@byCedric](https://github.com/byCedric))
- Add metadata schema to bundled eas-cli package. ([#1166](https://github.com/expo/eas-cli/pull/1166) by [@byCedric](https://github.com/byCedric))
## [0.54.0](https://github.com/expo/eas-cli/releases/tag/v0.54.0) - 2022-06-15
### 🛠 Breaking changes
- Remove fallback for legacy format in `eas.json`. ([#1158](https://github.com/expo/eas-cli/pull/1158), [#1163](https://github.com/expo/eas-cli/pull/1163) by [@wkozyra95](https://github.com/wkozyra95))
### 🎉 New features
- `eas update` now provides more information about the publish process including real-time feedback on asset uploads, update ids, and website links. ([#1152](https://github.com/expo/eas-cli/pull/1152) by [@kgc00](https://github.com/kgc00/))
- Added first beta of `eas metadata` to sync store information using store configuration files ([#1136](https://github.com/expo/eas-cli/pull/1136) by [@bycedric](https://github.com/bycedric))
### 🐛 Bug fixes
- Improved support for working on branches with many updates. ([#1144](https://github.com/expo/eas-cli/pull/1144), [#1148](https://github.com/expo/eas-cli/pull/1148) by [@kgc00](https://github.com/kgc00/))
- `eas update` project links no longer contain a `)` on unsupported terminals. ([#1152](https://github.com/expo/eas-cli/pull/1152) by [@kgc00](https://github.com/kgc00/))
### 🧹 Chores
- Update `eas-build-job`. ([#1162](https://github.com/expo/eas-cli/pull/1162) by [@wkozyra95](https://github.com/wkozyra95))
## [0.53.1](https://github.com/expo/eas-cli/releases/tag/v0.53.1) - 2022-06-07
### 🐛 Bug fixes
- No longer timeout on publishing updates for branches with many updates. ([#1119](https://github.com/expo/eas-cli/pull/1119) by [@kgc00](https://github.com/kgc00/))
### 🧹 Chores
- Display project archive docs before compressing/uploading to EAS Build. ([#1127](https://github.com/expo/eas-cli/pull/1127) by [@dsokal](https://github.com/dsokal))
- Add new common error code for submits. ([#1129](https://github.com/expo/eas-cli/pull/1129) by [@dsokal](https://github.com/dsokal))
## [0.53.0](https://github.com/expo/eas-cli/releases/tag/v0.53.0) - 2022-05-30
### 🛠 Breaking changes
- Drop support for Node < 14. ([#1098](https://github.com/expo/eas-cli/pull/1098) by [@dsokal](https://github.com/dsokal))
### 🐛 Bug fixes
- Match bundle identifier capabilities more accurately. ([#1112](https://github.com/expo/eas-cli/pull/1112) by [@EvanBacon](https://github.com/EvanBacon))
- Limit concurrent asset uploads. ([#1153](https://github.com/expo/eas-cli/pull/1153) by [@wschurman](https://github.com/wschurman))
### 🧹 Chores
- Update dependencies. ([#1095](https://github.com/expo/eas-cli/pull/1095), [#1115](https://github.com/expo/eas-cli/pull/1115) by [@dsokal](https://github.com/dsokal))
- Better spinner placement for multiple builds. ([#1105](https://github.com/expo/eas-cli/pull/1105) by [@dsokal](https://github.com/dsokal))
- Use getExpoConfig to access config. ([#1122](https://github.com/expo/eas-cli/pull/1122) by [@wkozyra95](https://github.com/wkozyra95))
- Update dependencies. ([#1095](https://github.com/expo/eas-cli/pull/1095) by [@dsokal](https://github.com/dsokal))
## [0.52.0](https://github.com/expo/eas-cli/releases/tag/v0.52.0) - 2022-04-26
-6
View File
@@ -1,13 +1,7 @@
* @dsokal @wkozyra95
packages/eas-cli/src/build @dsokal @wkozyra95
packages/eas-cli/src/commands/build @dsokal @wkozyra95
packages/eas-cli/src/submit @barthap @dsokal
packages/eas-cli/src/commands/submit.ts @barthap @dsokal
packages/eas-cli/schema @bycedric
packages/eas-cli/src/metadata @bycedric
packages/eas-cli/src/commands/metadata @bycedric
packages/eas-json @dsokal @wkozyra95
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.54.1"
"version": "0.52.0"
}
+2 -2
View File
@@ -32,7 +32,7 @@
"typescript": "4.6.4"
},
"volta": {
"node": "18.2.0",
"yarn": "1.22.18"
"node": "16.10.0",
"yarn": "1.22.15"
}
}
+44 -80
View File
@@ -65,8 +65,6 @@ eas --help COMMAND
* [`eas init`](#eas-init)
* [`eas login`](#eas-login)
* [`eas logout`](#eas-logout)
* [`eas metadata:pull`](#eas-metadatapull)
* [`eas metadata:push`](#eas-metadatapush)
* [`eas project:info`](#eas-projectinfo)
* [`eas project:init`](#eas-projectinit)
* [`eas secret:create`](#eas-secretcreate)
@@ -100,7 +98,7 @@ ALIASES
$ eas login
```
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/account/login.ts)_
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -117,7 +115,7 @@ ALIASES
$ eas logout
```
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/account/logout.ts)_
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view`
@@ -134,7 +132,7 @@ ALIASES
$ eas whoami
```
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/account/view.ts)_
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -148,7 +146,7 @@ DESCRIPTION
display or change analytics settings
```
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/analytics.ts)_
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]`
@@ -177,7 +175,7 @@ EXAMPLES
$ eas autocomplete --refresh-cache
```
_See code: [@expo/plugin-autocomplete](https://github.com/expo/plugin-autocomplete/blob/v1.4.0/src/commands/autocomplete/index.ts)_
_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v1.2.0/src/commands/autocomplete/index.ts)_
## `eas branch:create [NAME]`
@@ -197,7 +195,7 @@ DESCRIPTION
create a branch
```
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/branch/create.ts)_
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -217,7 +215,7 @@ DESCRIPTION
delete a branch
```
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/branch/delete.ts)_
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -234,7 +232,7 @@ DESCRIPTION
list all branches
```
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/branch/list.ts)_
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -253,7 +251,7 @@ DESCRIPTION
rename a branch
```
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/branch/rename.ts)_
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -273,7 +271,7 @@ DESCRIPTION
view a branch
```
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/branch/view.ts)_
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build`
@@ -302,7 +300,7 @@ DESCRIPTION
start a build
```
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/build/index.ts)_
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -316,7 +314,7 @@ DESCRIPTION
cancel a build
```
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/build/cancel.ts)_
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -333,7 +331,7 @@ DESCRIPTION
configure the project to support EAS Build
```
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/build/configure.ts)_
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:inspect`
@@ -368,7 +366,7 @@ DESCRIPTION
inspect the state of the project at specific build stages, useful for troubleshooting
```
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/build/inspect.ts)_
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -401,7 +399,7 @@ DESCRIPTION
list all builds for your project
```
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/build/list.ts)_
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:submit`
@@ -446,7 +444,7 @@ DESCRIPTION
view a build for your project
```
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/build/view.ts)_
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -466,7 +464,7 @@ DESCRIPTION
create a channel
```
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/channel/create.ts)_
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:edit [NAME]`
@@ -487,7 +485,7 @@ DESCRIPTION
point a channel at a new branch
```
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/channel/edit.ts)_
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list`
@@ -504,7 +502,7 @@ DESCRIPTION
list all channels
```
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/channel/list.ts)_
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:view [NAME]`
@@ -524,7 +522,7 @@ DESCRIPTION
view a channel
```
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/channel/view.ts)_
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -542,7 +540,7 @@ DESCRIPTION
display project configuration (app.json + eas.json)
```
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/config.ts)_
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/config.ts)_
## `eas credentials`
@@ -556,7 +554,7 @@ DESCRIPTION
manage credentials
```
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/credentials.ts)_
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/credentials.ts)_
## `eas device:create`
@@ -570,7 +568,7 @@ DESCRIPTION
register new Apple Devices to use for internal distribution
```
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/device/create.ts)_
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -588,7 +586,7 @@ DESCRIPTION
remove a registered device from your account
```
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/device/delete.ts)_
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -605,7 +603,7 @@ DESCRIPTION
list all registered devices for your account
```
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/device/list.ts)_
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:view [UDID]`
@@ -619,7 +617,7 @@ DESCRIPTION
view a device for your project
```
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/device/view.ts)_
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -633,7 +631,7 @@ DESCRIPTION
display environment info
```
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/diagnostics.ts)_
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas help [COMMAND]`
@@ -653,7 +651,7 @@ DESCRIPTION
Display help for eas.
```
_See code: [@expo/plugin-help](https://github.com/expo/oclif-plugin-help/blob/v5.2.0/src/commands/help.ts)_
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.1.12/src/commands/help.ts)_
## `eas init`
@@ -700,40 +698,6 @@ ALIASES
$ eas logout
```
## `eas metadata:pull`
generate the local store configuration from the app stores
```
USAGE
$ eas metadata:pull [--profile <value>]
FLAGS
--profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.
DESCRIPTION
generate the local store configuration from the app stores
```
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push`
sync the local store configuration to the app stores
```
USAGE
$ eas metadata:push [--profile <value>]
FLAGS
--profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.
DESCRIPTION
sync the local store configuration to the app stores
```
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas project:info`
information about the current project
@@ -746,7 +710,7 @@ DESCRIPTION
information about the current project
```
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/project/info.ts)_
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -763,7 +727,7 @@ ALIASES
$ eas init
```
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/project/init.ts)_
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/project/init.ts)_
## `eas secret:create`
@@ -783,7 +747,7 @@ DESCRIPTION
create an environment secret on the current project or owner account
```
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/secret/create.ts)_
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/secret/create.ts)_
## `eas secret:delete`
@@ -800,7 +764,7 @@ DESCRIPTION
delete an environment secret by ID
```
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/secret/delete.ts)_
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/secret/delete.ts)_
## `eas secret:list`
@@ -814,7 +778,7 @@ DESCRIPTION
list environment secrets available for your current app
```
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/secret/list.ts)_
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/secret/list.ts)_
## `eas submit`
@@ -844,7 +808,7 @@ ALIASES
$ eas build:submit
```
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/submit.ts)_
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/submit.ts)_
## `eas update`
@@ -874,7 +838,7 @@ DESCRIPTION
publish an update group
```
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/update/index.ts)_
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -891,7 +855,7 @@ DESCRIPTION
configure the project to support EAS Update
```
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/update/configure.ts)_
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -911,7 +875,7 @@ DESCRIPTION
delete all the updates in an update group
```
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/update/delete.ts)_
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:list`
@@ -930,7 +894,7 @@ DESCRIPTION
view the recent updates for a branch
```
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/update/list.ts)_
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:view GROUPID`
@@ -950,7 +914,7 @@ DESCRIPTION
update group details
```
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/update/view.ts)_
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/update/view.ts)_
## `eas webhook:create`
@@ -970,7 +934,7 @@ DESCRIPTION
create a webhook
```
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/webhook/create.ts)_
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -987,7 +951,7 @@ DESCRIPTION
delete a webhook
```
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/webhook/delete.ts)_
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -1004,7 +968,7 @@ DESCRIPTION
list webhooks
```
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/webhook/list.ts)_
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -1025,7 +989,7 @@ DESCRIPTION
update a webhook
```
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/webhook/update.ts)_
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -1042,7 +1006,7 @@ DESCRIPTION
view a webhook
```
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.54.1/packages/eas-cli/src/commands/webhook/view.ts)_
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.52.0/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
+2 -245
View File
@@ -10209,7 +10209,7 @@
"args": [
{
"name": "assetContentTypes",
"description": "max 1400",
"description": "max 600",
"type": {
"kind": "NON_NULL",
"name": null,
@@ -10523,22 +10523,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "canRetry",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "cancelingActor",
"description": null,
@@ -10564,7 +10548,7 @@
"deprecationReason": null
},
{
"name": "completedAt",
"name": "createdAt",
"description": null,
"args": [],
"type": {
@@ -10575,22 +10559,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createdAt",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "distribution",
"description": null,
@@ -10603,18 +10571,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "enqueuedAt",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "error",
"description": null,
@@ -10763,22 +10719,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "maxBuildTimeSeconds",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "metrics",
"description": null,
@@ -10791,18 +10731,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "parentBuild",
"description": null,
"args": [],
"type": {
"kind": "OBJECT",
"name": "Build",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "platform",
"description": null,
@@ -10851,18 +10779,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "provisioningStartedAt",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "queuePosition",
"description": "Queue position is 1-indexed",
@@ -10899,18 +10815,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "resourceClass",
"description": null,
"args": [],
"type": {
"kind": "ENUM",
"name": "BuildResourceClass",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "runtimeVersion",
"description": null,
@@ -10979,22 +10883,6 @@
"name": "updatedAt",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "workerStartedAt",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "DateTime",
@@ -12308,18 +12196,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "buildParams",
"description": null,
"type": {
"kind": "INPUT_OBJECT",
"name": "BuildParamsInput",
"ofType": null
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "job",
"description": null,
@@ -12381,18 +12257,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "buildParams",
"description": null,
"type": {
"kind": "INPUT_OBJECT",
"name": "BuildParamsInput",
"ofType": null
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "job",
"description": null,
@@ -12466,39 +12330,6 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "retryBuild",
"description": "Retry an EAS Build build",
"args": [
{
"name": "buildId",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Build",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
@@ -12544,33 +12375,6 @@
}
]
},
{
"kind": "INPUT_OBJECT",
"name": "BuildParamsInput",
"description": null,
"fields": null,
"inputFields": [
{
"name": "resourceClass",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "BuildResourceClass",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "BuildPriority",
@@ -12590,12 +12394,6 @@
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "NORMAL_PLUS",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
@@ -12953,47 +12751,6 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "BuildResourceClass",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "ANDROID_DEFAULT",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "ANDROID_LARGE",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "IOS_DEFAULT",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "IOS_LARGE",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "LEGACY",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "BuildStatus",
+13 -19
View File
@@ -1,36 +1,35 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "0.54.1",
"version": "0.52.0",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
},
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@expo/apple-utils": "0.0.0-alpha.33",
"@expo/apple-utils": "0.0.0-alpha.31",
"@expo/code-signing-certificates": "0.0.2",
"@expo/config": "6.0.23",
"@expo/config-plugins": "4.1.4",
"@expo/config-types": "45.0.0",
"@expo/eas-build-job": "0.2.77",
"@expo/eas-json": "0.54.0",
"@expo/eas-build-job": "0.2.72",
"@expo/eas-json": "0.52.0",
"@expo/json-file": "8.2.36",
"@expo/multipart-body-parser": "1.1.0",
"@expo/pkcs12": "0.0.8",
"@expo/plist": "0.0.18",
"@expo/plugin-autocomplete": "1.4.0",
"@expo/plugin-help": "5.2.0",
"@expo/plugin-warn-if-update-available": "2.3.0",
"@expo/plugin-warn-if-update-available": "2.2.0",
"@expo/prebuild-config": "3.1.6",
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.6.0",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "1.9.0",
"@oclif/core": "1.8.1",
"@oclif/plugin-autocomplete": "1.3.0",
"@oclif/plugin-help": "5.1.12",
"@urql/core": "2.4.4",
"@urql/exchange-retry": "0.3.3",
"ajv": "^6.12.6",
"chalk": "4.1.2",
"cli-progress": "3.11.0",
"cli-table3": "0.6.2",
@@ -61,7 +60,6 @@
"nullthrows": "1.1.1",
"ora": "5.1.0",
"pkg-dir": "4.2.0",
"promise-limit": "2.7.0",
"prompts": "2.4.2",
"qrcode-terminal": "0.12.0",
"resolve-from": "5.0.0",
@@ -98,7 +96,7 @@
"axios": "0.27.2",
"eslint-plugin-graphql": "4.0.0",
"form-data": "4.0.0",
"memfs": "3.4.3",
"memfs": "3.4.1",
"mockdate": "3.0.5",
"nock": "13.2.4",
"tempy": "0.7.0",
@@ -106,13 +104,12 @@
"typescript": "4.6.4"
},
"engines": {
"node": ">=14.0.0"
"node": ">=12.0.0"
},
"files": [
"/bin",
"/build",
"/oclif.manifest.json",
"/schema"
"/oclif.manifest.json"
],
"homepage": "https://github.com/expo/eas-cli",
"keywords": [
@@ -125,8 +122,8 @@
"bin": "eas",
"commands": "./build/commands",
"plugins": [
"@expo/plugin-autocomplete",
"@expo/plugin-help",
"@oclif/plugin-autocomplete",
"@oclif/plugin-help",
"@expo/plugin-warn-if-update-available"
],
"topics": {
@@ -145,9 +142,6 @@
"device": {
"description": "manage Apple devices for Internal Distribution"
},
"metadata": {
"description": "manage store configuration"
},
"project": {
"description": "manage project"
},
File diff suppressed because it is too large. Load diff
@@ -1,17 +0,0 @@
import { fixArchiveUrl } from '../uploads';
describe(fixArchiveUrl, () => {
it('fixes broken links', () => {
const input =
'https://submission-service-archives.s3.amazonaws.com/production%2Fdc98ca84-1473-4cb3-ae81-8c7b291cb27e%2F4424aa95-b985-4e2f-8755-9507b1037c1c';
const expectedOutput =
'https://submission-service-archives.s3.amazonaws.com/production/dc98ca84-1473-4cb3-ae81-8c7b291cb27e/4424aa95-b985-4e2f-8755-9507b1037c1c';
expect(fixArchiveUrl(input)).toBe(expectedOutput);
});
it('does not change correct urls', () => {
const correctUrl =
'https://submission-service-archives.s3.amazonaws.com/production/dc98ca84-1473-4cb3-ae81-8c7b291cb27e/4424aa95-b985-4e2f-8755-9507b1037c1c';
expect(fixArchiveUrl(correctUrl)).toBe(correctUrl);
});
});
+1 -6
View File
@@ -1,5 +1,5 @@
import { logEvent } from './rudderstackClient';
export type Event = BuildEvent | SubmissionEvent | MetadataEvent;
export type Event = BuildEvent | SubmissionEvent;
export enum SubmissionEvent {
SUBMIT_COMMAND = 'submit cli submit command',
@@ -45,11 +45,6 @@ export enum BuildEvent {
ANDROID_KEYSTORE_CREATE = 'build cli credentials keystore create',
}
export enum MetadataEvent {
APPLE_METADATA_DOWNLOAD = 'metadata cli download apple response',
APPLE_METADATA_UPLOAD = 'metadata cli upload apple response',
}
export class Analytics {
static logEvent(name: Event, properties: Record<string, any>): void {
logEvent(name, properties);
+1 -4
View File
@@ -5,7 +5,6 @@ import nullthrows from 'nullthrows';
import AndroidCredentialsProvider, {
AndroidCredentials,
} from '../../credentials/android/AndroidCredentialsProvider';
import { BuildParamsInput } from '../../graphql/generated';
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
import Log from '../../log';
import {
@@ -88,8 +87,7 @@ export async function prepareAndroidBuildAsync(
sendBuildRequestAsync: async (
appId: string,
job: Android.Job,
metadata: Metadata,
buildParams: BuildParamsInput
metadata: Metadata
): Promise<BuildResult> => {
const graphqlMetadata = transformMetadata(metadata);
const graphqlJob = transformJob(job);
@@ -97,7 +95,6 @@ export async function prepareAndroidBuildAsync(
appId,
job: graphqlJob,
metadata: graphqlMetadata,
buildParams,
});
},
});
+19 -45
View File
@@ -11,7 +11,6 @@ import { getExpoWebsiteBaseUrl } from '../api';
import {
AppPlatform,
BuildFragment,
BuildParamsInput,
BuildPriority,
BuildStatus,
UploadSessionType,
@@ -54,22 +53,11 @@ interface Builder<TPlatform extends Platform, Credentials, TJob extends Job> {
): Promise<CredentialsResult<Credentials> | undefined>;
syncProjectConfigurationAsync(ctx: BuildContext<TPlatform>): Promise<void>;
prepareJobAsync(ctx: BuildContext<TPlatform>, jobData: JobData<Credentials>): Promise<Job>;
sendBuildRequestAsync(
appId: string,
job: TJob,
metadata: Metadata,
buildParams: BuildParamsInput
): Promise<BuildResult>;
sendBuildRequestAsync(appId: string, job: TJob, metadata: Metadata): Promise<BuildResult>;
}
export type BuildRequestSender = () => Promise<BuildFragment | undefined>;
function resolveBuildParamsInput<T extends Platform>(ctx: BuildContext<T>): BuildParamsInput {
return {
resourceClass: ctx.resourceClass,
};
}
export async function prepareBuildRequestForPlatformAsync<
TPlatform extends Platform,
Credentials,
@@ -112,7 +100,6 @@ export async function prepareBuildRequestForPlatformAsync<
} as const);
const metadata = await collectMetadataAsync(ctx);
const buildParams = resolveBuildParamsInput(ctx);
const job = await builder.prepareJobAsync(ctx, {
projectArchive,
credentials: credentialsResult?.credentials,
@@ -124,7 +111,7 @@ export async function prepareBuildRequestForPlatformAsync<
return undefined;
} else {
try {
return await sendBuildRequestAsync(builder, job, metadata, buildParams);
return await sendBuildRequestAsync(builder, job, metadata);
} catch (error: any) {
handleBuildRequestError(error, job.platform);
}
@@ -170,12 +157,6 @@ async function uploadProjectAsync<TPlatform extends Platform>(
try {
return await withAnalyticsAsync(
async () => {
Log.newLine();
Log.log(
`Compressing project files and uploading to EAS Build. ${learnMore(
'https://expo.fyi/eas-build-archive'
)}`
);
const projectTarball = await makeProjectTarballAsync();
projectTarballPath = projectTarball.path;
@@ -188,7 +169,10 @@ async function uploadProjectAsync<TPlatform extends Platform>(
`Uploading to EAS Build (${formatBytes(projectTarball.size * ratio)} / ${formatBytes(
projectTarball.size
)})`,
completedMessage: (duration: string) => `Uploaded to EAS ${chalk.dim(duration)}`,
completedMessage: (duration: string) =>
`Uploaded to EAS ${chalk.dim(duration)} ${learnMore(
'https://expo.fyi/eas-build-archive'
)}`,
})
);
return bucketKey;
@@ -210,8 +194,7 @@ async function uploadProjectAsync<TPlatform extends Platform>(
async function sendBuildRequestAsync<TPlatform extends Platform, Credentials, TJob extends Job>(
builder: Builder<TPlatform, Credentials, TJob>,
job: TJob,
metadata: Metadata,
buildParams: BuildParamsInput
metadata: Metadata
): Promise<BuildFragment> {
const { ctx } = builder;
return await withAnalyticsAsync(
@@ -223,8 +206,7 @@ async function sendBuildRequestAsync<TPlatform extends Platform, Credentials, TJ
const { build, deprecationInfo } = await builder.sendBuildRequestAsync(
ctx.projectId,
job,
metadata,
buildParams
metadata
);
printDeprecationWarnings(deprecationInfo);
@@ -249,23 +231,16 @@ export async function waitForBuildEndAsync(
intervalSec = 10,
} = {}
): Promise<MaybeBuildFragment[]> {
let spinner;
let originalSpinnerText;
if (buildIds.length === 1) {
Log.log('Waiting for build to complete. You can press Ctrl+C to exit.');
originalSpinnerText = 'Waiting for build to complete.';
spinner = ora(originalSpinnerText).start();
} else {
originalSpinnerText = 'Waiting for builds to complete. You can press Ctrl+C to exit.';
spinner = ora('Waiting for builds to complete. You can press Ctrl+C to exit.').start();
}
const b = `build${buildIds.length > 1 ? 's' : ''}`;
Log.log(`Waiting for ${b} to complete. You can press Ctrl+C to exit.`);
const spinner = ora(`Waiting for ${b} to complete.`).start();
const endTime = new Date().getTime() + timeoutSec * 1000;
while (new Date().getTime() <= endTime) {
const builds = await getBuildsSafelyAsync(buildIds);
const { refetch } =
builds.length === 1
? await handleSingleBuildProgressAsync({ build: builds[0], accountName }, { spinner })
: await handleMultipleBuildsProgressAsync({ builds }, { spinner, originalSpinnerText });
: await handleMultipleBuildsProgressAsync({ builds }, { spinner });
if (!refetch) {
return builds;
}
@@ -398,7 +373,7 @@ const platforms = [AppPlatform.Android, AppPlatform.Ios];
async function handleMultipleBuildsProgressAsync(
{ builds: maybeBuilds }: { builds: MaybeBuildFragment[] },
{ spinner, originalSpinnerText }: { spinner: Ora; originalSpinnerText: string }
{ spinner }: { spinner: Ora }
): Promise<BuildProgressResult> {
const buildCount = maybeBuilds.length;
const builds = maybeBuilds.filter<BuildFragment>(isBuildFragment);
@@ -418,7 +393,7 @@ async function handleMultipleBuildsProgressAsync(
}
return { refetch: false };
} else {
spinner.text = formatPendingBuildsText(originalSpinnerText, builds);
spinner.text = formatPendingBuildsText(builds);
return { refetch: true };
}
}
@@ -437,10 +412,9 @@ function formatSettledBuildsText(builds: BuildFragment[]): string {
.join('\n ');
}
function formatPendingBuildsText(originalSpinnerText: string, builds: BuildFragment[]): string {
return [
originalSpinnerText,
...platforms.map(platform => {
function formatPendingBuildsText(builds: BuildFragment[]): string {
return platforms
.map(platform => {
const build = builds.find(build => build.platform === platform);
const status = build ? statusToDisplayName[build.status] : 'unknown';
let extraInfo = '';
@@ -463,8 +437,8 @@ function formatPendingBuildsText(originalSpinnerText: string, builds: BuildFragm
return `${appPlatformEmojis[platform]} ${
appPlatformDisplayNames[platform]
} build - status: ${chalk.bold(status)}${extraInfo}`;
}),
].join('\n ');
})
.join('\n ');
}
function isBuildFragment(maybeBuild: MaybeBuildFragment): maybeBuild is BuildFragment {
-2
View File
@@ -6,7 +6,6 @@ import type { XCBuildConfiguration } from 'xcode';
import { TrackingContext } from '../analytics/common';
import { CredentialsContext } from '../credentials/context';
import { Target } from '../credentials/ios/types';
import { BuildResourceClass } from '../graphql/generated';
import { GradleBuildContext } from '../project/android/gradle';
import { XcodeBuildContext } from '../project/ios/scheme';
import { Actor } from '../user/User';
@@ -31,7 +30,6 @@ export interface BuildContext<T extends Platform> {
accountName: string;
buildProfile: BuildProfile<T>;
buildProfileName: string;
resourceClass: BuildResourceClass;
clearCache: boolean;
credentialsCtx: CredentialsContext;
exp: ExpoConfig;
@@ -7,7 +7,6 @@ import { v4 as uuidv4 } from 'uuid';
import { TrackingContext } from '../analytics/common';
import { Analytics, BuildEvent } from '../analytics/events';
import { CredentialsContext } from '../credentials/context';
import { BuildResourceClass } from '../graphql/generated';
import { getExpoConfig } from '../project/expoConfig';
import { getProjectAccountName, getProjectIdAsync } from '../project/projectUtils';
import { resolveWorkflowAsync } from '../project/workflow';
@@ -26,7 +25,6 @@ export async function createBuildContextAsync<T extends Platform>({
nonInteractive = false,
platform,
projectDir,
resourceClass,
}: {
buildProfileName: string;
buildProfile: BuildProfile<T>;
@@ -35,7 +33,6 @@ export async function createBuildContextAsync<T extends Platform>({
nonInteractive: boolean;
platform: T;
projectDir: string;
resourceClass: BuildResourceClass;
}): Promise<BuildContext<T>> {
const exp = getExpoConfig(projectDir, { env: buildProfile.env });
@@ -73,7 +70,6 @@ export async function createBuildContextAsync<T extends Platform>({
accountName,
buildProfile,
buildProfileName,
resourceClass,
clearCache,
credentialsCtx,
exp,
+1 -4
View File
@@ -3,7 +3,6 @@ import { Ios, Job, Metadata, Platform, Workflow } from '@expo/eas-build-job';
import type { XCBuildConfiguration } from 'xcode';
import { IosCredentials, Target } from '../../credentials/ios/types';
import { BuildParamsInput } from '../../graphql/generated';
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
import { ensureBundleIdentifierIsDefinedForManagedProjectAsync } from '../../project/ios/bundleIdentifier';
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
@@ -83,8 +82,7 @@ export async function prepareIosBuildAsync(
sendBuildRequestAsync: async (
appId: string,
job: Ios.Job,
metadata: Metadata,
buildParams: BuildParamsInput
metadata: Metadata
): Promise<BuildResult> => {
const graphqlMetadata = transformMetadata(metadata);
const graphqlJob = transformJob(job);
@@ -92,7 +90,6 @@ export async function prepareIosBuildAsync(
appId,
job: graphqlJob,
metadata: graphqlMetadata,
buildParams,
});
},
});
+1 -1
View File
@@ -6,7 +6,7 @@ import semver from 'semver';
import { ora } from '../ora';
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
const PLUGIN_PACKAGE_VERSION = '0.0.85';
const PLUGIN_PACKAGE_VERSION = '0.0.78';
export interface LocalBuildOptions {
enable: boolean;
@@ -1,15 +1,9 @@
import { Platform, Workflow } from '@expo/eas-build-job';
import { BuildProfile, EasJsonReader, SubmitProfile } from '@expo/eas-json';
import { BuildProfile, SubmitProfile } from '@expo/eas-json';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
import {
AppPlatform,
BuildFragment,
BuildResourceClass,
BuildStatus,
SubmissionFragment,
} from '../graphql/generated';
import { AppPlatform, BuildFragment, BuildStatus, SubmissionFragment } from '../graphql/generated';
import { toAppPlatform, toPlatform } from '../graphql/types/AppPlatform';
import Log from '../log';
import {
@@ -36,7 +30,6 @@ import { BuildContext } from './context';
import { createBuildContextAsync } from './createContext';
import { prepareIosBuildAsync } from './ios/build';
import { LocalBuildOptions } from './local';
import { UserInputResourceClass } from './types';
import { ensureExpoDevClientInstalledForDevClientBuildsAsync } from './utils/devClient';
import { printBuildResults, printLogsUrls } from './utils/printBuildInfo';
import { ensureRepoIsCleanAsync } from './utils/repository';
@@ -54,23 +47,8 @@ export interface BuildFlags {
autoSubmit: boolean;
submitProfile?: string;
localBuildOptions: LocalBuildOptions;
userInputResourceClass?: UserInputResourceClass;
}
const platformToGraphQLResourceClassMapping: Record<
Platform,
Record<UserInputResourceClass, BuildResourceClass>
> = {
[Platform.ANDROID]: {
[UserInputResourceClass.DEFAULT]: BuildResourceClass.AndroidDefault,
[UserInputResourceClass.LARGE]: BuildResourceClass.AndroidLarge,
},
[Platform.IOS]: {
[UserInputResourceClass.DEFAULT]: BuildResourceClass.IosDefault,
[UserInputResourceClass.LARGE]: BuildResourceClass.IosLarge,
},
};
export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFlags): Promise<void> {
await getVcsClient().ensureRepoExistsAsync();
await ensureRepoIsCleanAsync(flags.nonInteractive);
@@ -79,12 +57,11 @@ export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFla
projectDir,
nonInteractive: flags.nonInteractive,
});
const easJsonReader = new EasJsonReader(projectDir);
const platforms = toPlatforms(flags.requestedPlatform);
const buildProfiles = await getProfilesAsync({
type: 'build',
easJsonReader,
projectDir,
platforms,
profileName: flags.profile ?? undefined,
});
@@ -107,10 +84,6 @@ export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFla
flags,
moreBuilds: platforms.length > 1,
buildProfile,
resourceClass:
platformToGraphQLResourceClassMapping[buildProfile.platform][
flags.userInputResourceClass ?? UserInputResourceClass.DEFAULT
],
});
if (maybeBuild) {
startedBuilds.push({ build: maybeBuild, buildProfile });
@@ -129,7 +102,7 @@ export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFla
const submissions: SubmissionFragment[] = [];
if (flags.autoSubmit) {
const submitProfiles = await getProfilesAsync({
easJsonReader,
projectDir,
platforms,
profileName: flags.submitProfile,
type: 'submit',
@@ -187,17 +160,14 @@ async function prepareAndStartBuildAsync({
flags,
moreBuilds,
buildProfile,
resourceClass,
}: {
projectDir: string;
flags: BuildFlags;
moreBuilds: boolean;
buildProfile: ProfileData<'build'>;
resourceClass: BuildResourceClass;
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
const buildCtx = await createBuildContextAsync({
buildProfileName: buildProfile.profileName,
resourceClass,
clearCache: flags.clearCache,
buildProfile: buildProfile.profile,
nonInteractive: flags.nonInteractive,
-5
View File
@@ -12,8 +12,3 @@ export enum BuildDistributionType {
INTERNAL = 'internal',
SIMULATOR = 'simulator',
}
export enum UserInputResourceClass {
DEFAULT = 'default',
LARGE = 'large',
}
-13
View File
@@ -35,16 +35,3 @@ export function getInternalDistributionInstallUrl(build: BuildFragment): string
return build.artifacts.buildUrl;
}
export function getUpdateGroupUrl(
accountName: string,
projectName: string,
updateGroupId: string
): string {
return new URL(
`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(
projectName
)}/updates/${encodeURIComponent(updateGroupId)}`,
getExpoWebsiteBaseUrl()
).toString();
}
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import gql from 'graphql-tag';
@@ -10,7 +11,6 @@ import {
UpdateBranch,
} from '../../graphql/generated';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectFullNameAsync,
@@ -79,7 +79,7 @@ export default class BranchCreate extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const fullName = await getProjectFullNameAsync(exp);
const projectId = await getProjectIdAsync(exp);
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import gql from 'graphql-tag';
@@ -12,7 +13,6 @@ import {
GetBranchInfoQueryVariables,
} from '../../graphql/generated';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectFullNameAsync,
@@ -103,7 +103,7 @@ export default class BranchDelete extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const fullName = await getProjectFullNameAsync(exp);
const projectId = await getProjectIdAsync(exp);
+19 -49
View File
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import CliTable from 'cli-table3';
@@ -13,28 +14,26 @@ import {
} from '../../graphql/generated';
import { UpdateBranchFragmentNode } from '../../graphql/types/UpdateBranch';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { UPDATE_COLUMNS, formatUpdate, getPlatformsForGroup } from '../../update/utils';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { PaginatedQueryResponse, performPaginatedQueryAsync } from '../../utils/queries';
export const BRANCHES_LIMIT = 50;
const BRANCHES_LIMIT = 10_000;
export async function listBranchesAsync({
appId,
limit = BRANCHES_LIMIT,
offset = 0,
}: BranchesByAppQueryVariables): Promise<UpdateBranchFragment[]> {
projectId,
}: {
projectId: string;
}): Promise<UpdateBranchFragment[]> {
const data = await withErrorHandlingAsync(
graphqlClient
.query<BranchesByAppQuery, BranchesByAppQueryVariables>(
gql`
query BranchesByAppQuery($appId: String!, $limit: Int!, $offset: Int!) {
query BranchesByAppQuery($appId: String!, $limit: Int!) {
app {
byId(appId: $appId) {
id
updateBranches(offset: $offset, limit: $limit) {
updateBranches(offset: 0, limit: $limit) {
id
...UpdateBranchFragment
}
@@ -44,9 +43,8 @@ export async function listBranchesAsync({
${print(UpdateBranchFragmentNode)}
`,
{
appId,
limit,
offset,
appId: projectId,
limit: BRANCHES_LIMIT,
},
{ additionalTypenames: ['UpdateBranch'] }
)
@@ -73,30 +71,11 @@ export default class BranchList extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
await queryForPaginatedBranchesAsync(projectId, flags);
}
}
async function queryForPaginatedBranchesAsync(
projectId: string,
flags: { json: boolean } & { json: boolean | undefined }
): Promise<void> {
const queryAdditionalBranchesAsync = async (
pageSize: number,
offset: number
): Promise<PaginatedQueryResponse<UpdateBranchFragment>> => {
const branches = await listBranchesAsync({ appId: projectId, limit: pageSize, offset });
return {
queryResponse: branches,
queryResponseRawLength: branches.length,
};
};
const renderPageOfBranches = (currentPage: UpdateBranchFragment[]): void => {
const branches = await listBranchesAsync({ projectId });
if (flags.json) {
printJsonOnlyOutput(currentPage);
printJsonOnlyOutput(branches);
} else {
const table = new CliTable({
head: ['Branch', ...UPDATE_COLUMNS],
@@ -104,7 +83,7 @@ async function queryForPaginatedBranchesAsync(
});
table.push(
...currentPage.map(branch => [
...branches.map(branch => [
branch.name,
formatUpdate(branch.updates[0]),
branch.updates[0]?.runtimeVersion ?? 'N/A',
@@ -117,20 +96,11 @@ async function queryForPaginatedBranchesAsync(
);
Log.addNewLineIfNone();
Log.log(chalk.bold('Branches:'));
Log.addNewLineIfNone();
Log.log(chalk.bold('Branches with their most recent update group:'));
Log.log(table.toString());
if (branches.length >= BRANCHES_LIMIT) {
Log.warn(`Showing first ${BRANCHES_LIMIT} branches, some results might be omitted.`);
}
}
};
await performPaginatedQueryAsync({
pageSize: 50,
offset: 0,
queryToPerform: queryAdditionalBranchesAsync,
promptOptions: {
type: 'confirm',
title: 'Fetch next page of branches?',
renderListItems: renderPageOfBranches,
},
});
}
}
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import gql from 'graphql-tag';
@@ -11,7 +12,6 @@ import {
UpdateBranch,
} from '../../graphql/generated';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectFullNameAsync,
@@ -78,7 +78,7 @@ export default class BranchRename extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const fullName = await getProjectFullNameAsync(exp);
const projectId = await getProjectIdAsync(exp);
+33 -82
View File
@@ -1,19 +1,17 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import Table from 'cli-table3';
import EasCommand from '../../commandUtils/EasCommand';
import { ViewBranchUpdatesQuery } from '../../graphql/generated';
import { UpdateQuery } from '../../graphql/queries/UpdateQuery';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { UPDATE_COLUMNS, formatUpdate, getPlatformsForGroup } from '../../update/utils';
import groupBy from '../../utils/expodash/groupBy';
import formatFields from '../../utils/formatFields';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { PaginatedQueryResponse, performPaginatedQueryAsync } from '../../utils/queries';
export default class BranchView extends EasCommand {
static description = 'view a branch';
@@ -43,7 +41,7 @@ export default class BranchView extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
if (!name) {
@@ -59,97 +57,50 @@ export default class BranchView extends EasCommand {
}));
}
await queryForPaginatedBranchesAsync(projectId, name, { json: jsonFlag });
}
}
type BranchUpdateObject = Exclude<
ViewBranchUpdatesQuery['app']['byId']['updateBranchByName'],
null | undefined
>['updates'][0];
async function queryForPaginatedBranchesAsync(
projectId: string,
name: string,
flags: { json: boolean } & { json: boolean | undefined }
): Promise<void> {
const queryAdditionalBranchesAsync = async (
pageSize: number,
offset: number
): Promise<PaginatedQueryResponse<BranchUpdateObject>> => {
const { app } = await UpdateQuery.viewBranchAsync({
appId: projectId,
name,
limit: pageSize,
offset,
});
const UpdateBranch = app?.byId.updateBranchByName;
if (!UpdateBranch) {
throw new Error(`Could not find branch "${name}"`);
}
const updateGroups = Object.values(groupBy(UpdateBranch.updates, u => u.group)).map(
updateGroup => ({
...updateGroup[0],
platform: getPlatformsForGroup({
updates: updateGroup,
group: updateGroup[0].group,
}),
})
const updates = Object.values(groupBy(UpdateBranch.updates, u => u.group)).map(
group => group[0]
);
return {
queryResponse: updateGroups,
queryResponseRawLength: UpdateBranch.updates.length,
};
};
const renderPageOfBranches = (currentPage: BranchUpdateObject[]): void => {
if (flags.json) {
printJsonOnlyOutput(currentPage);
if (jsonFlag) {
printJsonOnlyOutput({ ...UpdateBranch, updates });
} else {
if (flags.json) {
printJsonOnlyOutput({ currentPage });
} else {
const groupTable = new Table({
head: UPDATE_COLUMNS,
wordWrap: true,
});
const groupTable = new Table({
head: UPDATE_COLUMNS,
wordWrap: true,
});
for (const update of currentPage) {
groupTable.push([
formatUpdate(update),
update.runtimeVersion,
update.group,
getPlatformsForGroup({
updates: currentPage,
group: update.group,
}),
]);
}
Log.addNewLineIfNone();
Log.log(chalk.bold('Branch:'));
Log.log(
formatFields([
{ label: 'Name', value: currentPage[0]['branch']['name'] },
{ label: 'ID', value: currentPage[0]['branch']['id'] },
])
);
Log.addNewLineIfNone();
Log.log(groupTable.toString());
for (const update of updates) {
groupTable.push([
formatUpdate(update),
update.runtimeVersion,
update.group,
getPlatformsForGroup({
updates: UpdateBranch.updates,
group: update.group,
}),
]);
}
}
};
await performPaginatedQueryAsync({
pageSize: 50,
offset: 0,
queryToPerform: queryAdditionalBranchesAsync,
promptOptions: {
type: 'confirm',
title: 'Fetch next page of branches?',
renderListItems: renderPageOfBranches,
},
});
Log.addNewLineIfNone();
Log.log(chalk.bold('Branch:'));
Log.log(
formatFields([
{ label: 'Name', value: UpdateBranch.name },
{ label: 'ID', value: UpdateBranch.id },
])
);
Log.addNewLineIfNone();
Log.log(chalk.bold('Recently published update groups:'));
Log.log(groupTable.toString());
}
}
}
+46 -73
View File
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import chalk from 'chalk';
import gql from 'graphql-tag';
@@ -5,7 +6,6 @@ import EasCommand from '../../commandUtils/EasCommand';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import {
Build,
BuildFragment,
BuildStatus,
CancelBuildMutation,
CancelBuildMutationVariables,
@@ -14,14 +14,12 @@ import { BuildQuery } from '../../graphql/queries/BuildQuery';
import Log from '../../log';
import { ora } from '../../ora';
import { appPlatformEmojis } from '../../platform';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectFullNameAsync,
getProjectIdAsync,
} from '../../project/projectUtils';
import { confirmAsync } from '../../prompts';
import { PaginatedQueryResponse, performPaginatedQueryAsync } from '../../utils/queries';
import { confirmAsync, selectAsync } from '../../prompts';
async function cancelBuildAsync(buildId: string): Promise<Pick<Build, 'id' | 'status'>> {
const data = await withErrorHandlingAsync(
@@ -61,6 +59,48 @@ function formatUnfinishedBuild(
return `${platform} Started at: ${startTime}, Status: ${status}, Id: ${build.id}`;
}
async function selectBuildToCancelAsync(
projectId: string,
projectFullName: string
): Promise<string | null> {
const spinner = ora().start('Fetching the uncompleted builds…');
let builds;
try {
const [newBuilds, inQueueBuilds, inProgressBuilds] = await Promise.all([
BuildQuery.allForAppAsync(projectId, { filter: { status: BuildStatus.New } }),
BuildQuery.allForAppAsync(projectId, { filter: { status: BuildStatus.InQueue } }),
BuildQuery.allForAppAsync(projectId, { filter: { status: BuildStatus.InProgress } }),
]);
spinner.stop();
builds = [...newBuilds, ...inQueueBuilds, ...inProgressBuilds];
} catch (error) {
spinner.fail(
`Something went wrong and we couldn't fetch the builds for the project ${projectFullName}.`
);
throw error;
}
if (builds.length === 0) {
Log.warn(`There aren't any uncompleted builds for the project ${projectFullName}.`);
return null;
} else if (builds.length === 1) {
Log.log('Found one build');
Log.log(formatUnfinishedBuild(builds[0]));
await confirmAsync({
message: 'Do you want to cancel it?',
});
return builds[0].id;
} else {
const buildId = await selectAsync<string>(
'Which build do you want to cancel?',
builds.map(build => ({
title: formatUnfinishedBuild(build),
value: build.id,
}))
);
return buildId;
}
}
async function ensureBuildExistsAsync(buildId: string): Promise<void> {
try {
await BuildQuery.byIdAsync(buildId);
@@ -78,7 +118,7 @@ export default class BuildCancel extends EasCommand {
const { BUILD_ID: buildIdFromArg } = (await this.parse(BuildCancel)).args;
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const projectFullName = await getProjectFullNameAsync(exp);
@@ -86,8 +126,7 @@ export default class BuildCancel extends EasCommand {
await ensureBuildExistsAsync(buildIdFromArg);
}
const buildId =
buildIdFromArg || (await queryForBuildToCancelAsync(projectId, projectFullName));
const buildId = buildIdFromArg || (await selectBuildToCancelAsync(projectId, projectFullName));
if (!buildId) {
return;
}
@@ -107,69 +146,3 @@ export default class BuildCancel extends EasCommand {
}
}
}
async function queryForBuildToCancelAsync(
projectId: string,
projectFullName: string
): Promise<string | null> {
const queryToPerformAsync = async (
pageSize: number,
offset: number
): Promise<PaginatedQueryResponse<BuildFragment>> => {
const [newBuilds, inQueueBuilds, inProgressBuilds] = await Promise.all([
BuildQuery.allForAppAsync(projectId, {
limit: pageSize,
offset,
filter: { status: BuildStatus.New },
}),
BuildQuery.allForAppAsync(projectId, {
limit: pageSize,
offset,
filter: { status: BuildStatus.InQueue },
}),
BuildQuery.allForAppAsync(projectId, {
limit: pageSize,
offset,
filter: { status: BuildStatus.InProgress },
}),
]);
// this variable controls whether performPaginatedQueryAsync will query the next page.
// force another query if we have hit the query limit for any of these 3 queries.
const queryResponseLength =
newBuilds.length < pageSize &&
inQueueBuilds.length < pageSize &&
inProgressBuilds.length < pageSize
? pageSize - 1
: pageSize;
return {
queryResponse: [...newBuilds, ...inQueueBuilds, ...inProgressBuilds],
queryResponseRawLength: queryResponseLength,
};
};
const getIdentifierForQueryItem = (buildFragment: BuildFragment): string => buildFragment.id;
const selectedBuild = (
await performPaginatedQueryAsync({
pageSize: 50,
offset: 0,
queryToPerform: queryToPerformAsync,
promptOptions: {
type: 'select',
title: 'Which build would you like to cancel?',
getIdentifierForQueryItem,
createDisplayTextForSelectionPromptListItem: formatUnfinishedBuild,
},
})
).pop();
if (!selectedBuild) {
Log.warn(`There aren't any uncompleted builds for the project ${projectFullName}.`);
return null;
}
const userConfirmedCancellation = await confirmAsync({
message: `Are you sure you want to cancel ${formatUnfinishedBuild(selectedBuild)}?`,
});
return userConfirmedCancellation ? selectedBuild.id : null;
}
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Platform, Workflow } from '@expo/eas-build-job';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
@@ -7,7 +8,6 @@ import { ensureProjectConfiguredAsync } from '../../build/configure';
import EasCommand from '../../commandUtils/EasCommand';
import Log, { learnMore } from '../../log';
import { RequestedPlatform } from '../../platform';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, isExpoUpdatesInstalled } from '../../project/projectUtils';
import { resolveWorkflowAsync } from '../../project/workflow';
import { promptAsync } from '../../prompts';
@@ -55,7 +55,7 @@ export default class BuildConfigure extends EasCommand {
// configure expo-updates
if (expoUpdatesIsInstalled) {
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
if ([RequestedPlatform.Android, RequestedPlatform.All].includes(platform)) {
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
+75 -9
View File
@@ -1,13 +1,21 @@
import { EasJsonReader } from '@expo/eas-json';
import { Errors, Flags } from '@oclif/core';
import chalk from 'chalk';
import figures from 'figures';
import fs from 'fs-extra';
import path from 'path';
import { BuildFlags, runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
import { UserInputResourceClass } from '../../build/types';
import { ensureRepoIsCleanAsync, reviewAndCommitChangesAsync } from '../../build/utils/repository';
import EasCommand from '../../commandUtils/EasCommand';
import Log from '../../log';
import Log, { learnMore, link } from '../../log';
import { RequestedPlatform, selectRequestedPlatformAsync } from '../../platform';
import { findProjectRootAsync } from '../../project/projectUtils';
import { selectAsync } from '../../prompts';
import { easCliVersion } from '../../utils/easCli';
import { enableJsonOutput } from '../../utils/json';
import { getVcsClient, setVcsClient } from '../../vcs';
import GitClient from '../../vcs/clients/git';
interface RawBuildFlags {
platform?: string;
@@ -22,7 +30,6 @@ interface RawBuildFlags {
json: boolean;
'auto-submit': boolean;
'auto-submit-with-profile'?: string;
'resource-class'?: UserInputResourceClass;
}
export default class Build extends EasCommand {
@@ -81,11 +88,6 @@ export default class Build extends EasCommand {
helpValue: 'PROFILE_NAME',
exclusive: ['auto-submit'],
}),
'resource-class': Flags.enum({
options: Object.values(UserInputResourceClass),
hidden: true,
description: 'The instance type that will be used to run this build [experimental]',
}),
};
async runAsync(): Promise<void> {
@@ -96,6 +98,7 @@ export default class Build extends EasCommand {
const flags = await this.sanitizeFlagsAsync(rawFlags);
const projectDir = await findProjectRootAsync();
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
await runBuildAndSubmitAsync(projectDir, flags);
}
@@ -162,7 +165,70 @@ export default class Build extends EasCommand {
json: flags['json'],
autoSubmit: flags['auto-submit'] || flags['auto-submit-with-profile'] !== undefined,
submitProfile: flags['auto-submit-with-profile'] ?? profile,
userInputResourceClass: flags['resource-class'] ?? UserInputResourceClass.DEFAULT,
};
}
}
export async function handleDeprecatedEasJsonAsync(
projectDir: string,
nonInteractive: boolean
): Promise<void> {
const easJsonPath = EasJsonReader.formatEasJsonPath(projectDir);
if (!(await fs.pathExists(easJsonPath))) {
return;
}
const easJsonReader = new EasJsonReader(projectDir);
const rawEasJson = await easJsonReader.readAsync();
if (rawEasJson?.cli) {
return;
}
if (nonInteractive) {
Log.warn(
`${
figures.warning
} Action required: the default behavior of EAS CLI has changed and your eas.json must be updated to remove ambiguity around which Git integration workflow to use. Refer to ${link(
'https://expo.fyi/eas-vcs-workflow'
)} for more information.`
);
Log.warn(
'This warning will become an error in an upcoming EAS CLI release. For now, we will proceed with the old default behavior to avoid disruption of your builds.'
);
setVcsClient(new GitClient());
return;
}
Log.log(
`${chalk.bold(
'eas-cli@>=0.34.0 no longer requires that you commit changes to Git before starting a build.'
)} ${learnMore('https://expo.fyi/eas-vcs-workflow')}`
);
Log.log(
`If you want to continue using the Git integration, you can opt in with ${chalk.bold(
'cli.requireCommit'
)} in ${chalk.bold('eas.json')} or with the following prompt.`
);
Log.newLine();
const mode = await selectAsync('Select your preferred Git integration', [
{ title: 'Require changes to be committed in Git (old default)', value: 'requireCommit' },
{ title: 'Allow builds with dirty Git working tree (new default)', value: 'noCommit' },
]);
if (mode === 'requireCommit') {
setVcsClient(new GitClient());
await ensureRepoIsCleanAsync(nonInteractive);
}
rawEasJson.cli =
mode === 'requireCommit'
? { version: `>= ${easCliVersion}`, requireCommit: true }
: { version: `>= ${easCliVersion}` };
await fs.writeJSON(easJsonPath, rawEasJson, { spaces: 2 });
Log.withTick('Updated eas.json');
if (mode === 'requireCommit') {
await getVcsClient().trackFileAsync(easJsonPath);
await reviewAndCommitChangesAsync('Set cli.requireCommit to true in eas.json', {
nonInteractive,
});
}
}
+51 -77
View File
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
@@ -6,21 +7,19 @@ import { formatGraphQLBuild } from '../../build/utils/formatBuild';
import EasCommand from '../../commandUtils/EasCommand';
import {
AppPlatform,
BuildFragment,
DistributionType,
BuildStatus as GraphQLBuildStatus,
} from '../../graphql/generated';
import { BuildQuery, BuildsQuery } from '../../graphql/queries/BuildQuery';
import { BuildQuery } from '../../graphql/queries/BuildQuery';
import Log from '../../log';
import { ora } from '../../ora';
import { RequestedPlatform } from '../../platform';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectFullNameAsync,
getProjectIdAsync,
} from '../../project/projectUtils';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { PaginatedQueryResponse, performPaginatedQueryAsync } from '../../utils/queries';
export default class BuildList extends EasCommand {
static description = 'list all builds for your project';
@@ -57,6 +56,7 @@ export default class BuildList extends EasCommand {
appIdentifier: Flags.string(),
buildProfile: Flags.string(),
gitCommitHash: Flags.string(),
limit: Flags.integer(),
};
async runAsync(): Promise<void> {
@@ -66,6 +66,7 @@ export default class BuildList extends EasCommand {
platform: requestedPlatform,
status: buildStatus,
distribution: buildDistribution,
limit = 10,
} = flags;
if (json) {
enableJsonOutput();
@@ -76,28 +77,55 @@ export default class BuildList extends EasCommand {
const graphqlBuildDistribution = toGraphQLBuildDistribution(buildDistribution);
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const projectName = await getProjectFullNameAsync(exp);
await queryForPaginatedBuildsAsync(
projectId,
projectName,
{
platform,
status: graphqlBuildStatus,
distribution: graphqlBuildDistribution,
channel: flags.channel,
appVersion: flags.appVersion,
appBuildVersion: flags.appBuildVersion,
sdkVersion: flags.sdkVersion,
runtimeVersion: flags.runtimeVersion,
appIdentifier: flags.appIdentifier,
buildProfile: flags.buildProfile,
gitCommitHash: flags.gitCommitHash,
},
flags
);
const spinner = ora().start('Fetching the build list for the project…');
try {
const builds = await BuildQuery.allForAppAsync(projectId, {
limit,
filter: {
platform,
status: graphqlBuildStatus,
distribution: graphqlBuildDistribution,
channel: flags.channel,
appVersion: flags.appVersion,
appBuildVersion: flags.appBuildVersion,
sdkVersion: flags.sdkVersion,
runtimeVersion: flags.runtimeVersion,
appIdentifier: flags.appIdentifier,
buildProfile: flags.buildProfile,
gitCommitHash: flags.gitCommitHash,
},
});
if (builds.length) {
if (platform || graphqlBuildStatus) {
spinner.succeed(
`Showing ${builds.length} matching builds for the project ${projectName}`
);
} else {
spinner.succeed(`Showing last ${builds.length} builds for the project ${projectName}`);
}
if (json) {
printJsonOnlyOutput(builds);
} else {
const list = builds
.map(build => formatGraphQLBuild(build))
.join(`\n\n${chalk.dim('———')}\n\n`);
Log.log(`\n${list}`);
}
} else {
spinner.fail(`Couldn't find any builds for the project ${projectName}`);
}
} catch (e) {
spinner.fail(`Something went wrong and we couldn't fetch the build list ${projectName}`);
throw e;
}
}
}
@@ -142,57 +170,3 @@ const toGraphQLBuildDistribution = (
return undefined;
}
};
async function queryForPaginatedBuildsAsync(
projectId: string,
projectName: string,
filter: BuildsQuery['filter'],
flags: { json: boolean } & { json: boolean | undefined }
): Promise<void> {
const queryAdditionalBuildsAsync = async (
pageSize: number,
offset: number
): Promise<PaginatedQueryResponse<BuildFragment>> => {
const builds = await BuildQuery.allForAppAsync(projectId, {
limit: pageSize,
offset,
filter,
});
return {
queryResponse: builds,
queryResponseRawLength: builds.length,
};
};
const renderPageOfBuilds = (currentPage: BuildFragment[]): void => {
if (flags.json) {
printJsonOnlyOutput(currentPage);
} else {
if (currentPage.length) {
const list = currentPage
.map(build => formatGraphQLBuild(build))
.join(`\n\n${chalk.dim('———')}\n\n`);
Log.addNewLineIfNone();
Log.withTick(chalk.bold(`Builds for ${projectName}:`));
Log.log(`\n${list}`);
Log.addNewLineIfNone();
} else {
Log.withCross(chalk.bold(`Couldn't find any builds for the project ${projectName}`));
Log.addNewLineIfNone();
}
}
};
await performPaginatedQueryAsync({
pageSize: 50,
offset: 0,
queryToPerform: queryAdditionalBuildsAsync,
promptOptions: {
type: 'confirm',
title: 'Fetch next page of builds?',
renderListItems: renderPageOfBuilds,
},
});
}
+2 -2
View File
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import { formatGraphQLBuild } from '../../build/utils/formatBuild';
@@ -6,7 +7,6 @@ import { BuildFragment } from '../../graphql/generated';
import { BuildQuery } from '../../graphql/queries/BuildQuery';
import Log from '../../log';
import { ora } from '../../ora';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectFullNameAsync,
@@ -35,7 +35,7 @@ export default class BuildView extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const projectName = await getProjectFullNameAsync(exp);
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import gql from 'graphql-tag';
@@ -10,7 +11,6 @@ import {
} from '../../graphql/generated';
import { BranchQuery } from '../../graphql/queries/BranchQuery';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectFullNameAsync,
@@ -88,7 +88,7 @@ export default class ChannelCreate extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
if (!channelName) {
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import gql from 'graphql-tag';
@@ -11,7 +12,6 @@ import {
GetChannelInfoQueryVariables,
} from '../../graphql/generated';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectFullNameAsync,
@@ -55,7 +55,7 @@ export default class ChannelDelete extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const fullName = await getProjectFullNameAsync(exp);
const projectId = await getProjectIdAsync(exp);
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import gql from 'graphql-tag';
@@ -14,7 +15,6 @@ import {
} from '../../graphql/generated';
import { BranchQuery } from '../../graphql/queries/BranchQuery';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
@@ -120,7 +120,7 @@ export default class ChannelEdit extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const channelName = args.name ?? (await promptForChannelAsync());
+21 -102
View File
@@ -1,5 +1,5 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import gql from 'graphql-tag';
import EasCommand from '../../commandUtils/EasCommand';
@@ -9,45 +9,34 @@ import {
GetAllChannelsForAppQueryVariables,
} from '../../graphql/generated';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import formatFields from '../../utils/formatFields';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { PaginatedQueryResponse, performPaginatedQueryAsync } from '../../utils/queries';
import { BRANCHES_LIMIT } from '../branch/list';
import { logChannelDetails } from './view';
const CHANNEL_LIMIT = 50;
const CHANNEL_LIMIT = 10_000;
async function getAllUpdateChannelForAppAsync({
appId,
channelLimit = CHANNEL_LIMIT,
channelOffset = 0,
branchLimit = BRANCHES_LIMIT,
branchOffset = 0,
}: GetAllChannelsForAppQueryVariables): Promise<GetAllChannelsForAppQuery> {
}: {
appId: string;
}): Promise<GetAllChannelsForAppQuery> {
return await withErrorHandlingAsync(
graphqlClient
.query<GetAllChannelsForAppQuery, GetAllChannelsForAppQueryVariables>(
gql`
query GetAllChannelsForApp(
$appId: String!
$channelOffset: Int!
$channelLimit: Int!
$branchLimit: Int!
$branchOffset: Int!
) {
query GetAllChannelsForApp($appId: String!, $offset: Int!, $limit: Int!) {
app {
byId(appId: $appId) {
id
updateChannels(offset: $channelOffset, limit: $channelLimit) {
updateChannels(offset: $offset, limit: $limit) {
id
name
branchMapping
updateBranches(offset: $branchOffset, limit: $branchLimit) {
updateBranches(offset: 0, limit: $limit) {
id
name
updates(offset: 0, limit: 1) {
updates(offset: 0, limit: 10) {
id
group
message
@@ -70,7 +59,7 @@ async function getAllUpdateChannelForAppAsync({
}
}
`,
{ appId, channelOffset, channelLimit, branchLimit, branchOffset },
{ appId, offset: 0, limit: CHANNEL_LIMIT },
{ additionalTypenames: ['UpdateChannel', 'UpdateBranch', 'Update'] }
)
.toPromise()
@@ -96,80 +85,21 @@ export default class ChannelList extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
await queryForChannelsAsync(projectId, jsonFlag);
}
}
const getAllUpdateChannelForAppResult = await getAllUpdateChannelForAppAsync({
appId: projectId,
});
const channels = getAllUpdateChannelForAppResult.app?.byId.updateChannels;
if (!channels) {
throw new Error(`Could not find channels on project with id ${projectId}`);
}
type UpdateChannelObject = GetAllChannelsForAppQuery['app']['byId']['updateChannels'][0];
async function queryForChannelsAsync(projectId: string, jsonFlag: boolean): Promise<void> {
const queryToPerformAsync = async (
pageSize: number,
offset: number
): Promise<PaginatedQueryResponse<UpdateChannelObject>> => {
let queriedAllBranches = false;
let branchOffset = 0;
const branchLimit = 10;
const paginatedQuery: PaginatedQueryResponse<UpdateChannelObject> = {
queryResponse: [],
queryResponseRawLength: 0,
};
const branchesForChannels: Record<string, UpdateChannelObject['updateBranches']> = {};
// we need to paginate our paginated query. Each channel (N) can have M branches
// and we need to fetch all branches to populate our 'branch mapping' portion
// of the UI properly.
do {
const getAllUpdateChannelForAppResult = await getAllUpdateChannelForAppAsync({
appId: projectId,
channelLimit: pageSize,
channelOffset: offset,
branchLimit,
branchOffset,
});
const channels = getAllUpdateChannelForAppResult.app?.byId.updateChannels;
if (!channels) {
throw new Error(`Could not find channels on project with id ${projectId}`);
}
queriedAllBranches = true;
// save each array of branches using a record so we can lookup in O(1) later on
channels.forEach(channel => {
if (channel.updateBranches.length === branchLimit) {
queriedAllBranches = false;
}
branchesForChannels[channel.id] = [
...(branchesForChannels[channel.id] ? branchesForChannels[channel.id] : []),
...channel.updateBranches,
];
});
if (!paginatedQuery.queryResponse.length) {
paginatedQuery.queryResponse = channels;
} else {
paginatedQuery.queryResponse.forEach(channel => {
channel.updateBranches = branchesForChannels[channel.id];
});
}
paginatedQuery.queryResponseRawLength = channels.length;
branchOffset += branchLimit + 1;
} while (!queriedAllBranches);
return paginatedQuery;
};
const renderListItems = (currentPage: UpdateChannelObject[]): void => {
if (jsonFlag) {
printJsonOnlyOutput(currentPage);
printJsonOnlyOutput(channels);
} else {
Log.addNewLineIfNone();
Log.log(chalk.bold('Channels:'));
Log.addNewLineIfNone();
for (const channel of currentPage) {
for (const channel of channels) {
Log.addNewLineIfNone();
Log.log(
formatFields([
@@ -180,16 +110,5 @@ async function queryForChannelsAsync(projectId: string, jsonFlag: boolean): Prom
logChannelDetails(channel);
}
}
};
await performPaginatedQueryAsync({
pageSize: 25, // lower, because we need to query all branches per channel O(N*M)
offset: 0,
queryToPerform: queryToPerformAsync,
promptOptions: {
type: 'confirm',
title: 'Fetch next page of channels?',
renderListItems,
},
});
}
}
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
@@ -6,7 +7,6 @@ import { GetChannelByNameForAppQuery, UpdateBranch } from '../../graphql/generat
import { BranchQuery } from '../../graphql/queries/BranchQuery';
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectFullNameAsync,
@@ -344,15 +344,13 @@ export default class ChannelRollout extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const fullName = await getProjectFullNameAsync(exp);
const projectId = await getProjectIdAsync(exp);
const channel = await ChannelQuery.getUpdateChannelByNameForAppAsync({
appId: projectId,
channelName: channelName!,
offset: 0,
limit: 50,
});
if (!channel) {
throw new Error(
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import assert from 'assert';
import chalk from 'chalk';
@@ -6,7 +7,6 @@ import Table from 'cli-table3';
import EasCommand from '../../commandUtils/EasCommand';
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import {
@@ -111,7 +111,7 @@ export function logChannelDetails(channel: {
}
const { branchId } = branchMapping.data[index];
const branch = channel.updateBranches.find(branch => branch.id === branchId);
const branch = channel.updateBranches.filter(branch => branch.id === branchId)[0];
if (!branch) {
throw new Error('Branch mapping is pointing at a missing branch.');
}
@@ -165,7 +165,7 @@ export default class ChannelView extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
if (!channelName) {
@@ -184,8 +184,6 @@ export default class ChannelView extends EasCommand {
const channel = await ChannelQuery.getUpdateChannelByNameForAppAsync({
appId: projectId,
channelName,
offset: 0,
limit: 50,
});
if (!channel) {
throw new Error(`Could not find a channel with name: ${channelName}`);
+2
View File
@@ -11,6 +11,7 @@ import { appPlatformEmojis } from '../platform';
import { getExpoConfig } from '../project/expoConfig';
import { findProjectRootAsync } from '../project/projectUtils';
import { selectAsync } from '../prompts';
import { handleDeprecatedEasJsonAsync } from './build';
export default class Config extends EasCommand {
static description = 'display project configuration (app.json + eas.json)';
@@ -30,6 +31,7 @@ export default class Config extends EasCommand {
};
const projectDir = await findProjectRootAsync();
await handleDeprecatedEasJsonAsync(projectDir, false);
const reader = new EasJsonReader(projectDir);
const profileName =
@@ -1,4 +1,5 @@
import { Device, DeviceStatus } from '@expo/apple-utils';
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import assert from 'assert';
@@ -16,7 +17,6 @@ import formatDevice from '../../devices/utils/formatDevice';
import { AppleDevice, Maybe } from '../../graphql/generated';
import Log from '../../log';
import { ora } from '../../ora';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectAccountNameAsync } from '../../project/projectUtils';
import { promptAsync, toggleConfirmAsync } from '../../prompts';
@@ -34,7 +34,7 @@ export default class DeviceDelete extends EasCommand {
} = await this.parse(DeviceDelete);
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const accountName = await getProjectAccountNameAsync(exp);
if (!appleTeamIdentifier) {
+2 -2
View File
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import assert from 'assert';
import chalk from 'chalk';
@@ -8,7 +9,6 @@ import { AppleTeamQuery } from '../../credentials/ios/api/graphql/queries/AppleT
import formatDevice from '../../devices/utils/formatDevice';
import Log from '../../log';
import { Ora, ora } from '../../ora';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectAccountNameAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
@@ -23,7 +23,7 @@ export default class BuildList extends EasCommand {
let appleTeamIdentifier = (await this.parse(BuildList)).flags['apple-team-id'];
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const accountName = await getProjectAccountNameAsync(exp);
let spinner: Ora;
+3 -2
View File
@@ -1,9 +1,10 @@
import { getConfig } from '@expo/config';
import EasCommand from '../../commandUtils/EasCommand';
import { AppleDeviceQuery } from '../../credentials/ios/api/graphql/queries/AppleDeviceQuery';
import formatDevice from '../../devices/utils/formatDevice';
import Log from '../../log';
import { ora } from '../../ora';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectAccountNameAsync } from '../../project/projectUtils';
export default class DeviceView extends EasCommand {
@@ -29,7 +30,7 @@ If you are not sure what is the UDID of the device you are looking for, run:
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const accountName = await getProjectAccountNameAsync(exp);
const spinner = ora().start(`Fetching device details for ${UDID}…`);
@@ -1,64 +0,0 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import path from 'path';
import { ensureProjectConfiguredAsync } from '../../build/configure';
import EasCommand from '../../commandUtils/EasCommand';
import { CredentialsContext } from '../../credentials/context';
import Log, { learnMore } from '../../log';
import { createMetadataContextAsync } from '../../metadata/context';
import { downloadMetadataAsync } from '../../metadata/download';
import { handleMetadataError } from '../../metadata/errors';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { ensureLoggedInAsync } from '../../user/actions';
export default class MetadataPull extends EasCommand {
static description = 'generate the local store configuration from the app stores';
static flags = {
profile: Flags.string({
description:
'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.',
}),
};
async runAsync(): Promise<void> {
Log.warn('EAS Metadata is in beta and subject to breaking changes.');
const { flags } = await this.parse(MetadataPull);
const projectDir = await findProjectRootAsync();
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
await getProjectIdAsync(exp);
await ensureProjectConfiguredAsync({ projectDir, nonInteractive: false });
const credentialsCtx = new CredentialsContext({
exp,
projectDir,
user: await ensureLoggedInAsync(),
nonInteractive: false,
});
const metadataCtx = await createMetadataContextAsync({
credentialsCtx,
projectDir,
exp,
profileName: flags.profile,
});
try {
const filePath = await downloadMetadataAsync(metadataCtx);
const relativePath = path.relative(process.cwd(), filePath);
Log.addNewLineIfNone();
Log.log(`🎉 Your store configuration is ready.
- Update the ${chalk.bold(relativePath)} file to prepare the app information.
- Run ${chalk.bold('eas submit')} or manually upload a new app version to the app stores.
- Once the app is uploaded, run ${chalk.bold('eas metadata:push')} to sync the store configuration.
- ${learnMore('https://docs.expo.dev/eas-metadata/introduction/')}`);
} catch (error: any) {
handleMetadataError(error);
}
}
}
@@ -1,57 +0,0 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import { ensureProjectConfiguredAsync } from '../../build/configure';
import EasCommand from '../../commandUtils/EasCommand';
import { CredentialsContext } from '../../credentials/context';
import Log, { learnMore } from '../../log';
import { createMetadataContextAsync } from '../../metadata/context';
import { handleMetadataError } from '../../metadata/errors';
import { uploadMetadataAsync } from '../../metadata/upload';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { ensureLoggedInAsync } from '../../user/actions';
export default class MetadataPush extends EasCommand {
static description = 'sync the local store configuration to the app stores';
static flags = {
profile: Flags.string({
description:
'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.',
}),
};
async runAsync(): Promise<void> {
Log.warn('EAS Metadata is in beta and subject to breaking changes.');
const { flags } = await this.parse(MetadataPush);
const projectDir = await findProjectRootAsync();
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
await getProjectIdAsync(exp);
await ensureProjectConfiguredAsync({ projectDir, nonInteractive: false });
const credentialsCtx = new CredentialsContext({
exp,
projectDir,
user: await ensureLoggedInAsync(),
nonInteractive: false,
});
const metadataCtx = await createMetadataContextAsync({
credentialsCtx,
projectDir,
exp,
profileName: flags.profile,
});
try {
const { appleLink } = await uploadMetadataAsync(metadataCtx);
Log.addNewLineIfNone();
Log.log(`🎉 Store configuration is synced with the app stores.
${learnMore(appleLink, { learnMoreMessage: 'See the changes in App Store Connect' })}`);
} catch (error: any) {
handleMetadataError(error);
}
}
}
@@ -1,10 +1,10 @@
import { getConfig } from '@expo/config';
import gql from 'graphql-tag';
import EasCommand from '../../commandUtils/EasCommand';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import { AppInfoQuery, AppInfoQueryVariables } from '../../graphql/generated';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import formatFields from '../../utils/formatFields';
@@ -36,7 +36,7 @@ export default class ProjectInfo extends EasCommand {
async runAsync(): Promise<void> {
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const { app } = await projectInfoByIdAsync(projectId);
if (!app) {
@@ -1,8 +1,8 @@
import { getConfig } from '@expo/config';
import chalk from 'chalk';
import EasCommand from '../../commandUtils/EasCommand';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, setProjectIdAsync } from '../../project/projectUtils';
export default class ProjectInit extends EasCommand {
@@ -11,7 +11,7 @@ export default class ProjectInit extends EasCommand {
async runAsync(): Promise<void> {
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
if (exp.extra?.eas?.projectId) {
Log.error(
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
@@ -8,7 +9,6 @@ import {
EnvironmentSecretsQuery,
} from '../../graphql/queries/EnvironmentSecretsQuery';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectAccountNameAsync,
@@ -47,7 +47,7 @@ export default class EnvironmentSecretCreate extends EasCommand {
} = await this.parse(EnvironmentSecretCreate);
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const accountName = await getProjectAccountNameAsync(exp);
const { slug } = exp;
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import EasCommand from '../../commandUtils/EasCommand';
@@ -8,7 +9,6 @@ import {
EnvironmentSecretsQuery,
} from '../../graphql/queries/EnvironmentSecretsQuery';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectAccountNameAsync,
@@ -27,7 +27,7 @@ export default class EnvironmentSecretDelete extends EasCommand {
async runAsync(): Promise<void> {
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const projectAccountName = await getProjectAccountNameAsync(exp);
+2 -2
View File
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import chalk from 'chalk';
import Table from 'cli-table3';
import dateFormat from 'dateformat';
@@ -5,7 +6,6 @@ import dateFormat from 'dateformat';
import EasCommand from '../../commandUtils/EasCommand';
import { EnvironmentSecretsQuery } from '../../graphql/queries/EnvironmentSecretsQuery';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectAccountNameAsync,
@@ -17,7 +17,7 @@ export default class EnvironmentSecretList extends EasCommand {
async runAsync(): Promise<void> {
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const projectAccountName = await getProjectAccountNameAsync(exp);
+3 -4
View File
@@ -1,4 +1,4 @@
import { EasJsonReader } from '@expo/eas-json';
import { getConfig } from '@expo/config';
import { Errors, Flags } from '@oclif/core';
import chalk from 'chalk';
@@ -13,7 +13,6 @@ import {
selectRequestedPlatformAsync,
toPlatforms,
} from '../platform';
import { getExpoConfig } from '../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../project/projectUtils';
import { SubmitArchiveFlags, createSubmissionContextAsync } from '../submit/context';
import { submitAsync, waitToCompleteAsync } from '../submit/submit';
@@ -90,13 +89,13 @@ export default class Submit extends EasCommand {
const flags = await this.sanitizeFlagsAsync(rawFlags);
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const platforms = toPlatforms(flags.requestedPlatform);
const submissionProfiles = await getProfilesAsync({
type: 'submit',
easJsonReader: new EasJsonReader(projectDir),
projectDir,
platforms,
profileName: flags.profile,
});
@@ -1,4 +1,4 @@
import { ExpoConfig, modifyConfigAsync } from '@expo/config';
import { ExpoConfig, getConfig, modifyConfigAsync } from '@expo/config';
import { Platform, Workflow } from '@expo/eas-build-job';
import { Flags } from '@oclif/core';
import assert from 'assert';
@@ -9,7 +9,6 @@ import EasCommand from '../../commandUtils/EasCommand';
import { AppPlatform } from '../../graphql/generated';
import Log, { learnMore } from '../../log';
import { RequestedPlatform, appPlatformDisplayNames } from '../../platform';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectIdAsync,
@@ -42,7 +41,9 @@ export default class UpdateConfigure extends EasCommand {
const { flags } = await this.parse(UpdateConfigure);
const platform = flags.platform as RequestedPlatform;
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, {
skipSDKVersionRequirement: true,
});
if (!isExpoUpdatesInstalledOrAvailable(projectDir, exp.sdkVersion)) {
await installExpoUpdatesAsync(projectDir);
+93 -191
View File
@@ -1,4 +1,4 @@
import { ExpoConfig } from '@expo/config';
import { ExpoConfig, getConfig } from '@expo/config';
import { Updates } from '@expo/config-plugins';
import { Platform, Workflow } from '@expo/eas-build-job';
import { Errors, Flags } from '@oclif/core';
@@ -9,7 +9,6 @@ import gql from 'graphql-tag';
import nullthrows from 'nullthrows';
import { getEASUpdateURL } from '../../api';
import { getUpdateGroupUrl } from '../../build/utils/url';
import EasCommand from '../../commandUtils/EasCommand';
import fetch from '../../fetch';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
@@ -19,7 +18,6 @@ import {
Robot,
RootQueryUpdatesByGroupArgs,
Update,
UpdateBranchFragment,
UpdateInfoGroup,
UpdatePublishMutation,
User,
@@ -27,12 +25,10 @@ import {
} from '../../graphql/generated';
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
import { UpdateQuery } from '../../graphql/queries/UpdateQuery';
import Log, { link } from '../../log';
import Log from '../../log';
import { ora } from '../../ora';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectAccountName,
getProjectIdAsync,
installExpoUpdatesAsync,
isExpoUpdatesInstalledOrAvailable,
@@ -45,25 +41,24 @@ import {
uploadAssetsAsync,
} from '../../project/publish';
import { resolveWorkflowAsync } from '../../project/workflow';
import { confirmAsync, promptAsync } from '../../prompts';
import { confirmAsync, promptAsync, selectAsync } from '../../prompts';
import { formatUpdate } from '../../update/utils';
import { ensureLoggedInAsync } from '../../user/actions';
import {
checkManifestBodyAgainstUpdateInfoGroup,
getCodeSigningInfoAsync,
getManifestBodyAsync,
signManifestBody,
} from '../../utils/code-signing';
import uniqBy from '../../utils/expodash/uniqBy';
import formatFields from '../../utils/formatFields';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { PaginatedQueryResponse, performPaginatedQueryAsync } from '../../utils/queries';
import { getVcsClient } from '../../vcs';
import { createUpdateBranchOnAppAsync } from '../branch/create';
import { listBranchesAsync } from '../branch/list';
import { createUpdateChannelOnAppAsync } from '../channel/create';
export const defaultPublishPlatforms: PublishPlatform[] = ['android', 'ios'];
export type PublishPlatformFlag = PublishPlatform | 'all';
type PlatformFlag = PublishPlatform | 'all';
async function getUpdateGroupAsync({
group,
@@ -121,16 +116,12 @@ async function ensureChannelExistsAsync({
}
}
export async function ensureBranchExistsAsync({
async function ensureBranchExistsAsync({
appId,
name: branchName,
limit,
offset,
}: {
appId: string;
name: string;
limit: number;
offset: number;
}): Promise<{
id: string;
updates: Exclude<
@@ -141,8 +132,6 @@ export async function ensureBranchExistsAsync({
const { app } = await UpdateQuery.viewBranchAsync({
appId,
name: branchName,
limit,
offset,
});
const updateBranch = app?.byId.updateBranchByName;
if (updateBranch) {
@@ -231,16 +220,18 @@ export default class UpdatePublish extends EasCommand {
enableJsonOutput();
}
const platformFlag = platform as PublishPlatformFlag;
const platformFlag = platform as PlatformFlag;
// If a group was specified, that means we are republishing it.
republish = group ? true : republish;
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir, {
const { exp } = getConfig(projectDir, {
skipSDKVersionRequirement: true,
isPublicConfig: true,
});
const expPrivate = getExpoConfig(projectDir, {
const { exp: expPrivate } = getConfig(projectDir, {
skipSDKVersionRequirement: true,
isPublicConfig: false,
});
@@ -283,22 +274,49 @@ export default class UpdatePublish extends EasCommand {
}
if (!branchName) {
const validationMessage = 'Branch name may not be empty.';
if (nonInteractive) {
throw new Error('Must supply --branch or use --auto when in non-interactive mode');
}
const validationMessage = 'Branch name may not be empty.';
if (jsonFlag) {
throw new Error(validationMessage);
}
branchName = await getBranchNameToPublishOnAsync(projectId, validationMessage);
const branches = await listBranchesAsync({ projectId });
if (branches.length === 0) {
({ name: branchName } = await promptAsync({
type: 'text',
name: 'name',
message: 'No branches found. Creating a new one. Please name the new branch:',
initial:
(await getVcsClient().getBranchNameAsync()) ||
`branch-${Math.random().toString(36).substr(2, 4)}`,
validate: value => (value ? true : validationMessage),
}));
} else {
branchName = await selectAsync<string>(
'Which branch would you like to publish on?',
branches.map(branch => {
return {
title: `${branch.name} ${chalk.grey(
`- current update: ${formatUpdate(branch.updates[0])}`
)}`,
value: branch.name,
};
})
);
}
assert(branchName, 'Branch name must be specified.');
}
const { id: branchId, updates } = await ensureBranchExistsAsync({
appId: projectId,
name: branchName,
});
let unsortedUpdateInfoGroups: UpdateInfoGroup = {};
let oldMessage: string, oldRuntimeVersion: string;
let uploadedAssetCount = 0;
if (republish) {
// If we are republishing, we don't need to worry about building the bundle or uploading the assets.
// Instead we get the `updateInfoGroup` from the update we wish to republish.
@@ -318,7 +336,26 @@ export default class UpdatePublish extends EasCommand {
throw new Error('You must specify the update group to republish.');
}
updatesToRepublish = await getUpdatesToRepublishAsync(projectId, branchName, platformFlag);
const updateGroups = uniqBy(updates, u => u.group)
.filter(update => {
// Only show groups that have updates on the specified platform(s).
return platformFlag === 'all' || update.platform === platformFlag;
})
.map(update => ({
title: formatUpdateTitle(update),
value: update.group,
}));
if (updateGroups.length === 0) {
throw new Error(
`There are no updates on branch "${branchName}" published on the platform(s) ${platformFlag}. Did you mean to publish a new update instead?`
);
}
const selectedUpdateGroup = await selectAsync<string>(
'which update would you like to republish?',
updateGroups
);
updatesToRepublish = updates.filter(update => update.group === selectedUpdateGroup);
}
const updatesToRepublishFilteredByPlatform = updatesToRepublish.filter(
// Only republish to the specified platforms
@@ -326,7 +363,7 @@ export default class UpdatePublish extends EasCommand {
);
if (updatesToRepublishFilteredByPlatform.length === 0) {
throw new Error(
`There are no updates on branch "${branchName}" published for the platform(s) "${platformFlag}" with group ID "${
`There are no updates on branch "${branchName}" published on the platform(s) "${platformFlag}" with group ID "${
group ? group : updatesToRepublish[0].group
}". Did you mean to publish a new update instead?`
);
@@ -383,6 +420,7 @@ export default class UpdatePublish extends EasCommand {
if (nonInteractive) {
throw new Error('Must supply --message or use --auto when in non-interactive mode');
}
const validationMessage = 'publish message may not be empty.';
if (jsonFlag) {
throw new Error(validationMessage);
@@ -412,24 +450,15 @@ export default class UpdatePublish extends EasCommand {
try {
const platforms = platformFlag === 'all' ? defaultPublishPlatforms : [platformFlag];
const assets = await collectAssetsAsync({ inputDir: inputDir!, platforms });
const { uniqueUploadedAssetCount } = await uploadAssetsAsync(
assets,
spinnerText => (assetSpinner.text = `Uploading assets... ${spinnerText}`)
);
uploadedAssetCount = uniqueUploadedAssetCount;
await uploadAssetsAsync(assets);
unsortedUpdateInfoGroups = await buildUnsortedUpdateInfoGroupAsync(assets, exp);
const uploadAssetSuccessMessage = uploadedAssetCount
? `Uploaded ${uploadedAssetCount} ${uploadedAssetCount === 1 ? 'asset' : 'assets'}!`
: `Uploading assets skipped -- no new assets found!`;
assetSpinner.succeed(uploadAssetSuccessMessage);
assetSpinner.succeed('Uploaded assets!');
} catch (e) {
assetSpinner.fail('Failed to upload assets');
throw e;
}
}
const truncatedMessage = truncatePublishUpdateMessage(message!);
const runtimeToPlatformMapping: Record<string, string[]> = {};
for (const runtime of new Set(Object.values(runtimeVersions))) {
runtimeToPlatformMapping[runtime] = Object.entries(runtimeVersions)
@@ -437,13 +466,6 @@ export default class UpdatePublish extends EasCommand {
.map(pair => pair[0]);
}
const { id: branchId } = await ensureBranchExistsAsync({
appId: projectId,
name: branchName,
limit: 0,
offset: 0,
});
// Sort the updates into different groups based on their platform specific runtime versions
const updateGroups: PublishUpdateGroupInput[] = Object.entries(runtimeToPlatformMapping).map(
([runtime, platforms]) => {
@@ -463,7 +485,7 @@ export default class UpdatePublish extends EasCommand {
branchId,
updateInfoGroup: localUpdateInfoGroup,
runtimeVersion: republish ? oldRuntimeVersion : runtime,
message: truncatedMessage,
message,
awaitingCodeSigningInfo: !!codeSigningInfo,
};
}
@@ -533,36 +555,20 @@ export default class UpdatePublish extends EasCommand {
Log.addNewLineIfNone();
for (const runtime of new Set(Object.values(runtimeVersions))) {
const newUpdatesForRuntimeVersion = newUpdates.filter(
update => update.runtimeVersion === runtime
);
if (!newUpdatesForRuntimeVersion.length) {
const platforms = newUpdates
.filter(update => update.runtimeVersion === runtime)
.map(update => update.platform);
const newUpdate = newUpdates.find(update => update.runtimeVersion === runtime);
if (!newUpdate) {
throw new Error(`Publish response is missing updates with runtime ${runtime}.`);
}
const platforms = newUpdatesForRuntimeVersion.map(update => update.platform);
const newAndroidUpdate = newUpdatesForRuntimeVersion.find(
update => update.platform === 'android'
);
const newIosUpdate = newUpdatesForRuntimeVersion.find(update => update.platform === 'ios');
const updateGroupId = newUpdatesForRuntimeVersion[0].group;
const projectName = exp.slug;
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
const updateGroupUrl = getUpdateGroupUrl(accountName, projectName, updateGroupId);
const updateGroupLink = link(updateGroupUrl, { dim: false });
Log.log(
formatFields([
{ label: 'Branch', value: branchName },
{ label: 'Runtime version', value: runtime },
{ label: 'Platform', value: platforms.join(', ') },
{ label: 'Update group ID', value: updateGroupId },
...(newAndroidUpdate
? [{ label: 'Android update ID', value: newAndroidUpdate.id }]
: []),
...(newIosUpdate ? [{ label: 'iOS update ID', value: newIosUpdate.id }] : []),
{ label: 'Message', value: truncatedMessage },
{ label: 'Website link', value: updateGroupLink },
{ label: 'branch', value: branchName },
{ label: 'runtime version', value: runtime },
{ label: 'platform', value: platforms.join(', ') },
{ label: 'update group ID', value: newUpdate.group },
{ label: 'message', value: message! },
])
);
Log.addNewLineIfNone();
@@ -571,57 +577,9 @@ export default class UpdatePublish extends EasCommand {
}
}
export async function getUpdatesToRepublishAsync(
projectId: string,
branchName: string,
platformFlag: string
): Promise<AppUpdateObject[]> {
const queryUpdatesForBranchAsync = async (
pageSize: number,
offset: number
): Promise<PaginatedQueryResponse<AppUpdateObject>> => {
const { updates } = await ensureBranchExistsAsync({
appId: projectId,
name: branchName!,
limit: pageSize,
offset,
});
if (!updates.length) {
throw new Error(
`There are no updates on branch "${branchName}" published for the platform(s) ${platformFlag}. Did you mean to publish a new update instead?`
);
}
const relevantUpdates =
platformFlag === 'all'
? updates
: updates.filter(update => {
// Only show groups that have updates on the specified platform(s).
return update.platform === platformFlag;
});
return {
queryResponse: relevantUpdates,
queryResponseRawLength: updates.length,
};
};
const getUpdateIdentifier = (update: AppUpdateObject): string => update.group;
return await performPaginatedQueryAsync({
pageSize: 50,
offset: 0,
queryToPerform: queryUpdatesForBranchAsync,
promptOptions: {
type: 'select',
title: 'Which update would you like to republish?',
createDisplayTextForSelectionPromptListItem: formatUpdateTitle,
getIdentifierForQueryItem: getUpdateIdentifier,
},
});
}
async function getRuntimeVersionObjectAsync(
exp: ExpoConfig,
platformFlag: PublishPlatformFlag,
platformFlag: PlatformFlag,
projectDir: string
): Promise<Record<string, string>> {
const platforms = (platformFlag === 'all' ? ['android', 'ios'] : [platformFlag]) as Platform[];
@@ -649,24 +607,12 @@ async function getRuntimeVersionObjectAsync(
);
}
async function checkEASUpdateURLIsSetAsync(exp: ExpoConfig): Promise<void> {
const configuredURL = exp.updates?.url;
const projectId = await getProjectIdAsync(exp);
const expectedURL = getEASUpdateURL(projectId);
if (configuredURL !== expectedURL) {
throw new Error(
`The update URL is incorrectly configured for EAS Update. Please set updates.url to ${expectedURL} in your app.json.`
);
}
}
export type AppUpdateObject = Exclude<
Exclude<ViewBranchUpdatesQuery['app'], null | undefined>['byId']['updateBranchByName'],
null | undefined
>['updates'][0];
export function formatUpdateTitle(update: AppUpdateObject): string {
function formatUpdateTitle(
update: Exclude<
Exclude<ViewBranchUpdatesQuery['app'], null | undefined>['byId']['updateBranchByName'],
null | undefined
>['updates'][number]
): string {
const { message, createdAt, actor, runtimeVersion } = update;
let actorName: string;
@@ -689,58 +635,14 @@ export function formatUpdateTitle(update: AppUpdateObject): string {
)} by ${actorName}, runtimeVersion: ${runtimeVersion}] ${message}`;
}
async function getBranchNameToPublishOnAsync(
projectId: string,
validationMessage: string
): Promise<string> {
const queryForBanchesToPublishOnAsync = async (
pageSize: number,
offset: number
): Promise<PaginatedQueryResponse<UpdateBranchFragment>> => {
const branches = await listBranchesAsync({ appId: projectId, limit: pageSize, offset });
if (branches.length === 0) {
const { name: newBranchName } = await promptAsync({
type: 'text',
name: 'name',
message: 'No branches found. Creating a new one. Please name the new branch:',
initial:
(await getVcsClient().getBranchNameAsync()) ||
`branch-${Math.random().toString(36).substring(2, 7)}`,
validate: value => (value ? true : validationMessage),
});
branches.push({
id: '_defaultId',
name: newBranchName,
updates: [],
});
}
return { queryResponse: branches, queryResponseRawLength: branches.length };
};
const formatBranchTitle = (branch: UpdateBranchFragment): string =>
`${branch.name} ${chalk.grey(`- current update: ${formatUpdate(branch.updates[0])}`)}`;
const getBranchIdentifier = (branch: UpdateBranchFragment): string => branch.id;
async function checkEASUpdateURLIsSetAsync(exp: ExpoConfig): Promise<void> {
const configuredURL = exp.updates?.url;
const projectId = await getProjectIdAsync(exp);
const expectedURL = getEASUpdateURL(projectId);
const branches = await performPaginatedQueryAsync({
pageSize: 50,
offset: 0,
queryToPerform: queryForBanchesToPublishOnAsync,
promptOptions: {
type: 'select',
createDisplayTextForSelectionPromptListItem: formatBranchTitle,
getIdentifierForQueryItem: getBranchIdentifier,
title: 'Which branch would you like to publish on?',
},
});
const branchName = branches?.pop()?.name;
assert(branchName, validationMessage);
return branchName;
}
export const truncatePublishUpdateMessage = (originalMessage: string): string => {
if (originalMessage.length > 1024) {
Log.warn('Update message exceeds the allowed 1024 character limit. Truncating message...');
return originalMessage.substring(0, 1021) + '...';
if (configuredURL !== expectedURL) {
throw new Error(
`The update URL is incorrectly configured for EAS Update. Please set updates.url to ${expectedURL} in your app.json.`
);
}
return originalMessage;
};
}
+41 -112
View File
@@ -1,19 +1,18 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import assert from 'assert';
import chalk from 'chalk';
import CliTable from 'cli-table3';
import EasCommand from '../../commandUtils/EasCommand';
import { ViewAllUpdatesQuery, ViewBranchUpdatesQuery } from '../../graphql/generated';
import { ViewAllUpdatesQuery } from '../../graphql/generated';
import { UpdateQuery } from '../../graphql/queries/UpdateQuery';
import Log from '../../log';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { FormatUpdateParameter, UPDATE_COLUMNS, formatUpdate } from '../../update/utils';
import groupBy from '../../utils/expodash/groupBy';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { PaginatedQueryResponse, performPaginatedQueryAsync } from '../../utils/queries';
import { getVcsClient } from '../../vcs';
type UpdateGroupDescription = FormatUpdateParameter & {
@@ -51,11 +50,15 @@ export default class BranchView extends EasCommand {
}
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
let updateGroupDescriptions: UpdateGroupDescription[];
if (all) {
await fetchAllUpdatesForAppAsync(projectId, jsonFlag);
const branchesAndUpdates = await UpdateQuery.viewAllAsync({ appId: projectId });
updateGroupDescriptions = getUpdateGroupDescriptions(
branchesAndUpdates.app.byId.updateBranches
);
} else {
let branchInteractive: string | undefined;
if (!branchFlag) {
@@ -74,120 +77,46 @@ export default class BranchView extends EasCommand {
const branch = branchFlag ?? branchInteractive;
assert(branch, 'Branch name may not be empty.');
fetchAllUpdatesForBranchAsync(projectId, branch, jsonFlag);
const branchesAndUpdates = await UpdateQuery.viewBranchAsync({
appId: projectId,
name: branch,
});
const UpdateBranch = branchesAndUpdates.app?.byId.updateBranchByName;
if (!UpdateBranch) {
throw new Error(`Could not find branch "${branch}"`);
}
updateGroupDescriptions = getUpdateGroupDescriptions([UpdateBranch]);
}
if (jsonFlag) {
printJsonOnlyOutput(updateGroupDescriptions);
} else {
logAsTable(updateGroupDescriptions);
}
}
}
type BranchUpdateObject = Exclude<
ViewBranchUpdatesQuery['app']['byId']['updateBranchByName'],
null | undefined
>['updates'][0];
type AppUpdateObject = ViewAllUpdatesQuery['app']['byId']['updates'][0];
async function fetchAllUpdatesForBranchAsync(
projectId: string,
branchName: string,
jsonFlag: boolean
): Promise<void> {
const queryToPerformAsync = async (
pageSize: number,
offset: number
): Promise<PaginatedQueryResponse<BranchUpdateObject>> => {
const branchesAndUpdates = await UpdateQuery.viewBranchAsync({
appId: projectId,
name: branchName,
limit: pageSize,
offset,
});
const UpdateBranch = branchesAndUpdates.app?.byId.updateBranchByName;
if (!UpdateBranch) {
throw new Error(`Could not find branch "${branchName}"`);
}
return {
queryResponse: UpdateBranch.updates,
queryResponseRawLength: UpdateBranch.updates.length,
};
};
const renderListItems = (currentPage: BranchUpdateObject[]): void => {
const updateGroupDescriptions = getUpdateGroupDescriptions(currentPage);
if (jsonFlag) {
printJsonOnlyOutput(updateGroupDescriptions);
} else {
logAsTable(updateGroupDescriptions);
}
};
await performPaginatedQueryAsync({
pageSize: 50,
offset: 0,
queryToPerform: queryToPerformAsync,
promptOptions: {
type: 'confirm',
title: 'View next page of updates?',
renderListItems,
},
});
}
async function fetchAllUpdatesForAppAsync(projectId: string, jsonFlag: boolean): Promise<void> {
const queryToPerformAsync = async (
pageSize: number,
offset: number
): Promise<PaginatedQueryResponse<AppUpdateObject>> => {
const viewAllUpdates = await UpdateQuery.viewAllAsync({
appId: projectId,
limit: pageSize,
offset,
});
const { updates } = viewAllUpdates.app.byId;
return {
queryResponse: updates,
queryResponseRawLength: updates.length,
};
};
const renderListItems = (currentPage: AppUpdateObject[]): void => {
const updateGroupDescriptions = getUpdateGroupDescriptions(currentPage);
if (jsonFlag) {
printJsonOnlyOutput(updateGroupDescriptions);
} else {
logAsTable(updateGroupDescriptions);
}
};
await performPaginatedQueryAsync({
pageSize: 50,
offset: 0,
queryToPerform: queryToPerformAsync,
promptOptions: {
type: 'confirm',
title: 'View next page of updates?',
renderListItems,
},
});
}
function getUpdateGroupDescriptions(
updates: (BranchUpdateObject | AppUpdateObject)[]
branchesAndUpdates: ViewAllUpdatesQuery['app']['byId']['updateBranches']
): UpdateGroupDescription[] {
return Object.values(groupBy(updates, update => update.group))
.map(updateGroup => {
return {
platforms: updateGroup
.map(update => update.platform)
.sort()
.join(', '),
...updateGroup[0],
branch: updateGroup[0].branch.name,
} as UpdateGroupDescription;
const flattenedBranchesAndUpdates = branchesAndUpdates.flatMap(branch =>
branch.updates.map(update => {
return { branch: branch.name, ...update };
})
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
);
const updateGroupDescriptions = Object.values(
groupBy(flattenedBranchesAndUpdates, update => update.group)
).map(updateGroup => {
const platforms = updateGroup
.map(update => update.platform)
.sort()
.join(', ');
return { ...updateGroup[0], platforms };
});
updateGroupDescriptions.sort(
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
return updateGroupDescriptions;
}
function logAsTable(updateGroupDescriptions: UpdateGroupDescription[]): void {
@@ -1,10 +1,10 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import EasCommand from '../../commandUtils/EasCommand';
import { WebhookType } from '../../graphql/generated';
import { WebhookMutation } from '../../graphql/mutations/WebhookMutation';
import { ora } from '../../ora';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { prepareInputParamsAsync } from '../../webhooks/input';
@@ -30,7 +30,7 @@ export default class WebhookCreate extends EasCommand {
const webhookInputParams = await prepareInputParamsAsync(flags);
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const spinner = ora('Creating a webhook').start();
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import assert from 'assert';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
@@ -8,7 +9,6 @@ import { WebhookMutation } from '../../graphql/mutations/WebhookMutation';
import { WebhookQuery } from '../../graphql/queries/WebhookQuery';
import Log from '../../log';
import { ora } from '../../ora';
import { getExpoConfig } from '../../project/expoConfig';
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
import { promptAsync, toggleConfirmAsync } from '../../prompts';
import { formatWebhook } from '../../webhooks/formatWebhook';
@@ -30,7 +30,7 @@ export default class WebhookDelete extends EasCommand {
} = await this.parse(WebhookDelete);
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
let webhook: WebhookFragment | undefined =
@@ -1,3 +1,4 @@
import { getConfig } from '@expo/config';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
@@ -6,7 +7,6 @@ import { WebhookType } from '../../graphql/generated';
import { WebhookQuery } from '../../graphql/queries/WebhookQuery';
import Log from '../../log';
import { ora } from '../../ora';
import { getExpoConfig } from '../../project/expoConfig';
import {
findProjectRootAsync,
getProjectFullNameAsync,
@@ -30,7 +30,7 @@ export default class WebhookList extends EasCommand {
} = await this.parse(WebhookList);
const projectDir = await findProjectRootAsync();
const exp = getExpoConfig(projectDir);
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const projectId = await getProjectIdAsync(exp);
const projectFullName = await getProjectFullNameAsync(exp);
+2 -2
View File
@@ -1,4 +1,4 @@
import { ExpoConfig } from '@expo/config';
import { ExpoConfig, getConfig } from '@expo/config';
import { Env } from '@expo/eas-build-job';
import chalk from 'chalk';
@@ -70,7 +70,7 @@ export class CredentialsContext {
return;
}
// trigger getConfig error
getExpoConfig(this.options.projectDir);
getConfig(this.options.projectDir, { skipSDKVersionRequirement: true });
}
public logOwnerAndProject(): void {
@@ -18,43 +18,6 @@ describe(assertValidOptions, () => {
});
describe(syncCapabilitiesForEntitlementsAsync, () => {
it(`does not disable associated domains when MDM managed domains is active`, async () => {
const ctx = { providerId: 123195, teamId: 'MyteamId' };
// https://forums.expo.dev/t/eas-build-failed-on-ios-associated-domains-capability/61662/5
const capabilities = [
{
id: 'U78L9459DG_MDM_MANAGED_ASSOCIATED_DOMAINS',
attributes: {
ownerType: 'BUNDLE',
settings: null,
editable: true,
inputs: null,
enabled: true,
responseId: '77eb74ad-7c24-48de-9d35-1fabb4c5afd9',
},
},
].map(({ id, attributes }) => new BundleIdCapability(ctx, id, attributes as any));
const entitlements = {
'com.apple.developer.associated-domains': ['applinks:packagename.fr'],
};
const bundleId = {
getBundleIdCapabilitiesAsync: jest.fn(() => capabilities),
updateBundleIdCapabilityAsync: jest.fn(),
id: 'XXX',
} as any;
await syncCapabilitiesForEntitlementsAsync(bundleId, entitlements);
expect(bundleId.updateBundleIdCapabilityAsync).toBeCalledWith([
{
capabilityType: 'ASSOCIATED_DOMAINS',
option: 'ON',
},
]);
});
it('enables all capabilities', async () => {
const bundleId = {
getBundleIdCapabilitiesAsync: jest.fn(() => []),
+12 -51
View File
@@ -1564,13 +1564,10 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
appVersion?: Maybe<Scalars['String']>;
artifacts?: Maybe<BuildArtifacts>;
buildProfile?: Maybe<Scalars['String']>;
canRetry: Scalars['Boolean'];
cancelingActor?: Maybe<Actor>;
channel?: Maybe<Scalars['String']>;
completedAt?: Maybe<Scalars['DateTime']>;
createdAt: Scalars['DateTime'];
createdAt?: Maybe<Scalars['DateTime']>;
distribution?: Maybe<DistributionType>;
enqueuedAt?: Maybe<Scalars['DateTime']>;
error?: Maybe<BuildError>;
estimatedWaitTimeLeftSeconds?: Maybe<Scalars['Int']>;
expirationDate?: Maybe<Scalars['DateTime']>;
@@ -1584,24 +1581,19 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
iosEnterpriseProvisioning?: Maybe<BuildIosEnterpriseProvisioning>;
isGitWorkingTreeDirty?: Maybe<Scalars['Boolean']>;
logFiles: Array<Scalars['String']>;
maxBuildTimeSeconds: Scalars['Int'];
metrics?: Maybe<BuildMetrics>;
parentBuild?: Maybe<Build>;
platform: AppPlatform;
priority: BuildPriority;
project: Project;
provisioningStartedAt?: Maybe<Scalars['DateTime']>;
/** Queue position is 1-indexed */
queuePosition?: Maybe<Scalars['Int']>;
reactNativeVersion?: Maybe<Scalars['String']>;
releaseChannel?: Maybe<Scalars['String']>;
resourceClass?: Maybe<BuildResourceClass>;
runtimeVersion?: Maybe<Scalars['String']>;
sdkVersion?: Maybe<Scalars['String']>;
status: BuildStatus;
submissions: Array<Submission>;
updatedAt: Scalars['DateTime'];
workerStartedAt?: Maybe<Scalars['DateTime']>;
updatedAt?: Maybe<Scalars['DateTime']>;
};
export type BuildArtifact = {
@@ -1773,8 +1765,6 @@ export type BuildMutation = {
createIosBuild: CreateBuildResult;
/** Delete an EAS Build build */
deleteBuild: Build;
/** Retry an EAS Build build */
retryBuild: Build;
};
@@ -1785,7 +1775,6 @@ export type BuildMutationCancelBuildArgs = {
export type BuildMutationCreateAndroidBuildArgs = {
appId: Scalars['ID'];
buildParams?: InputMaybe<BuildParamsInput>;
job: AndroidJobInput;
metadata?: InputMaybe<BuildMetadataInput>;
};
@@ -1793,7 +1782,6 @@ export type BuildMutationCreateAndroidBuildArgs = {
export type BuildMutationCreateIosBuildArgs = {
appId: Scalars['ID'];
buildParams?: InputMaybe<BuildParamsInput>;
job: IosJobInput;
metadata?: InputMaybe<BuildMetadataInput>;
};
@@ -1803,23 +1791,13 @@ export type BuildMutationDeleteBuildArgs = {
buildId: Scalars['ID'];
};
export type BuildMutationRetryBuildArgs = {
buildId: Scalars['ID'];
};
export type BuildOrBuildJob = {
id: Scalars['ID'];
};
export type BuildParamsInput = {
resourceClass: BuildResourceClass;
};
export enum BuildPriority {
High = 'HIGH',
Normal = 'NORMAL',
NormalPlus = 'NORMAL_PLUS'
Normal = 'NORMAL'
}
/** Publicly visible data for a Build. */
@@ -1884,14 +1862,6 @@ export type BuildQueryByIdArgs = {
buildId: Scalars['ID'];
};
export enum BuildResourceClass {
AndroidDefault = 'ANDROID_DEFAULT',
AndroidLarge = 'ANDROID_LARGE',
IosDefault = 'IOS_DEFAULT',
IosLarge = 'IOS_LARGE',
Legacy = 'LEGACY'
}
export enum BuildStatus {
Canceled = 'CANCELED',
Errored = 'ERRORED',
@@ -3851,7 +3821,6 @@ export type DeleteUpdateBranchMutation = { __typename?: 'RootMutation', updateBr
export type BranchesByAppQueryVariables = Exact<{
appId: Scalars['String'];
limit: Scalars['Int'];
offset: Scalars['Int'];
}>;
@@ -3913,10 +3882,8 @@ export type UpdateChannelBranchMappingMutation = { __typename?: 'RootMutation',
export type GetAllChannelsForAppQueryVariables = Exact<{
appId: Scalars['String'];
channelOffset: Scalars['Int'];
channelLimit: Scalars['Int'];
branchLimit: Scalars['Int'];
branchOffset: Scalars['Int'];
offset: Scalars['Int'];
limit: Scalars['Int'];
}>;
@@ -4339,21 +4306,19 @@ export type CreateAndroidBuildMutationVariables = Exact<{
appId: Scalars['ID'];
job: AndroidJobInput;
metadata?: InputMaybe<BuildMetadataInput>;
buildParams?: InputMaybe<BuildParamsInput>;
}>;
export type CreateAndroidBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createAndroidBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
export type CreateAndroidBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createAndroidBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
export type CreateIosBuildMutationVariables = Exact<{
appId: Scalars['ID'];
job: IosJobInput;
metadata?: InputMaybe<BuildMetadataInput>;
buildParams?: InputMaybe<BuildParamsInput>;
}>;
export type CreateIosBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createIosBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
export type CreateIosBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createIosBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
export type CreateEnvironmentSecretForAccountMutationVariables = Exact<{
input: CreateEnvironmentSecretInput;
@@ -4466,7 +4431,7 @@ export type BuildsByIdQueryVariables = Exact<{
}>;
export type BuildsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
export type BuildsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
export type GetAllBuildsForAppQueryVariables = Exact<{
appId: Scalars['String'];
@@ -4476,13 +4441,11 @@ export type GetAllBuildsForAppQueryVariables = Exact<{
}>;
export type GetAllBuildsForAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, builds: Array<{ __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }> } } };
export type GetAllBuildsForAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, builds: Array<{ __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }> } } };
export type GetChannelByNameForAppQueryVariables = Exact<{
appId: Scalars['String'];
channelName: Scalars['String'];
limit: Scalars['Int'];
offset: Scalars['Int'];
}>;
@@ -4538,21 +4501,19 @@ export type GetAllSubmissionsForAppQuery = { __typename?: 'RootQuery', app: { __
export type ViewAllUpdatesQueryVariables = Exact<{
appId: Scalars['String'];
limit: Scalars['Int'];
offset: Scalars['Int'];
}>;
export type ViewAllUpdatesQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, actor?: { __typename?: 'Robot', firstName?: string | null, id: string } | { __typename?: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } }> } } };
export type ViewAllUpdatesQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, actor?: { __typename?: 'Robot', firstName?: string | null, id: string } | { __typename?: 'User', username: string, id: string } | null }> }> } } };
export type ViewBranchUpdatesQueryVariables = Exact<{
appId: Scalars['String'];
name: Scalars['String'];
limit: Scalars['Int'];
offset: Scalars['Int'];
}>;
export type ViewBranchUpdatesQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranchByName?: { __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename?: 'Robot', firstName?: string | null, id: string } | { __typename?: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } }> } | null } } };
export type ViewBranchUpdatesQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranchByName?: { __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename?: 'Robot', firstName?: string | null, id: string } | { __typename?: 'User', username: string, id: string } | null }> } | null } } };
export type CurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
@@ -4576,7 +4537,7 @@ export type WebhookByIdQuery = { __typename?: 'RootQuery', webhook: { __typename
export type AppFragment = { __typename?: 'App', id: string, fullName: string, slug: string };
export type BuildFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
export type BuildFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
export type EnvironmentSecretFragment = { __typename?: 'EnvironmentSecret', id: string, name: string, createdAt: any };
@@ -7,7 +7,6 @@ import {
AndroidJobInput,
BuildFragment,
BuildMetadataInput,
BuildParamsInput,
CreateAndroidBuildMutation,
CreateAndroidBuildMutationVariables,
CreateIosBuildMutation,
@@ -27,7 +26,6 @@ export const BuildMutation = {
appId: string;
job: AndroidJobInput;
metadata: BuildMetadataInput;
buildParams: BuildParamsInput;
}): Promise<BuildResult> {
const data = await withErrorHandlingAsync(
graphqlClient
@@ -37,15 +35,9 @@ export const BuildMutation = {
$appId: ID!
$job: AndroidJobInput!
$metadata: BuildMetadataInput
$buildParams: BuildParamsInput
) {
build {
createAndroidBuild(
appId: $appId
job: $job
metadata: $metadata
buildParams: $buildParams
) {
createAndroidBuild(appId: $appId, job: $job, metadata: $metadata) {
build {
id
...BuildFragment
@@ -70,7 +62,6 @@ export const BuildMutation = {
appId: string;
job: IosJobInput;
metadata: BuildMetadataInput;
buildParams: BuildParamsInput;
}): Promise<BuildResult> {
const data = await withErrorHandlingAsync(
graphqlClient
@@ -80,15 +71,9 @@ export const BuildMutation = {
$appId: ID!
$job: IosJobInput!
$metadata: BuildMetadataInput
$buildParams: BuildParamsInput
) {
build {
createIosBuild(
appId: $appId
job: $job
metadata: $metadata
buildParams: $buildParams
) {
createIosBuild(appId: $appId, job: $job, metadata: $metadata) {
build {
id
...BuildFragment
@@ -14,7 +14,7 @@ import {
} from '../generated';
import { BuildFragmentNode } from '../types/Build';
export type BuildsQuery = {
type BuildsQuery = {
offset?: number;
limit?: number;
filter?: {
@@ -3,14 +3,10 @@ import gql from 'graphql-tag';
import { graphqlClient, withErrorHandlingAsync } from '../client';
import { GetChannelByNameForAppQuery, GetChannelByNameForAppQueryVariables } from '../generated';
const BRANCHES_LIMIT = 50;
export const ChannelQuery = {
async getUpdateChannelByNameForAppAsync({
appId,
channelName,
offset = 0,
limit = BRANCHES_LIMIT,
}: GetChannelByNameForAppQueryVariables): Promise<
GetChannelByNameForAppQuery['app']['byId']['updateChannelByName']
> {
@@ -22,12 +18,7 @@ export const ChannelQuery = {
graphqlClient
.query<GetChannelByNameForAppQuery, GetChannelByNameForAppQueryVariables>(
gql`
query GetChannelByNameForApp(
$appId: String!
$channelName: String!
$limit: Int!
$offset: Int!
) {
query GetChannelByNameForApp($appId: String!, $channelName: String!) {
app {
byId(appId: $appId) {
id
@@ -36,10 +27,10 @@ export const ChannelQuery = {
name
createdAt
branchMapping
updateBranches(offset: $offset, limit: $limit) {
updateBranches(offset: 0, limit: 1000) {
id
name
updates(offset: 0, limit: 1) {
updates(offset: 0, limit: 10) {
id
group
message
@@ -62,7 +53,7 @@ export const ChannelQuery = {
}
}
`,
{ appId, channelName, offset, limit },
{ appId, channelName },
{ additionalTypenames: ['UpdateChannel', 'UpdateBranch', 'Update'] }
)
.toPromise()
@@ -8,41 +8,37 @@ import {
ViewBranchUpdatesQueryVariables,
} from '../generated';
const UPDATE_LIMIT = 50;
const PAGE_LIMIT = 10_000;
export const UpdateQuery = {
async viewAllAsync({
appId,
limit = UPDATE_LIMIT,
offset = 0,
}: ViewAllUpdatesQueryVariables): Promise<ViewAllUpdatesQuery> {
async viewAllAsync({ appId }: { appId: string }): Promise<ViewAllUpdatesQuery> {
return withErrorHandlingAsync(
graphqlClient
.query<ViewAllUpdatesQuery, ViewAllUpdatesQueryVariables>(
gql`
query ViewAllUpdates($appId: String!, $limit: Int!, $offset: Int!) {
query ViewAllUpdates($appId: String!, $limit: Int!) {
app {
byId(appId: $appId) {
id
updates(offset: $offset, limit: $limit) {
updateBranches(offset: 0, limit: $limit) {
id
group
message
createdAt
runtimeVersion
platform
actor {
name
updates(offset: 0, limit: $limit) {
id
... on User {
username
group
message
createdAt
runtimeVersion
platform
actor {
id
... on User {
username
}
... on Robot {
firstName
}
}
... on Robot {
firstName
}
}
branch {
id
name
}
}
}
@@ -51,32 +47,26 @@ export const UpdateQuery = {
`,
{
appId,
limit,
offset,
limit: PAGE_LIMIT,
},
{ additionalTypenames: ['UpdateBranch', 'Update'] }
)
.toPromise()
);
},
async viewBranchAsync({
appId,
name,
limit = UPDATE_LIMIT,
offset = 0,
}: ViewBranchUpdatesQueryVariables) {
async viewBranchAsync({ appId, name }: Pick<ViewBranchUpdatesQueryVariables, 'appId' | 'name'>) {
return withErrorHandlingAsync<ViewBranchUpdatesQuery>(
graphqlClient
.query<ViewBranchUpdatesQuery, ViewBranchUpdatesQueryVariables>(
gql`
query ViewBranchUpdates($appId: String!, $name: String!, $limit: Int!, $offset: Int!) {
query ViewBranchUpdates($appId: String!, $name: String!, $limit: Int!) {
app {
byId(appId: $appId) {
id
updateBranchByName(name: $name) {
id
name
updates(offset: $offset, limit: $limit) {
updates(offset: 0, limit: $limit) {
id
group
message
@@ -93,10 +83,6 @@ export const UpdateQuery = {
firstName
}
}
branch {
id
name
}
}
}
}
@@ -106,8 +92,7 @@ export const UpdateQuery = {
{
appId,
name,
limit,
offset,
limit: PAGE_LIMIT,
},
{ additionalTypenames: ['UpdateBranch', 'Update'] }
)
@@ -4,7 +4,7 @@ export const UpdateBranchFragmentNode = gql`
fragment UpdateBranchFragment on UpdateBranch {
id
name
updates(offset: 0, limit: 1) {
updates(offset: 0, limit: 10) {
id
actor {
__typename
-4
View File
@@ -49,10 +49,6 @@ export default class Log {
Log.log(`${chalk.green(logSymbols.success)} ${message}`);
}
public static withCross(...args: any[]): void {
Log.consoleLog(chalk.red(figures.cross), ...args);
}
public static withTick(...args: any[]): void {
Log.consoleLog(chalk.green(figures.tick), ...args);
}
@@ -1,84 +0,0 @@
import { AgeRatingDeclaration, KidsAge, Rating } from '@expo/apple-utils';
import { AttributesOf } from '../../../../utils/asc';
// @ts-expect-error: Deprecated property `gamblingAndContests` needs to be deleted from the types
// These attributes is what we get from the API when no questions are answered
export const emptyAdvisory: AttributesOf<AgeRatingDeclaration> = {
alcoholTobaccoOrDrugUseOrReferences: null,
contests: null,
gamblingSimulated: null,
horrorOrFearThemes: null,
matureOrSuggestiveThemes: null,
medicalOrTreatmentInformation: null,
profanityOrCrudeHumor: null,
sexualContentGraphicAndNudity: null,
sexualContentOrNudity: null,
violenceCartoonOrFantasy: null,
violenceRealistic: null,
violenceRealisticProlongedGraphicOrSadistic: null,
gambling: false,
unrestrictedWebAccess: false,
kidsAgeBand: null,
seventeenPlus: false,
};
// @ts-expect-error: Deprecated property `gamblingAndContests` needs to be deleted from the types
export const leastRestrictiveAdvisory: AttributesOf<AgeRatingDeclaration> = {
alcoholTobaccoOrDrugUseOrReferences: Rating.NONE,
contests: Rating.NONE,
gamblingSimulated: Rating.NONE,
horrorOrFearThemes: Rating.NONE,
matureOrSuggestiveThemes: Rating.NONE,
medicalOrTreatmentInformation: Rating.NONE,
profanityOrCrudeHumor: Rating.NONE,
sexualContentGraphicAndNudity: Rating.NONE,
sexualContentOrNudity: Rating.NONE,
violenceCartoonOrFantasy: Rating.NONE,
violenceRealistic: Rating.NONE,
violenceRealisticProlongedGraphicOrSadistic: Rating.NONE,
gambling: false,
unrestrictedWebAccess: false,
kidsAgeBand: null,
seventeenPlus: false,
};
// @ts-expect-error: Deprecated property `gamblingAndContests` needs to be deleted from the types
export const mostRestrictiveAdvisory: AttributesOf<AgeRatingDeclaration> = {
alcoholTobaccoOrDrugUseOrReferences: Rating.FREQUENT_OR_INTENSE,
contests: Rating.FREQUENT_OR_INTENSE,
gamblingSimulated: Rating.FREQUENT_OR_INTENSE,
horrorOrFearThemes: Rating.FREQUENT_OR_INTENSE,
matureOrSuggestiveThemes: Rating.FREQUENT_OR_INTENSE,
medicalOrTreatmentInformation: Rating.FREQUENT_OR_INTENSE,
profanityOrCrudeHumor: Rating.FREQUENT_OR_INTENSE,
sexualContentGraphicAndNudity: Rating.FREQUENT_OR_INTENSE,
sexualContentOrNudity: Rating.FREQUENT_OR_INTENSE,
violenceCartoonOrFantasy: Rating.FREQUENT_OR_INTENSE,
violenceRealistic: Rating.FREQUENT_OR_INTENSE,
violenceRealisticProlongedGraphicOrSadistic: Rating.FREQUENT_OR_INTENSE,
gambling: true,
unrestrictedWebAccess: true,
kidsAgeBand: null,
seventeenPlus: true,
};
// @ts-expect-error: Deprecated property `gamblingAndContests` needs to be deleted from the types
export const kidsSixToEightAdvisory: AttributesOf<AgeRatingDeclaration> = {
alcoholTobaccoOrDrugUseOrReferences: Rating.NONE,
contests: Rating.NONE,
gamblingSimulated: Rating.NONE,
horrorOrFearThemes: Rating.NONE,
matureOrSuggestiveThemes: Rating.NONE,
medicalOrTreatmentInformation: Rating.NONE,
profanityOrCrudeHumor: Rating.NONE,
sexualContentGraphicAndNudity: Rating.NONE,
sexualContentOrNudity: Rating.NONE,
violenceCartoonOrFantasy: Rating.NONE,
violenceRealistic: Rating.NONE,
violenceRealisticProlongedGraphicOrSadistic: Rating.NONE,
gambling: false,
unrestrictedWebAccess: false,
kidsAgeBand: KidsAge.SIX_TO_EIGHT,
seventeenPlus: false,
};
@@ -1,23 +0,0 @@
import { AppCategory, AppInfo, BundleIdPlatform } from '@expo/apple-utils';
import { AttributesOf } from '../../../../utils/asc';
export const primaryOnlyCategory: Pick<AttributesOf<AppInfo>, 'primaryCategory'> = {
primaryCategory: new AppCategory({} as any, 'ENTERTAINMENT', {
platforms: [BundleIdPlatform.IOS, BundleIdPlatform.MAC_OS],
}),
};
export const secondaryOnlyCategory: Pick<AttributesOf<AppInfo>, 'secondaryCategory'> = {
secondaryCategory: new AppCategory({} as any, 'GAMES', {
platforms: [BundleIdPlatform.IOS, BundleIdPlatform.MAC_OS],
}),
};
export const primaryAndSecondaryCategory: Pick<
AttributesOf<AppInfo>,
'primaryCategory' | 'secondaryCategory'
> = {
...primaryOnlyCategory,
...secondaryOnlyCategory,
};
@@ -1,21 +0,0 @@
import { AppInfoLocalization } from '@expo/apple-utils';
import { AttributesOf } from '../../../../utils/asc';
export const englishInfo: AttributesOf<AppInfoLocalization> = {
locale: 'en-US',
name: 'Awesome test app',
subtitle: 'This is just a test',
privacyPolicyUrl: 'https://example.com/en/privacy-policy',
privacyChoicesUrl: 'https://exmaple.com/en/privacy-choices',
privacyPolicyText: 'This is some privacy policy text',
};
export const dutchInfo: AttributesOf<AppInfoLocalization> = {
locale: 'nl-NL',
name: 'Geweldige test app',
subtitle: 'Dit is maar een test',
privacyPolicyUrl: 'https://example.com/nl/privacy-policy',
privacyChoicesUrl: 'https://example.com/nl/privacy-choices',
privacyPolicyText: 'Dit is wat privacy policy tekst',
};
@@ -1,25 +0,0 @@
import { AppStoreReviewDetail } from '@expo/apple-utils';
import { AttributesOf } from '../../../../utils/asc';
export const nameOnlyReviewDetails: AttributesOf<AppStoreReviewDetail> = {
contactFirstName: 'Evan',
contactLastName: 'Bacon',
contactEmail: '[email protected]',
contactPhone: '+1 (555) 555-5555',
demoAccountName: null,
demoAccountPassword: null,
demoAccountRequired: null,
notes: null,
};
export const nameAndDemoReviewDetails: AttributesOf<AppStoreReviewDetail> = {
contactFirstName: 'Evan',
contactLastName: 'Bacon',
contactEmail: '[email protected]',
contactPhone: '+1 (555) 555-5555',
demoAccountName: '[email protected]',
demoAccountPassword: 'secretpassword',
demoAccountRequired: true,
notes: null,
};
@@ -1,48 +0,0 @@
import { AppStoreState, AppStoreVersion, Platform, ReleaseType } from '@expo/apple-utils';
import { AttributesOf } from '../../../../utils/asc';
export const manualRelease: AttributesOf<AppStoreVersion> = {
platform: Platform.IOS,
versionString: '1.0.0',
appStoreState: AppStoreState.WAITING_FOR_REVIEW,
storeIcon: null,
watchStoreIcon: null,
copyright: '2022 - ACME',
releaseType: ReleaseType.MANUAL,
earliestReleaseDate: null,
usesIdfa: null,
isWatchOnly: false,
downloadable: false,
createdDate: '2022-05-23T00:00:00.000Z',
};
export const automaticRelease: AttributesOf<AppStoreVersion> = {
platform: Platform.IOS,
versionString: '2.0.0',
appStoreState: AppStoreState.WAITING_FOR_REVIEW,
storeIcon: null,
watchStoreIcon: null,
copyright: '2022 - ACME',
releaseType: ReleaseType.AFTER_APPROVAL,
earliestReleaseDate: null,
usesIdfa: null,
isWatchOnly: false,
downloadable: false,
createdDate: '2022-05-23T00:00:00.000Z',
};
export const scheduledRelease: AttributesOf<AppStoreVersion> = {
platform: Platform.IOS,
versionString: '3.0.0',
appStoreState: AppStoreState.READY_FOR_SALE,
storeIcon: null,
watchStoreIcon: null,
copyright: '2022 - ACME',
releaseType: ReleaseType.SCHEDULED,
earliestReleaseDate: '2022-05-29T00:00:00.000Z',
usesIdfa: null,
isWatchOnly: false,
downloadable: false,
createdDate: '2022-05-23T00:00:00.000Z',
};
@@ -1,23 +0,0 @@
import { AppStoreVersionLocalization } from '@expo/apple-utils';
import { AttributesOf } from '../../../../utils/asc';
export const englishVersion: AttributesOf<AppStoreVersionLocalization> = {
locale: 'en-US',
description: 'This is a description of this version',
keywords: 'some, description',
marketingUrl: 'https://example.com/en/marketing',
promotionalText: 'This is some promotional text',
supportUrl: 'https://example.com/en/support',
whatsNew: 'Bugfixes and improvements',
};
export const dutchVersion: AttributesOf<AppStoreVersionLocalization> = {
locale: 'nl-NL',
description: 'Dit is een beschrijving van deze versie',
keywords: 'een, beschrijving',
marketingUrl: 'https://example.com/nl/marketing',
promotionalText: 'Dit is wat promotie tekst',
supportUrl: 'https://example.com/nl/support',
whatsNew: 'Beestreparaties en verbeteringen',
};
@@ -1,19 +0,0 @@
import { AppleConfigReader } from '../reader';
import { leastRestrictiveAdvisory, mostRestrictiveAdvisory } from './fixtures/ageRatingDeclaration';
describe('setAgeRating', () => {
it('ignores advisory when not set', () => {
const reader = new AppleConfigReader({ advisory: undefined });
expect(reader.getAgeRating()).toBeNull();
});
it('auto-fills least restrictive advisory', () => {
const reader = new AppleConfigReader({ advisory: {} });
expect(reader.getAgeRating()).toMatchObject(leastRestrictiveAdvisory);
});
it('returns most restrictive advisory', () => {
const reader = new AppleConfigReader({ advisory: mostRestrictiveAdvisory });
expect(reader.getAgeRating()).toMatchObject(mostRestrictiveAdvisory);
});
});
@@ -1,210 +0,0 @@
import { AppleConfigWriter } from '../writer';
import {
emptyAdvisory,
kidsSixToEightAdvisory,
leastRestrictiveAdvisory,
mostRestrictiveAdvisory,
} from './fixtures/ageRatingDeclaration';
import { primaryAndSecondaryCategory, secondaryOnlyCategory } from './fixtures/appInfo';
import { dutchInfo, englishInfo } from './fixtures/appInfoLocalization';
import { nameAndDemoReviewDetails, nameOnlyReviewDetails } from './fixtures/appStoreReviewDetail';
import { automaticRelease, manualRelease, scheduledRelease } from './fixtures/appStoreVersion';
import { dutchVersion, englishVersion } from './fixtures/appStoreVersionLocalization';
describe('toSchema', () => {
it('returns object with apple schema', () => {
const writer = new AppleConfigWriter();
expect(writer.toSchema()).toMatchObject({
configVersion: 0,
apple: expect.any(Object),
});
});
});
describe('setAgeRating', () => {
it('auto-fills least restrictive advisory', () => {
const writer = new AppleConfigWriter();
writer.setAgeRating(emptyAdvisory);
expect(writer.schema.advisory).toMatchObject(leastRestrictiveAdvisory);
});
it('modifies kids band rating', () => {
const writer = new AppleConfigWriter();
writer.setAgeRating(kidsSixToEightAdvisory);
expect(writer.schema.advisory).toMatchObject(kidsSixToEightAdvisory);
});
it('modifies most restrictive advisory', () => {
const writer = new AppleConfigWriter();
writer.setAgeRating(mostRestrictiveAdvisory);
expect(writer.schema.advisory).toMatchObject(mostRestrictiveAdvisory);
});
});
describe('setInfoLocale', () => {
it('creates and modifies the locale', () => {
const writer = new AppleConfigWriter();
writer.setInfoLocale(englishInfo);
expect(writer.schema.info?.[englishInfo.locale]).toMatchObject({
title: englishInfo.name,
subtitle: englishInfo.subtitle,
privacyPolicyUrl: englishInfo.privacyPolicyUrl,
privacyPolicyText: englishInfo.privacyPolicyText,
privacyChoicesUrl: englishInfo.privacyChoicesUrl,
});
});
it('modifies existing locales', () => {
const writer = new AppleConfigWriter();
writer.setInfoLocale(englishInfo);
writer.setInfoLocale(dutchInfo);
writer.setInfoLocale({
...englishInfo,
name: 'This is now different',
privacyPolicyText: null,
});
expect(writer.schema.info?.[dutchInfo.locale]).toHaveProperty('title', dutchInfo.name);
expect(writer.schema.info?.[englishInfo.locale]).toMatchObject({
title: 'This is now different',
privacyPolicyText: undefined,
});
});
});
describe('setCategories', () => {
it('modifies the categories', () => {
const writer = new AppleConfigWriter();
writer.setCategories(primaryAndSecondaryCategory as any);
expect(writer.schema.categories).toHaveLength(2);
expect((writer.schema.categories as any)[0]).toBe(
primaryAndSecondaryCategory.primaryCategory?.id
);
expect((writer.schema.categories as any)[1]).toBe(
primaryAndSecondaryCategory.secondaryCategory?.id
);
});
it('skips secondary category without primary category', () => {
const writer = new AppleConfigWriter();
writer.setCategories(secondaryOnlyCategory as any);
expect(writer.schema.categories).toHaveLength(0);
});
});
describe('setVersion', () => {
it('modifies the copyright', () => {
const writer = new AppleConfigWriter();
writer.setVersion(manualRelease);
expect(writer.schema.copyright).toBe(manualRelease.copyright);
});
});
describe('setVersionRelease', () => {
it('modifies scheduled release', () => {
const writer = new AppleConfigWriter();
writer.setVersionRelease(scheduledRelease);
expect(writer.schema.release).toMatchObject({
autoReleaseDate: scheduledRelease.earliestReleaseDate,
});
});
it('modifies automatic release', () => {
const writer = new AppleConfigWriter();
writer.setVersionRelease(automaticRelease);
expect(writer.schema.release).toMatchObject({
automaticRelease: true,
});
});
it('does not modify manual release', () => {
const writer = new AppleConfigWriter();
writer.setVersionRelease(manualRelease);
expect(writer.schema.release).toBeUndefined();
});
it('overwrites all release fields', () => {
const writer = new AppleConfigWriter();
writer.setVersionRelease(scheduledRelease);
writer.setVersionRelease(automaticRelease);
expect(writer.schema.release).not.toHaveProperty('autoReleaseDate');
});
});
describe('setVersionLocale', () => {
it('creates and modifies the locale', () => {
const writer = new AppleConfigWriter();
writer.setVersionLocale(englishVersion);
expect(writer.schema.info?.[englishVersion.locale]).toMatchObject({
description: englishVersion.description,
keywords: englishVersion.keywords?.split(', '),
releaseNotes: englishVersion.whatsNew,
marketingUrl: englishVersion.marketingUrl,
promoText: englishVersion.promotionalText,
supportUrl: englishVersion.supportUrl,
});
});
it('modifies existing locales', () => {
const writer = new AppleConfigWriter();
writer.setVersionLocale(englishVersion);
writer.setVersionLocale(dutchVersion);
writer.setVersionLocale({
...englishVersion,
description: 'This is now different',
whatsNew: null,
});
expect(writer.schema.info?.[dutchVersion.locale]).toHaveProperty(
'description',
dutchVersion.description
);
expect(writer.schema.info?.[englishVersion.locale]).toMatchObject({
description: 'This is now different',
releaseNotes: undefined,
});
});
});
describe('setReviewDetails', () => {
it('modifies name only review details', () => {
const writer = new AppleConfigWriter();
writer.setReviewDetails(nameOnlyReviewDetails);
expect(writer.schema.review).toMatchObject({
firstName: nameOnlyReviewDetails.contactFirstName,
lastName: nameOnlyReviewDetails.contactLastName,
email: nameOnlyReviewDetails.contactEmail,
phone: nameOnlyReviewDetails.contactPhone,
demoUsername: undefined,
demoPassword: undefined,
demoRequired: undefined,
notes: undefined,
});
});
it('modifies name and demo review details', () => {
const writer = new AppleConfigWriter();
writer.setReviewDetails(nameAndDemoReviewDetails);
expect(writer.schema.review).toMatchObject({
firstName: nameAndDemoReviewDetails.contactFirstName,
lastName: nameAndDemoReviewDetails.contactLastName,
email: nameAndDemoReviewDetails.contactEmail,
phone: nameAndDemoReviewDetails.contactPhone,
demoUsername: nameAndDemoReviewDetails.demoAccountName,
demoPassword: nameAndDemoReviewDetails.demoAccountPassword,
demoRequired: nameAndDemoReviewDetails.demoAccountRequired,
notes: undefined,
});
});
it('replaces existing review details', () => {
const writer = new AppleConfigWriter();
writer.setReviewDetails(nameAndDemoReviewDetails);
writer.setReviewDetails(nameOnlyReviewDetails);
expect(writer.schema.review).toMatchObject({
demoUsername: undefined,
demoPassword: undefined,
demoRequired: undefined,
});
});
});
@@ -1,167 +0,0 @@
import {
AgeRatingDeclaration,
AppInfoLocalization,
AppStoreReviewDetail,
AppStoreVersion,
AppStoreVersionLocalization,
CategoryIds,
Rating,
ReleaseType,
} from '@expo/apple-utils';
import uniq from '../../../utils/expodash/uniq';
import { AttributesOf } from '../../utils/asc';
import { removeDatePrecision } from '../../utils/date';
import { AppleMetadata } from '../types';
type PartialExcept<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>>;
// TODO: find out if we can move this to default JSON schema normalization
export const DEFAULT_WHATSNEW = 'Bug fixes and improved stability';
/**
* Deserializes the metadata config schema into attributes for different models.
* This uses version 0 of the config schema.
*/
export class AppleConfigReader {
public constructor(public readonly schema: AppleMetadata) {}
public getAgeRating(): Partial<AttributesOf<AgeRatingDeclaration>> | null {
const attributes = this.schema.advisory;
if (!attributes) {
return null;
}
return {
alcoholTobaccoOrDrugUseOrReferences:
attributes.alcoholTobaccoOrDrugUseOrReferences ?? Rating.NONE,
contests: attributes.contests ?? Rating.NONE,
gamblingSimulated: attributes.gamblingSimulated ?? Rating.NONE,
horrorOrFearThemes: attributes.horrorOrFearThemes ?? Rating.NONE,
matureOrSuggestiveThemes: attributes.matureOrSuggestiveThemes ?? Rating.NONE,
medicalOrTreatmentInformation: attributes.medicalOrTreatmentInformation ?? Rating.NONE,
profanityOrCrudeHumor: attributes.profanityOrCrudeHumor ?? Rating.NONE,
sexualContentGraphicAndNudity: attributes.sexualContentGraphicAndNudity ?? Rating.NONE,
sexualContentOrNudity: attributes.sexualContentOrNudity ?? Rating.NONE,
violenceCartoonOrFantasy: attributes.violenceCartoonOrFantasy ?? Rating.NONE,
violenceRealistic: attributes.violenceRealistic ?? Rating.NONE,
violenceRealisticProlongedGraphicOrSadistic:
attributes.violenceRealisticProlongedGraphicOrSadistic ?? Rating.NONE,
gambling: attributes.gambling ?? false,
unrestrictedWebAccess: attributes.unrestrictedWebAccess ?? false,
kidsAgeBand: attributes.kidsAgeBand ?? null,
seventeenPlus: attributes.seventeenPlus ?? false,
};
}
public getLocales(): string[] {
// TODO: filter "default" locales, add option to add non-localized info to the config
return uniq(Object.keys(this.schema.info || {}));
}
public getInfoLocale(
locale: string
): PartialExcept<AttributesOf<AppInfoLocalization>, 'locale' | 'name'> | null {
const info = this.schema.info?.[locale];
if (!info) {
return null;
}
return {
locale,
name: info.title,
subtitle: info.subtitle,
privacyChoicesUrl: info.privacyChoicesUrl,
privacyPolicyText: info.privacyPolicyText,
privacyPolicyUrl: info.privacyPolicyUrl,
};
}
public getCategories(): CategoryIds | null {
if (Array.isArray(this.schema.categories) && this.schema.categories.length > 0) {
return {
primaryCategory: this.schema.categories[0],
secondaryCategory: this.schema.categories[1],
};
}
return null;
}
/** Get the `AppStoreVersion` object. */
public getVersion(): Partial<
Omit<AttributesOf<AppStoreVersion>, 'releaseType' | 'earliestReleaseDate'>
> | null {
return this.schema.copyright ? { copyright: this.schema.copyright } : null;
}
public getVersionRelease(): Partial<
Pick<AttributesOf<AppStoreVersion>, 'releaseType' | 'earliestReleaseDate'>
> | null {
const { release } = this.schema;
if (release?.autoReleaseDate) {
return {
releaseType: ReleaseType.SCHEDULED,
// Convert time format to 2020-06-17T12:00:00-07:00
earliestReleaseDate: removeDatePrecision(release.autoReleaseDate)?.toISOString() ?? null,
};
}
if (release?.automaticRelease === true) {
return {
releaseType: ReleaseType.AFTER_APPROVAL,
earliestReleaseDate: null,
};
}
if (release?.automaticRelease === false) {
return {
releaseType: ReleaseType.MANUAL,
earliestReleaseDate: null,
};
}
return null;
}
public getVersionLocale(
locale: string,
context: { versionIsFirst: boolean }
): Partial<AttributesOf<AppStoreVersionLocalization>> | null {
const info = this.schema.info?.[locale];
if (!info) {
return null;
}
return {
locale,
description: info.description,
keywords: info.keywords?.join(', '),
// TODO: maybe move this to task logic, it's more an exception than data handling
whatsNew: context.versionIsFirst ? undefined : info.releaseNotes || DEFAULT_WHATSNEW,
marketingUrl: info.marketingUrl,
promotionalText: info.promoText,
supportUrl: info.supportUrl,
};
}
public getReviewDetails(): Partial<AttributesOf<AppStoreReviewDetail>> | null {
const review = this.schema.review;
if (!review) {
return null;
}
return {
contactFirstName: review.firstName,
contactLastName: review.lastName,
contactEmail: review.email,
contactPhone: review.phone,
demoAccountName: review.demoUsername,
demoAccountPassword: review.demoPassword,
demoAccountRequired: review.demoRequired,
notes: review.notes,
// TODO: add attachment
};
}
}
@@ -1,142 +0,0 @@
import {
AgeRatingDeclaration,
AppInfo,
AppInfoLocalization,
AppStoreReviewDetail,
AppStoreVersion,
AppStoreVersionLocalization,
Rating,
ReleaseType,
} from '@expo/apple-utils';
import { AttributesOf } from '../../utils/asc';
import { AppleMetadata } from '../types';
/**
* Serializes the Apple ASC entities into the metadata configuration schema.
* This uses version 0 of the config schema.
*/
export class AppleConfigWriter {
constructor(public readonly schema: Partial<AppleMetadata> = {}) {}
/** Get the schema result to write it to the config file */
public toSchema(): { configVersion: number; apple: Partial<AppleMetadata> } {
return {
configVersion: 0,
apple: this.schema,
};
}
public setAgeRating(attributes: AttributesOf<AgeRatingDeclaration>): void {
this.schema.advisory = {
alcoholTobaccoOrDrugUseOrReferences:
attributes.alcoholTobaccoOrDrugUseOrReferences ?? Rating.NONE,
contests: attributes.contests ?? Rating.NONE,
gamblingSimulated: attributes.gamblingSimulated ?? Rating.NONE,
horrorOrFearThemes: attributes.horrorOrFearThemes ?? Rating.NONE,
matureOrSuggestiveThemes: attributes.matureOrSuggestiveThemes ?? Rating.NONE,
medicalOrTreatmentInformation: attributes.medicalOrTreatmentInformation ?? Rating.NONE,
profanityOrCrudeHumor: attributes.profanityOrCrudeHumor ?? Rating.NONE,
sexualContentGraphicAndNudity: attributes.sexualContentGraphicAndNudity ?? Rating.NONE,
sexualContentOrNudity: attributes.sexualContentOrNudity ?? Rating.NONE,
violenceCartoonOrFantasy: attributes.violenceCartoonOrFantasy ?? Rating.NONE,
violenceRealistic: attributes.violenceRealistic ?? Rating.NONE,
violenceRealisticProlongedGraphicOrSadistic:
attributes.violenceRealisticProlongedGraphicOrSadistic ?? Rating.NONE,
gambling: attributes.gambling ?? false,
unrestrictedWebAccess: attributes.unrestrictedWebAccess ?? false,
kidsAgeBand: attributes.kidsAgeBand ?? null,
seventeenPlus: attributes.seventeenPlus ?? false,
};
}
public setInfoLocale(attributes: AttributesOf<AppInfoLocalization>): void {
this.schema.info = this.schema.info ?? {};
const existing = this.schema.info[attributes.locale] ?? {};
this.schema.info[attributes.locale] = {
...existing,
title: attributes.name ?? '',
subtitle: optional(attributes.subtitle),
privacyPolicyUrl: optional(attributes.privacyPolicyUrl),
privacyPolicyText: optional(attributes.privacyPolicyText),
privacyChoicesUrl: optional(attributes.privacyChoicesUrl),
};
}
public setCategories({ primaryCategory, secondaryCategory }: AttributesOf<AppInfo>): void {
this.schema.categories = [];
// TODO: see why these types are conflicting
if (primaryCategory) {
this.schema.categories.push(primaryCategory.id as any);
if (secondaryCategory) {
this.schema.categories.push(secondaryCategory.id as any);
}
}
}
public setVersion(
attributes: Omit<AttributesOf<AppStoreVersion>, 'releaseType' | 'earliestReleaseDate'>
): void {
this.schema.copyright = optional(attributes.copyright);
}
public setVersionRelease(
attributes: Pick<AttributesOf<AppStoreVersion>, 'releaseType' | 'earliestReleaseDate'>
): void {
if (attributes.releaseType === ReleaseType.SCHEDULED) {
this.schema.release = {
autoReleaseDate: optional(attributes.earliestReleaseDate),
};
}
if (attributes.releaseType === ReleaseType.AFTER_APPROVAL) {
this.schema.release = {
automaticRelease: true,
};
}
if (attributes.releaseType === ReleaseType.MANUAL) {
// ReleaseType.MANUAL is the default behavior, so we don't need to configure it.
// Setting `"automaticRelease": false` is a bit confusing for people who don't know what automaticRelease does.
this.schema.release = undefined;
}
}
public setVersionLocale(attributes: AttributesOf<AppStoreVersionLocalization>): void {
this.schema.info = this.schema.info ?? {};
const existing = this.schema.info[attributes.locale] ?? {};
this.schema.info[attributes.locale] = {
...existing,
description: optional(attributes.description),
keywords: optional(attributes.keywords)
?.split(',')
.map(keyword => keyword.trim()),
releaseNotes: optional(attributes.whatsNew),
marketingUrl: optional(attributes.marketingUrl),
promoText: optional(attributes.promotionalText),
supportUrl: optional(attributes.supportUrl),
};
}
public setReviewDetails(attributes: AttributesOf<AppStoreReviewDetail>): void {
this.schema.review = {
firstName: attributes.contactFirstName ?? '',
lastName: attributes.contactLastName ?? '',
email: attributes.contactEmail ?? '',
phone: attributes.contactPhone ?? '',
demoUsername: optional(attributes.demoAccountName),
demoPassword: optional(attributes.demoAccountPassword),
demoRequired: optional(attributes.demoAccountRequired),
notes: optional(attributes.notes),
// TODO: add attachment
};
}
}
/** Helper function to convert `T | null` to `T | undefined`, required for the entity properties */
function optional<T>(value: T | null): T | undefined {
return value ?? undefined;
}
@@ -1,19 +0,0 @@
import type { App } from '@expo/apple-utils';
import type { AgeRatingData } from './tasks/age-rating';
import type { AppInfoData } from './tasks/app-info';
import type { AppReviewData } from './tasks/app-review-detail';
import type { AppVersionData } from './tasks/app-version';
/**
* The fully prepared apple data, used within the `downloadAsync` or `uploadAsync` tasks.
* It contains references to each individual models, to either upload or download App Store data.
*/
export type AppleData = { app: App } & AppInfoData & AppVersionData & AgeRatingData & AppReviewData;
/**
* The unprepared partial apple data, used within the `prepareAsync` tasks.
* It contains a reference to the app, each task should populate the necessary data.
* If an entity fails to prepare the data, individual tasks should raise errors about the missing data.
*/
export type PartialAppleData = { app: App } & Partial<Omit<AppleData, 'app'>>;
@@ -1,31 +0,0 @@
import { AppleConfigReader } from './config/reader';
import { AppleConfigWriter } from './config/writer';
import { AppleData, PartialAppleData } from './data';
export abstract class AppleTask {
/** Get a description from the task to use as section headings in the log */
public abstract name(): string;
/** Prepare the data from the App Store to start syncing with the store configuration */
public abstract prepareAsync(options: TaskPrepareOptions): Promise<void>;
/** Download all information from the App Store to generate the store configuration */
public abstract downloadAsync(options: TaskDownloadOptions): Promise<void>;
/** Upload all information from the store configuration to the App Store */
public abstract uploadAsync(options: TaskUploadOptions): Promise<void>;
}
export type TaskPrepareOptions = {
context: PartialAppleData;
};
export type TaskDownloadOptions = {
config: AppleConfigWriter;
context: AppleData;
};
export type TaskUploadOptions = {
config: AppleConfigReader;
context: AppleData;
};
@@ -1,8 +0,0 @@
import { getRequestClient } from '@expo/apple-utils';
export { default } from 'nock';
// Axios from apple-utils needs to be patched when we use Nock.
// Instead of the default adapter, we need to use `axios/lib/adapters/http`
// see: https://github.com/nock/nock#axios
getRequestClient().defaults.adapter = require('axios/lib/adapters/http');
@@ -1,111 +0,0 @@
import { AgeRatingDeclaration, AppStoreVersion, Rating } from '@expo/apple-utils';
import nock from 'nock';
import { AppleConfigReader } from '../../config/reader';
import { AppleConfigWriter } from '../../config/writer';
import { AppleData } from '../../data';
import { AgeRatingTask } from '../age-rating';
import { requestContext } from './fixtures/requestContext';
jest.mock('../../../../ora');
jest.mock('../../config/writer');
describe(AgeRatingTask, () => {
describe('prepareAsync', () => {
it('aborts when version is not loaded', async () => {
const promise = new AgeRatingTask().prepareAsync({ context: {} as any });
await expect(promise).rejects.toThrow('not prepared');
});
it('loads age rating from app version', async () => {
const scope = nock('https://api.appstoreconnect.apple.com')
.get(`/v1/${AppStoreVersion.type}/stub-id/ageRatingDeclaration`)
.reply(200, require('./fixtures/appStoreVersions/get-ageRatingDeclaration-200.json'));
const context: any = {
version: new AppStoreVersion(requestContext, 'stub-id', {} as any),
};
await new AgeRatingTask().prepareAsync({ context });
expect(context.ageRating).toBeInstanceOf(AgeRatingDeclaration);
expect(scope.isDone()).toBeTruthy();
});
});
describe('downloadAsync', () => {
it('skips age rating when not prepared', async () => {
const writer = jest.mocked(new AppleConfigWriter());
await new AgeRatingTask().downloadAsync({
config: writer,
context: { ageRating: undefined } as any,
});
expect(writer.setAgeRating).not.toBeCalled();
});
it('sets age rating when prepared', async () => {
const writer = jest.mocked(new AppleConfigWriter());
const ageRating = new AgeRatingDeclaration(requestContext, 'stub-id', {} as any);
await new AgeRatingTask().downloadAsync({
config: writer,
context: { ageRating } as any,
});
expect(writer.setAgeRating).toBeCalledWith(ageRating.attributes);
});
});
describe('uploadAsync', () => {
it('aborts when age rating is not loaded', async () => {
const promise = new AgeRatingTask().uploadAsync({
config: new AppleConfigReader({}),
context: { ageRating: undefined } as any,
});
await expect(promise).rejects.toThrow('rating not initialized');
});
it('skips updating age rating when not configured', async () => {
const scope = nock('https://api.appstoreconnect.apple.com')
.patch(`/v1/${AgeRatingDeclaration.type}/stub-id`)
.reply(200, require('./fixtures/ageRatingDeclarations/patch-200.json'));
await new AgeRatingTask().uploadAsync({
config: new AppleConfigReader({ advisory: undefined }),
context: {
ageRating: new AgeRatingDeclaration(requestContext, 'stub-id', {} as any),
} as AppleData,
});
expect(scope.isDone()).toBeFalsy();
nock.cleanAll();
});
it('updates age rating from config when configured', async () => {
const scope = nock('https://api.appstoreconnect.apple.com')
.patch(`/v1/${AgeRatingDeclaration.type}/stub-id`)
.reply(200, require('./fixtures/ageRatingDeclarations/patch-200.json'));
const context = {
ageRating: new AgeRatingDeclaration(requestContext, 'stub-id', {} as any),
};
await new AgeRatingTask().uploadAsync({
config: new AppleConfigReader({
advisory: {
// See fixture value for horrorOrFearThemes
horrorOrFearThemes: Rating.INFREQUENT_OR_MILD,
},
}),
context: context as AppleData,
});
expect(context.ageRating.id).not.toMatch('stub-id');
expect(scope.isDone()).toBeTruthy();
});
});
});
@@ -1,173 +0,0 @@
import { App, AppCategoryId, AppInfo, AppInfoLocalization } from '@expo/apple-utils';
import nock from 'nock';
import { AppleConfigReader } from '../../config/reader';
import { AppleConfigWriter } from '../../config/writer';
import { AppleData, PartialAppleData } from '../../data';
import { AppInfoTask } from '../app-info';
import { requestContext } from './fixtures/requestContext';
jest.mock('../../../../ora');
jest.mock('../../config/writer');
describe(AppInfoTask, () => {
describe('prepareAsync', () => {
it('loads editable app info and locales from app instance', async () => {
const scopeInfo = nock('https://api.appstoreconnect.apple.com')
.get(uri => uri.startsWith(`/v1/${App.type}/stub-id/${AppInfo.type}`)) // allow any query params
.reply(200, require('./fixtures/apps/get-appInfos-200.json'));
const scopeLocales = nock('https://api.appstoreconnect.apple.com')
.get(/\/v1\/appInfos\/.*\/appInfoLocalizations/) // allow any id from fixture
.reply(200, require('./fixtures/appInfos/get-appInfoLocalizations-200.json'));
const context: PartialAppleData = {
app: new App(requestContext, 'stub-id', {} as any),
};
await new AppInfoTask().prepareAsync({ context });
expect(context.info).toBeInstanceOf(AppInfo);
expect(context.infoLocales).toBeInstanceOf(Array);
expect(scopeInfo.isDone()).toBeTruthy();
expect(scopeLocales.isDone()).toBeTruthy();
});
});
describe('downloadAsync', () => {
it('aborts when app info is not loaded', async () => {
const writer = jest.mocked(new AppleConfigWriter());
const promise = new AppInfoTask().downloadAsync({
config: writer,
context: { info: undefined, infoLocales: [] } as any,
});
await expect(promise).rejects.toThrow('info not initialized');
});
it('sets categories when info is loaded', async () => {
const writer = jest.mocked(new AppleConfigWriter());
const info = new AppInfo(requestContext, 'stub-id', {} as any);
await new AppInfoTask().downloadAsync({
config: writer,
context: { info, infoLocales: [] } as any,
});
expect(writer.setCategories).toBeCalledWith(info.attributes);
});
it('skips when no locales are loaded', async () => {
const writer = jest.mocked(new AppleConfigWriter());
await new AppInfoTask().downloadAsync({
config: writer,
context: {
info: new AppInfo(requestContext, 'stub-id', {} as any),
infoLocales: [],
} as any,
});
expect(writer.setInfoLocale).not.toBeCalled();
});
it('sets locales when loaded', async () => {
const writer = jest.mocked(new AppleConfigWriter());
const infoLocales = [
new AppInfoLocalization(requestContext, 'stub-id-1', {} as any),
new AppInfoLocalization(requestContext, 'stub-id-2', {} as any),
];
await new AppInfoTask().downloadAsync({
config: writer,
context: {
info: new AppInfo(requestContext, 'stub-id', {} as any),
infoLocales,
} as any,
});
expect(writer.setInfoLocale).toBeCalledWith(infoLocales[0].attributes);
expect(writer.setInfoLocale).toBeCalledWith(infoLocales[1].attributes);
});
});
describe('uploadAsync', () => {
it('aborts when app info is not loaded', async () => {
const promise = new AppInfoTask().uploadAsync({
config: new AppleConfigReader({}),
context: { info: undefined } as any,
});
await expect(promise).rejects.toThrow('info not initialized');
});
it('skips updating categories when not configured', async () => {
const scope = nock('https://api.appstoreconnect.apple.com')
.patch(`/v1/${AppInfo.type}/stub-id`)
.reply(200, require('./fixtures/appInfos/patch-200.json'));
await new AppInfoTask().uploadAsync({
config: new AppleConfigReader({ categories: undefined }),
context: {
info: new AppInfo(requestContext, 'stub-id', {} as any),
} as AppleData,
});
expect(scope.isDone()).toBeFalsy();
nock.cleanAll();
});
it('skips updating localized info when not configured', async () => {
const scope = nock('https://api.appstoreconnect.apple.com')
.patch(`/v1/${AppInfoLocalization.type}/stub-id`)
.reply(200, require('./fixtures/appInfos/patch-200.json'));
await new AppInfoTask().uploadAsync({
config: new AppleConfigReader({ info: undefined }),
context: {
info: new AppInfo(requestContext, 'stub-id', {} as any),
} as AppleData,
});
expect(scope.isDone()).toBeFalsy();
nock.cleanAll();
});
it('updates categories and localized info', async () => {
const updateCategoryScope = nock('https://api.appstoreconnect.apple.com')
.patch(`/v1/${AppInfo.type}/stub-id`)
.reply(200, require('./fixtures/appInfos/patch-200.json'));
const updateENInfoScope = nock('https://api.appstoreconnect.apple.com')
.patch(`/v1/${AppInfoLocalization.type}/APP_INFO_LOCALE_1`) // see fixture ID
.reply(200, require('./fixtures/appInfoLocalizations/patch-200.json'));
const updateNLInfoScope = nock('https://api.appstoreconnect.apple.com')
.patch(`/v1/${AppInfoLocalization.type}/APP_INFO_LOCALE_2`) // see fixture ID
.reply(200, require('./fixtures/appInfoLocalizations/patch-200.json'));
const fetchLocalizedInfoScope = nock('https://api.appstoreconnect.apple.com')
.get(`/v1/${AppInfo.type}/APP_INFO_1/${AppInfoLocalization.type}`) // see fixture ID
.twice()
.reply(200, require('./fixtures/appInfos/get-appInfoLocalizations-200.json'));
await new AppInfoTask().uploadAsync({
config: new AppleConfigReader({
categories: [AppCategoryId.ENTERTAINMENT],
info: {
'en-US': { title: 'Hello' },
'nl-NL': { title: 'Hallo' },
},
}),
context: {
info: new AppInfo(requestContext, 'stub-id', {} as any),
} as AppleData,
});
expect(updateCategoryScope.isDone()).toBeTruthy();
expect(fetchLocalizedInfoScope.isDone()).toBeTruthy();
expect(updateENInfoScope.isDone()).toBeTruthy();
expect(updateNLInfoScope.isDone()).toBeTruthy();
});
});
});
@@ -1,151 +0,0 @@
import { AppStoreReviewAttachment, AppStoreReviewDetail, AppStoreVersion } from '@expo/apple-utils';
import nock from 'nock';
import { AppleConfigReader } from '../../config/reader';
import { AppleConfigWriter } from '../../config/writer';
import { AppReviewDetailTask } from '../app-review-detail';
import { requestContext } from './fixtures/requestContext';
jest.mock('../../../../ora');
jest.mock('../../config/writer');
describe(AppReviewDetailTask, () => {
describe('prepareAsync', () => {
it('aborts when version is not loaded', async () => {
const promise = new AppReviewDetailTask().prepareAsync({ context: {} as any });
await expect(promise).rejects.toThrow('version not init');
});
it('loads review details from app version', async () => {
const scope = nock('https://api.appstoreconnect.apple.com')
.get(`/v1/${AppStoreVersion.type}/stub-id/appStoreReviewDetail`)
.query((params: any) => params['include'] === AppStoreReviewAttachment.type)
.reply(200, require('./fixtures/appStoreVersions/get-appStoreReviewDetail-200.json'));
const context: any = {
version: new AppStoreVersion(requestContext, 'stub-id', {} as any),
};
await new AppReviewDetailTask().prepareAsync({ context });
expect(context.reviewDetail).toBeInstanceOf(AppStoreReviewDetail);
expect(scope.isDone()).toBeTruthy();
});
});
describe('downloadAsync', () => {
it('sets review details when loaded', async () => {
const writer = jest.mocked(new AppleConfigWriter());
const reviewDetail = new AppStoreReviewDetail(requestContext, 'stub-id', {} as any);
await new AppReviewDetailTask().downloadAsync({
config: writer,
context: { reviewDetail } as any,
});
expect(writer.setReviewDetails).toBeCalledWith(reviewDetail.attributes);
});
it('skips when no review details are loaded', async () => {
const writer = jest.mocked(new AppleConfigWriter());
await new AppReviewDetailTask().downloadAsync({
config: writer,
context: {} as any,
});
expect(writer.setReviewDetails).not.toBeCalled();
});
});
describe('uploadAsync', () => {
it('skips when review details are not configured', async () => {
const scope = nock('https://api.appstoreconnect.apple.com')
.post(`/v1/${AppStoreReviewDetail.type}`)
.reply(201, require('./fixtures/appStoreReviewDetails/post-201.json'));
await new AppReviewDetailTask().uploadAsync({
config: new AppleConfigReader({
review: undefined,
}),
context: {
version: new AppStoreVersion(requestContext, 'stub-id', {} as any),
} as any,
});
expect(scope.isDone()).toBeFalsy();
nock.cleanAll();
});
it('aborts when version is not loaded', async () => {
const promise = new AppReviewDetailTask().uploadAsync({
config: new AppleConfigReader({
review: {
firstName: 'Evan',
lastName: 'Bacon',
email: '[email protected]',
phone: '+1 555 555 5555',
},
}),
context: {} as any,
});
await expect(promise).rejects.toThrow('version not init');
});
it('updates review details when loaded', async () => {
const scope = nock('https://api.appstoreconnect.apple.com')
.patch(`/v1/${AppStoreReviewDetail.type}/APP_STORE_REVIEW_DETAILS_1`)
.reply(200, require('./fixtures/appStoreReviewDetails/patch-200.json'));
await new AppReviewDetailTask().uploadAsync({
config: new AppleConfigReader({
review: {
firstName: 'Evan',
lastName: 'Bacon',
email: '[email protected]',
phone: '+1 555 555 5555',
},
}),
context: {
version: new AppStoreVersion(requestContext, 'stub-id', {} as any),
reviewDetail: new AppStoreReviewDetail(
requestContext,
'APP_STORE_REVIEW_DETAILS_1',
{} as any
),
} as any,
});
expect(scope.isDone()).toBeTruthy();
});
it('creates and updates review details when not loaded', async () => {
const createReviewDetailScope = nock('https://api.appstoreconnect.apple.com')
.post(`/v1/${AppStoreReviewDetail.type}`)
.reply(201, require('./fixtures/appStoreReviewDetails/post-201.json'));
const updateReviewDetailScope = nock('https://api.appstoreconnect.apple.com')
.patch(`/v1/${AppStoreReviewDetail.type}/APP_STORE_REVIEW_DETAILS_1`)
.reply(200, require('./fixtures/appStoreReviewDetails/patch-200.json'));
await new AppReviewDetailTask().uploadAsync({
config: new AppleConfigReader({
review: {
firstName: 'Evan',
lastName: 'Bacon',
email: '[email protected]',
phone: '+1 555 555 5555',
},
}),
context: {
version: new AppStoreVersion(requestContext, 'stub-id', {} as any),
} as any,
});
expect(createReviewDetailScope.isDone()).toBeTruthy();
expect(updateReviewDetailScope.isDone()).toBeTruthy();
});
});
});
@@ -1,246 +0,0 @@
import {
App,
AppStoreState,
AppStoreVersion,
AppStoreVersionLocalization,
} from '@expo/apple-utils';
import nock from 'nock';
import { AppleConfigReader } from '../../config/reader';
import { AppleConfigWriter } from '../../config/writer';
import { AppleData, PartialAppleData } from '../../data';
import { AppVersionTask } from '../app-version';
import { requestContext } from './fixtures/requestContext';
jest.mock('../../../../ora');
jest.mock('../../config/writer');
describe(AppVersionTask, () => {
describe('prepareAsync', () => {
it('loads live version', async () => {
const scope = nock('https://api.appstoreconnect.apple.com')
// Respond to app.getLiveAppStoreVersionAsync
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
.query((params: any) => params['filter[appStoreState]'] === AppStoreState.READY_FOR_SALE)
.reply(200, require('./fixtures/apps/get-appStoreVersions-200.json'))
// Respond to app.getAppStoreVersionsAsync
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
.query((params: any) => params['filter[appStoreState]'] !== AppStoreState.READY_FOR_SALE)
.reply(200, require('./fixtures/apps/get-appStoreVersions-count-200.json'))
// Respond to version.getLocalizationsAsync
.get(`/v1/${AppStoreVersion.type}/APP_STORE_VERSION_1/${AppStoreVersionLocalization.type}`)
.reply(
200,
require('./fixtures/appStoreVersions/get-appStoreVersionLocalizations-200.json')
);
const context: PartialAppleData = {
app: new App(requestContext, 'stub-id', {} as any),
};
await new AppVersionTask({ editLive: true }).prepareAsync({ context });
expect(context.version).toBeInstanceOf(AppStoreVersion);
expect(context.versionIsLive).toBeTruthy();
expect(context.versionIsFirst).toBeTruthy();
expect(scope.isDone()).toBeTruthy();
});
it('falls back to editable version if live version is not found', async () => {
const scope = nock('https://api.appstoreconnect.apple.com')
// Respond to app.getLiveAppStoreVersionAsync, failing with 404
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
.query((params: any) => params['filter[appStoreState]'] === AppStoreState.READY_FOR_SALE)
.reply(404, {})
// Respond to app.getEditAppStoreVersionAsync
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
.query((params: any) => params['filter[appStoreState]'] !== AppStoreState.READY_FOR_SALE)
.reply(200, require('./fixtures/apps/get-appStoreVersions-count-200.json'))
// Respond to app.getAppStoreVersionsAsync
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
.query(true)
.reply(200, require('./fixtures/apps/get-appStoreVersions-count-200.json'))
// Respond to version.getLocalizationsAsync
.get(`/v1/${AppStoreVersion.type}/APP_STORE_VERSION_1/${AppStoreVersionLocalization.type}`)
.reply(
200,
require('./fixtures/appStoreVersions/get-appStoreVersionLocalizations-200.json')
);
const context: PartialAppleData = {
app: new App(requestContext, 'stub-id', {} as any),
};
await new AppVersionTask({ editLive: true }).prepareAsync({ context });
expect(context.version).toBeInstanceOf(AppStoreVersion);
expect(context.versionIsLive).toBeFalsy();
expect(context.versionIsFirst).toBeTruthy();
expect(scope.isDone()).toBeTruthy();
});
it('loads last editable version', async () => {
const scope = nock('https://api.appstoreconnect.apple.com')
// Respond to app.getEditAppStoreVersionAsync
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
.query((params: any) => params['filter[appStoreState]'] !== AppStoreState.READY_FOR_SALE)
.reply(200, require('./fixtures/apps/get-appStoreVersions-200.json'))
// Respond to app.getAppStoreVersionsAsync
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
.query((params: any) => params['filter[appStoreState]'] !== AppStoreState.READY_FOR_SALE)
.reply(200, require('./fixtures/apps/get-appStoreVersions-count-200.json'))
// Respond to version.getLocalizationsAsync
.get(`/v1/${AppStoreVersion.type}/APP_STORE_VERSION_1/${AppStoreVersionLocalization.type}`)
.reply(
200,
require('./fixtures/appStoreVersions/get-appStoreVersionLocalizations-200.json')
);
const context: PartialAppleData = {
app: new App(requestContext, 'stub-id', {} as any),
};
await new AppVersionTask().prepareAsync({ context });
expect(context.version).toBeInstanceOf(AppStoreVersion);
expect(context.versionIsLive).toBeFalsy();
expect(context.versionIsFirst).toBeTruthy();
expect(scope.isDone()).toBeTruthy();
});
it('sets first version property for multiple versions', async () => {
const scope = nock('https://api.appstoreconnect.apple.com')
// Respond to app.getEditAppStoreVersionAsync
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
.query((params: any) => params['filter[appStoreState]'] !== AppStoreState.READY_FOR_SALE)
.reply(200, require('./fixtures/apps/get-appStoreVersions-200.json'))
// Respond to app.getAppStoreVersionsAsync
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
.query((params: any) => params['filter[appStoreState]'] !== AppStoreState.READY_FOR_SALE)
.reply(200, require('./fixtures/apps/get-appStoreVersions-count-multiple-200.json'))
// Respond to version.getLocalizationsAsync
.get(`/v1/${AppStoreVersion.type}/APP_STORE_VERSION_1/${AppStoreVersionLocalization.type}`)
.reply(
200,
require('./fixtures/appStoreVersions/get-appStoreVersionLocalizations-200.json')
);
const context: PartialAppleData = {
app: new App(requestContext, 'stub-id', {} as any),
};
await new AppVersionTask().prepareAsync({ context });
expect(context.versionIsFirst).toBeFalsy();
expect(scope.isDone()).toBeTruthy();
});
});
describe('downloadAsync', () => {
it('aborts when version is not loaded', async () => {
const promise = new AppVersionTask().downloadAsync({
config: new AppleConfigWriter(),
context: { version: undefined } as any,
});
await expect(promise).rejects.toThrow('not initialized');
});
it('sets version when loaded', async () => {
const writer = new AppleConfigWriter();
const version = new AppStoreVersion(requestContext, 'stub-id', {} as any);
await new AppVersionTask().downloadAsync({
config: writer,
context: { version, versionLocales: [] } as any,
});
expect(writer.setVersion).toBeCalledWith(version.attributes);
});
it('sets version release when loaded', async () => {
const writer = new AppleConfigWriter();
const version = new AppStoreVersion(requestContext, 'stub-id', {} as any);
await new AppVersionTask().downloadAsync({
config: writer,
context: { version, versionLocales: [] } as any,
});
expect(writer.setVersionRelease).toBeCalledWith(version.attributes);
});
it('skips when no locales are loaded', async () => {
const writer = jest.mocked(new AppleConfigWriter());
await new AppVersionTask().downloadAsync({
config: writer,
context: {
version: new AppStoreVersion(requestContext, 'stub-id', {} as any),
versionLocales: [],
} as any,
});
expect(writer.setInfoLocale).not.toBeCalled();
});
it('sets locales when loaded', async () => {
const writer = jest.mocked(new AppleConfigWriter());
const versionLocales = [
new AppStoreVersionLocalization(requestContext, 'stub-id-1', {} as any),
new AppStoreVersionLocalization(requestContext, 'stub-id-2', {} as any),
];
await new AppVersionTask().downloadAsync({
config: writer,
context: {
version: new AppStoreVersion(requestContext, 'stub-id', {} as any),
versionLocales,
} as any,
});
expect(writer.setVersionLocale).toBeCalledWith(versionLocales[0].attributes);
expect(writer.setVersionLocale).toBeCalledWith(versionLocales[1].attributes);
});
});
describe('uploadAsync', () => {
it('aborts when version is not loaded', async () => {
const promise = new AppVersionTask().uploadAsync({
context: {} as any,
config: new AppleConfigReader({}),
});
await expect(promise).rejects.toThrow('not initialized');
});
it('updates version and release from config', async () => {
const config = new AppleConfigReader({
copyright: '2022 ACME',
release: { automaticRelease: true },
});
const attributes = { ...config.getVersion(), ...config.getVersionRelease() };
const scope = nock('https://api.appstoreconnect.apple.com')
// Respond to version.updateAsync
.patch(`/v1/${AppStoreVersion.type}/APP_STORE_VERSION_1`)
.reply(200, {
data: {
id: 'APP_STORE_VERSION_1',
type: AppStoreVersion.type,
attributes,
},
});
const context: PartialAppleData = {
app: new App(requestContext, 'stub-id', {} as any),
version: new AppStoreVersion(requestContext, 'APP_STORE_VERSION_1', {} as any),
};
await new AppVersionTask().uploadAsync({ context: context as AppleData, config });
expect(context.version?.attributes).toMatchObject(attributes);
expect(scope.isDone()).toBeTruthy();
});
});
});
@@ -1,31 +0,0 @@
{
"data": {
"type": "ageRatingDeclarations",
"id": "AGE_RATING_1",
"attributes": {
"alcoholTobaccoOrDrugUseOrReferences": "NONE",
"contests": "NONE",
"gamblingAndContests": false,
"gambling": false,
"gamblingSimulated": "NONE",
"kidsAgeBand": null,
"medicalOrTreatmentInformation": "NONE",
"profanityOrCrudeHumor": "NONE",
"sexualContentGraphicAndNudity": "NONE",
"sexualContentOrNudity": "NONE",
"seventeenPlus": false,
"horrorOrFearThemes": "INFREQUENT_OR_MILD",
"matureOrSuggestiveThemes": "NONE",
"unrestrictedWebAccess": false,
"violenceCartoonOrFantasy": "NONE",
"violenceRealisticProlongedGraphicOrSadistic": "NONE",
"violenceRealistic": "NONE"
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/ageRatingDeclarations/AGE_RATING_1"
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/ageRatingDeclarations/AGE_RATING_1"
}
}
@@ -1,20 +0,0 @@
{
"data": {
"type": "appInfoLocalizations",
"id": "APP_INFO_LOCALE_1",
"attributes": {
"locale": "en-US",
"name": "Rexpo Square App",
"subtitle": "144 fps red squares",
"privacyPolicyUrl": "https://github.com/bycedric/red-square-app",
"privacyChoicesUrl": null,
"privacyPolicyText": null
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfoLocalizations/APP_INFO_LOCALE_1"
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfoLocalizations/APP_INFO_LOCALE_1"
}
}
@@ -1,43 +0,0 @@
{
"data": [
{
"type": "appInfoLocalizations",
"id": "APP_INFO_LOCALE_1",
"attributes": {
"locale": "en-US",
"name": "Rexpo Square App",
"subtitle": "144 fps red squares",
"privacyPolicyUrl": "https://github.com/bycedric/red-square-app",
"privacyChoicesUrl": null,
"privacyPolicyText": null
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfoLocalizations/APP_INFO_LOCALE_1"
}
},
{
"type": "appInfoLocalizations",
"id": "APP_INFO_LOCALE_2",
"attributes": {
"locale": "nl-NL",
"name": "Rexpo Vierkant App",
"subtitle": "144 fps red vierkant",
"privacyPolicyUrl": "https://github.com/bycedric/red-square-app",
"privacyChoicesUrl": null,
"privacyPolicyText": null
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfoLocalizations/APP_INFO_LOCALE_2"
}
}
],
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/a9c2ad6a-182f-4021-9456-6b609ebc5eab/appInfoLocalizations"
},
"meta": {
"paging": {
"total": 2,
"limit": 50
}
}
}
@@ -1,68 +0,0 @@
{
"data": {
"type": "appInfos",
"id": "APP_INFO_1",
"attributes": {
"appStoreState": "PREPARE_FOR_SUBMISSION",
"appStoreAgeRating": "NINE_PLUS",
"brazilAgeRating": "TEN",
"kidsAgeBand": null
},
"relationships": {
"ageRatingDeclaration": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/ageRatingDeclaration",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/ageRatingDeclaration"
}
},
"appInfoLocalizations": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/appInfoLocalizations",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/appInfoLocalizations"
}
},
"primaryCategory": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/primaryCategory",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/primaryCategory"
}
},
"primarySubcategoryOne": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/primarySubcategoryOne",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/primarySubcategoryOne"
}
},
"primarySubcategoryTwo": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/primarySubcategoryTwo",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/primarySubcategoryTwo"
}
},
"secondaryCategory": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/secondaryCategory",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/secondaryCategory"
}
},
"secondarySubcategoryOne": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/secondarySubcategoryOne",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/secondarySubcategoryOne"
}
},
"secondarySubcategoryTwo": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/secondarySubcategoryTwo",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/secondarySubcategoryTwo"
}
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1"
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1"
}
}
@@ -1,36 +0,0 @@
{
"data": {
"type": "appStoreReviewDetails",
"id": "APP_STORE_REVIEW_DETAILS_1",
"attributes": {
"contactFirstName": "Evan",
"contactLastName": "Bacon",
"contactPhone": "+1-555-555-5555",
"contactEmail": "[email protected]",
"demoAccountName": null,
"demoAccountPassword": null,
"demoAccountRequired": false,
"notes": ""
},
"relationships": {
"appStoreReviewAttachments": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1/relationships/appStoreReviewAttachments",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1/appStoreReviewAttachments"
}
},
"appSandboxEntitlementUsages": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1/relationships/appSandboxEntitlementUsages",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1/appSandboxEntitlementUsages"
}
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1"
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1"
}
}
@@ -1,36 +0,0 @@
{
"data": {
"type": "appStoreReviewDetails",
"id": "APP_STORE_REVIEW_DETAILS_1",
"attributes": {
"contactFirstName": "Evan",
"contactLastName": "Bacon",
"contactPhone": "+1-555-555-5555",
"contactEmail": "[email protected]",
"demoAccountName": null,
"demoAccountPassword": null,
"demoAccountRequired": false,
"notes": ""
},
"relationships": {
"appStoreReviewAttachments": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1/relationships/appStoreReviewAttachments",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1/appStoreReviewAttachments"
}
},
"appSandboxEntitlementUsages": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1/relationships/appSandboxEntitlementUsages",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1/appSandboxEntitlementUsages"
}
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1"
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails"
}
}
@@ -1,31 +0,0 @@
{
"data": {
"type": "ageRatingDeclarations",
"id": "a9c2ad6a-182f-4021-9456-6b609ebc5eab",
"attributes": {
"alcoholTobaccoOrDrugUseOrReferences": "NONE",
"contests": "NONE",
"gamblingAndContests": false,
"gambling": false,
"gamblingSimulated": "NONE",
"kidsAgeBand": null,
"medicalOrTreatmentInformation": "NONE",
"profanityOrCrudeHumor": "NONE",
"sexualContentGraphicAndNudity": "NONE",
"sexualContentOrNudity": "NONE",
"seventeenPlus": false,
"horrorOrFearThemes": "NONE",
"matureOrSuggestiveThemes": "NONE",
"unrestrictedWebAccess": false,
"violenceCartoonOrFantasy": "NONE",
"violenceRealisticProlongedGraphicOrSadistic": "NONE",
"violenceRealistic": "NONE"
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/ageRatingDeclarations/a9c2ad6a-182f-4021-9456-6b609ebc5eab"
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/949768f6-58e5-46b4-b046-23861f61831a/ageRatingDeclaration"
}
}
@@ -1,11 +0,0 @@
{
"errors": [
{
"id": "9901eeb2-81e7-4562-9beb-c5f72010ab71",
"status": "404",
"code": "NOT_FOUND",
"title": "The specified resource does not exist",
"detail": "There is no resource of type 'appStoreVersions' with id 'non-existing'"
}
]
}
@@ -1,6 +0,0 @@
{
"data": null,
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreReviewDetail?include=appStoreReviewAttachments"
}
}
@@ -1,36 +0,0 @@
{
"data": {
"type": "appStoreReviewDetails",
"id": "APP_STORE_REVIEW_DETAILS_1",
"attributes": {
"contactFirstName": "Evan",
"contactLastName": "Bacon",
"contactPhone": "+1-555-555-5555",
"contactEmail": "[email protected]",
"demoAccountName": null,
"demoAccountPassword": null,
"demoAccountRequired": false,
"notes": ""
},
"relationships": {
"appStoreReviewAttachments": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1/relationships/appStoreReviewAttachments",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1/appStoreReviewAttachments"
}
},
"appSandboxEntitlementUsages": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1/relationships/appSandboxEntitlementUsages",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1/appSandboxEntitlementUsages"
}
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1"
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreReviewDetails/APP_STORE_REVIEW_DETAILS_1"
}
}
@@ -1,70 +0,0 @@
{
"data": [{
"type": "appStoreVersionLocalizations",
"id": "4bad54bc-3810-401a-a7f4-b02ef8206c41",
"attributes": {
"description": "Some of the best apps",
"locale": "nl-NL",
"keywords": "meme, red, square, x2",
"marketingUrl": "https://github.com/bycedric/red-square-app",
"promotionalText": null,
"supportUrl": "https://github.com/bycedric/red-square-app/issues",
"whatsNew": null
},
"relationships": {
"appScreenshotSets": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/4bad54bc-3810-401a-a7f4-b02ef8206c41/relationships/appScreenshotSets",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/4bad54bc-3810-401a-a7f4-b02ef8206c41/appScreenshotSets"
}
},
"appPreviewSets": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/4bad54bc-3810-401a-a7f4-b02ef8206c41/relationships/appPreviewSets",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/4bad54bc-3810-401a-a7f4-b02ef8206c41/appPreviewSets"
}
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/4bad54bc-3810-401a-a7f4-b02ef8206c41"
}
}, {
"type": "appStoreVersionLocalizations",
"id": "61153520-c5df-40a1-bb2b-e09cbfc33fe9",
"attributes": {
"description": "Some of the best apps",
"locale": "en-US",
"keywords": "meme, red, square, x2",
"marketingUrl": "https://github.com/bycedric/red-square-app",
"promotionalText": null,
"supportUrl": "https://github.com/bycedric/red-square-app/issues",
"whatsNew": null
},
"relationships": {
"appScreenshotSets": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/61153520-c5df-40a1-bb2b-e09cbfc33fe9/relationships/appScreenshotSets",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/61153520-c5df-40a1-bb2b-e09cbfc33fe9/appScreenshotSets"
}
},
"appPreviewSets": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/61153520-c5df-40a1-bb2b-e09cbfc33fe9/relationships/appPreviewSets",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/61153520-c5df-40a1-bb2b-e09cbfc33fe9/appPreviewSets"
}
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/61153520-c5df-40a1-bb2b-e09cbfc33fe9"
}
}],
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/949768f6-58e5-46b4-b046-23861f61831a/appStoreVersionLocalizations"
},
"meta": {
"paging": {
"total": 2,
"limit": 50
}
}
}
@@ -1,115 +0,0 @@
{
"data": [
{
"type": "appInfos",
"id": "APP_INFO_1",
"attributes": {
"appStoreState": "PREPARE_FOR_SUBMISSION",
"appStoreAgeRating": "NINE_PLUS",
"brazilAgeRating": "TEN",
"kidsAgeBand": null
},
"relationships": {
"ageRatingDeclaration": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/ageRatingDeclaration",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/ageRatingDeclaration"
}
},
"appInfoLocalizations": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/appInfoLocalizations",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/appInfoLocalizations"
}
},
"primaryCategory": {
"data": {
"type": "appCategories",
"id": "ENTERTAINMENT"
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/primaryCategory",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/primaryCategory"
}
},
"primarySubcategoryOne": {
"data": null,
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/primarySubcategoryOne",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/primarySubcategoryOne"
}
},
"primarySubcategoryTwo": {
"data": null,
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/primarySubcategoryTwo",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/primarySubcategoryTwo"
}
},
"secondaryCategory": {
"data": null,
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/secondaryCategory",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/secondaryCategory"
}
},
"secondarySubcategoryOne": {
"data": null,
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/secondarySubcategoryOne",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/secondarySubcategoryOne"
}
},
"secondarySubcategoryTwo": {
"data": null,
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/secondarySubcategoryTwo",
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/secondarySubcategoryTwo"
}
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1"
}
}
],
"included": [
{
"type": "appCategories",
"id": "ENTERTAINMENT",
"attributes": {
"platforms": [
"IOS",
"MAC_OS",
"TV_OS"
]
},
"relationships": {
"subcategories": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appCategories/ENTERTAINMENT/relationships/subcategories",
"related": "https://appstoreconnect.apple.com/iris/v1/appCategories/ENTERTAINMENT/subcategories"
}
},
"parent": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appCategories/ENTERTAINMENT/relationships/parent",
"related": "https://appstoreconnect.apple.com/iris/v1/appCategories/ENTERTAINMENT/parent"
}
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appCategories/ENTERTAINMENT"
}
}
],
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/apps/1611050397/appInfos?include=secondaryCategory%2CprimaryCategory%2CsecondarySubcategoryOne%2CprimarySubcategoryTwo%2CsecondarySubcategoryTwo%2CprimarySubcategoryOne"
},
"meta": {
"paging": {
"total": 1,
"limit": 50
}
}
}
@@ -1,11 +0,0 @@
{
"errors": [
{
"id": "APP_INFO_0",
"status": "404",
"code": "NOT_FOUND",
"title": "The specified resource does not exist",
"detail": "There is no resource of type 'appInfo' with id 'non-existing'"
}
]
}
@@ -1,116 +0,0 @@
{
"data": [
{
"type": "appStoreVersions",
"id": "APP_STORE_VERSION_1",
"attributes": {
"platform": "IOS",
"versionString": "1.0",
"appStoreState": "PREPARE_FOR_SUBMISSION",
"storeIcon": null,
"watchStoreIcon": null,
"copyright": "2022 name",
"releaseType": "SCHEDULED",
"earliestReleaseDate": "2022-03-14T17:00:00-07:00",
"usesIdfa": null,
"isWatchOnly": false,
"downloadable": true,
"createdDate": "2022-02-21T09:26:24-08:00",
"promotedDate": null
},
"relationships": {
"ageRatingDeclaration": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/ageRatingDeclaration",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/ageRatingDeclaration"
}
},
"appStoreVersionLocalizations": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionLocalizations",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionLocalizations"
}
},
"build": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/build",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/build"
}
},
"appStoreVersionPhasedRelease": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionPhasedRelease",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionPhasedRelease"
}
},
"gameCenterConfiguration": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/gameCenterConfiguration",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/gameCenterConfiguration"
}
},
"routingAppCoverage": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/routingAppCoverage",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/routingAppCoverage"
}
},
"appStoreReviewDetail": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreReviewDetail",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreReviewDetail"
}
},
"appStoreVersionSubmission": {
"data": null,
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionSubmission",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionSubmission"
}
},
"resetRatingsRequest": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/resetRatingsRequest",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/resetRatingsRequest"
}
},
"idfaDeclaration": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/idfaDeclaration",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/idfaDeclaration"
}
},
"appClipDefaultExperience": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appClipDefaultExperience",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appClipDefaultExperience"
}
},
"appStoreVersionStateChanges": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionStateChanges",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionStateChanges"
}
},
"appStoreVersionExperiments": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionExperiments",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionExperiments"
}
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1"
}
}
],
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/apps/1611050397/appStoreVersions?include=appStoreVersionSubmission&filter%5Bplatform%5D=IOS&filter%5BappStoreState%5D=DEVELOPER_REJECTED%2CINVALID_BINARY%2CMETADATA_REJECTED%2CPREPARE_FOR_SUBMISSION%2CREJECTED%2CWAITING_FOR_REVIEW"
},
"meta": {
"paging": {
"total": 1,
"limit": 50
}
}
}
@@ -1,116 +0,0 @@
{
"data": [
{
"type": "appStoreVersions",
"id": "APP_STORE_VERSION_1",
"attributes": {
"platform": "IOS",
"versionString": "1.0",
"appStoreState": "PREPARE_FOR_SUBMISSION",
"storeIcon": null,
"watchStoreIcon": null,
"copyright": "2022 name",
"releaseType": "SCHEDULED",
"earliestReleaseDate": "2022-03-14T17:00:00-07:00",
"usesIdfa": null,
"isWatchOnly": false,
"downloadable": true,
"createdDate": "2022-02-21T09:26:24-08:00",
"promotedDate": null
},
"relationships": {
"ageRatingDeclaration": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/ageRatingDeclaration",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/ageRatingDeclaration"
}
},
"appStoreVersionLocalizations": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionLocalizations",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionLocalizations"
}
},
"build": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/build",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/build"
}
},
"appStoreVersionPhasedRelease": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionPhasedRelease",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionPhasedRelease"
}
},
"gameCenterConfiguration": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/gameCenterConfiguration",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/gameCenterConfiguration"
}
},
"routingAppCoverage": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/routingAppCoverage",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/routingAppCoverage"
}
},
"appStoreReviewDetail": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreReviewDetail",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreReviewDetail"
}
},
"appStoreVersionSubmission": {
"data": null,
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionSubmission",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionSubmission"
}
},
"resetRatingsRequest": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/resetRatingsRequest",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/resetRatingsRequest"
}
},
"idfaDeclaration": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/idfaDeclaration",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/idfaDeclaration"
}
},
"appClipDefaultExperience": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appClipDefaultExperience",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appClipDefaultExperience"
}
},
"appStoreVersionStateChanges": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionStateChanges",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionStateChanges"
}
},
"appStoreVersionExperiments": {
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionExperiments",
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionExperiments"
}
}
},
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1"
}
}
],
"links": {
"self": "https://appstoreconnect.apple.com/iris/v1/apps/1611050397/appStoreVersions?include=appStoreVersionSubmission&filter%5Bplatform%5D=IOS&limit=2"
},
"meta": {
"paging": {
"total": 1,
"limit": 2
}
}
}
Loaded 100 of 141 files, more files were not shown because too many files have changed in this diff. Show more