mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-14 10:08:06 -10:00
Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f33d859d75 | ||
|
|
69515ab47b | ||
|
|
ac5f7ab186 | ||
|
|
265c7eacc5 | ||
|
|
98229581ca | ||
|
|
a718fd10d3 | ||
|
|
78a63b8ca1 | ||
|
|
7487f413de | ||
|
|
da5f4e644a | ||
|
|
be38117012 | ||
|
|
8f3314f188 | ||
|
|
31b6cc9ab6 | ||
|
|
04a8b08f59 | ||
|
|
8bce9b19b2 | ||
|
|
d3d6d41f9f | ||
|
|
0534328dfc | ||
|
|
d0b9af2553 | ||
|
|
80674a3451 | ||
|
|
41b48a80be | ||
|
|
3d5b77a263 | ||
|
|
fa25d259d9 | ||
|
|
26596f32e9 | ||
|
|
031c610d58 | ||
|
|
fcb5beccb4 | ||
|
|
3003a712f6 | ||
|
|
e89f030c29 | ||
|
|
3e224f9583 |
No files matched your search
@@ -0,0 +1,146 @@
|
||||
name: Issue Triage
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
needs-repro:
|
||||
runs-on: ubuntu-20.04
|
||||
if: "${{ contains(github.event.label.name, 'incomplete issue: missing or invalid repro') }}"
|
||||
steps:
|
||||
- uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
|
||||
script: |
|
||||
github.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `Hi there! It looks like your issue requires a minimal reproducible example, but it is invalid or absent. Please prepare such an example and share it in a new issue.
|
||||
|
||||
**The best way to get attention to your issue is to provide a clean and easy way for a developer to reproduce the issue on their own machine.** Please do not provide your entire project, or a project with more code than is necessary to reproduce the issue.
|
||||
|
||||
A side benefit of going through the process of narrowing down the minimal amount of code needed to reproduce the issue is that you may get lucky and discover that the bug is due to a mistake in your application code that you can quickly fix on your own.
|
||||
|
||||
### Resources
|
||||
|
||||
- ["How to create a Minimal, Reproducible Example"](https://stackoverflow.com/help/minimal-reproducible-example)
|
||||
- ["How to narrow down the source of an error"](https://expo.fyi/manual-debugging)
|
||||
|
||||
### Common concerns
|
||||
|
||||
#### "I've only been able to reproduce it in private, proprietary code"
|
||||
|
||||
You may not have spent enough time narrowing down the root cause of the issue. Try out the techniques discussed in this [manual debugging guide](https://expo.fyi/manual-debugging) to learn how to isolate the problem from the rest of your codebase.
|
||||
|
||||
#### "I didn't have time to create one"
|
||||
|
||||
That's understandable, it can take some time to prepare. We ask that you hold off on filing an issue until you are able to fully complete the required fields in the issue template.
|
||||
|
||||
#### "You can reproduce it by yourself by creating a project and following these steps"
|
||||
|
||||
This is useful knowledge, but it's still valuable to have the resulting project that is produced from running the steps, where you have verified you can reproduce the issue.
|
||||
`})
|
||||
github.issues.update({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'closed'
|
||||
})
|
||||
|
||||
needs-info:
|
||||
runs-on: ubuntu-20.04
|
||||
if: "${{ contains(github.event.label.name, 'incomplete issue: missing info') }}"
|
||||
steps:
|
||||
- uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
|
||||
script: |
|
||||
github.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `Hello! There isn't enough information provided in your issue description for us to be able to help you. The best way to get help is to provide information to enable other developers to understand the issue and reproduce it on their own machines.
|
||||
|
||||
Please create a new issue and fill out the entire issue template to the best of your ability. Refer to the following resources for more information on how to create a good issue report.
|
||||
|
||||
### Resources
|
||||
|
||||
- ["How do I ask a good question?"](https://stackoverflow.com/help/how-to-ask)
|
||||
- ["How to create a Minimal, Reproducible Example"](https://stackoverflow.com/help/minimal-reproducible-example)
|
||||
- ["How to narrow down the source of an error"](https://expo.fyi/manual-debugging)
|
||||
`})
|
||||
github.issues.update({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'closed'
|
||||
})
|
||||
|
||||
issue-accepted:
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.event.label.name == 'issue accepted'
|
||||
steps:
|
||||
- uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
|
||||
script: |
|
||||
github.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `Thank you for filing this issue!
|
||||
This comment acknowledges we believe this may be a bug and there’s enough information to investigate it.
|
||||
However, we can’t promise any sort of timeline for resolution. We prioritize issues based on severity, breadth of impact, and alignment with our roadmap. If you’d like to help move it more quickly, you can continue to investigate it more deeply and/or you can open a pull request that fixes the cause.
|
||||
`})
|
||||
|
||||
question:
|
||||
runs-on: ubuntu-20.04
|
||||
if: "${{ contains(github.event.label.name, 'invalid issue: question') }}"
|
||||
steps:
|
||||
- uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
|
||||
script: |
|
||||
github.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `Hello! Our GitHub issues are reserved for bug reports.
|
||||
|
||||
If you have a question about Expo or related tools, please post on our forums at https://forums.expo.dev/ or join our Discord at https://chat.expo.dev/.
|
||||
|
||||
### Resources
|
||||
|
||||
- ["How do I ask a good question?"](https://stackoverflow.com/help/how-to-ask)
|
||||
- ["Join the community"](https://docs.expo.dev/next-steps/community/)
|
||||
`})
|
||||
github.issues.update({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'closed'
|
||||
})
|
||||
|
||||
feature-request:
|
||||
runs-on: ubuntu-20.04
|
||||
if: "${{ contains(github.event.label.name, 'invalid issue: feature request') }}"
|
||||
steps:
|
||||
- uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
|
||||
script: |
|
||||
github.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `Hello! Our GitHub issues are reserved for bug reports.
|
||||
|
||||
If you would like to request a feature, please post to https://expo.canny.io/feature-requests.
|
||||
`})
|
||||
github.issues.update({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'closed'
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Test scripts
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: scripts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'scripts/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'scripts/**'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: yarn typecheck
|
||||
@@ -27,6 +27,10 @@ jobs:
|
||||
if: ${{ !matrix.coverage }}
|
||||
- run: yarn test --coverage
|
||||
if: ${{ matrix.coverage }}
|
||||
env:
|
||||
# Oclif seems to cause OOM errors when testing with coverage enabled.
|
||||
# Remove this whenever we solved those OOM errors.
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
- run: yarn lint --max-warnings=0
|
||||
- run: shellcheck bin/*.sh
|
||||
- uses: codecov/codecov-action@v1
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
packages/*/build
|
||||
packages/eas-cli/dist
|
||||
packages/eas-cli/tmp
|
||||
scripts/build
|
||||
|
||||
# Code editors
|
||||
.idea
|
||||
|
||||
+42
-3
@@ -8,13 +8,53 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Show spinner instead of silently timing out during asset upload. ([#1206](https://github.com/expo/eas-cli/pull/1206) by [@wschurman](https://github.com/wschurman))
|
||||
- Fix build archive S3 URLs. ([#1207](https://github.com/expo/eas-cli/pull/1207) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Increase max number of chained "extends" in build profiles. ([#1208](https://github.com/expo/eas-cli/pull/1208) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
## [0.55.1](https://github.com/expo/eas-cli/releases/tag/v0.55.1) - 2022-07-12
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix asset upload crash. ([#1205](https://github.com/expo/eas-cli/pull/1205) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
## [0.55.0](https://github.com/expo/eas-cli/releases/tag/v0.55.0) - 2022-07-11
|
||||
|
||||
### 🎉 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))
|
||||
- Override applicationId via env. ([#1203](https://github.com/expo/eas-cli/pull/1203) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 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))
|
||||
- Improve store config categories configuration. ([#1187](https://github.com/expo/eas-cli/pull/1187) by [@byCedric](https://github.com/byCedric))
|
||||
- Remove deprecated idfa and price tier from store config. ([#1193](https://github.com/expo/eas-cli/pull/1193) 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))
|
||||
|
||||
### 🧹 Chores
|
||||
- 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
|
||||
|
||||
@@ -31,7 +71,6 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
- 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/))
|
||||
- Add metadata schema to bundled eas-cli package. ([#1166](https://github.com/expo/eas-cli/pull/1166) by [@byCedric](https://github.com/byCedric))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ 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
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.54.1"
|
||||
"version": "0.55.1"
|
||||
}
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
"oclif": "3.0.1",
|
||||
"prettier": "2.6.2",
|
||||
"ts-jest": "28.0.2",
|
||||
"typescript": "4.6.4"
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.2.0",
|
||||
|
||||
+100
-98
@@ -22,68 +22,69 @@ yarn global add eas-cli
|
||||
# Usage
|
||||
|
||||
```sh
|
||||
eas COMMAND
|
||||
# runs the command
|
||||
eas (-v|--version|version)
|
||||
eas COMMAND
|
||||
# prints the version
|
||||
eas --help COMMAND
|
||||
eas (-v|--version|version)
|
||||
# outputs help for specific command
|
||||
eas --help COMMAND
|
||||
```
|
||||
|
||||
# Commands
|
||||
|
||||
<!-- commands -->
|
||||
* [`eas account:login`](#eas-accountlogin)
|
||||
* [`eas account:logout`](#eas-accountlogout)
|
||||
* [`eas account:view`](#eas-accountview)
|
||||
* [`eas analytics [STATUS]`](#eas-analytics-status)
|
||||
* [`eas autocomplete [SHELL]`](#eas-autocomplete-shell)
|
||||
* [`eas branch:create [NAME]`](#eas-branchcreate-name)
|
||||
* [`eas branch:delete [NAME]`](#eas-branchdelete-name)
|
||||
* [`eas branch:list`](#eas-branchlist)
|
||||
* [`eas branch:rename`](#eas-branchrename)
|
||||
* [`eas branch:view [NAME]`](#eas-branchview-name)
|
||||
* [`eas build`](#eas-build)
|
||||
* [`eas build:cancel [BUILD_ID]`](#eas-buildcancel-build_id)
|
||||
* [`eas build:configure`](#eas-buildconfigure)
|
||||
* [`eas build:inspect`](#eas-buildinspect)
|
||||
* [`eas build:list`](#eas-buildlist)
|
||||
* [`eas build:submit`](#eas-buildsubmit)
|
||||
* [`eas build:view [BUILD_ID]`](#eas-buildview-build_id)
|
||||
* [`eas channel:create [NAME]`](#eas-channelcreate-name)
|
||||
* [`eas channel:edit [NAME]`](#eas-channeledit-name)
|
||||
* [`eas channel:list`](#eas-channellist)
|
||||
* [`eas channel:view [NAME]`](#eas-channelview-name)
|
||||
* [`eas config`](#eas-config)
|
||||
* [`eas credentials`](#eas-credentials)
|
||||
* [`eas device:create`](#eas-devicecreate)
|
||||
* [`eas device:delete`](#eas-devicedelete)
|
||||
* [`eas device:list`](#eas-devicelist)
|
||||
* [`eas device:view [UDID]`](#eas-deviceview-udid)
|
||||
* [`eas diagnostics`](#eas-diagnostics)
|
||||
* [`eas help [COMMAND]`](#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)
|
||||
* [`eas secret:delete`](#eas-secretdelete)
|
||||
* [`eas secret:list`](#eas-secretlist)
|
||||
* [`eas submit`](#eas-submit)
|
||||
* [`eas update`](#eas-update)
|
||||
* [`eas update:configure`](#eas-updateconfigure)
|
||||
* [`eas update:delete GROUPID`](#eas-updatedelete-groupid)
|
||||
* [`eas update:list`](#eas-updatelist)
|
||||
* [`eas update:view GROUPID`](#eas-updateview-groupid)
|
||||
* [`eas webhook:create`](#eas-webhookcreate)
|
||||
* [`eas webhook:delete [ID]`](#eas-webhookdelete-id)
|
||||
* [`eas webhook:list`](#eas-webhooklist)
|
||||
* [`eas webhook:update`](#eas-webhookupdate)
|
||||
* [`eas webhook:view ID`](#eas-webhookview-id)
|
||||
* [`eas whoami`](#eas-whoami)
|
||||
|
||||
- [`eas account:login`](#eas-accountlogin)
|
||||
- [`eas account:logout`](#eas-accountlogout)
|
||||
- [`eas account:view`](#eas-accountview)
|
||||
- [`eas analytics [STATUS]`](#eas-analytics-status)
|
||||
- [`eas autocomplete [SHELL]`](#eas-autocomplete-shell)
|
||||
- [`eas branch:create [NAME]`](#eas-branchcreate-name)
|
||||
- [`eas branch:delete [NAME]`](#eas-branchdelete-name)
|
||||
- [`eas branch:list`](#eas-branchlist)
|
||||
- [`eas branch:rename`](#eas-branchrename)
|
||||
- [`eas branch:view [NAME]`](#eas-branchview-name)
|
||||
- [`eas build`](#eas-build)
|
||||
- [`eas build:cancel [BUILD_ID]`](#eas-buildcancel-build_id)
|
||||
- [`eas build:configure`](#eas-buildconfigure)
|
||||
- [`eas build:inspect`](#eas-buildinspect)
|
||||
- [`eas build:list`](#eas-buildlist)
|
||||
- [`eas build:submit`](#eas-buildsubmit)
|
||||
- [`eas build:view [BUILD_ID]`](#eas-buildview-build_id)
|
||||
- [`eas channel:create [NAME]`](#eas-channelcreate-name)
|
||||
- [`eas channel:edit [NAME]`](#eas-channeledit-name)
|
||||
- [`eas channel:list`](#eas-channellist)
|
||||
- [`eas channel:view [NAME]`](#eas-channelview-name)
|
||||
- [`eas config`](#eas-config)
|
||||
- [`eas credentials`](#eas-credentials)
|
||||
- [`eas device:create`](#eas-devicecreate)
|
||||
- [`eas device:delete`](#eas-devicedelete)
|
||||
- [`eas device:list`](#eas-devicelist)
|
||||
- [`eas device:view [UDID]`](#eas-deviceview-udid)
|
||||
- [`eas diagnostics`](#eas-diagnostics)
|
||||
- [`eas help [COMMAND]`](#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)
|
||||
- [`eas secret:delete`](#eas-secretdelete)
|
||||
- [`eas secret:list`](#eas-secretlist)
|
||||
- [`eas submit`](#eas-submit)
|
||||
- [`eas update`](#eas-update)
|
||||
- [`eas update:configure`](#eas-updateconfigure)
|
||||
- [`eas update:delete GROUPID`](#eas-updatedelete-groupid)
|
||||
- [`eas update:list`](#eas-updatelist)
|
||||
- [`eas update:view GROUPID`](#eas-updateview-groupid)
|
||||
- [`eas webhook:create`](#eas-webhookcreate)
|
||||
- [`eas webhook:delete [ID]`](#eas-webhookdelete-id)
|
||||
- [`eas webhook:list`](#eas-webhooklist)
|
||||
- [`eas webhook:update`](#eas-webhookupdate)
|
||||
- [`eas webhook:view ID`](#eas-webhookview-id)
|
||||
- [`eas whoami`](#eas-whoami)
|
||||
|
||||
## `eas account:login`
|
||||
|
||||
@@ -100,7 +101,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.55.1/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -117,7 +118,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.55.1/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -134,7 +135,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.55.1/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -148,7 +149,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.55.1/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -197,7 +198,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.55.1/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -217,7 +218,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.55.1/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -234,7 +235,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.55.1/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -253,7 +254,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.55.1/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -273,7 +274,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.55.1/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -302,7 +303,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.55.1/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -316,7 +317,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.55.1/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -333,7 +334,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.55.1/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -368,7 +369,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.55.1/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -401,7 +402,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.55.1/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -446,7 +447,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.55.1/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -466,7 +467,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.55.1/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -487,7 +488,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.55.1/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -504,7 +505,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.55.1/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -524,7 +525,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.55.1/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -542,7 +543,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.55.1/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -556,7 +557,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.55.1/packages/eas-cli/src/commands/credentials.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -570,7 +571,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.55.1/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -588,7 +589,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.55.1/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -605,7 +606,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.55.1/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -619,7 +620,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.55.1/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -633,7 +634,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.55.1/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -715,7 +716,7 @@ 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)_
|
||||
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v0.55.1/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -732,7 +733,7 @@ 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)_
|
||||
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v0.55.1/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -746,7 +747,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.55.1/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -763,7 +764,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.55.1/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
@@ -783,7 +784,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.55.1/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -800,7 +801,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.55.1/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -814,7 +815,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.55.1/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -844,7 +845,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.55.1/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -874,7 +875,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.55.1/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -891,7 +892,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.55.1/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -911,7 +912,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.55.1/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -930,7 +931,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.55.1/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -950,7 +951,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.55.1/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -970,7 +971,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.55.1/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -987,7 +988,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.55.1/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -1004,7 +1005,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.55.1/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -1025,7 +1026,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.55.1/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -1042,7 +1043,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.55.1/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
@@ -1058,4 +1059,5 @@ DESCRIPTION
|
||||
ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
<!-- commandsstop -->
|
||||
Generated
+104
@@ -10883,6 +10883,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "resourceClass",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "BuildResourceClass",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "runtimeVersion",
|
||||
"description": null,
|
||||
@@ -12280,6 +12292,18 @@
|
||||
"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,
|
||||
@@ -12341,6 +12365,18 @@
|
||||
"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,
|
||||
@@ -12492,6 +12528,33 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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",
|
||||
@@ -12868,6 +12931,47 @@
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.54.1",
|
||||
"version": "0.55.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
@@ -14,7 +14,7 @@
|
||||
"@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-json": "0.55.0",
|
||||
"@expo/json-file": "8.2.36",
|
||||
"@expo/multipart-body-parser": "1.1.0",
|
||||
"@expo/pkcs12": "0.0.8",
|
||||
@@ -103,7 +103,7 @@
|
||||
"nock": "13.2.4",
|
||||
"tempy": "0.7.0",
|
||||
"ts-deepmerge": "2.0.1",
|
||||
"typescript": "4.6.4"
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
|
||||
@@ -19,102 +19,166 @@
|
||||
]
|
||||
},
|
||||
"AppleKidsAge": {
|
||||
"enum": ["FIVE_AND_UNDER", "SIX_TO_EIGHT", "NINE_TO_ELEVEN"]
|
||||
"enum": [
|
||||
"FIVE_AND_UNDER",
|
||||
"SIX_TO_EIGHT",
|
||||
"NINE_TO_ELEVEN"
|
||||
]
|
||||
},
|
||||
"AppleRating": {
|
||||
"enum": ["NONE", "INFREQUENT_OR_MILD", "FREQUENT_OR_INTENSE"]
|
||||
"enum": [
|
||||
"NONE",
|
||||
"INFREQUENT_OR_MILD",
|
||||
"FREQUENT_OR_INTENSE"
|
||||
]
|
||||
},
|
||||
"AppleCategory": {
|
||||
"enum": [
|
||||
"FOOD_AND_DRINK",
|
||||
"BUSINESS",
|
||||
"EDUCATION",
|
||||
"SOCIAL_NETWORKING",
|
||||
"BOOKS",
|
||||
"SPORTS",
|
||||
"FINANCE",
|
||||
"REFERENCE",
|
||||
"GRAPHICS_AND_DESIGN",
|
||||
"DEVELOPER_TOOLS",
|
||||
"HEALTH_AND_FITNESS",
|
||||
"MUSIC",
|
||||
"WEATHER",
|
||||
"TRAVEL",
|
||||
"ENTERTAINMENT",
|
||||
"STICKERS",
|
||||
"GAMES",
|
||||
"LIFESTYLE",
|
||||
"MEDICAL",
|
||||
"MAGAZINES_AND_NEWSPAPERS",
|
||||
"UTILITIES",
|
||||
"SHOPPING",
|
||||
"PRODUCTIVITY",
|
||||
"NEWS",
|
||||
"PHOTO_AND_VIDEO",
|
||||
"NAVIGATION"
|
||||
]
|
||||
},
|
||||
"AppleSubcategory": {
|
||||
"enum": [
|
||||
"STICKERS_PLACES_AND_OBJECTS",
|
||||
"STICKERS_EMOJI_AND_EXPRESSIONS",
|
||||
"STICKERS_CELEBRATIONS",
|
||||
"STICKERS_CELEBRITIES",
|
||||
"STICKERS_MOVIES_AND_TV",
|
||||
"STICKERS_SPORTS_AND_ACTIVITIES",
|
||||
"STICKERS_EATING_AND_DRINKING",
|
||||
"STICKERS_CHARACTERS",
|
||||
"STICKERS_ANIMALS",
|
||||
"STICKERS_FASHION",
|
||||
"STICKERS_ART",
|
||||
"STICKERS_GAMING",
|
||||
"STICKERS_KIDS_AND_FAMILY",
|
||||
"STICKERS_PEOPLE",
|
||||
"STICKERS_MUSIC",
|
||||
"GAMES_SPORTS",
|
||||
"GAMES_WORD",
|
||||
"GAMES_MUSIC",
|
||||
"GAMES_ADVENTURE",
|
||||
"GAMES_ACTION",
|
||||
"GAMES_ROLE_PLAYING",
|
||||
"GAMES_CASUAL",
|
||||
"GAMES_BOARD",
|
||||
"GAMES_TRIVIA",
|
||||
"GAMES_CARD",
|
||||
"GAMES_PUZZLE",
|
||||
"GAMES_CASINO",
|
||||
"GAMES_STRATEGY",
|
||||
"GAMES_SIMULATION",
|
||||
"GAMES_RACING",
|
||||
"GAMES_FAMILY"
|
||||
]
|
||||
},
|
||||
"AppleIdfa": {
|
||||
"description": "Identifier for Advertisers",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"type": "array",
|
||||
"description": "App Store categories for the app, can add up to two categories. The first item in this list is the primary category, the second is the secondary and optional category. `STICKERS` and `GAMES` categories can add up to two additional subcategories, written as nested array.",
|
||||
"markdownDescription": "App Store categories for the app, can add up to two categories. The first item in this list is the primary category, the second is the secondary and optional category. `STICKERS` and `GAMES` categories can add up to two additional subcategories, written as nested array. E.g.\n\n- `[\"SPORTS\"]`\n- `[\"SPORTS\", \"LIFESTYLE\"]`\n- `[[\"GAMES\", \"GAMES_CARD\"], \"ENTERTAINMENT\"]`\n- `[\"GAMES\", \"WEATHER\"]`",
|
||||
"minItems": 1,
|
||||
"maxItems": 2,
|
||||
"uniqueItems": true,
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"label": "Basic categories",
|
||||
"description": "Add two simple categories",
|
||||
"body": [
|
||||
"$1",
|
||||
"$2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Category with subcategories",
|
||||
"description": "Add a category containing subcategories",
|
||||
"body": [
|
||||
[
|
||||
"${1|GAMES,STICKERS|}",
|
||||
"$3",
|
||||
"$4"
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"body": {
|
||||
"servesAds": false,
|
||||
"attributesAppInstallationToPreviousAd": false,
|
||||
"attributesActionWithPreviousAd": false,
|
||||
"honorsLimitedAdTracking": false
|
||||
}
|
||||
"$ref": "#/definitions/apple/AppleCategory/categories/root"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/apple/AppleCategory/categories/parent"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"description": "The GAMES category can have additional subcategories",
|
||||
"minItems": 1,
|
||||
"maxItems": 3,
|
||||
"uniqueItems": true,
|
||||
"items": [
|
||||
{
|
||||
"const": "GAMES"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/apple/AppleCategory/categories/games"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/apple/AppleCategory/categories/games"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"description": "The STICKERS category can have additional subcategories",
|
||||
"minItems": 1,
|
||||
"maxItems": 3,
|
||||
"uniqueItems": true,
|
||||
"items": [
|
||||
{
|
||||
"const": "STICKERS"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/apple/AppleCategory/categories/stickers"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/apple/AppleCategory/categories/stickers"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"servesAds": {
|
||||
"type": "boolean"
|
||||
]
|
||||
},
|
||||
"categories": {
|
||||
"root": {
|
||||
"enum": [
|
||||
"BUSINESS",
|
||||
"SOCIAL_NETWORKING",
|
||||
"MEDICAL",
|
||||
"FOOD_AND_DRINK",
|
||||
"EDUCATION",
|
||||
"BOOKS",
|
||||
"SPORTS",
|
||||
"FINANCE",
|
||||
"REFERENCE",
|
||||
"GRAPHICS_AND_DESIGN",
|
||||
"DEVELOPER_TOOLS",
|
||||
"HEALTH_AND_FITNESS",
|
||||
"MUSIC",
|
||||
"WEATHER",
|
||||
"TRAVEL",
|
||||
"ENTERTAINMENT",
|
||||
"LIFESTYLE",
|
||||
"MAGAZINES_AND_NEWSPAPERS",
|
||||
"UTILITIES",
|
||||
"SHOPPING",
|
||||
"PRODUCTIVITY",
|
||||
"NEWS",
|
||||
"PHOTO_AND_VIDEO",
|
||||
"NAVIGATION"
|
||||
]
|
||||
},
|
||||
"attributesAppInstallationToPreviousAd": {
|
||||
"type": "boolean"
|
||||
"parent": {
|
||||
"enum": [
|
||||
"GAMES",
|
||||
"STICKERS"
|
||||
]
|
||||
},
|
||||
"attributesActionWithPreviousAd": {
|
||||
"type": "boolean"
|
||||
"games": {
|
||||
"enum": [
|
||||
"GAMES_SPORTS",
|
||||
"GAMES_WORD",
|
||||
"GAMES_MUSIC",
|
||||
"GAMES_ADVENTURE",
|
||||
"GAMES_ACTION",
|
||||
"GAMES_ROLE_PLAYING",
|
||||
"GAMES_CASUAL",
|
||||
"GAMES_BOARD",
|
||||
"GAMES_TRIVIA",
|
||||
"GAMES_CARD",
|
||||
"GAMES_PUZZLE",
|
||||
"GAMES_CASINO",
|
||||
"GAMES_STRATEGY",
|
||||
"GAMES_SIMULATION",
|
||||
"GAMES_RACING",
|
||||
"GAMES_FAMILY"
|
||||
]
|
||||
},
|
||||
"honorsLimitedAdTracking": {
|
||||
"type": "boolean"
|
||||
"stickers": {
|
||||
"enum": [
|
||||
"STICKERS_PLACES_AND_OBJECTS",
|
||||
"STICKERS_EMOJI_AND_EXPRESSIONS",
|
||||
"STICKERS_CELEBRATIONS",
|
||||
"STICKERS_CELEBRITIES",
|
||||
"STICKERS_MOVIES_AND_TV",
|
||||
"STICKERS_SPORTS_AND_ACTIVITIES",
|
||||
"STICKERS_EATING_AND_DRINKING",
|
||||
"STICKERS_CHARACTERS",
|
||||
"STICKERS_ANIMALS",
|
||||
"STICKERS_FASHION",
|
||||
"STICKERS_ART",
|
||||
"STICKERS_GAMING",
|
||||
"STICKERS_KIDS_AND_FAMILY",
|
||||
"STICKERS_PEOPLE",
|
||||
"STICKERS_MUSIC"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -123,240 +187,278 @@
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"title",
|
||||
"subtitle",
|
||||
"description",
|
||||
"keywords",
|
||||
"marketingUrl",
|
||||
"supportUrl",
|
||||
"privacyPolicyUrl"
|
||||
],
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"label": "Basic",
|
||||
"description": "Create the minimum required locale properties",
|
||||
"body": {
|
||||
"title": "${1:App title}",
|
||||
"privacyPolicyUrl": "https://${3:www.}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Interactive",
|
||||
"description": "Creates localized app info",
|
||||
"label": "Full",
|
||||
"description": "Create all available locale properties",
|
||||
"body": {
|
||||
"title": "$1",
|
||||
"subtitle": "$2",
|
||||
"description": "$3",
|
||||
"keywords": [],
|
||||
"marketingUrl": "",
|
||||
"supportUrl": "",
|
||||
"privacyPolicyUrl": ""
|
||||
"title": "${1:App title}",
|
||||
"subtitle": "${2:Subtitle for your app}",
|
||||
"description": "${3:A longer description of what your app does}",
|
||||
"keywords": [
|
||||
"${4:keyword}"
|
||||
],
|
||||
"releaseNotes": "${5:Bug fixes and improved stability}",
|
||||
"promoText": "${6:Short tagline for your app}",
|
||||
"marketingUrl": "https://${7:www.}",
|
||||
"supportUrl": "https://${8:www.}",
|
||||
"privacyPolicyUrl": "https://${9:www.}",
|
||||
"privacyChoicesUrl": "https://${10:www.}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"title": {
|
||||
"description": "Name of the app in the store. This should be similar to the installed app name.",
|
||||
"type": "string",
|
||||
"description": "Name of the app in the store. This should be similar to the installed app name.",
|
||||
"minLength": 2,
|
||||
"maxLength": 30,
|
||||
"meta": {
|
||||
"storeInfo": "The name will be reviewed before it is made available on the App Store."
|
||||
}
|
||||
},
|
||||
"subtitle": {
|
||||
"description": "Subtext for the app. Example: 'A Fun Game For Friends'",
|
||||
"type": "string",
|
||||
"description": "Subtext for the app. Example: 'A Fun Game For Friends'",
|
||||
"maxLength": 30,
|
||||
"meta": {
|
||||
"storeInfo": "The subtitle will be reviewed before it is made available on the App Store."
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Main description of what the app does",
|
||||
"minLength": 10,
|
||||
"maxLength": 4000,
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
},
|
||||
"maxLength": 4000,
|
||||
"description": "Main description of what the app does",
|
||||
"type": "string"
|
||||
},
|
||||
"promoText": {
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true,
|
||||
"aso": false
|
||||
},
|
||||
"maxLength": 170,
|
||||
"description": "Short tagline for the app",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"keywords": {
|
||||
"meta": {
|
||||
"versioned": true
|
||||
},
|
||||
"description": "List of keywords to help users find the app in the App Store",
|
||||
"type": "array",
|
||||
"description": "List of keywords to help users find the app in the App Store",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"maxLength": 100,
|
||||
"type": "string"
|
||||
},
|
||||
"meta": {
|
||||
"versioned": true
|
||||
}
|
||||
},
|
||||
"releaseNotes": {
|
||||
"type": "string",
|
||||
"description": "Changes since the last public version",
|
||||
"maxLength": 4000,
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
}
|
||||
},
|
||||
"promoText": {
|
||||
"type": "string",
|
||||
"description": "Short tagline for the app",
|
||||
"maxLength": 170,
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true,
|
||||
"aso": false
|
||||
}
|
||||
},
|
||||
"marketingUrl": {
|
||||
"type": "string",
|
||||
"description": "URL to the app marketing page",
|
||||
"maxLength": 255,
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
}
|
||||
},
|
||||
"supportUrl": {
|
||||
"type": "string",
|
||||
"description": "URL to the app support page",
|
||||
"maxLength": 255,
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
}
|
||||
},
|
||||
"privacyPolicyText": {
|
||||
"type": "string",
|
||||
"description": "Privacy policy for Apple TV"
|
||||
},
|
||||
"whatsNew": {
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
},
|
||||
"maxLength": 4000,
|
||||
"description": "Changes since the last public version",
|
||||
"type": "string"
|
||||
},
|
||||
"marketingUrl": {
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
},
|
||||
"description": "URL to the app marketing page",
|
||||
"type": "string"
|
||||
},
|
||||
"supportUrl": {
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
},
|
||||
"description": "URL to the app support page",
|
||||
"type": "string"
|
||||
},
|
||||
"privacyPolicyUrl": {
|
||||
"type": "string",
|
||||
"description": "A URL that links to your privacy policy. A privacy policy is required for all apps.",
|
||||
"maxLength": 255,
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
},
|
||||
"maxLength": 255,
|
||||
"description": "A URL that links to your privacy policy. A privacy policy is required for all apps.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"privacyChoicesUrl": {
|
||||
"type": "string",
|
||||
"description": "A URL where users can modify and delete the data collected from the app, or decide how their data is used and shared.",
|
||||
"maxLength": 255,
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true,
|
||||
"optional": true
|
||||
},
|
||||
"maxLength": 255,
|
||||
"description": "A URL where users can modify and delete the data collected from the app, or decide how their data is used and shared.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AppleAdvisory": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"alcoholTobaccoOrDrugUseOrReferences",
|
||||
"contests",
|
||||
"gamblingSimulated",
|
||||
"horrorOrFearThemes",
|
||||
"matureOrSuggestiveThemes",
|
||||
"medicalOrTreatmentInformation",
|
||||
"profanityOrCrudeHumor",
|
||||
"sexualContentGraphicAndNudity",
|
||||
"sexualContentOrNudity",
|
||||
"violenceCartoonOrFantasy",
|
||||
"violenceRealistic",
|
||||
"violenceRealisticProlongedGraphicOrSadistic",
|
||||
"gambling",
|
||||
"unrestrictedWebAccess",
|
||||
"kidsAgeBand",
|
||||
"seventeenPlus"
|
||||
],
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"label": "Minimum",
|
||||
"description": "The bare minimum rating, your app does nothing",
|
||||
"body": {
|
||||
"alcoholTobaccoOrDrugUseOrReferences": "NONE",
|
||||
"contests": "NONE",
|
||||
"gamblingSimulated": "NONE",
|
||||
"medicalOrTreatmentInformation": "NONE",
|
||||
"profanityOrCrudeHumor": "NONE",
|
||||
"sexualContentGraphicAndNudity": "NONE",
|
||||
"sexualContentOrNudity": "NONE",
|
||||
"horrorOrFearThemes": "NONE",
|
||||
"matureOrSuggestiveThemes": "NONE",
|
||||
"violenceCartoonOrFantasy": "NONE",
|
||||
"violenceRealisticProlongedGraphicOrSadistic": "NONE",
|
||||
"violenceRealistic": "NONE",
|
||||
"gamblingAndContests":false,
|
||||
"gambling": false,
|
||||
"kidsAgeBand": null,
|
||||
"seventeenPlus": false,
|
||||
"unrestrictedWebAccess": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Interactive",
|
||||
"description": "Step through options",
|
||||
"body": {
|
||||
"alcoholTobaccoOrDrugUseOrReferences": "${1|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"contests": "${2|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"gamblingSimulated": "${3|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"medicalOrTreatmentInformation": "${4|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"profanityOrCrudeHumor": "${5|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"sexualContentGraphicAndNudity": "${6|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"sexualContentOrNudity": "${7|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"horrorOrFearThemes": "${8|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"matureOrSuggestiveThemes": "${9|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"violenceCartoonOrFantasy": "${10|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"violenceRealisticProlongedGraphicOrSadistic": "${11|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"violenceRealistic": "${12|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"gamblingAndContests":false,
|
||||
"gambling": false,
|
||||
"kidsAgeBand": null,
|
||||
"seventeenPlus": false,
|
||||
"unrestrictedWebAccess": false
|
||||
}
|
||||
{
|
||||
"label": "Basic",
|
||||
"description": "Create an advisory that disables all age-rated properties, for when your app does nothing",
|
||||
"body": {
|
||||
"alcoholTobaccoOrDrugUseOrReferences": "NONE",
|
||||
"contests": "NONE",
|
||||
"gamblingSimulated": "NONE",
|
||||
"horrorOrFearThemes": "NONE",
|
||||
"matureOrSuggestiveThemes": "NONE",
|
||||
"medicalOrTreatmentInformation": "NONE",
|
||||
"profanityOrCrudeHumor": "NONE",
|
||||
"sexualContentGraphicAndNudity": "NONE",
|
||||
"sexualContentOrNudity": "NONE",
|
||||
"violenceCartoonOrFantasy": "NONE",
|
||||
"violenceRealistic": "NONE",
|
||||
"violenceRealisticProlongedGraphicOrSadistic": "NONE",
|
||||
"gambling": false,
|
||||
"unrestrictedWebAccess": false,
|
||||
"kidsAgeBand": null,
|
||||
"seventeenPlus": false
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": "Full",
|
||||
"description": "Create an advisory that enables some of the age-rated properties",
|
||||
"body": {
|
||||
"alcoholTobaccoOrDrugUseOrReferences": "${1|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"contests": "${2|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"gamblingSimulated": "${3|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"horrorOrFearThemes": "${4|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"matureOrSuggestiveThemes": "${5|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"medicalOrTreatmentInformation": "${6|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"profanityOrCrudeHumor": "${7|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"sexualContentGraphicAndNudity": "${8|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"sexualContentOrNudity": "${9|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"violenceCartoonOrFantasy": "${10|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"violenceRealistic": "${11|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"violenceRealisticProlongedGraphicOrSadistic": "${12|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"unrestrictedWebAccess": false,
|
||||
"gambling": false,
|
||||
"kidsAgeBand": null,
|
||||
"seventeenPlus": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"alcoholTobaccoOrDrugUseOrReferences": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
"$ref": "#/definitions/apple/AppleRating",
|
||||
"description": "Does the app contain alcohol, tobacco, or drug use or references?"
|
||||
},
|
||||
"contests": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"gamblingAndContests": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"gambling": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/apple/AppleRating",
|
||||
"description": "Does the app contain contests?"
|
||||
},
|
||||
"gamblingSimulated": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"kidsAgeBand": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/apple/AppleKidsAge"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/apple/AppleRating",
|
||||
"description": "Does the app contain simulated gambling?"
|
||||
},
|
||||
"medicalOrTreatmentInformation": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
"$ref": "#/definitions/apple/AppleRating",
|
||||
"description": "Does the app contain medical or treatment information?"
|
||||
},
|
||||
"profanityOrCrudeHumor": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
"$ref": "#/definitions/apple/AppleRating",
|
||||
"description": "Does the app contain profanity or crude humor?"
|
||||
},
|
||||
"sexualContentGraphicAndNudity": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
"$ref": "#/definitions/apple/AppleRating",
|
||||
"description": "Does the app contain graphic sexual content and nudity?"
|
||||
},
|
||||
"sexualContentOrNudity": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"seventeenPlus": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/apple/AppleRating",
|
||||
"description": "Does the app contain sexual content or nudity?"
|
||||
},
|
||||
"horrorOrFearThemes": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
"$ref": "#/definitions/apple/AppleRating",
|
||||
"description": "Does the app contain horror or fear themes?"
|
||||
},
|
||||
"matureOrSuggestiveThemes": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"unrestrictedWebAccess": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/apple/AppleRating",
|
||||
"description": "Does the app contain mature or suggestive themes?"
|
||||
},
|
||||
"violenceCartoonOrFantasy": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
"$ref": "#/definitions/apple/AppleRating",
|
||||
"description": "Does the app contain cartoon or fantasy violence?"
|
||||
},
|
||||
"violenceRealisticProlongedGraphicOrSadistic": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
"$ref": "#/definitions/apple/AppleRating",
|
||||
"description": "Does the app contain prolonged graphic or sadistic realistic violence?"
|
||||
},
|
||||
"violenceRealistic": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
"$ref": "#/definitions/apple/AppleRating",
|
||||
"description": "Does the app contain realistic violence?"
|
||||
},
|
||||
"unrestrictedWebAccess": {
|
||||
"type": "boolean",
|
||||
"description": "Does your app contain unrestricted web access, such as with an embedded browser?"
|
||||
},
|
||||
"gambling": {
|
||||
"type": "boolean",
|
||||
"description": "Does your app contain gambling?"
|
||||
},
|
||||
"kidsAgeBand": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/apple/AppleKidsAge"
|
||||
}
|
||||
],
|
||||
"description": "When parents visit the Kids category on the App Store, they expect the apps they find will protect their children’s data, provide only age-appropriate content, and require a parental gate in order to link out of the app, request permissions, or present purchasing opportunities. It’s critical that no personally identifiable information or device information be transmitted to third parties, and that advertisements are human-reviewed for age appropriateness in order to be displayed.\n@see https://developer.apple.com/news/?id=091202019a",
|
||||
"markdownDescription": "When parents visit the Kids category on the App Store, they expect the apps they find will protect their children’s data, provide only age-appropriate content, and require a parental gate in order to link out of the app, request permissions, or present purchasing opportunities. It’s critical that no personally identifiable information or device information be transmitted to third parties, and that advertisements are human-reviewed for age appropriateness in order to be displayed.\n\n[Learn more](https://developer.apple.com/news/?id=091202019a)"
|
||||
},
|
||||
"seventeenPlus": {
|
||||
"type": "boolean",
|
||||
"description": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 17, you can manually set the age rating to 17+.\n@see https://help.apple.com/app-store-connect/#/dev599d50efb",
|
||||
"markdownDescription": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 17, you can manually set the age rating to 17+.\n\n[Learn more](https://help.apple.com/app-store-connect/#/dev599d50efb)"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -402,8 +504,8 @@
|
||||
}
|
||||
},
|
||||
"shouldResetRatings": {
|
||||
"description": "Should App Store ratings be reset for this version of the app.",
|
||||
"type": "boolean",
|
||||
"description": "Should App Store ratings be reset for this version of the app.",
|
||||
"meta": {
|
||||
"storeInfo": "You can reset your app’s summary rating for all countries or regions when you release this version. Keep in mind that once this version is released, you won’t be able to restore the rating. Your app’s existing customer reviews will still appear on the App Store."
|
||||
}
|
||||
@@ -412,144 +514,107 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"automaticRelease": {
|
||||
"description": "Should the app automatically be released when approved by App Store review.",
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"description": "Should the app automatically be released when approved by App Store review."
|
||||
},
|
||||
"usesNonExemptEncryption": {
|
||||
"description": "Alternative to setting `ITSAppUsesNonExemptEncryption` in the binary's `Info.plist`.",
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"description": "Alternative to setting `ITSAppUsesNonExemptEncryption` in the binary's `Info.plist`."
|
||||
}
|
||||
}
|
||||
},
|
||||
"AppleReview": {
|
||||
"type": "object",
|
||||
"description": "Info provided to the App Store review team to help them understand how to use your app.",
|
||||
"required": ["firstName", "lastName"],
|
||||
"required": [
|
||||
"firstName",
|
||||
"lastName",
|
||||
"email",
|
||||
"phone"
|
||||
],
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"label": "Basic",
|
||||
"description": "Only define the contact details in case communcation is needed with the App Store review team.",
|
||||
"body": {
|
||||
"firstName": "$1",
|
||||
"lastName": "$2"
|
||||
"lastName": "$2",
|
||||
"email": "$3",
|
||||
"phone": "+$4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Full",
|
||||
"description": "Define the demo account credentials, additional info, and contact details for the App Store review team.",
|
||||
"body": {
|
||||
"firstName": "$1",
|
||||
"lastName": "$2",
|
||||
"email": "$3",
|
||||
"phone": "+$4",
|
||||
"demoUsername": "$5",
|
||||
"demoPassword": "$6",
|
||||
"demoRequired": true,
|
||||
"notes": "$7"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"demoUsername": {
|
||||
"description": "Optional username for testing the app.",
|
||||
"type": "string"
|
||||
},
|
||||
"demoPassword": {
|
||||
"description": "Optional password for testing the app.",
|
||||
"type": "string"
|
||||
},
|
||||
"attachment": {
|
||||
"description": "Local file path to upload to the review team.",
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"description": "Developer first name for the reviewer to contact.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "First name of contact in case communication is needed with the App Store review team.",
|
||||
"minLength": 1
|
||||
},
|
||||
"lastName": {
|
||||
"description": "Developer last name (surname) for the reviewer to contact.",
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"description": "Developer phone number for the reviewer to contact.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Last name of contact in case communication is needed with the App Store review team.",
|
||||
"minLength": 1
|
||||
},
|
||||
"email": {
|
||||
"description": "Developer email address for the reviewer to contact.",
|
||||
"type": "string",
|
||||
"description": "Email address of contact in case communication is needed with the App Store review team.",
|
||||
"format": "email"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string",
|
||||
"description": "Phone number of contact in case communication is needed with the App Store review team. Preface the phone number with ‘+’ followed by the country code (for example, +44 844 209 0611)"
|
||||
},
|
||||
"demoUsername": {
|
||||
"type": "string",
|
||||
"description": "The user name to sign in to your app to review its features."
|
||||
},
|
||||
"demoPassword": {
|
||||
"type": "string",
|
||||
"description": "The password to sign in to your app to review its features."
|
||||
},
|
||||
"demoRequired": {
|
||||
"type": "boolean",
|
||||
"description": "A Boolean value that indicates if sign-in information is required to review all the features of your app. If users sign in using social media, provide information for an account for review. Credentials must be valid and active for duration of review.",
|
||||
"markdownDescription": "A Boolean value that indicates if sign-in information is required to review all the features of your app. If users sign in using social media, provide information for an account for review.\n\n**Credentials must be valid and active for duration of review.**"
|
||||
},
|
||||
"notes": {
|
||||
"description": "Any additional notes for the App Store reviewer.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Additional information about your app that can help during the review process. Do not include demo account details.",
|
||||
"markdownDescription": "Additional information about your app that can help during the review process.\n\n**Do not include demo account details.**",
|
||||
"minLength": 2,
|
||||
"maxLength": 4000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AppleConfig": {
|
||||
"description": "Configuration that is specific to the Apple App Store.",
|
||||
"type": "object",
|
||||
"description": "Configuration that is specific to the Apple App Store.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"advisory": {
|
||||
"$ref": "#/definitions/apple/AppleAdvisory"
|
||||
},
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"maxItems": 2,
|
||||
"description": "App Store categories for the app. Can add up to two categories. STICKERS and GAMES categories can add an additional subcategory.",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/apple/AppleCategory"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"maxItems": 2,
|
||||
"items": [
|
||||
{
|
||||
"enum": ["GAMES"]
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"GAMES_SPORTS",
|
||||
"GAMES_WORD",
|
||||
"GAMES_MUSIC",
|
||||
"GAMES_ADVENTURE",
|
||||
"GAMES_ACTION",
|
||||
"GAMES_ROLE_PLAYING",
|
||||
"GAMES_CASUAL",
|
||||
"GAMES_BOARD",
|
||||
"GAMES_TRIVIA",
|
||||
"GAMES_CARD",
|
||||
"GAMES_PUZZLE",
|
||||
"GAMES_CASINO",
|
||||
"GAMES_STRATEGY",
|
||||
"GAMES_SIMULATION",
|
||||
"GAMES_RACING",
|
||||
"GAMES_FAMILY"
|
||||
]
|
||||
}
|
||||
],
|
||||
"additionalItems": false
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"maxItems": 2,
|
||||
"items": [
|
||||
{
|
||||
"enum": ["STICKERS"]
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"STICKERS_PLACES_AND_OBJECTS",
|
||||
"STICKERS_EMOJI_AND_EXPRESSIONS",
|
||||
"STICKERS_CELEBRATIONS",
|
||||
"STICKERS_CELEBRITIES",
|
||||
"STICKERS_MOVIES_AND_TV",
|
||||
"STICKERS_SPORTS_AND_ACTIVITIES",
|
||||
"STICKERS_EATING_AND_DRINKING",
|
||||
"STICKERS_CHARACTERS",
|
||||
"STICKERS_ANIMALS",
|
||||
"STICKERS_FASHION",
|
||||
"STICKERS_ART",
|
||||
"STICKERS_GAMING",
|
||||
"STICKERS_KIDS_AND_FAMILY",
|
||||
"STICKERS_PEOPLE",
|
||||
"STICKERS_MUSIC"
|
||||
]
|
||||
}
|
||||
],
|
||||
"additionalItems": false
|
||||
}
|
||||
]
|
||||
}
|
||||
"$ref": "#/definitions/apple/AppleCategory"
|
||||
},
|
||||
"copyright": {
|
||||
"type": "string",
|
||||
"description": "The updated company copyright. Example: 2021 Evan Bacon",
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"body": "$CURRENT_YEAR ${1:name}"
|
||||
@@ -559,12 +624,7 @@
|
||||
"versioned": true,
|
||||
"liveEdits": true,
|
||||
"storeInfo": "The name of the person or entity that owns the exclusive rights to your app, preceded by the year the rights were obtained (for example, \"2008 Acme Inc.\"). Do not provide a URL."
|
||||
},
|
||||
"description": "The updated company copyright. Example: 2021 Evan Bacon",
|
||||
"type": "string"
|
||||
},
|
||||
"idfa": {
|
||||
"$ref": "#/definitions/apple/AppleIdfa"
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"description": "Localized core app info",
|
||||
@@ -901,28 +961,28 @@
|
||||
]
|
||||
},
|
||||
"release": {
|
||||
"description": "Release strategy",
|
||||
"$ref": "#/definitions/apple/AppleRelease"
|
||||
"$ref": "#/definitions/apple/AppleRelease",
|
||||
"description": "Release strategy"
|
||||
},
|
||||
"review": {
|
||||
"description": "Info for the App Store reviewer",
|
||||
"$ref": "#/definitions/apple/AppleReview"
|
||||
},
|
||||
"priceTier": {
|
||||
"description": "App price tier. 0 is Free.",
|
||||
"type": "number"
|
||||
"$ref": "#/definitions/apple/AppleReview",
|
||||
"description": "Info for the App Store reviewer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"configVersion": {
|
||||
"const": 0
|
||||
"const": 0,
|
||||
"description": "The EAS metadata store configuration version"
|
||||
},
|
||||
"apple": {
|
||||
"$ref": "#/definitions/AppleConfig"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["configVersion", "apple"]
|
||||
"required": [
|
||||
"configVersion",
|
||||
"apple"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { fixS3Url } from '../uploads';
|
||||
|
||||
describe(fixS3Url, () => {
|
||||
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(fixS3Url(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(fixS3Url(correctUrl)).toBe(correctUrl);
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@ 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 {
|
||||
@@ -87,7 +88,8 @@ export async function prepareAndroidBuildAsync(
|
||||
sendBuildRequestAsync: async (
|
||||
appId: string,
|
||||
job: Android.Job,
|
||||
metadata: Metadata
|
||||
metadata: Metadata,
|
||||
buildParams: BuildParamsInput
|
||||
): Promise<BuildResult> => {
|
||||
const graphqlMetadata = transformMetadata(metadata);
|
||||
const graphqlJob = transformJob(job);
|
||||
@@ -95,6 +97,7 @@ export async function prepareAndroidBuildAsync(
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
buildParams,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import { getExpoWebsiteBaseUrl } from '../api';
|
||||
import {
|
||||
AppPlatform,
|
||||
BuildFragment,
|
||||
BuildParamsInput,
|
||||
BuildPriority,
|
||||
BuildStatus,
|
||||
UploadSessionType,
|
||||
@@ -53,11 +54,22 @@ 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): Promise<BuildResult>;
|
||||
sendBuildRequestAsync(
|
||||
appId: string,
|
||||
job: TJob,
|
||||
metadata: Metadata,
|
||||
buildParams: BuildParamsInput
|
||||
): 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,
|
||||
@@ -100,6 +112,7 @@ 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,
|
||||
@@ -111,7 +124,7 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
return undefined;
|
||||
} else {
|
||||
try {
|
||||
return await sendBuildRequestAsync(builder, job, metadata);
|
||||
return await sendBuildRequestAsync(builder, job, metadata, buildParams);
|
||||
} catch (error: any) {
|
||||
handleBuildRequestError(error, job.platform);
|
||||
}
|
||||
@@ -197,7 +210,8 @@ 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
|
||||
metadata: Metadata,
|
||||
buildParams: BuildParamsInput
|
||||
): Promise<BuildFragment> {
|
||||
const { ctx } = builder;
|
||||
return await withAnalyticsAsync(
|
||||
@@ -209,7 +223,8 @@ async function sendBuildRequestAsync<TPlatform extends Platform, Credentials, TJ
|
||||
const { build, deprecationInfo } = await builder.sendBuildRequestAsync(
|
||||
ctx.projectId,
|
||||
job,
|
||||
metadata
|
||||
metadata,
|
||||
buildParams
|
||||
);
|
||||
|
||||
printDeprecationWarnings(deprecationInfo);
|
||||
|
||||
@@ -6,6 +6,7 @@ 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';
|
||||
@@ -30,6 +31,7 @@ export interface BuildContext<T extends Platform> {
|
||||
accountName: string;
|
||||
buildProfile: BuildProfile<T>;
|
||||
buildProfileName: string;
|
||||
resourceClass: BuildResourceClass;
|
||||
clearCache: boolean;
|
||||
credentialsCtx: CredentialsContext;
|
||||
exp: ExpoConfig;
|
||||
|
||||
@@ -7,6 +7,7 @@ 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';
|
||||
@@ -25,6 +26,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
nonInteractive = false,
|
||||
platform,
|
||||
projectDir,
|
||||
resourceClass,
|
||||
}: {
|
||||
buildProfileName: string;
|
||||
buildProfile: BuildProfile<T>;
|
||||
@@ -33,6 +35,7 @@ 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 });
|
||||
|
||||
@@ -70,6 +73,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
accountName,
|
||||
buildProfile,
|
||||
buildProfileName,
|
||||
resourceClass,
|
||||
clearCache,
|
||||
credentialsCtx,
|
||||
exp,
|
||||
|
||||
@@ -3,6 +3,7 @@ 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';
|
||||
@@ -82,7 +83,8 @@ export async function prepareIosBuildAsync(
|
||||
sendBuildRequestAsync: async (
|
||||
appId: string,
|
||||
job: Ios.Job,
|
||||
metadata: Metadata
|
||||
metadata: Metadata,
|
||||
buildParams: BuildParamsInput
|
||||
): Promise<BuildResult> => {
|
||||
const graphqlMetadata = transformMetadata(metadata);
|
||||
const graphqlJob = transformJob(job);
|
||||
@@ -90,6 +92,7 @@ export async function prepareIosBuildAsync(
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
buildParams,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,7 +3,13 @@ import { BuildProfile, EasJsonReader, SubmitProfile } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { AppPlatform, BuildFragment, BuildStatus, SubmissionFragment } from '../graphql/generated';
|
||||
import {
|
||||
AppPlatform,
|
||||
BuildFragment,
|
||||
BuildResourceClass,
|
||||
BuildStatus,
|
||||
SubmissionFragment,
|
||||
} from '../graphql/generated';
|
||||
import { toAppPlatform, toPlatform } from '../graphql/types/AppPlatform';
|
||||
import Log from '../log';
|
||||
import {
|
||||
@@ -30,6 +36,7 @@ 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';
|
||||
@@ -47,8 +54,23 @@ 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);
|
||||
@@ -85,6 +107,10 @@ 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 });
|
||||
@@ -161,14 +187,17 @@ 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,
|
||||
|
||||
@@ -12,3 +12,8 @@ export enum BuildDistributionType {
|
||||
INTERNAL = 'internal',
|
||||
SIMULATOR = 'simulator',
|
||||
}
|
||||
|
||||
export enum UserInputResourceClass {
|
||||
DEFAULT = 'default',
|
||||
LARGE = 'large',
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Errors, Flags } from '@oclif/core';
|
||||
import path from 'path';
|
||||
|
||||
import { BuildFlags, runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
||||
import { UserInputResourceClass } from '../../build/types';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log from '../../log';
|
||||
import { RequestedPlatform, selectRequestedPlatformAsync } from '../../platform';
|
||||
@@ -21,6 +22,7 @@ interface RawBuildFlags {
|
||||
json: boolean;
|
||||
'auto-submit': boolean;
|
||||
'auto-submit-with-profile'?: string;
|
||||
'resource-class'?: UserInputResourceClass;
|
||||
}
|
||||
|
||||
export default class Build extends EasCommand {
|
||||
@@ -79,6 +81,11 @@ 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> {
|
||||
@@ -155,6 +162,7 @@ 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -443,7 +443,11 @@ export default class UpdatePublish extends EasCommand {
|
||||
const assets = await collectAssetsAsync({ inputDir: inputDir!, platforms });
|
||||
const { uniqueUploadedAssetCount } = await uploadAssetsAsync(
|
||||
assets,
|
||||
spinnerText => (assetSpinner.text = `Uploading assets... ${spinnerText}`)
|
||||
(totalAssets, missingAssets) => {
|
||||
assetSpinner.text = `Uploading assets. Finished (${
|
||||
totalAssets - missingAssets
|
||||
}/${totalAssets})`;
|
||||
}
|
||||
);
|
||||
uploadedAssetCount = uniqueUploadedAssetCount;
|
||||
unsortedUpdateInfoGroups = await buildUnsortedUpdateInfoGroupAsync(assets, exp);
|
||||
@@ -457,6 +461,8 @@ export default class UpdatePublish extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
const truncatedMessage = truncatePublishUpdateMessage(message!);
|
||||
|
||||
const runtimeToPlatformMapping: Record<string, string[]> = {};
|
||||
for (const runtime of new Set(Object.values(runtimeVersions))) {
|
||||
runtimeToPlatformMapping[runtime] = Object.entries(runtimeVersions)
|
||||
@@ -489,7 +495,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
branchId,
|
||||
updateInfoGroup: localUpdateInfoGroup,
|
||||
runtimeVersion: republish ? oldRuntimeVersion : runtime,
|
||||
message,
|
||||
message: truncatedMessage,
|
||||
awaitingCodeSigningInfo: !!codeSigningInfo,
|
||||
};
|
||||
}
|
||||
@@ -587,7 +593,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
? [{ label: 'Android update ID', value: newAndroidUpdate.id }]
|
||||
: []),
|
||||
...(newIosUpdate ? [{ label: 'iOS update ID', value: newIosUpdate.id }] : []),
|
||||
{ label: 'Message', value: message! },
|
||||
{ label: 'Message', value: truncatedMessage },
|
||||
{ label: 'Website link', value: updateGroupLink },
|
||||
])
|
||||
);
|
||||
@@ -733,3 +739,11 @@ async function checkEASUpdateURLIsSetAsync(exp: ExpoConfig): Promise<void> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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) + '...';
|
||||
}
|
||||
return originalMessage;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
export default {
|
||||
/**
|
||||
* Dangerous overrides, use only if you know what you are doing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Overrides applicationId from Android project, setting this option will also
|
||||
* ignore failures when parsing build.gradle.
|
||||
*/
|
||||
overrideAndroidApplicationId: process.env.EAS_DANGEROUS_OVERRIDE_ANDROID_APPLICATION_ID,
|
||||
};
|
||||
+17
@@ -1594,6 +1594,7 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
|
||||
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;
|
||||
@@ -1783,6 +1784,7 @@ export type BuildMutationCancelBuildArgs = {
|
||||
|
||||
export type BuildMutationCreateAndroidBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
buildParams?: InputMaybe<BuildParamsInput>;
|
||||
job: AndroidJobInput;
|
||||
metadata?: InputMaybe<BuildMetadataInput>;
|
||||
};
|
||||
@@ -1790,6 +1792,7 @@ export type BuildMutationCreateAndroidBuildArgs = {
|
||||
|
||||
export type BuildMutationCreateIosBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
buildParams?: InputMaybe<BuildParamsInput>;
|
||||
job: IosJobInput;
|
||||
metadata?: InputMaybe<BuildMetadataInput>;
|
||||
};
|
||||
@@ -1808,6 +1811,10 @@ export type BuildOrBuildJob = {
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type BuildParamsInput = {
|
||||
resourceClass: BuildResourceClass;
|
||||
};
|
||||
|
||||
export enum BuildPriority {
|
||||
High = 'HIGH',
|
||||
Normal = 'NORMAL'
|
||||
@@ -1875,6 +1882,14 @@ 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',
|
||||
@@ -4319,6 +4334,7 @@ export type CreateAndroidBuildMutationVariables = Exact<{
|
||||
appId: Scalars['ID'];
|
||||
job: AndroidJobInput;
|
||||
metadata?: InputMaybe<BuildMetadataInput>;
|
||||
buildParams?: InputMaybe<BuildParamsInput>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -4328,6 +4344,7 @@ export type CreateIosBuildMutationVariables = Exact<{
|
||||
appId: Scalars['ID'];
|
||||
job: IosJobInput;
|
||||
metadata?: InputMaybe<BuildMetadataInput>;
|
||||
buildParams?: InputMaybe<BuildParamsInput>;
|
||||
}>;
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
AndroidJobInput,
|
||||
BuildFragment,
|
||||
BuildMetadataInput,
|
||||
BuildParamsInput,
|
||||
CreateAndroidBuildMutation,
|
||||
CreateAndroidBuildMutationVariables,
|
||||
CreateIosBuildMutation,
|
||||
@@ -26,6 +27,7 @@ export const BuildMutation = {
|
||||
appId: string;
|
||||
job: AndroidJobInput;
|
||||
metadata: BuildMetadataInput;
|
||||
buildParams: BuildParamsInput;
|
||||
}): Promise<BuildResult> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
@@ -35,9 +37,15 @@ export const BuildMutation = {
|
||||
$appId: ID!
|
||||
$job: AndroidJobInput!
|
||||
$metadata: BuildMetadataInput
|
||||
$buildParams: BuildParamsInput
|
||||
) {
|
||||
build {
|
||||
createAndroidBuild(appId: $appId, job: $job, metadata: $metadata) {
|
||||
createAndroidBuild(
|
||||
appId: $appId
|
||||
job: $job
|
||||
metadata: $metadata
|
||||
buildParams: $buildParams
|
||||
) {
|
||||
build {
|
||||
id
|
||||
...BuildFragment
|
||||
@@ -62,6 +70,7 @@ export const BuildMutation = {
|
||||
appId: string;
|
||||
job: IosJobInput;
|
||||
metadata: BuildMetadataInput;
|
||||
buildParams: BuildParamsInput;
|
||||
}): Promise<BuildResult> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
@@ -71,9 +80,15 @@ export const BuildMutation = {
|
||||
$appId: ID!
|
||||
$job: IosJobInput!
|
||||
$metadata: BuildMetadataInput
|
||||
$buildParams: BuildParamsInput
|
||||
) {
|
||||
build {
|
||||
createIosBuild(appId: $appId, job: $job, metadata: $metadata) {
|
||||
createIosBuild(
|
||||
appId: $appId
|
||||
job: $job
|
||||
metadata: $metadata
|
||||
buildParams: $buildParams
|
||||
) {
|
||||
build {
|
||||
id
|
||||
...BuildFragment
|
||||
|
||||
+70
-9
@@ -1,23 +1,84 @@
|
||||
import { AgeRatingDeclaration, KidsAge } from '@expo/apple-utils';
|
||||
import { AgeRatingDeclaration, KidsAge, Rating } from '@expo/apple-utils';
|
||||
|
||||
import { AttributesOf } from '../../../../utils/asc';
|
||||
|
||||
export const kidsSixToEightAdvisory: AttributesOf<AgeRatingDeclaration> = {
|
||||
// @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,
|
||||
gamblingSimulated: null,
|
||||
gambling: null,
|
||||
contests: null,
|
||||
gamblingSimulated: null,
|
||||
horrorOrFearThemes: null,
|
||||
matureOrSuggestiveThemes: null,
|
||||
medicalOrTreatmentInformation: null,
|
||||
profanityOrCrudeHumor: null,
|
||||
sexualContentGraphicAndNudity: null,
|
||||
sexualContentOrNudity: null,
|
||||
horrorOrFearThemes: null,
|
||||
matureOrSuggestiveThemes: null,
|
||||
violenceCartoonOrFantasy: null,
|
||||
violenceRealisticProlongedGraphicOrSadistic: 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,
|
||||
kidsAgeBand: KidsAge.SIX_TO_EIGHT,
|
||||
gamblingAndContests: false,
|
||||
};
|
||||
@@ -1,23 +1,37 @@
|
||||
import { AppCategory, AppInfo, BundleIdPlatform } from '@expo/apple-utils';
|
||||
import { AppCategory, AppCategoryId, AppSubcategoryId, BundleIdPlatform } from '@expo/apple-utils';
|
||||
|
||||
import { AttributesOf } from '../../../../utils/asc';
|
||||
import { AppleConfigWriter } from '../../writer';
|
||||
|
||||
export const primaryOnlyCategory: Pick<AttributesOf<AppInfo>, 'primaryCategory'> = {
|
||||
primaryCategory: new AppCategory({} as any, 'ENTERTAINMENT', {
|
||||
/** Infered type of the AppleConfigWriter.setCategories attributes */
|
||||
type CategoryInfoProps = Parameters<typeof AppleConfigWriter.prototype.setCategories>[0];
|
||||
|
||||
/**
|
||||
* Export a factory method instead of fixed categories.
|
||||
* In the tests, we need to create a couple of different categories,
|
||||
* adding them here as fixed objects won't be readable.
|
||||
*/
|
||||
export function makeCategory(id?: AppCategoryId | AppSubcategoryId): AppCategory | undefined {
|
||||
if (!id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new AppCategory({} as any, id, {
|
||||
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,
|
||||
};
|
||||
/**
|
||||
* Export a helper method to create the AppInfo object, using only AppCategoryIds or AppSubcategoryId.
|
||||
*/
|
||||
export function makeCategoryInfo(
|
||||
attributes: Partial<Record<keyof CategoryInfoProps, AppCategoryId | AppSubcategoryId>>
|
||||
): CategoryInfoProps {
|
||||
return {
|
||||
primaryCategory: makeCategory(attributes.primaryCategory),
|
||||
primarySubcategoryOne: makeCategory(attributes.primarySubcategoryOne),
|
||||
primarySubcategoryTwo: makeCategory(attributes.primarySubcategoryTwo),
|
||||
secondaryCategory: makeCategory(attributes.secondaryCategory),
|
||||
secondarySubcategoryOne: makeCategory(attributes.secondarySubcategoryOne),
|
||||
secondarySubcategoryTwo: makeCategory(attributes.secondarySubcategoryTwo),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import { AppCategoryId, AppSubcategoryId } from '@expo/apple-utils';
|
||||
|
||||
import { AppleConfigReader } from '../reader';
|
||||
import { leastRestrictiveAdvisory, mostRestrictiveAdvisory } from './fixtures/ageRatingDeclaration';
|
||||
|
||||
describe('getCategories', () => {
|
||||
it('ignores categories when not set', () => {
|
||||
const reader = new AppleConfigReader({ categories: undefined });
|
||||
expect(reader.getCategories()).toBeNull();
|
||||
});
|
||||
|
||||
it('returns primary category only', () => {
|
||||
const reader = new AppleConfigReader({ categories: [AppCategoryId.ENTERTAINMENT] });
|
||||
expect(reader.getCategories()).toMatchObject({
|
||||
primaryCategory: AppCategoryId.ENTERTAINMENT,
|
||||
secondaryCategory: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns primary and secondary categories', () => {
|
||||
const reader = new AppleConfigReader({
|
||||
categories: [AppCategoryId.ENTERTAINMENT, AppCategoryId.FOOD_AND_DRINK],
|
||||
});
|
||||
expect(reader.getCategories()).toMatchObject({
|
||||
primaryCategory: AppCategoryId.ENTERTAINMENT,
|
||||
secondaryCategory: AppCategoryId.FOOD_AND_DRINK,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns primary category without subcategory', () => {
|
||||
const reader = new AppleConfigReader({ categories: [AppCategoryId.GAMES] });
|
||||
expect(reader.getCategories()).toMatchObject({
|
||||
primaryCategory: AppCategoryId.GAMES,
|
||||
secondaryCategory: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns primary category with two subcategories', () => {
|
||||
const reader = new AppleConfigReader({
|
||||
categories: [
|
||||
[AppCategoryId.GAMES, AppSubcategoryId.GAMES_CARD, AppSubcategoryId.GAMES_ADVENTURE],
|
||||
],
|
||||
});
|
||||
expect(reader.getCategories()).toMatchObject({
|
||||
primaryCategory: AppCategoryId.GAMES,
|
||||
primarySubcategoryOne: AppSubcategoryId.GAMES_CARD,
|
||||
primarySubcategoryTwo: AppSubcategoryId.GAMES_ADVENTURE,
|
||||
secondaryCategory: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns primary and secondary categories with two subcategories', () => {
|
||||
const reader = new AppleConfigReader({
|
||||
categories: [
|
||||
AppCategoryId.ENTERTAINMENT,
|
||||
[AppCategoryId.GAMES, AppSubcategoryId.GAMES_CARD, AppSubcategoryId.GAMES_ADVENTURE],
|
||||
],
|
||||
});
|
||||
expect(reader.getCategories()).toMatchObject({
|
||||
primaryCategory: AppCategoryId.ENTERTAINMENT,
|
||||
secondaryCategory: AppCategoryId.GAMES,
|
||||
secondarySubcategoryOne: AppSubcategoryId.GAMES_CARD,
|
||||
secondarySubcategoryTwo: AppSubcategoryId.GAMES_ADVENTURE,
|
||||
});
|
||||
expect(reader.getCategories()).not.toHaveProperty('primarySubcategoryOne');
|
||||
expect(reader.getCategories()).not.toHaveProperty('primarySubcategoryTwo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAgeRating', () => {
|
||||
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,7 +1,15 @@
|
||||
import { AppCategoryId, AppSubcategoryId } from '@expo/apple-utils';
|
||||
|
||||
import { AppleConfigWriter } from '../writer';
|
||||
import { kidsSixToEightAdvisory } from './fixtures/ageRatingDeclaration';
|
||||
import { primaryAndSecondaryCategory, secondaryOnlyCategory } from './fixtures/appInfo';
|
||||
import {
|
||||
emptyAdvisory,
|
||||
kidsSixToEightAdvisory,
|
||||
leastRestrictiveAdvisory,
|
||||
mostRestrictiveAdvisory,
|
||||
} from './fixtures/ageRatingDeclaration';
|
||||
import { makeCategoryInfo } 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';
|
||||
|
||||
@@ -16,11 +24,23 @@ describe('toSchema', () => {
|
||||
});
|
||||
|
||||
describe('setAgeRating', () => {
|
||||
it('modifies the advisory', () => {
|
||||
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', () => {
|
||||
@@ -55,22 +75,109 @@ describe('setInfoLocale', () => {
|
||||
});
|
||||
|
||||
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);
|
||||
writer.setCategories(makeCategoryInfo({}));
|
||||
expect(writer.schema.categories).toBeUndefined();
|
||||
});
|
||||
|
||||
it('removes existing values when undefined', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setCategories(makeCategoryInfo({ primaryCategory: AppCategoryId.BUSINESS }));
|
||||
writer.setCategories(makeCategoryInfo({}));
|
||||
expect(writer.schema.categories).toBeUndefined();
|
||||
});
|
||||
|
||||
it('modifies primary category only', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setCategories(makeCategoryInfo({ primaryCategory: AppCategoryId.ENTERTAINMENT }));
|
||||
expect(writer.schema.categories).toEqual([AppCategoryId.ENTERTAINMENT]);
|
||||
});
|
||||
|
||||
it('modifies primary and secondary categories', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setCategories(
|
||||
makeCategoryInfo({
|
||||
primaryCategory: AppCategoryId.ENTERTAINMENT,
|
||||
secondaryCategory: AppCategoryId.FOOD_AND_DRINK,
|
||||
})
|
||||
);
|
||||
expect(writer.schema.categories).toEqual([
|
||||
AppCategoryId.ENTERTAINMENT,
|
||||
AppCategoryId.FOOD_AND_DRINK,
|
||||
]);
|
||||
});
|
||||
|
||||
it('modifies primary category without subcategories', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setCategories(makeCategoryInfo({ primaryCategory: AppCategoryId.GAMES }));
|
||||
expect(writer.schema.categories).toEqual([AppCategoryId.GAMES]);
|
||||
});
|
||||
|
||||
it('modifies primary category with one subcategories', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setCategories(
|
||||
makeCategoryInfo({
|
||||
primaryCategory: AppCategoryId.GAMES,
|
||||
primarySubcategoryOne: AppSubcategoryId.GAMES_CARD,
|
||||
})
|
||||
);
|
||||
expect(writer.schema.categories).toEqual([[AppCategoryId.GAMES, AppSubcategoryId.GAMES_CARD]]);
|
||||
// We need to filter the lists to avoid writing `[GAMES, GAMES_CARD, null]`
|
||||
expect(writer.schema.categories![0]).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('modifies primary category with two subcategories', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setCategories(
|
||||
makeCategoryInfo({
|
||||
primaryCategory: AppCategoryId.GAMES,
|
||||
primarySubcategoryOne: AppSubcategoryId.GAMES_CARD,
|
||||
primarySubcategoryTwo: AppSubcategoryId.GAMES_ADVENTURE,
|
||||
})
|
||||
);
|
||||
expect(writer.schema.categories).toEqual([
|
||||
[AppCategoryId.GAMES, AppSubcategoryId.GAMES_CARD, AppSubcategoryId.GAMES_ADVENTURE],
|
||||
]);
|
||||
});
|
||||
|
||||
it('modifies primary and secondary categories with one subcategories', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setCategories(
|
||||
makeCategoryInfo({
|
||||
primaryCategory: AppCategoryId.ENTERTAINMENT,
|
||||
secondaryCategory: AppCategoryId.GAMES,
|
||||
secondarySubcategoryOne: AppSubcategoryId.GAMES_CARD,
|
||||
})
|
||||
);
|
||||
expect(writer.schema.categories).toEqual([
|
||||
AppCategoryId.ENTERTAINMENT,
|
||||
[AppCategoryId.GAMES, AppSubcategoryId.GAMES_CARD],
|
||||
]);
|
||||
// We need to filter the lists to avoid writing `[GAMES, GAMES_CARD, null]`
|
||||
expect(writer.schema.categories![1]).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('modifies primary and secondary categories with two subcategories', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setCategories(
|
||||
makeCategoryInfo({
|
||||
primaryCategory: AppCategoryId.ENTERTAINMENT,
|
||||
secondaryCategory: AppCategoryId.GAMES,
|
||||
secondarySubcategoryOne: AppSubcategoryId.GAMES_CARD,
|
||||
secondarySubcategoryTwo: AppSubcategoryId.GAMES_ADVENTURE,
|
||||
})
|
||||
);
|
||||
expect(writer.schema.categories).toEqual([
|
||||
AppCategoryId.ENTERTAINMENT,
|
||||
[AppCategoryId.GAMES, AppSubcategoryId.GAMES_CARD, AppSubcategoryId.GAMES_ADVENTURE],
|
||||
]);
|
||||
});
|
||||
|
||||
it('modifies secondary category without primary', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setCategories(makeCategoryInfo({ secondaryCategory: AppCategoryId.GAMES }));
|
||||
expect(writer.schema.categories).toEqual(['', AppCategoryId.GAMES]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -99,18 +206,16 @@ describe('setVersionRelease', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('modifies manual release', () => {
|
||||
it('does not modify manual release', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setVersionRelease(manualRelease);
|
||||
expect(writer.schema.release).toMatchObject({
|
||||
automaticRelease: false,
|
||||
});
|
||||
expect(writer.schema.release).toBeUndefined();
|
||||
});
|
||||
|
||||
it('overwrites all release fields', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setVersionRelease(scheduledRelease);
|
||||
writer.setVersionRelease(manualRelease);
|
||||
writer.setVersionRelease(automaticRelease);
|
||||
expect(writer.schema.release).not.toHaveProperty('autoReleaseDate');
|
||||
});
|
||||
});
|
||||
@@ -149,3 +254,46 @@ describe('setVersionLocale', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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,9 +1,11 @@
|
||||
import {
|
||||
AgeRatingDeclaration,
|
||||
AppInfoLocalization,
|
||||
AppStoreReviewDetail,
|
||||
AppStoreVersion,
|
||||
AppStoreVersionLocalization,
|
||||
CategoryIds,
|
||||
Rating,
|
||||
ReleaseType,
|
||||
} from '@expo/apple-utils';
|
||||
|
||||
@@ -25,7 +27,31 @@ export class AppleConfigReader {
|
||||
public constructor(public readonly schema: AppleMetadata) {}
|
||||
|
||||
public getAgeRating(): Partial<AttributesOf<AgeRatingDeclaration>> | null {
|
||||
return this.schema.advisory || 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[] {
|
||||
@@ -43,7 +69,7 @@ export class AppleConfigReader {
|
||||
|
||||
return {
|
||||
locale,
|
||||
name: info.title ?? 'no name provided',
|
||||
name: info.title,
|
||||
subtitle: info.subtitle,
|
||||
privacyChoicesUrl: info.privacyChoicesUrl,
|
||||
privacyPolicyText: info.privacyPolicyText,
|
||||
@@ -52,14 +78,33 @@ export class AppleConfigReader {
|
||||
}
|
||||
|
||||
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],
|
||||
};
|
||||
const { categories } = this.schema;
|
||||
if (!categories || categories.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
// We validate the categories based on enums, but they will still be strings here.
|
||||
const categoryIds: Partial<Record<keyof CategoryIds, string>> = {};
|
||||
|
||||
if (Array.isArray(categories[0])) {
|
||||
categoryIds.primaryCategory = categories[0][0];
|
||||
categoryIds.primarySubcategoryOne = categories[0][1];
|
||||
categoryIds.primarySubcategoryTwo = categories[0][2];
|
||||
} else {
|
||||
categoryIds.primaryCategory = categories[0];
|
||||
}
|
||||
|
||||
if (Array.isArray(categories[1])) {
|
||||
categoryIds.secondaryCategory = categories[1][0];
|
||||
categoryIds.secondarySubcategoryOne = categories[1][1];
|
||||
categoryIds.secondarySubcategoryTwo = categories[1][2];
|
||||
} else {
|
||||
categoryIds.secondaryCategory = categories[1];
|
||||
}
|
||||
|
||||
// Because we handle categories as normal strings,
|
||||
// the type doesn't match with the actual CategoryIds types.
|
||||
return categoryIds as CategoryIds;
|
||||
}
|
||||
|
||||
/** Get the `AppStoreVersion` object. */
|
||||
@@ -119,4 +164,23 @@ export class AppleConfigReader {
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@ import {
|
||||
AgeRatingDeclaration,
|
||||
AppInfo,
|
||||
AppInfoLocalization,
|
||||
AppStoreReviewDetail,
|
||||
AppStoreVersion,
|
||||
AppStoreVersionLocalization,
|
||||
Rating,
|
||||
ReleaseType,
|
||||
} from '@expo/apple-utils';
|
||||
|
||||
@@ -26,7 +28,26 @@ export class AppleConfigWriter {
|
||||
}
|
||||
|
||||
public setAgeRating(attributes: AttributesOf<AgeRatingDeclaration>): void {
|
||||
this.schema.advisory = attributes;
|
||||
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 {
|
||||
@@ -35,7 +56,7 @@ export class AppleConfigWriter {
|
||||
|
||||
this.schema.info[attributes.locale] = {
|
||||
...existing,
|
||||
title: attributes.name ?? 'no name provided',
|
||||
title: attributes.name ?? '',
|
||||
subtitle: optional(attributes.subtitle),
|
||||
privacyPolicyUrl: optional(attributes.privacyPolicyUrl),
|
||||
privacyPolicyText: optional(attributes.privacyPolicyText),
|
||||
@@ -43,15 +64,44 @@ export class AppleConfigWriter {
|
||||
};
|
||||
}
|
||||
|
||||
public setCategories({ primaryCategory, secondaryCategory }: AttributesOf<AppInfo>): void {
|
||||
public setCategories(
|
||||
attributes: Pick<
|
||||
AttributesOf<AppInfo>,
|
||||
| 'primaryCategory'
|
||||
| 'primarySubcategoryOne'
|
||||
| 'primarySubcategoryTwo'
|
||||
| 'secondaryCategory'
|
||||
| 'secondarySubcategoryOne'
|
||||
| 'secondarySubcategoryTwo'
|
||||
>
|
||||
): void {
|
||||
this.schema.categories = undefined;
|
||||
if (!attributes.primaryCategory && !attributes.secondaryCategory) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
if (attributes.primaryCategory && attributes.primarySubcategoryOne) {
|
||||
this.schema.categories[0] = [
|
||||
attributes.primaryCategory.id,
|
||||
attributes.primarySubcategoryOne?.id,
|
||||
attributes.primarySubcategoryTwo?.id,
|
||||
].filter(Boolean) as string[];
|
||||
} else {
|
||||
// If only the secondaryCategory was provided,
|
||||
// autofill with an empty string and cause a store config error.
|
||||
this.schema.categories[0] = attributes.primaryCategory?.id ?? '';
|
||||
}
|
||||
|
||||
if (attributes.secondaryCategory && attributes.secondarySubcategoryOne) {
|
||||
this.schema.categories[1] = [
|
||||
attributes.secondaryCategory.id,
|
||||
attributes.secondarySubcategoryOne?.id,
|
||||
attributes.secondarySubcategoryTwo?.id,
|
||||
].filter(Boolean) as string[];
|
||||
} else if (attributes.secondaryCategory) {
|
||||
this.schema.categories[1] = attributes.secondaryCategory.id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,9 +127,9 @@ export class AppleConfigWriter {
|
||||
}
|
||||
|
||||
if (attributes.releaseType === ReleaseType.MANUAL) {
|
||||
this.schema.release = {
|
||||
automaticRelease: false,
|
||||
};
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +149,20 @@ export class AppleConfigWriter {
|
||||
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 */
|
||||
|
||||
@@ -2,13 +2,14 @@ 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;
|
||||
export type AppleData = { app: App } & AppInfoData & AppVersionData & AgeRatingData & AppReviewData;
|
||||
|
||||
/**
|
||||
* The unprepared partial apple data, used within the `prepareAsync` tasks.
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"data": null,
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreReviewDetail?include=appStoreReviewAttachments"
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { AppStoreReviewDetail } from '@expo/apple-utils';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import Log from '../../../log';
|
||||
import { logAsync } from '../../utils/log';
|
||||
import { AppleTask, TaskDownloadOptions, TaskPrepareOptions, TaskUploadOptions } from '../task';
|
||||
|
||||
export type AppReviewData = {
|
||||
/** The current app info that should be edited */
|
||||
reviewDetail: AppStoreReviewDetail;
|
||||
};
|
||||
|
||||
/** Handle all contact, demo account, or instruction info that are required for the App Store review team. */
|
||||
export class AppReviewDetailTask extends AppleTask {
|
||||
public name = (): string => 'app review detail';
|
||||
|
||||
public async prepareAsync({ context }: TaskPrepareOptions): Promise<void> {
|
||||
assert(context.version, `App version not initialized, can't download store review details`);
|
||||
context.reviewDetail = (await context.version.getAppStoreReviewDetailAsync()) ?? undefined;
|
||||
}
|
||||
|
||||
public async downloadAsync({ config, context }: TaskDownloadOptions): Promise<void> {
|
||||
if (context.reviewDetail) {
|
||||
config.setReviewDetails(context.reviewDetail.attributes);
|
||||
}
|
||||
}
|
||||
|
||||
public async uploadAsync({ config, context }: TaskUploadOptions): Promise<void> {
|
||||
const reviewDetail = config.getReviewDetails();
|
||||
if (!reviewDetail) {
|
||||
return Log.log(chalk`{dim - Skipped store review details, not configured}`);
|
||||
}
|
||||
|
||||
assert(context.version, `App version not initialized, can't upload store review details`);
|
||||
const { versionString } = context.version.attributes;
|
||||
|
||||
if (!context.reviewDetail) {
|
||||
// We can't set the demo required property when creating, omit it from the request
|
||||
const { demoAccountRequired, ...reviewDetailsToCreate } = reviewDetail;
|
||||
|
||||
context.reviewDetail = await logAsync(
|
||||
() => context.version.createReviewDetailAsync(reviewDetailsToCreate),
|
||||
{
|
||||
pending: `Creating store review details for ${chalk.bold(versionString)}...`,
|
||||
success: `Created store review details for ${chalk.bold(versionString)}`,
|
||||
failure: `Failed creating store review details for ${chalk.bold(versionString)}`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
context.reviewDetail = await logAsync(() => context.reviewDetail.updateAsync(reviewDetail), {
|
||||
pending: `Updating store review details for ${chalk.bold(versionString)}...`,
|
||||
success: `Updated store review details for ${chalk.bold(versionString)}`,
|
||||
failure: `Failed updating store review details for ${chalk.bold(versionString)}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -80,8 +80,8 @@ export class AppVersionTask extends AppleTask {
|
||||
() => context.version.updateAsync({ ...version, ...release }),
|
||||
{
|
||||
pending: `Updating ${description} info for ${chalk.bold(versionString)}...`,
|
||||
success: `Updated ${description} info for ${chalk.bold(versionString)}...`,
|
||||
failure: `Failed updating ${description} info for ${chalk.bold(versionString)}...`,
|
||||
success: `Updated ${description} info for ${chalk.bold(versionString)}`,
|
||||
failure: `Failed updating ${description} info for ${chalk.bold(versionString)}`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ import { MetadataContext } from '../../context';
|
||||
import { AppleTask } from '../task';
|
||||
import { AgeRatingTask } from './age-rating';
|
||||
import { AppInfoTask } from './app-info';
|
||||
import { AppReviewDetailTask } from './app-review-detail';
|
||||
import { AppVersionTask } from './app-version';
|
||||
|
||||
/**
|
||||
* List of all eligible tasks to sync local store configuration to the App store.
|
||||
*/
|
||||
export function createAppleTasks(_ctx: MetadataContext): AppleTask[] {
|
||||
return [new AppVersionTask(), new AppInfoTask(), new AgeRatingTask()];
|
||||
return [new AppVersionTask(), new AppInfoTask(), new AgeRatingTask(), new AppReviewDetailTask()];
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { AgeRatingDeclarationProps, AppCategoryId, AppSubcategoryId } from '@expo/apple-utils';
|
||||
import type { AgeRatingDeclarationProps } from '@expo/apple-utils';
|
||||
|
||||
export type AppleLocale = string;
|
||||
|
||||
export interface AppleMetadata {
|
||||
copyright?: string;
|
||||
info?: Record<AppleLocale, AppleInfo>;
|
||||
categories?: AppCategoryId[] | AppleCategory;
|
||||
categories?: AppleCategory;
|
||||
release?: AppleRelease;
|
||||
advisory?: AppleAdvisory;
|
||||
preview?: Record<string, string[]>;
|
||||
@@ -14,10 +14,8 @@ export interface AppleMetadata {
|
||||
|
||||
export type AppleAdvisory = Partial<AgeRatingDeclarationProps>;
|
||||
|
||||
export interface AppleCategory {
|
||||
category: AppCategoryId;
|
||||
subcategory?: AppSubcategoryId[];
|
||||
}
|
||||
/** Apps can define up to two categories, or categories with up to two subcategories */
|
||||
export type AppleCategory = (string | string[])[];
|
||||
|
||||
export interface AppleRelease {
|
||||
isPhasedReleaseEnabled?: boolean;
|
||||
@@ -45,12 +43,13 @@ export interface AppleInfo {
|
||||
}
|
||||
|
||||
export interface AppleReview {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
demoUsername?: string;
|
||||
demoPassword?: string;
|
||||
demoRequired?: boolean;
|
||||
notes?: string;
|
||||
attachment?: string;
|
||||
// attachment?: string;
|
||||
}
|
||||
@@ -46,18 +46,26 @@ export class MetadataDownloadError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the encountered metadata validation error in detail for the user.
|
||||
* This should help communicate any possible configuration error and help the user resolve it.
|
||||
*/
|
||||
export function logMetadataValidationError(error: MetadataValidationError): void {
|
||||
Log.newLine();
|
||||
Log.error(chalk.bold(error.message));
|
||||
if (error.errors?.length > 0) {
|
||||
// TODO(cedric): group errors by property to make multiple errors for same property more readable
|
||||
Log.log(error.errors.map(err => ` - ${chalk.bold(err.dataPath)} ${err.message}`).join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a thrown metadata error by informing the user what went wrong.
|
||||
* If a normal error is thrown, this method will re-throw that error to avoid consuming it.
|
||||
*/
|
||||
export function handleMetadataError(error: Error): void {
|
||||
if (error instanceof MetadataValidationError) {
|
||||
Log.newLine();
|
||||
Log.error(chalk.bold(error.message));
|
||||
if (error.errors?.length > 0) {
|
||||
Log.log(error.errors.map(err => ` - ${err.dataPath} ${err.message}`).join('\n'));
|
||||
}
|
||||
return;
|
||||
return logMetadataValidationError(error);
|
||||
}
|
||||
|
||||
if (error instanceof MetadataDownloadError || error instanceof MetadataUploadError) {
|
||||
|
||||
@@ -3,11 +3,12 @@ import path from 'path';
|
||||
|
||||
import { MetadataEvent } from '../analytics/events';
|
||||
import Log from '../log';
|
||||
import { confirmAsync } from '../prompts';
|
||||
import { AppleData } from './apple/data';
|
||||
import { createAppleTasks } from './apple/tasks';
|
||||
import { createAppleReader, validateConfig } from './config';
|
||||
import { MetadataContext, ensureMetadataAppStoreAuthenticatedAsync } from './context';
|
||||
import { MetadataUploadError, MetadataValidationError } from './errors';
|
||||
import { MetadataUploadError, MetadataValidationError, logMetadataValidationError } from './errors';
|
||||
import { subscribeTelemetry } from './utils/telemetry';
|
||||
|
||||
/**
|
||||
@@ -22,18 +23,29 @@ export async function uploadMetadataAsync(
|
||||
throw new MetadataValidationError(`Store configuration file not found "${filePath}"`);
|
||||
}
|
||||
|
||||
const fileData = await fs.readJson(filePath);
|
||||
const { valid, errors: validationErrors } = validateConfig(fileData);
|
||||
if (!valid) {
|
||||
const error = new MetadataValidationError(`Store configuration errors found`, validationErrors);
|
||||
logMetadataValidationError(error);
|
||||
Log.newLine();
|
||||
Log.warn(
|
||||
'Without further updates, the current store configuration may fail to be synchronized with the App Store or pass App Store review.'
|
||||
);
|
||||
const attempt = await confirmAsync({
|
||||
message: 'Do you still want to push the store configuration?',
|
||||
});
|
||||
if (!attempt) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const { app, auth } = await ensureMetadataAppStoreAuthenticatedAsync(metadataCtx);
|
||||
const { unsubscribeTelemetry, executionId } = subscribeTelemetry(
|
||||
MetadataEvent.APPLE_METADATA_UPLOAD,
|
||||
{ app, auth }
|
||||
);
|
||||
|
||||
const fileData = await fs.readJson(filePath);
|
||||
const { valid, errors: validationErrors } = validateConfig(fileData);
|
||||
if (!valid) {
|
||||
throw new MetadataValidationError(`Store configuration errors found`, validationErrors);
|
||||
}
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log('Uploading App Store configuration...');
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import { PublishMutation } from '../../graphql/mutations/PublishMutation';
|
||||
import { PublishQuery } from '../../graphql/queries/PublishQuery';
|
||||
import {
|
||||
MetadataJoi,
|
||||
TIMEOUT_LIMIT,
|
||||
buildUnsortedUpdateInfoGroupAsync,
|
||||
collectAssetsAsync,
|
||||
convertAssetToUpdateInfoGroupFormatAsync,
|
||||
@@ -412,38 +411,6 @@ describe(uploadAssetsAsync, () => {
|
||||
return { specifications: ['{}', '{}', '{}'] };
|
||||
});
|
||||
|
||||
// beforeEach(() => {
|
||||
// jest.useFakeTimers();
|
||||
// });
|
||||
it('throws an error if the upload exceeds TIMEOUT_LIMIT', async () => {
|
||||
jest.spyOn(PublishQuery, 'getAssetMetadataAsync').mockImplementation(async () => {
|
||||
const status = AssetMetadataStatus.DoesNotExist;
|
||||
mockdate.set(Date.now() + TIMEOUT_LIMIT + 1);
|
||||
jest.runAllTimers();
|
||||
return [
|
||||
{
|
||||
storageKey: 'qbgckgkgfdjnNuf9dQd7FDTWUmlEEzg7l1m1sKzQaq0',
|
||||
status,
|
||||
__typename: 'AssetMetadataResult',
|
||||
}, // userDefinedAsset
|
||||
{
|
||||
storageKey: 'bbjgXFSIXtjviGwkaPFY0HG4dVVIGiXHAboRFQEqVa4',
|
||||
status,
|
||||
__typename: 'AssetMetadataResult',
|
||||
}, // android.code
|
||||
{
|
||||
storageKey: 'dP-nC8EJXKz42XKh_Rc9tYxiGAT-ilpkRltEi6HIKeQ',
|
||||
status,
|
||||
__typename: 'AssetMetadataResult',
|
||||
}, // ios.code
|
||||
];
|
||||
});
|
||||
|
||||
mockdate.set(0);
|
||||
await expect(uploadAssetsAsync(assetsForUpdateInfoGroup)).rejects.toThrow(
|
||||
'Asset upload timed out. Please try again.'
|
||||
);
|
||||
});
|
||||
it('resolves if the assets are already uploaded', async () => {
|
||||
jest.spyOn(PublishQuery, 'getAssetMetadataAsync').mockImplementation(async () => {
|
||||
const status = AssetMetadataStatus.Exists;
|
||||
@@ -530,15 +497,15 @@ describe(uploadAssetsAsync, () => {
|
||||
}, // ios.code
|
||||
];
|
||||
});
|
||||
const updateSpinnerFn = jest.fn(_text => {});
|
||||
const updateSpinnerFn = jest.fn((_totalAssets, _missingAssets) => {});
|
||||
|
||||
mockdate.set(0);
|
||||
await uploadAssetsAsync(assetsForUpdateInfoGroup, updateSpinnerFn);
|
||||
const calls = updateSpinnerFn.mock.calls;
|
||||
expect(calls).toEqual([
|
||||
['6 assets present'],
|
||||
['3 unique assets found'],
|
||||
['2 new assets uploading'],
|
||||
[3, 3],
|
||||
[3, 2],
|
||||
[3, 0],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import fs from 'fs-extra';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { readAppJson } from '../../build/utils/appJson';
|
||||
import env from '../../env';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { getProjectConfigDescription, getUsername } from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
@@ -43,6 +44,9 @@ export async function getApplicationIdFromBareAsync(
|
||||
projectDir: string,
|
||||
gradleContext?: GradleBuildContext
|
||||
): Promise<string> {
|
||||
if (env.overrideAndroidApplicationId) {
|
||||
return env.overrideAndroidApplicationId;
|
||||
}
|
||||
const errorMessage = 'Could not read applicationId from Android project.';
|
||||
|
||||
if (gradleContext) {
|
||||
|
||||
@@ -11,13 +11,10 @@ import { AssetMetadataStatus, PartialManifestAsset } from '../graphql/generated'
|
||||
import { PublishMutation } from '../graphql/mutations/PublishMutation';
|
||||
import { PresignedPost } from '../graphql/mutations/UploadSessionMutation';
|
||||
import { PublishQuery } from '../graphql/queries/PublishQuery';
|
||||
import Log from '../log';
|
||||
import { uploadWithPresignedPostAsync } from '../uploads';
|
||||
import { expoCommandAsync } from '../utils/expoCli';
|
||||
import uniqBy from '../utils/expodash/uniqBy';
|
||||
|
||||
export const TIMEOUT_LIMIT = 60_000; // 1 minute
|
||||
|
||||
export type PublishPlatform = Extract<'android' | 'ios', Platform>;
|
||||
type Metadata = {
|
||||
version: number;
|
||||
@@ -158,11 +155,13 @@ export async function buildBundlesAsync({
|
||||
throw new Error('Could not locate package.json');
|
||||
}
|
||||
|
||||
await expoCommandAsync(
|
||||
projectDir,
|
||||
['export', '--output-dir', inputDir, '--experimental-bundle'],
|
||||
{ silent: !Log.isDebug }
|
||||
);
|
||||
await expoCommandAsync(projectDir, [
|
||||
'export',
|
||||
'--output-dir',
|
||||
inputDir,
|
||||
'--experimental-bundle',
|
||||
'--non-interactive',
|
||||
]);
|
||||
}
|
||||
|
||||
export async function resolveInputDirectoryAsync(customInputDirectory: string): Promise<string> {
|
||||
@@ -254,7 +253,7 @@ type AssetUploadResult = {
|
||||
|
||||
export async function uploadAssetsAsync(
|
||||
assetsForUpdateInfoGroup: CollectedAssets,
|
||||
updateSpinnerText?: (updatedText: string) => void
|
||||
updateSpinnerText?: (totalAssets: number, missingAssets: number) => void
|
||||
): Promise<AssetUploadResult> {
|
||||
let assets: RawAsset[] = [];
|
||||
let platform: keyof CollectedAssets;
|
||||
@@ -265,7 +264,6 @@ export async function uploadAssetsAsync(
|
||||
...assetsForUpdateInfoGroup[platform]!.assets,
|
||||
];
|
||||
}
|
||||
updateSpinnerText?.(`${assets.length} ${assets.length === 1 ? 'asset' : 'assets'} present`);
|
||||
|
||||
const assetsWithStorageKey = await Promise.all(
|
||||
assets.map(async asset => {
|
||||
@@ -280,9 +278,10 @@ export async function uploadAssetsAsync(
|
||||
storageKey: string;
|
||||
}
|
||||
>(assetsWithStorageKey, asset => asset.storageKey);
|
||||
updateSpinnerText?.(
|
||||
`${uniqueAssets.length} unique ${uniqueAssets.length === 1 ? 'asset' : 'assets'} found`
|
||||
);
|
||||
|
||||
const totalAssets = uniqueAssets.length;
|
||||
|
||||
updateSpinnerText?.(totalAssets, totalAssets);
|
||||
|
||||
let missingAssets = await filterOutAssetsThatAlreadyExistAsync(uniqueAssets);
|
||||
const uniqueUploadedAssetCount = missingAssets.length;
|
||||
@@ -290,32 +289,28 @@ export async function uploadAssetsAsync(
|
||||
missingAssets.map(ma => ma.contentType)
|
||||
);
|
||||
|
||||
updateSpinnerText?.(totalAssets, missingAssets.length);
|
||||
|
||||
const assetUploadPromiseLimit = promiseLimit(15);
|
||||
|
||||
await Promise.all(
|
||||
missingAssets.map((missingAsset, i) => {
|
||||
assetUploadPromiseLimit(async () => {
|
||||
const presignedPost: PresignedPost = JSON.parse(specifications[i]);
|
||||
return uploadWithPresignedPostAsync(missingAsset.path, presignedPost);
|
||||
await uploadWithPresignedPostAsync(missingAsset.path, presignedPost);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
updateSpinnerText?.(
|
||||
`${missingAssets.length} new ${missingAssets.length === 1 ? 'asset' : 'assets'} uploading`
|
||||
);
|
||||
// Wait up to TIMEOUT_LIMIT for assets to be uploaded and processed
|
||||
const start = Date.now();
|
||||
let timeout = 1;
|
||||
while (missingAssets.length > 0) {
|
||||
const timeoutPromise = new Promise(resolve => setTimeout(resolve, timeout * 1000)); // linear backoff
|
||||
const timeoutPromise = new Promise(resolve =>
|
||||
setTimeout(resolve, Math.min(timeout * 1000, 5000))
|
||||
); // linear backoff
|
||||
missingAssets = await filterOutAssetsThatAlreadyExistAsync(missingAssets);
|
||||
await timeoutPromise; // await after filterOutAssetsThatAlreadyExistAsync for easy mocking with jest.runAllTimers
|
||||
timeout += 1;
|
||||
|
||||
if (Date.now() - start > TIMEOUT_LIMIT) {
|
||||
throw new Error('Asset upload timed out. Please try again.');
|
||||
}
|
||||
updateSpinnerText?.(totalAssets, missingAssets.length);
|
||||
}
|
||||
return {
|
||||
assetCount: assets.length,
|
||||
|
||||
@@ -9,23 +9,26 @@ export async function resolveWorkflowAsync(
|
||||
projectDir: string,
|
||||
platform: Platform
|
||||
): Promise<Workflow> {
|
||||
let platformWorkflowMarker;
|
||||
let platformWorkflowMarkers: string[];
|
||||
try {
|
||||
platformWorkflowMarker =
|
||||
platformWorkflowMarkers =
|
||||
platform === Platform.ANDROID
|
||||
? await AndroidConfig.Paths.getAndroidManifestAsync(projectDir)
|
||||
: IOSConfig.Paths.getPBXProjectPath(projectDir);
|
||||
? [
|
||||
path.join(projectDir, 'android/app/build.gradle'),
|
||||
await AndroidConfig.Paths.getAndroidManifestAsync(projectDir),
|
||||
]
|
||||
: [IOSConfig.Paths.getPBXProjectPath(projectDir)];
|
||||
} catch {
|
||||
return Workflow.MANAGED;
|
||||
}
|
||||
|
||||
if (await fs.pathExists(platformWorkflowMarker)) {
|
||||
return (await getVcsClient().isFileIgnoredAsync(
|
||||
path.relative(projectDir, platformWorkflowMarker)
|
||||
))
|
||||
? Workflow.MANAGED
|
||||
: Workflow.GENERIC;
|
||||
} else {
|
||||
return Workflow.MANAGED;
|
||||
for (const marker of platformWorkflowMarkers) {
|
||||
if (
|
||||
(await fs.pathExists(marker)) &&
|
||||
!(await getVcsClient().isFileIgnoredAsync(path.relative(projectDir, marker)))
|
||||
) {
|
||||
return Workflow.GENERIC;
|
||||
}
|
||||
}
|
||||
return Workflow.MANAGED;
|
||||
}
|
||||
@@ -242,6 +242,7 @@ describe(getArchiveAsync, () => {
|
||||
it('handles path source', async () => {
|
||||
const path = '/archive.apk';
|
||||
await fs.writeFile(path, 'some content');
|
||||
|
||||
jest.mocked(uploadAsync).mockResolvedValueOnce({ url: ARCHIVE_URL, bucketKey: 'wat' });
|
||||
|
||||
const archive = await getArchiveAsync({
|
||||
|
||||
@@ -4,10 +4,12 @@ import {
|
||||
PublishPlatformFlag,
|
||||
ensureBranchExistsAsync,
|
||||
getUpdatesToRepublishInteractiveAsync,
|
||||
truncatePublishUpdateMessage,
|
||||
} from '../../commands/update';
|
||||
import { graphqlClient } from '../../graphql/client';
|
||||
import { ViewBranchUpdatesQuery } from '../../graphql/generated';
|
||||
import { viewBranchUpdatesQueryUpdateLimit } from '../../graphql/queries/UpdateQuery';
|
||||
import Log from '../../log';
|
||||
import { selectAsync } from '../../prompts';
|
||||
|
||||
const appId = '6c94sxe6-37d2-4700-52fa-1b813204dad2';
|
||||
@@ -300,4 +302,28 @@ describe('UpdatePublish', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe(truncatePublishUpdateMessage.name, () => {
|
||||
const warnSpy = jest.spyOn(Log, 'warn');
|
||||
beforeEach(() => {
|
||||
warnSpy.mockClear();
|
||||
});
|
||||
|
||||
it('does not alter messages with less than 1024 characters', () => {
|
||||
const message = 'Small message =)';
|
||||
const truncatedMessage = truncatePublishUpdateMessage(message);
|
||||
expect(truncatedMessage).toEqual(message);
|
||||
expect(warnSpy).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('truncates messages to a length of 1024, including ellipses', () => {
|
||||
const longMessage = Array.from({ length: 2024 }, () => 'a').join('');
|
||||
const truncatedMessage = truncatePublishUpdateMessage(longMessage);
|
||||
expect(truncatedMessage.length).toEqual(1024);
|
||||
expect(truncatedMessage.slice(-3)).toEqual('...');
|
||||
expect(warnSpy).toBeCalledWith(
|
||||
'Update message exceeds the allowed 1024 character limit. Truncating message...'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,9 @@
|
||||
import assert from 'assert';
|
||||
import FormData from 'form-data';
|
||||
import fs from 'fs-extra';
|
||||
import { Response } from 'node-fetch';
|
||||
import nullthrows from 'nullthrows';
|
||||
import { URL } from 'url';
|
||||
|
||||
import fetch from './fetch';
|
||||
import { UploadSessionType } from './graphql/generated';
|
||||
@@ -13,8 +16,14 @@ export async function uploadAsync(
|
||||
handleProgressEvent: ProgressHandler
|
||||
): Promise<{ url: string; bucketKey: string }> {
|
||||
const presignedPost = await UploadSessionMutation.createUploadSessionAsync(type);
|
||||
const url = await uploadWithPresignedPostAsync(path, presignedPost, handleProgressEvent);
|
||||
assert(presignedPost.fields.key, 'key is not specified in in presigned post');
|
||||
|
||||
const response = await uploadWithPresignedPostAsync(path, presignedPost, handleProgressEvent);
|
||||
const location = nullthrows(
|
||||
response.headers.get('location'),
|
||||
`location does not exist in response headers (make sure you're uploading to AWS S3)`
|
||||
);
|
||||
const url = fixS3Url(location);
|
||||
return { url, bucketKey: presignedPost.fields.key };
|
||||
}
|
||||
|
||||
@@ -22,7 +31,7 @@ export async function uploadWithPresignedPostAsync(
|
||||
file: string,
|
||||
presignedPost: PresignedPost,
|
||||
handleProgressEvent?: ProgressHandler
|
||||
): Promise<string> {
|
||||
): Promise<Response> {
|
||||
const fileStat = await fs.stat(file);
|
||||
const fileSize = fileStat.size;
|
||||
const form = new FormData();
|
||||
@@ -54,13 +63,23 @@ export async function uploadWithPresignedPostAsync(
|
||||
try {
|
||||
const response = await uploadPromise;
|
||||
handleProgressEvent({ isComplete: true });
|
||||
return String(response.headers.get('location'));
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
handleProgressEvent({ isComplete: true, error });
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
const response = await uploadPromise;
|
||||
return String(response.headers.get('location'));
|
||||
return await uploadPromise;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* S3 returns broken URLs, sth like:
|
||||
* https://submission-service-archives.s3.amazonaws.com/production%2Fdc98ca84-1473-4cb3-ae81-8c7b291cb27e%2F4424aa95-b985-4e2f-8755-9507b1037c1c
|
||||
* This function replaces %2F with /.
|
||||
*/
|
||||
export function fixS3Url(archiveUrl: string): string {
|
||||
const parsed = new URL(archiveUrl);
|
||||
parsed.pathname = decodeURIComponent(parsed.pathname);
|
||||
return parsed.toString();
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"description": "A library for interacting with eas.json",
|
||||
"version": "0.54.0",
|
||||
"version": "0.55.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
@@ -18,7 +18,7 @@
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"memfs": "3.4.3",
|
||||
"typescript": "4.6.4"
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
|
||||
@@ -34,7 +34,7 @@ function resolveProfile({
|
||||
profileName: string;
|
||||
depth?: number;
|
||||
}): EasJsonBuildProfileResolved {
|
||||
if (depth >= 2) {
|
||||
if (depth >= 5) {
|
||||
throw new Error(
|
||||
'Too long chain of profile extensions, make sure "extends" keys do not make a cycle'
|
||||
);
|
||||
|
||||
File renamed without changes.
@@ -1,33 +1,34 @@
|
||||
{
|
||||
"name": "scripts",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "0.31.0",
|
||||
"scripts": {
|
||||
"changelog-entry": "ts-node src/changelogEntry.ts",
|
||||
"release-changelog": "ts-node src/releaseChangelog.ts",
|
||||
"update-local-plugin": "./src/updateLocalPlugin.sh"
|
||||
"changelog-entry": "ts-node-esm src/changelogEntry.ts",
|
||||
"release-changelog": "ts-node-esm src/releaseChangelog.ts",
|
||||
"update-local-plugin": "./src/updateLocalPlugin.sh",
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"author": "Expo <[email protected]>",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"dateformat": "4.5.1",
|
||||
"fs-extra": "10.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"lodash-es": "4.17.21",
|
||||
"marked": "3.0.4",
|
||||
"nullthrows": "1.1.1",
|
||||
"semver": "7.3.7",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dateformat": "3.0.1",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/lodash": "4.14.182",
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@types/node": "17.0.32",
|
||||
"@types/semver": "7.3.9",
|
||||
"ts-node": "10.7.0",
|
||||
"typescript": "4.6.4"
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import path from 'path';
|
||||
import { URL } from 'node:url';
|
||||
|
||||
export enum EntryCategory {
|
||||
BreakingChange = 'breaking-change',
|
||||
@@ -7,7 +7,7 @@ export enum EntryCategory {
|
||||
Chore = 'chore',
|
||||
}
|
||||
|
||||
export const CHANGELOG_PATH = path.join(__dirname, '../../../CHANGELOG.md');
|
||||
export const CHANGELOG_PATH = new URL('../../../CHANGELOG.md', import.meta.url);
|
||||
|
||||
export const CATEGORY_HEADERS: Record<EntryCategory, string> = {
|
||||
[EntryCategory.BreakingChange]: '🛠 Breaking changes',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import * as markdown from '../markdown';
|
||||
import { CHANGELOG_PATH } from './consts';
|
||||
import * as markdown from '../markdown.js';
|
||||
import { CHANGELOG_PATH } from './consts.js';
|
||||
|
||||
export async function readAndParseChangelogAsync(): Promise<markdown.Tokens> {
|
||||
const contents = await fs.readFile(CHANGELOG_PATH, 'utf8');
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { CATEGORY_HEADERS, EntryCategory } from './changelog/consts';
|
||||
import { readAndParseChangelogAsync, writeChangelogAsync } from './changelog/file';
|
||||
import * as markdown from './markdown';
|
||||
import { CATEGORY_HEADERS, EntryCategory } from './changelog/consts.js';
|
||||
import { readAndParseChangelogAsync, writeChangelogAsync } from './changelog/file.js';
|
||||
import * as markdown from './markdown.js';
|
||||
import { nullthrows } from './nullthrows.js';
|
||||
|
||||
const [rawCategory, ...rest] = process.argv.slice(2);
|
||||
const message = rest.join(' ');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// this is mostly copy-pasted from https://github.com/expo/expo/blob/master/tools/src/Markdown.ts
|
||||
|
||||
import { unescape } from 'lodash';
|
||||
import { unescape } from 'lodash-es';
|
||||
import marked from 'marked';
|
||||
|
||||
export enum TokenType {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export function nullthrows<T>(value: T | null | undefined, message?: string): T {
|
||||
if (value !== null && value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const error = new Error(message !== undefined ? message : `Got unexpected ${value}`);
|
||||
(error as any).framesToPop = 1; // Skip nullthrows's own stack frame.
|
||||
throw error;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import dateFormat from 'dateformat';
|
||||
import semver from 'semver';
|
||||
|
||||
import { CATEGORY_HEADERS } from './changelog/consts';
|
||||
import { readAndParseChangelogAsync, writeChangelogAsync } from './changelog/file';
|
||||
import * as markdown from './markdown';
|
||||
import { CATEGORY_HEADERS } from './changelog/consts.js';
|
||||
import { readAndParseChangelogAsync, writeChangelogAsync } from './changelog/file.js';
|
||||
import * as markdown from './markdown.js';
|
||||
|
||||
const MAIN_CATEGORIES = Object.values(CATEGORY_HEADERS).map(text =>
|
||||
markdown.createHeadingToken(text, 3)
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"importHelpers": true,
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"module": "node16",
|
||||
"moduleResolution": "node16",
|
||||
"noImplicitReturns": true,
|
||||
"strict": true,
|
||||
"target": "ES2019",
|
||||
"target": "ES2020",
|
||||
"outDir": "build",
|
||||
"rootDir": "src",
|
||||
"typeRoots": [
|
||||
|
||||
Generated
+15
-13
@@ -46,7 +46,14 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/[email protected]4.182":
|
||||
"@types/lodash-es@4.17.6":
|
||||
version "4.17.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.6.tgz#c2ed4c8320ffa6f11b43eb89e9eaeec65966a0a0"
|
||||
integrity sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash@*":
|
||||
version "4.14.182"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
|
||||
integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
|
||||
@@ -119,10 +126,10 @@ jsonfile@^6.0.1:
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
[email protected]:
|
||||
lodash-es@4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||
|
||||
lru-cache@^6.0.0:
|
||||
version "6.0.0"
|
||||
@@ -141,11 +148,6 @@ [email protected]:
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-3.0.4.tgz#b8a1539e5e05c6ea9e93f15c0bad1d54ce890406"
|
||||
integrity sha512-jBo8AOayNaEcvBhNobg6/BLhdsK3NvnKWJg33MAAPbvTWiG4QBn9gpW1+7RssrKu4K1dKlN+0goVQwV41xEfOA==
|
||||
|
||||
[email protected]:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1"
|
||||
integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==
|
||||
|
||||
[email protected]:
|
||||
version "7.3.7"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
|
||||
@@ -177,10 +179,10 @@ [email protected]:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
||||
|
||||
typescript@4.6.4:
|
||||
version "4.6.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
|
||||
integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
|
||||
typescript@4.7.4:
|
||||
version "4.7.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
|
||||
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
|
||||
|
||||
universalify@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
||||
@@ -12288,10 +12288,10 @@ typedarray@^0.0.6:
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript@4.6.4:
|
||||
version "4.6.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
|
||||
integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
|
||||
typescript@4.7.4:
|
||||
version "4.7.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
|
||||
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
|
||||
|
||||
ua-parser-js@^0.7.30:
|
||||
version "0.7.31"
|
||||
|
||||
Reference in new issue
Block a user