mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd7a3912c6 | ||
|
|
77ac0550a9 | ||
|
|
f9978baba2 | ||
|
|
2023f6161d | ||
|
|
a4f35466c3 | ||
|
|
5f73b0a2d7 | ||
|
|
d194f18b8e | ||
|
|
17e1a145e8 | ||
|
|
7ebb547fd9 | ||
|
|
6eca9ad564 | ||
|
|
229f00a8ed | ||
|
|
2bbf7f2b6d | ||
|
|
d578abc69a | ||
|
|
89253f5767 | ||
|
|
dd5afd354a | ||
|
|
bdcc768354 | ||
|
|
9cd2bafac5 | ||
|
|
69515ab47b | ||
|
|
ac5f7ab186 | ||
|
|
265c7eacc5 | ||
|
|
98229581ca | ||
|
|
a718fd10d3 | ||
|
|
78a63b8ca1 | ||
|
|
7487f413de | ||
|
|
da5f4e644a | ||
|
|
be38117012 | ||
|
|
8f3314f188 | ||
|
|
31b6cc9ab6 | ||
|
|
04a8b08f59 | ||
|
|
8bce9b19b2 | ||
|
|
d3d6d41f9f | ||
|
|
0534328dfc |
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
|
||||
|
||||
@@ -8,11 +8,48 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add support for attaching messages to builds. ([#1237](https://github.com/expo/eas-cli/pull/1237) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [0.56.0](https://github.com/expo/eas-cli/releases/tag/v0.56.0) - 2022-07-28
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Display build queue type when waiting. ([#1217](https://github.com/expo/eas-cli/pull/1217) by [@dsokal](https://github.com/dsokal))
|
||||
- Add better message on how to upgrade EAS CLI. ([#1222](https://github.com/expo/eas-cli/pull/1222) by [@jeremybarbet](https://github.com/jeremybarbet), [#1231](https://github.com/expo/eas-cli/pull/1231) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Invoke export with sourcemaps on update. ([#1228](https://github.com/expo/eas-cli/pull/1228) by [@kbrandwijk](https://github.com/kbrandwijk))
|
||||
- Add new option to `eas device:create` - allow importing devices from Apple Developer Portal. ([#1236](https://github.com/expo/eas-cli/pull/1236) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 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))
|
||||
- `autoIncrement` option on iOS will update versions of all targets that depend on an application target. ([#1219](https://github.com/expo/eas-cli/pull/1219) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🧹 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))
|
||||
- Change wording on build warning for bundle id/package name. ([#1211](https://github.com/expo/eas-cli/pull/1211) by [@kbrandwijk](https://github.com/kbrandwijk))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -22,6 +59,8 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
- 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
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ 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/src/update @kgc00 @wschurman
|
||||
packages/eas-cli/src/commands/update @kgc00 @wschurman
|
||||
|
||||
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.56.0"
|
||||
}
|
||||
+3
-3
@@ -29,10 +29,10 @@
|
||||
"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",
|
||||
"yarn": "1.22.18"
|
||||
"node": "18.6.0",
|
||||
"yarn": "1.22.19"
|
||||
}
|
||||
}
|
||||
+44
-44
@@ -100,7 +100,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.56.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -117,7 +117,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.56.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -134,7 +134,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.56.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -148,7 +148,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.56.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -197,7 +197,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.56.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -217,7 +217,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.56.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -234,7 +234,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.56.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -253,7 +253,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.56.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -273,7 +273,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.56.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -302,7 +302,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.56.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -316,7 +316,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.56.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -333,7 +333,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.56.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -368,7 +368,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.56.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -401,7 +401,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.56.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -446,7 +446,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.56.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -466,7 +466,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.56.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -487,7 +487,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.56.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -504,7 +504,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.56.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -524,7 +524,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.56.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -542,7 +542,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.56.0/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -556,7 +556,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.56.0/packages/eas-cli/src/commands/credentials.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -570,7 +570,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.56.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -588,7 +588,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.56.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -605,7 +605,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.56.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -619,7 +619,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.56.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -633,7 +633,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.56.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -715,7 +715,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.56.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -732,7 +732,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.56.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -746,7 +746,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.56.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -763,7 +763,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.56.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
@@ -783,7 +783,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.56.0/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -800,7 +800,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.56.0/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -814,7 +814,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.56.0/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -844,7 +844,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.56.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -874,7 +874,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.56.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -891,7 +891,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.56.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -911,7 +911,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.56.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -930,7 +930,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.56.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -950,7 +950,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.56.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -970,7 +970,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.56.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -987,7 +987,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.56.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -1004,7 +1004,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.56.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -1025,7 +1025,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.56.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -1042,7 +1042,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.56.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
|
||||
Generated
+1456
-4
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.54.1",
|
||||
"version": "0.56.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
@@ -13,8 +13,8 @@
|
||||
"@expo/config": "6.0.23",
|
||||
"@expo/config-plugins": "4.1.4",
|
||||
"@expo/config-types": "45.0.0",
|
||||
"@expo/eas-build-job": "0.2.77",
|
||||
"@expo/eas-json": "0.54.0",
|
||||
"@expo/eas-build-job": "0.2.82",
|
||||
"@expo/eas-json": "0.56.0",
|
||||
"@expo/json-file": "8.2.36",
|
||||
"@expo/multipart-body-parser": "1.1.0",
|
||||
"@expo/pkcs12": "0.0.8",
|
||||
@@ -103,7 +103,8 @@
|
||||
"nock": "13.2.4",
|
||||
"tempy": "0.7.0",
|
||||
"ts-deepmerge": "2.0.1",
|
||||
"typescript": "4.6.4"
|
||||
"ts-mockito": "2.6.1",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -166,7 +167,7 @@
|
||||
},
|
||||
"warn-if-update-available": {
|
||||
"timeoutInDays": 0.5,
|
||||
"message": "<%= chalk.bold('★') %> <%= chalk.bold(config.name + '@' + latest) %> <%= chalk.bold('is now available, please upgrade.') %>\n<%= chalk.dim('Proceeding with outdated version') %>\n"
|
||||
"message": "<%= chalk('★') %> <%= chalk.bold(config.name + '@' + latest) %> <%= chalk.bold('is now available.') %>\n<%= chalk.dim('To upgrade, run ') %><%= chalk.dim(chalk.bold('npm install -g eas-cli')) %><%= chalk.dim('.') %>\n<%= chalk.dim('Proceeding with outdated version.') %>\n"
|
||||
},
|
||||
"update": {
|
||||
"node": {
|
||||
@@ -200,5 +201,9 @@
|
||||
"version": "yarn oclif readme && node scripts/patch-readme && git add README.md",
|
||||
"generate-graphql-code": "graphql-codegen --config graphql-codegen.yml",
|
||||
"clean": "rm -rf dist build tmp node_modules yarn-error.log"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.6.0",
|
||||
"yarn": "1.22.19"
|
||||
}
|
||||
}
|
||||
@@ -33,96 +33,152 @@
|
||||
]
|
||||
},
|
||||
"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": [
|
||||
{
|
||||
"body": {
|
||||
"servesAds": false,
|
||||
"attributesAppInstallationToPreviousAd": false,
|
||||
"attributesActionWithPreviousAd": false,
|
||||
"honorsLimitedAdTracking": false
|
||||
}
|
||||
"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"
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"servesAds": {
|
||||
"type": "boolean"
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -554,79 +610,7 @@
|
||||
"$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",
|
||||
@@ -642,9 +626,6 @@
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"idfa": {
|
||||
"$ref": "#/definitions/apple/AppleIdfa"
|
||||
},
|
||||
"info": {
|
||||
"description": "Localized core app info",
|
||||
"type": "object",
|
||||
@@ -986,10 +967,6 @@
|
||||
"review": {
|
||||
"$ref": "#/definitions/apple/AppleReview",
|
||||
"description": "Info for the App Store reviewer"
|
||||
},
|
||||
"priceTier": {
|
||||
"type": "number",
|
||||
"description": "App price tier. 0 is Free."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { fixArchiveUrl } from '../uploads';
|
||||
import { fixS3Url } from '../uploads';
|
||||
|
||||
describe(fixArchiveUrl, () => {
|
||||
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(fixArchiveUrl(input)).toBe(expectedOutput);
|
||||
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(fixArchiveUrl(correctUrl)).toBe(correctUrl);
|
||||
expect(fixS3Url(correctUrl)).toBe(correctUrl);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { instance, mock, when } from 'ts-mockito';
|
||||
|
||||
import { Client } from '../../vcs/vcs';
|
||||
import { getFormattedCommitMessageAsync } from '../metadata';
|
||||
|
||||
describe(getFormattedCommitMessageAsync, () => {
|
||||
it(`returns undefined if it's not a git project`, async () => {
|
||||
const clientMock = mock<Client>();
|
||||
when(clientMock.getLastCommitMessageAsync()).thenResolve(null);
|
||||
const client = instance(clientMock);
|
||||
|
||||
await expect(getFormattedCommitMessageAsync(client)).resolves.toBe(undefined);
|
||||
});
|
||||
|
||||
it('returns commit message', async () => {
|
||||
const clientMock = mock<Client>();
|
||||
when(clientMock.getLastCommitMessageAsync()).thenResolve('lorem ipsum');
|
||||
const client = instance(clientMock);
|
||||
|
||||
await expect(getFormattedCommitMessageAsync(client)).resolves.toBe('lorem ipsum');
|
||||
});
|
||||
|
||||
it('trims long commit messages', async () => {
|
||||
const clientMock = mock<Client>();
|
||||
when(clientMock.getLastCommitMessageAsync()).thenResolve('a'.repeat(1500));
|
||||
const client = instance(clientMock);
|
||||
|
||||
await expect(getFormattedCommitMessageAsync(client)).resolves.toBe(`${'a'.repeat(1021)}...`);
|
||||
});
|
||||
|
||||
it('replaces new lines with spaces', async () => {
|
||||
const clientMock = mock<Client>();
|
||||
when(clientMock.getLastCommitMessageAsync()).thenResolve(`Lorem\nipsum\ndolor\nsit\namet`);
|
||||
const client = instance(clientMock);
|
||||
|
||||
await expect(getFormattedCommitMessageAsync(client)).resolves.toBe(
|
||||
`Lorem ipsum dolor sit amet`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -89,7 +89,7 @@ describe(bumpVersionAsync, () => {
|
||||
expect(resolveConfigValue(buildGradle, 'versionName')).toBe('3.0.0');
|
||||
});
|
||||
|
||||
it('bumps expo.version and gradle versionCode when strategy = BumpStrategy.SHORT_VERSION', async () => {
|
||||
it('bumps expo.version and gradle versionCode when strategy = BumpStrategy.APP_VERSION', async () => {
|
||||
const nativeVersionName = '1.0.0'; // this should be overwritten by bumpVersionAsync
|
||||
const fakeExp = initBareWorkflowProject({ versionName: nativeVersionName });
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
parseGradleCommand,
|
||||
resolveConfigValue,
|
||||
} from '../../project/android/gradleUtils';
|
||||
import { getNextVersionCode } from '../../project/android/versions';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
|
||||
@@ -46,9 +47,10 @@ export async function bumpVersionAsync({
|
||||
|
||||
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
|
||||
Log.log('Updated versions in app.json');
|
||||
await writeVersionsToBuildGradleAsync({
|
||||
await updateNativeVersionsAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
version: exp.version,
|
||||
versionCode: exp.android?.versionCode,
|
||||
});
|
||||
Log.log('Synchronized versions with build gradle');
|
||||
}
|
||||
@@ -74,7 +76,7 @@ export async function bumpVersionInAppJsonAsync({
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp });
|
||||
} else {
|
||||
const versionCode = AndroidConfig.Version.getVersionCode(exp);
|
||||
const bumpedVersionCode = versionCode + 1;
|
||||
const bumpedVersionCode = getNextVersionCode(versionCode);
|
||||
Log.log(
|
||||
`Bumping ${chalk.bold('expo.android.versionCode')} from ${chalk.bold(
|
||||
versionCode
|
||||
@@ -116,21 +118,33 @@ export async function maybeResolveVersionsAsync(
|
||||
}
|
||||
}
|
||||
|
||||
async function writeVersionsToBuildGradleAsync({
|
||||
export async function updateNativeVersionsAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
version,
|
||||
versionCode,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
}): Promise<string> {
|
||||
version?: string;
|
||||
versionCode?: number;
|
||||
}): Promise<void> {
|
||||
const buildGradle = await readBuildGradleAsync(projectDir);
|
||||
if (!buildGradle) {
|
||||
throw new Error('This project is missing a build.gradle file.');
|
||||
}
|
||||
let updatedBuildGradle = AndroidConfig.Version.setVersionName(exp, buildGradle);
|
||||
updatedBuildGradle = AndroidConfig.Version.setVersionCode(exp, updatedBuildGradle);
|
||||
let updatedBuildGradle = buildGradle;
|
||||
if (version !== undefined) {
|
||||
updatedBuildGradle = updatedBuildGradle.replace(
|
||||
new RegExp(`versionName ".*"`),
|
||||
`versionName "${version}"`
|
||||
);
|
||||
}
|
||||
if (versionCode !== undefined) {
|
||||
updatedBuildGradle = updatedBuildGradle.replace(
|
||||
new RegExp(`versionCode.*`),
|
||||
`versionCode ${versionCode}`
|
||||
);
|
||||
}
|
||||
await writeBuildGradleAsync({ projectDir, buildGradle: updatedBuildGradle });
|
||||
return updatedBuildGradle;
|
||||
}
|
||||
|
||||
async function readBuildGradleAsync(projectDir: string): Promise<string | undefined> {
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
appPlatformEmojis,
|
||||
requestedPlatformDisplayNames,
|
||||
} from '../platform';
|
||||
import { uploadAsync } from '../uploads';
|
||||
import { uploadFileAtPathToS3Async } from '../uploads';
|
||||
import { formatBytes } from '../utils/files';
|
||||
import { createProgressTracker } from '../utils/progress';
|
||||
import { sleepAsync } from '../utils/promise';
|
||||
@@ -179,7 +179,7 @@ async function uploadProjectAsync<TPlatform extends Platform>(
|
||||
const projectTarball = await makeProjectTarballAsync();
|
||||
projectTarballPath = projectTarball.path;
|
||||
|
||||
const { bucketKey } = await uploadAsync(
|
||||
const { bucketKey } = await uploadFileAtPathToS3Async(
|
||||
UploadSessionType.EasBuildProjectSources,
|
||||
projectTarball.path,
|
||||
createProgressTracker({
|
||||
@@ -352,7 +352,7 @@ async function handleSingleBuildProgressAsync(
|
||||
);
|
||||
}
|
||||
Log.newLine();
|
||||
Log.log('Waiting in queue');
|
||||
Log.log(`Waiting in ${priorityToQueueDisplayName[build.priority]}`);
|
||||
queueProgressBar.start(
|
||||
build.initialQueuePosition + 1,
|
||||
build.initialQueuePosition - build.queuePosition + 1,
|
||||
@@ -385,6 +385,12 @@ async function handleSingleBuildProgressAsync(
|
||||
return { refetch: true };
|
||||
}
|
||||
|
||||
const priorityToQueueDisplayName: Record<BuildPriority, string> = {
|
||||
[BuildPriority.Normal]: 'queue',
|
||||
[BuildPriority.NormalPlus]: 'queue',
|
||||
[BuildPriority.High]: 'priority queue',
|
||||
};
|
||||
|
||||
const statusToDisplayName: Record<BuildStatus, string> = {
|
||||
[BuildStatus.New]: 'waiting to enter the queue (concurrency limit reached)',
|
||||
[BuildStatus.InQueue]: 'in queue',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
import { BuildProfile, EasJson } from '@expo/eas-json';
|
||||
|
||||
import { TrackingContext } from '../analytics/common';
|
||||
import { CredentialsContext } from '../credentials/context';
|
||||
@@ -21,7 +20,6 @@ export interface AndroidBuildContext {
|
||||
|
||||
export interface IosBuildContext {
|
||||
bundleIdentifier: string;
|
||||
applicationTargetBuildSettings: XCBuildConfiguration['buildSettings'];
|
||||
applicationTarget: Target;
|
||||
targets: Target[];
|
||||
xcodeBuildContext: XcodeBuildContext;
|
||||
@@ -29,6 +27,7 @@ export interface IosBuildContext {
|
||||
|
||||
export interface BuildContext<T extends Platform> {
|
||||
accountName: string;
|
||||
easJsonCliConfig: EasJson['cli'];
|
||||
buildProfile: BuildProfile<T>;
|
||||
buildProfileName: string;
|
||||
resourceClass: BuildResourceClass;
|
||||
@@ -41,6 +40,7 @@ export interface BuildContext<T extends Platform> {
|
||||
projectDir: string;
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
message?: string;
|
||||
trackingCtx: TrackingContext;
|
||||
user: Actor;
|
||||
workflow: Workflow;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
import { BuildProfile, EasJson } from '@expo/eas-json';
|
||||
import JsonFile from '@expo/json-file';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
@@ -21,21 +21,25 @@ import { LocalBuildOptions } from './local';
|
||||
export async function createBuildContextAsync<T extends Platform>({
|
||||
buildProfileName,
|
||||
buildProfile,
|
||||
easJsonCliConfig,
|
||||
clearCache = false,
|
||||
localBuildOptions,
|
||||
nonInteractive = false,
|
||||
platform,
|
||||
projectDir,
|
||||
resourceClass,
|
||||
message,
|
||||
}: {
|
||||
buildProfileName: string;
|
||||
buildProfile: BuildProfile<T>;
|
||||
easJsonCliConfig: EasJson['cli'];
|
||||
clearCache: boolean;
|
||||
localBuildOptions: LocalBuildOptions;
|
||||
nonInteractive: boolean;
|
||||
platform: T;
|
||||
projectDir: string;
|
||||
resourceClass: BuildResourceClass;
|
||||
message?: string;
|
||||
}): Promise<BuildContext<T>> {
|
||||
const exp = getExpoConfig(projectDir, { env: buildProfile.env });
|
||||
|
||||
@@ -74,6 +78,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
buildProfile,
|
||||
buildProfileName,
|
||||
resourceClass,
|
||||
easJsonCliConfig,
|
||||
clearCache,
|
||||
credentialsCtx,
|
||||
exp,
|
||||
@@ -86,6 +91,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
trackingCtx,
|
||||
user,
|
||||
workflow,
|
||||
message,
|
||||
};
|
||||
if (platform === Platform.ANDROID) {
|
||||
const common = commonContext as CommonContext<Platform.ANDROID>;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IOSConfig } from '@expo/config-plugins';
|
||||
import fs from 'fs-extra';
|
||||
import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { readPlistAsync } from '../../../utils/plist';
|
||||
import {
|
||||
@@ -17,6 +18,12 @@ import {
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
const getXCBuildConfigurationFromPbxproj = jest.spyOn(
|
||||
IOSConfig.Target,
|
||||
'getXCBuildConfigurationFromPbxproj'
|
||||
);
|
||||
const getPbxproj = jest.spyOn(IOSConfig.XcodeUtils, 'getPbxproj');
|
||||
|
||||
afterAll(async () => {
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
@@ -51,14 +58,34 @@ describe(evaluateTemplateString, () => {
|
||||
|
||||
// bare workflow
|
||||
describe(bumpVersionAsync, () => {
|
||||
beforeEach(() => {
|
||||
getPbxproj.mockImplementation(() => ({} as any));
|
||||
});
|
||||
afterEach(() => {
|
||||
getXCBuildConfigurationFromPbxproj.mockReset();
|
||||
getPbxproj.mockReset();
|
||||
});
|
||||
it('bumps expo.ios.buildNumber and CFBundleVersion when strategy = BumpStrategy.BUILD_NUMBER', async () => {
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
|
||||
getXCBuildConfigurationFromPbxproj.mockImplementation(
|
||||
() =>
|
||||
({
|
||||
buildSettings: {
|
||||
INFOPLIST_FILE: 'myproject/Info.plist',
|
||||
},
|
||||
} as XCBuildConfiguration)
|
||||
);
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.BUILD_NUMBER,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
buildSettings: {},
|
||||
targets: [
|
||||
{
|
||||
targetName: 'example',
|
||||
bundleIdentifier: 'example.com',
|
||||
entitlements: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
@@ -76,13 +103,25 @@ describe(bumpVersionAsync, () => {
|
||||
it('bumps expo.ios.buildNumber and CFBundleVersion for non default Info.plist location', async () => {
|
||||
const fakeExp = initBareWorkflowProject({ infoPlistName: 'Info2.plist' });
|
||||
|
||||
getXCBuildConfigurationFromPbxproj.mockImplementation(
|
||||
() =>
|
||||
({
|
||||
buildSettings: {
|
||||
INFOPLIST_FILE: '$(SRCROOT)/myproject/Info2.plist',
|
||||
},
|
||||
} as XCBuildConfiguration)
|
||||
);
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.BUILD_NUMBER,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
buildSettings: {
|
||||
INFOPLIST_FILE: '$(SRCROOT)/myproject/Info2.plist',
|
||||
},
|
||||
targets: [
|
||||
{
|
||||
targetName: 'example',
|
||||
bundleIdentifier: 'example.com',
|
||||
entitlements: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
@@ -97,14 +136,27 @@ describe(bumpVersionAsync, () => {
|
||||
expect(infoPlist['CFBundleVersion']).toBe('2');
|
||||
});
|
||||
|
||||
it('bumps expo.version and CFBundleShortVersionString when strategy = BumpStrategy.SHORT_VERSION', async () => {
|
||||
it('bumps expo.version and CFBundleShortVersionString when strategy = BumpStrategy.APP_VERSION', async () => {
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
|
||||
getXCBuildConfigurationFromPbxproj.mockImplementation(
|
||||
() =>
|
||||
({
|
||||
buildSettings: {
|
||||
INFOPLIST_FILE: 'myproject/Info.plist',
|
||||
},
|
||||
} as XCBuildConfiguration)
|
||||
);
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.APP_VERSION,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
buildSettings: {},
|
||||
targets: [
|
||||
{
|
||||
targetName: 'example',
|
||||
bundleIdentifier: 'example.com',
|
||||
entitlements: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
@@ -121,12 +173,25 @@ describe(bumpVersionAsync, () => {
|
||||
|
||||
it('does not bump any version when strategy = BumpStrategy.NOOP', async () => {
|
||||
const fakeExp = initBareWorkflowProject();
|
||||
|
||||
getXCBuildConfigurationFromPbxproj.mockImplementation(
|
||||
() =>
|
||||
({
|
||||
buildSettings: {
|
||||
INFOPLIST_FILE: 'myproject/Info.plist',
|
||||
},
|
||||
} as XCBuildConfiguration)
|
||||
);
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.NOOP,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
buildSettings: {},
|
||||
targets: [
|
||||
{
|
||||
targetName: 'example',
|
||||
bundleIdentifier: 'example.com',
|
||||
entitlements: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
@@ -288,9 +353,9 @@ describe(getInfoPlistPath, () => {
|
||||
|
||||
function initBareWorkflowProject({
|
||||
appVersion = '1.0.0',
|
||||
version = '1',
|
||||
buildNumber = '1',
|
||||
infoPlistName = 'Info.plist',
|
||||
}: { appVersion?: string; version?: string; infoPlistName?: string } = {}): ExpoConfig {
|
||||
}: { appVersion?: string; buildNumber?: string; infoPlistName?: string } = {}): ExpoConfig {
|
||||
const fakeExp: ExpoConfig = {
|
||||
name: 'myproject',
|
||||
slug: 'myproject',
|
||||
@@ -311,7 +376,7 @@ function initBareWorkflowProject({
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${appVersion}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${version}</string>
|
||||
<string>${buildNumber}</string>
|
||||
</dict>
|
||||
</plist>`,
|
||||
},
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { IOSConfig } from '@expo/config-plugins';
|
||||
import { Ios, Job, Metadata, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { IosCredentials, Target } from '../../credentials/ios/types';
|
||||
import { IosCredentials } from '../../credentials/ios/types';
|
||||
import { BuildParamsInput } from '../../graphql/generated';
|
||||
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
|
||||
import { ensureBundleIdentifierIsDefinedForManagedProjectAsync } from '../../project/ios/bundleIdentifier';
|
||||
@@ -44,12 +42,10 @@ export async function createIosContextAsync(
|
||||
env: buildProfile.env,
|
||||
});
|
||||
const applicationTarget = findApplicationTarget(targets);
|
||||
const applicationTargetBuildSettings = resolveBuildSettings(ctx, applicationTarget);
|
||||
|
||||
return {
|
||||
bundleIdentifier: applicationTarget.bundleIdentifier,
|
||||
applicationTarget,
|
||||
applicationTargetBuildSettings,
|
||||
targets,
|
||||
xcodeBuildContext,
|
||||
};
|
||||
@@ -68,7 +64,7 @@ export async function prepareIosBuildAsync(
|
||||
projectDir: ctx.projectDir,
|
||||
exp: ctx.exp,
|
||||
buildProfile: ctx.buildProfile,
|
||||
buildSettings: ctx.ios.applicationTargetBuildSettings,
|
||||
targets: ctx.ios.targets,
|
||||
});
|
||||
},
|
||||
prepareJobAsync: async (
|
||||
@@ -97,18 +93,3 @@ export async function prepareIosBuildAsync(
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function resolveBuildSettings(
|
||||
ctx: CommonContext<Platform.IOS>,
|
||||
applicationTarget: Target
|
||||
): XCBuildConfiguration['buildSettings'] {
|
||||
if (ctx.workflow === Workflow.MANAGED) {
|
||||
return {};
|
||||
}
|
||||
const project = IOSConfig.XcodeUtils.getPbxproj(ctx.projectDir);
|
||||
const xcBuildConfiguration = IOSConfig.Target.getXCBuildConfigurationFromPbxproj(project, {
|
||||
targetName: applicationTarget.targetName,
|
||||
buildConfiguration: applicationTarget.buildConfiguration,
|
||||
});
|
||||
return xcBuildConfiguration?.buildSettings ?? {};
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { BuildProfile, IosVersionAutoIncrement } from '@expo/eas-json';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { Target } from '../../credentials/ios/types';
|
||||
import { isExpoUpdatesInstalled } from '../../project/projectUtils';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { syncUpdatesConfigurationAsync } from '../../update/ios/UpdatesModule';
|
||||
@@ -12,12 +12,12 @@ export async function syncProjectConfigurationAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
buildProfile,
|
||||
buildSettings,
|
||||
targets,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
buildProfile: BuildProfile<Platform.IOS>;
|
||||
buildSettings: XCBuildConfiguration['buildSettings'];
|
||||
targets: Target[];
|
||||
}): Promise<void> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
const { autoIncrement } = buildProfile;
|
||||
@@ -27,7 +27,7 @@ export async function syncProjectConfigurationAsync({
|
||||
if (isExpoUpdatesInstalled(projectDir)) {
|
||||
await syncUpdatesConfigurationAsync(projectDir, exp);
|
||||
}
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, buildSettings });
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, targets });
|
||||
} else {
|
||||
await bumpVersionInAppJsonAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { ExpoConfig, getConfigFilePaths } from '@expo/config';
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { IOSConfig } from '@expo/config-plugins';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
import path from 'path';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { Target } from '../../credentials/ios/types';
|
||||
import Log from '../../log';
|
||||
import { findApplicationTarget } from '../../project/ios/target';
|
||||
import { getNextBuildNumber, isValidBuildNumber } from '../../project/ios/versions';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import uniqBy from '../../utils/expodash/uniqBy';
|
||||
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
|
||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||
import { bumpAppVersionAsync } from '../utils/version';
|
||||
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
|
||||
|
||||
export enum BumpStrategy {
|
||||
APP_VERSION,
|
||||
@@ -22,21 +26,25 @@ export async function bumpVersionAsync({
|
||||
bumpStrategy,
|
||||
projectDir,
|
||||
exp,
|
||||
buildSettings,
|
||||
targets,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
bumpStrategy: BumpStrategy;
|
||||
buildSettings: XCBuildConfiguration['buildSettings'];
|
||||
targets: Target[];
|
||||
}): Promise<void> {
|
||||
if (bumpStrategy === BumpStrategy.NOOP) {
|
||||
return;
|
||||
}
|
||||
ensureStaticConfigExists(projectDir);
|
||||
const infoPlist = await readInfoPlistAsync(projectDir, buildSettings);
|
||||
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
|
||||
Log.log('Updated versions in app.json');
|
||||
await writeVersionsToInfoPlistAsync({ projectDir, exp, infoPlist, buildSettings });
|
||||
await updateNativeVersionsAsync({
|
||||
projectDir,
|
||||
version: exp.version,
|
||||
buildNumber: exp.ios?.buildNumber,
|
||||
targets,
|
||||
});
|
||||
Log.log('Synchronized versions with Info.plist');
|
||||
}
|
||||
|
||||
@@ -59,10 +67,8 @@ export async function bumpVersionInAppJsonAsync({
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp });
|
||||
} else {
|
||||
const buildNumber = IOSConfig.Version.getBuildNumber(exp);
|
||||
if (buildNumber.match(/^\d+(\.\d+)*$/)) {
|
||||
const comps = buildNumber.split('.');
|
||||
comps[comps.length - 1] = String(Number(comps[comps.length - 1]) + 1);
|
||||
const bumpedBuildNumber = comps.join('.');
|
||||
if (isValidBuildNumber(buildNumber)) {
|
||||
const bumpedBuildNumber = getNextBuildNumber(buildNumber);
|
||||
Log.log(
|
||||
`Bumping ${chalk.bold('expo.ios.buildNumber')} from ${chalk.bold(
|
||||
buildNumber
|
||||
@@ -121,12 +127,21 @@ export async function readBuildNumberAsync(
|
||||
export async function maybeResolveVersionsAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig,
|
||||
buildSettings: XCBuildConfiguration['buildSettings']
|
||||
targets: Target[]
|
||||
): Promise<{ appVersion?: string; appBuildVersion?: string }> {
|
||||
const applicationTarget = findApplicationTarget(targets);
|
||||
try {
|
||||
return {
|
||||
appBuildVersion: await readBuildNumberAsync(projectDir, exp, buildSettings),
|
||||
appVersion: await readShortVersionAsync(projectDir, exp, buildSettings),
|
||||
appBuildVersion: await readBuildNumberAsync(
|
||||
projectDir,
|
||||
exp,
|
||||
applicationTarget.buildSettings ?? {}
|
||||
),
|
||||
appVersion: await readShortVersionAsync(
|
||||
projectDir,
|
||||
exp,
|
||||
applicationTarget.buildSettings ?? {}
|
||||
),
|
||||
};
|
||||
} catch (err: any) {
|
||||
Log.warn('Failed to read app versions.');
|
||||
@@ -137,23 +152,6 @@ export async function maybeResolveVersionsAsync(
|
||||
}
|
||||
}
|
||||
|
||||
async function writeVersionsToInfoPlistAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
infoPlist,
|
||||
buildSettings,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
infoPlist: IOSConfig.InfoPlist;
|
||||
buildSettings: XCBuildConfiguration['buildSettings'];
|
||||
}): Promise<IOSConfig.InfoPlist> {
|
||||
let updatedInfoPlist = IOSConfig.Version.setVersion(exp, infoPlist);
|
||||
updatedInfoPlist = IOSConfig.Version.setBuildNumber(exp, updatedInfoPlist);
|
||||
await writeInfoPlistAsync({ projectDir, infoPlist: updatedInfoPlist, buildSettings });
|
||||
return updatedInfoPlist;
|
||||
}
|
||||
|
||||
export function getInfoPlistPath(
|
||||
projectDir: string,
|
||||
buildSettings: XCBuildConfiguration['buildSettings']
|
||||
@@ -181,24 +179,55 @@ async function readInfoPlistAsync(
|
||||
return ((await readPlistAsync(infoPlistPath)) ?? {}) as IOSConfig.InfoPlist;
|
||||
}
|
||||
|
||||
async function writeInfoPlistAsync({
|
||||
export async function updateNativeVersionsAsync({
|
||||
projectDir,
|
||||
infoPlist,
|
||||
buildSettings,
|
||||
version,
|
||||
buildNumber,
|
||||
targets,
|
||||
}: {
|
||||
projectDir: string;
|
||||
infoPlist: IOSConfig.InfoPlist;
|
||||
buildSettings: XCBuildConfiguration['buildSettings'];
|
||||
version?: string;
|
||||
buildNumber?: string;
|
||||
targets: Target[];
|
||||
}): Promise<void> {
|
||||
const infoPlistPath = getInfoPlistPath(projectDir, buildSettings);
|
||||
await writePlistAsync(infoPlistPath, infoPlist);
|
||||
const project = IOSConfig.XcodeUtils.getPbxproj(projectDir);
|
||||
const iosDir = path.join(projectDir, 'ios');
|
||||
|
||||
const infoPlistFiles: string[] = [];
|
||||
for (const target of targets) {
|
||||
const { targetName, buildConfiguration } = target;
|
||||
const xcBuildConfiguration = IOSConfig.Target.getXCBuildConfigurationFromPbxproj(project, {
|
||||
targetName,
|
||||
buildConfiguration,
|
||||
});
|
||||
const infoPlist = xcBuildConfiguration?.buildSettings?.INFOPLIST_FILE;
|
||||
if (infoPlist) {
|
||||
const evaluatedInfoPlistPath = trimQuotes(
|
||||
evaluateTemplateString(infoPlist, {
|
||||
SRCROOT: iosDir,
|
||||
})
|
||||
);
|
||||
const absolutePath = path.isAbsolute(evaluatedInfoPlistPath)
|
||||
? evaluatedInfoPlistPath
|
||||
: path.join(iosDir, evaluatedInfoPlistPath);
|
||||
infoPlistFiles.push(path.normalize(absolutePath));
|
||||
}
|
||||
}
|
||||
const uniqueInfoPlistPaths = uniqBy(infoPlistFiles, i => i);
|
||||
for (const infoPlistPath of uniqueInfoPlistPaths) {
|
||||
const infoPlist = (await readPlistAsync(infoPlistPath)) as IOSConfig.InfoPlist;
|
||||
if (buildNumber) {
|
||||
infoPlist.CFBundleVersion = buildNumber;
|
||||
}
|
||||
if (version) {
|
||||
infoPlist.CFBundleShortVersionString = version;
|
||||
}
|
||||
await writePlistAsync(infoPlistPath, infoPlist);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureStaticConfigExists(projectDir: string): void {
|
||||
const paths = getConfigFilePaths(projectDir);
|
||||
if (!paths.staticConfigPath) {
|
||||
throw new Error('autoIncrement option is not supported when using app.config.js');
|
||||
}
|
||||
function trimQuotes(s: string): string {
|
||||
return s?.startsWith('"') && s.endsWith('"') ? s.slice(1, -1) : s;
|
||||
}
|
||||
|
||||
export function evaluateTemplateString(
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import { Client } from '../vcs/vcs';
|
||||
import { maybeResolveVersionsAsync as maybeResolveAndroidVersionsAsync } from './android/version';
|
||||
import { BuildContext } from './context';
|
||||
import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios/version';
|
||||
@@ -29,7 +30,9 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
('simulator' in ctx.buildProfile && ctx.buildProfile.simulator
|
||||
? 'simulator'
|
||||
: ctx.buildProfile.distribution) ?? 'store';
|
||||
const metadata = {
|
||||
|
||||
const vcsClient = getVcsClient();
|
||||
const metadata: Metadata = {
|
||||
trackingContext: ctx.trackingCtx,
|
||||
...(await maybeResolveVersionsAsync(ctx)),
|
||||
cliVersion: easCliVersion,
|
||||
@@ -43,9 +46,10 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
appName: ctx.exp.name,
|
||||
appIdentifier: resolveAppIdentifier(ctx),
|
||||
buildProfile: ctx.buildProfileName,
|
||||
gitCommitHash: await getVcsClient().getCommitHashAsync(),
|
||||
isGitWorkingTreeDirty: await getVcsClient().hasUncommittedChangesAsync(),
|
||||
gitCommitHash: await vcsClient.getCommitHashAsync(),
|
||||
isGitWorkingTreeDirty: await vcsClient.hasUncommittedChangesAsync(),
|
||||
username: getUsername(ctx.exp, await ensureLoggedInAsync()),
|
||||
message: ctx.message ?? (await getFormattedCommitMessageAsync(vcsClient)),
|
||||
...(ctx.platform === Platform.IOS && {
|
||||
iosEnterpriseProvisioning: resolveIosEnterpriseProvisioning(
|
||||
ctx as BuildContext<Platform.IOS>
|
||||
@@ -60,11 +64,7 @@ async function maybeResolveVersionsAsync<T extends Platform>(
|
||||
): Promise<{ appBuildVersion?: string; appVersion?: string }> {
|
||||
if (ctx.platform === Platform.IOS) {
|
||||
const iosContext = ctx as BuildContext<Platform.IOS>;
|
||||
return await maybeResolveIosVersionsAsync(
|
||||
ctx.projectDir,
|
||||
ctx.exp,
|
||||
iosContext.ios.applicationTargetBuildSettings
|
||||
);
|
||||
return await maybeResolveIosVersionsAsync(ctx.projectDir, ctx.exp, iosContext.ios.targets);
|
||||
} else if (ctx.platform === Platform.ANDROID) {
|
||||
const androidCtx = ctx as BuildContext<Platform.ANDROID>;
|
||||
return await maybeResolveAndroidVersionsAsync(ctx.projectDir, ctx.exp, androidCtx.buildProfile);
|
||||
@@ -149,3 +149,16 @@ function resolveIosEnterpriseProvisioning(
|
||||
): IosEnterpriseProvisioning | undefined {
|
||||
return ctx.buildProfile.enterpriseProvisioning;
|
||||
}
|
||||
|
||||
export async function getFormattedCommitMessageAsync(
|
||||
vcsClient: Client
|
||||
): Promise<string | undefined> {
|
||||
const maybeCommitMessage = (await vcsClient.getLastCommitMessageAsync())?.trim();
|
||||
if (!maybeCommitMessage) {
|
||||
return undefined;
|
||||
}
|
||||
const lines = maybeCommitMessage.split('\n');
|
||||
const withoutEmptyLines = lines.filter(i => i);
|
||||
const message = withoutEmptyLines.join(' ');
|
||||
return message.length > 1024 ? message.substring(0, 1021) + '...' : message;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { BuildProfile, EasJsonReader, SubmitProfile } from '@expo/eas-json';
|
||||
import { BuildProfile, EasJson, EasJsonReader, SubmitProfile } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
@@ -55,6 +55,7 @@ export interface BuildFlags {
|
||||
submitProfile?: string;
|
||||
localBuildOptions: LocalBuildOptions;
|
||||
userInputResourceClass?: UserInputResourceClass;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
const platformToGraphQLResourceClassMapping: Record<
|
||||
@@ -80,6 +81,7 @@ export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFla
|
||||
nonInteractive: flags.nonInteractive,
|
||||
});
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const easJsonCliConfig: EasJson['cli'] = (await easJsonReader.getCliConfigAsync()) ?? {};
|
||||
|
||||
const platforms = toPlatforms(flags.requestedPlatform);
|
||||
const buildProfiles = await getProfilesAsync({
|
||||
@@ -111,6 +113,7 @@ export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFla
|
||||
platformToGraphQLResourceClassMapping[buildProfile.platform][
|
||||
flags.userInputResourceClass ?? UserInputResourceClass.DEFAULT
|
||||
],
|
||||
easJsonCliConfig,
|
||||
});
|
||||
if (maybeBuild) {
|
||||
startedBuilds.push({ build: maybeBuild, buildProfile });
|
||||
@@ -188,12 +191,14 @@ async function prepareAndStartBuildAsync({
|
||||
moreBuilds,
|
||||
buildProfile,
|
||||
resourceClass,
|
||||
easJsonCliConfig,
|
||||
}: {
|
||||
projectDir: string;
|
||||
flags: BuildFlags;
|
||||
moreBuilds: boolean;
|
||||
buildProfile: ProfileData<'build'>;
|
||||
resourceClass: BuildResourceClass;
|
||||
easJsonCliConfig: EasJson['cli'];
|
||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
||||
const buildCtx = await createBuildContextAsync({
|
||||
buildProfileName: buildProfile.profileName,
|
||||
@@ -204,6 +209,8 @@ async function prepareAndStartBuildAsync({
|
||||
platform: buildProfile.platform,
|
||||
projectDir,
|
||||
localBuildOptions: flags.localBuildOptions,
|
||||
easJsonCliConfig,
|
||||
message: flags.message,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
|
||||
@@ -23,6 +23,7 @@ interface RawBuildFlags {
|
||||
'auto-submit': boolean;
|
||||
'auto-submit-with-profile'?: string;
|
||||
'resource-class'?: UserInputResourceClass;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export default class Build extends EasCommand {
|
||||
@@ -86,6 +87,10 @@ export default class Build extends EasCommand {
|
||||
hidden: true,
|
||||
description: 'The instance type that will be used to run this build [experimental]',
|
||||
}),
|
||||
message: Flags.string({
|
||||
char: 'm',
|
||||
description: 'A short message describing the build',
|
||||
}),
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
@@ -143,6 +148,11 @@ export default class Build extends EasCommand {
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
const message = flags['message'];
|
||||
if (message && message.length > 1024) {
|
||||
Errors.error('Message cannot be longer than 1024 characters.', { exit: 1 });
|
||||
}
|
||||
|
||||
const profile = flags['profile'];
|
||||
return {
|
||||
requestedPlatform,
|
||||
@@ -163,6 +173,7 @@ export default class Build extends EasCommand {
|
||||
autoSubmit: flags['auto-submit'] || flags['auto-submit-with-profile'] !== undefined,
|
||||
submitProfile: flags['auto-submit-with-profile'] ?? profile,
|
||||
userInputResourceClass: flags['resource-class'] ?? UserInputResourceClass.DEFAULT,
|
||||
message,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { getRuntimeVersionNullable } from '@expo/config-plugins/build/utils/Updates';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
import { toAppPlatform } from '../../../graphql/types/AppPlatform';
|
||||
import Log from '../../../log';
|
||||
import { selectPlatformAsync } from '../../../platform';
|
||||
import { VERSION_CODE_REQUIREMENTS, isValidVersionCode } from '../../../project/android/versions';
|
||||
import { getApplicationIdentifierAsync } from '../../../project/applicationIdentifier';
|
||||
import { getExpoConfig } from '../../../project/expoConfig';
|
||||
import { BUILD_NUMBER_REQUIREMENTS, isValidBuildNumber } from '../../../project/ios/versions';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectFullNameAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../../project/projectUtils';
|
||||
import {
|
||||
ensureRemoteVersionPolicyAsync,
|
||||
getBuildVersionName,
|
||||
validateAppConfigForRemoteVersionPolicyAsync,
|
||||
} from '../../../project/remoteVersionSource';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
|
||||
export default class BuildVersionSetView extends EasCommand {
|
||||
static description = 'Update version of an app.';
|
||||
static hidden = true;
|
||||
|
||||
static flags = {
|
||||
platform: Flags.enum({
|
||||
char: 'p',
|
||||
options: ['android', 'ios'],
|
||||
}),
|
||||
profile: Flags.string({
|
||||
description:
|
||||
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
helpValue: 'PROFILE_NAME',
|
||||
}),
|
||||
};
|
||||
|
||||
public async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(BuildVersionSetView);
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
|
||||
const platform = await selectPlatformAsync(flags.platform);
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
await ensureRemoteVersionPolicyAsync(projectDir, easJsonReader);
|
||||
const profile = await easJsonReader.getBuildProfileAsync(platform, flags.profile ?? undefined);
|
||||
|
||||
const exp = getExpoConfig(projectDir, { env: profile.env });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectFullName = await getProjectFullNameAsync(exp);
|
||||
await validateAppConfigForRemoteVersionPolicyAsync(exp);
|
||||
|
||||
const applicationIdentifier = await getApplicationIdentifierAsync(
|
||||
projectDir,
|
||||
exp,
|
||||
profile,
|
||||
platform
|
||||
);
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
projectId,
|
||||
toAppPlatform(platform),
|
||||
applicationIdentifier
|
||||
);
|
||||
const currentStateMessage = remoteVersions?.buildVersion
|
||||
? `Project ${chalk.bold(projectFullName)} with ${getApplicationIdentifierName(
|
||||
platform
|
||||
)} "${applicationIdentifier}" is configured with ${getBuildVersionName(platform)} ${
|
||||
remoteVersions.buildVersion
|
||||
}.`
|
||||
: `Project ${chalk.bold(projectFullName)} with ${getApplicationIdentifierName(
|
||||
platform
|
||||
)} "${applicationIdentifier}" does not have any ${getBuildVersionName(
|
||||
platform
|
||||
)} configured.`;
|
||||
|
||||
const versionPromptMessage = remoteVersions?.buildVersion
|
||||
? `What version would you like to set?`
|
||||
: `What version would you like to initialize it with?`;
|
||||
Log.log(currentStateMessage);
|
||||
|
||||
const { version } = await promptAsync({
|
||||
type: platform === Platform.ANDROID ? 'number' : 'text',
|
||||
name: 'version',
|
||||
message: versionPromptMessage,
|
||||
validate:
|
||||
platform === Platform.ANDROID
|
||||
? value => isValidVersionCode(value) || `Invalid value: ${VERSION_CODE_REQUIREMENTS}.`
|
||||
: value => isValidBuildNumber(value) || `Invalid value: ${BUILD_NUMBER_REQUIREMENTS}.`,
|
||||
});
|
||||
await AppVersionMutation.createAppVersionAsync({
|
||||
appId: projectId,
|
||||
platform: toAppPlatform(platform),
|
||||
applicationIdentifier,
|
||||
storeVersion: exp.version ?? '1.0.0',
|
||||
buildVersion: String(version),
|
||||
runtimeVersion: getRuntimeVersionNullable(exp, platform) ?? undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getApplicationIdentifierName(platform: Platform): string {
|
||||
if (platform === Platform.ANDROID) {
|
||||
return 'application ID';
|
||||
} else {
|
||||
return 'bundle identifier';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { BuildProfile, EasJsonReader } from '@expo/eas-json';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../../build/android/version';
|
||||
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../../build/ios/version';
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
import { toAppPlatform } from '../../../graphql/types/AppPlatform';
|
||||
import Log from '../../../log';
|
||||
import {
|
||||
appPlatformDisplayNames,
|
||||
selectRequestedPlatformAsync,
|
||||
toPlatforms,
|
||||
} from '../../../platform';
|
||||
import { getAppBuildGradleAsync } from '../../../project/android/gradleUtils';
|
||||
import { VERSION_CODE_REQUIREMENTS, isValidVersionCode } from '../../../project/android/versions';
|
||||
import { getApplicationIdentifierAsync } from '../../../project/applicationIdentifier';
|
||||
import { getExpoConfig } from '../../../project/expoConfig';
|
||||
import { resolveXcodeBuildContextAsync } from '../../../project/ios/scheme';
|
||||
import { resolveTargetsAsync } from '../../../project/ios/target';
|
||||
import { BUILD_NUMBER_REQUIREMENTS, isValidBuildNumber } from '../../../project/ios/versions';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../../project/projectUtils';
|
||||
import {
|
||||
ensureRemoteVersionPolicyAsync,
|
||||
getBuildVersionName,
|
||||
validateAppConfigForRemoteVersionPolicyAsync,
|
||||
} from '../../../project/remoteVersionSource';
|
||||
import { resolveWorkflowAsync } from '../../../project/workflow';
|
||||
import { getProfilesAsync } from '../../../utils/profiles';
|
||||
|
||||
interface SyncContext<T extends Platform> {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
workflow: Workflow;
|
||||
profile: BuildProfile<T>;
|
||||
buildVersion: string;
|
||||
}
|
||||
|
||||
export default class BuildVersionSyncView extends EasCommand {
|
||||
public static description = 'Update a version in native code with a value stored on EAS servers';
|
||||
static hidden = true;
|
||||
|
||||
public static flags = {
|
||||
platform: Flags.enum({
|
||||
char: 'p',
|
||||
options: ['android', 'ios', 'all'],
|
||||
}),
|
||||
profile: Flags.string({
|
||||
description:
|
||||
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
helpValue: 'PROFILE_NAME',
|
||||
}),
|
||||
};
|
||||
|
||||
public async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(BuildVersionSyncView);
|
||||
const projectDir = await findProjectRootAsync();
|
||||
|
||||
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
await ensureRemoteVersionPolicyAsync(projectDir, easJsonReader);
|
||||
|
||||
const platforms = toPlatforms(requestedPlatform);
|
||||
const buildProfiles = await getProfilesAsync({
|
||||
type: 'build',
|
||||
easJsonReader,
|
||||
platforms,
|
||||
profileName: flags.profile ?? undefined,
|
||||
});
|
||||
for (const profileInfo of buildProfiles) {
|
||||
const exp = getExpoConfig(projectDir, { env: profileInfo.profile.env });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
await validateAppConfigForRemoteVersionPolicyAsync(exp);
|
||||
const platformDisplayName = appPlatformDisplayNames[toAppPlatform(profileInfo.platform)];
|
||||
|
||||
const applicationIdentifier = await getApplicationIdentifierAsync(
|
||||
projectDir,
|
||||
exp,
|
||||
profileInfo.profile,
|
||||
profileInfo.platform
|
||||
);
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
projectId,
|
||||
toAppPlatform(profileInfo.platform),
|
||||
applicationIdentifier
|
||||
);
|
||||
const workflow = await resolveWorkflowAsync(projectDir, profileInfo.platform);
|
||||
if (!remoteVersions?.buildVersion) {
|
||||
Log.warn(
|
||||
`Skipping versions sync for ${platformDisplayName}. There are no versions configured on Expo servers, use "eas build:version:set" or run a build to initialize it.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (workflow === Workflow.MANAGED) {
|
||||
Log.warn(
|
||||
`The remote value for the ${platformDisplayName} ${getBuildVersionName(
|
||||
profileInfo.platform
|
||||
)} is ${chalk.bold(
|
||||
remoteVersions?.buildVersion
|
||||
)}, but it was not synced to the local project. This command has no effect on projects using managed workflow.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (profileInfo.platform === Platform.ANDROID) {
|
||||
this.syncAndroidAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
profile: profileInfo.profile as BuildProfile<Platform.ANDROID>,
|
||||
workflow,
|
||||
buildVersion: remoteVersions.buildVersion,
|
||||
});
|
||||
} else {
|
||||
this.syncIosAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
profile: profileInfo.profile as BuildProfile<Platform.IOS>,
|
||||
workflow,
|
||||
buildVersion: remoteVersions.buildVersion,
|
||||
});
|
||||
}
|
||||
Log.withTick(
|
||||
`Successfully updated the ${platformDisplayName} ${getBuildVersionName(
|
||||
profileInfo.platform
|
||||
)} in native code to ${chalk.bold(remoteVersions?.buildVersion)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async syncIosAsync({
|
||||
workflow,
|
||||
projectDir,
|
||||
exp,
|
||||
profile,
|
||||
buildVersion,
|
||||
}: SyncContext<Platform.IOS>): Promise<void> {
|
||||
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
|
||||
{ exp, projectDir, nonInteractive: false },
|
||||
profile
|
||||
);
|
||||
const targets = await resolveTargetsAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
xcodeBuildContext,
|
||||
env: profile.env,
|
||||
});
|
||||
|
||||
if (!isValidBuildNumber(buildVersion)) {
|
||||
throw new Error(`Invalid buildNumber (${buildVersion}), ${BUILD_NUMBER_REQUIREMENTS}.`);
|
||||
}
|
||||
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
await updateIosNativeVersionsAsync({
|
||||
projectDir,
|
||||
buildNumber: buildVersion,
|
||||
targets,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async syncAndroidAsync({
|
||||
projectDir,
|
||||
workflow,
|
||||
buildVersion,
|
||||
}: SyncContext<Platform.ANDROID>): Promise<void> {
|
||||
if (!isValidVersionCode(buildVersion)) {
|
||||
throw new Error(`Invalid versionCode (${buildVersion}), ${VERSION_CODE_REQUIREMENTS}.`);
|
||||
}
|
||||
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const buildGradle = await getAppBuildGradleAsync(projectDir);
|
||||
const isMultiFlavor =
|
||||
buildGradle.android?.productFlavors || buildGradle.android?.flavorDimensions;
|
||||
if (isMultiFlavor) {
|
||||
throw new Error(
|
||||
"Versions in native code can't be automatically synced in multi-flavor Android projects. If you are using EAS Build with app version source set to remote, the correct values will be injected at the build time."
|
||||
);
|
||||
}
|
||||
await updateAndroidNativeVersionsAsync({ projectDir, versionCode: Number(buildVersion) });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -254,11 +254,11 @@ export class SetUpAdhocProvisioningProfile {
|
||||
ctx: CredentialsContext,
|
||||
appleTeam: AppleTeamFragment
|
||||
): Promise<AppleDeviceFragment[]> {
|
||||
const action = new DeviceCreateAction(this.app.account, appleTeam);
|
||||
const action = new DeviceCreateAction(ctx.appStore, this.app.account, appleTeam);
|
||||
const method = await action.runAsync();
|
||||
|
||||
while (true) {
|
||||
if (method !== RegistrationMethod.INPUT) {
|
||||
if (method === RegistrationMethod.WEBSITE) {
|
||||
Log.newLine();
|
||||
Log.log(chalk.bold("Press any key if you've already finished device registration."));
|
||||
await pressAnyKeyToContinueAsync();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { JSONObject } from '@expo/json-file';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import {
|
||||
CommonIosAppCredentialsFragment,
|
||||
@@ -17,6 +18,7 @@ export interface Target {
|
||||
bundleIdentifier: string;
|
||||
parentBundleIdentifier?: string;
|
||||
entitlements: JSONObject;
|
||||
buildSettings?: XCBuildConfiguration['buildSettings'];
|
||||
}
|
||||
|
||||
export interface TargetCredentials {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const runDeveloperPortalMethodAsync = jest.fn();
|
||||
@@ -1,13 +1,17 @@
|
||||
import prompts from 'prompts';
|
||||
import { instance, mock } from 'ts-mockito';
|
||||
|
||||
import AppStoreApi from '../../../../credentials/ios/appstore/AppStoreApi';
|
||||
import { Account } from '../../../../user/Account';
|
||||
import DeviceCreateAction, { RegistrationMethod } from '../action';
|
||||
import { runDeveloperPortalMethodAsync } from '../developerPortalMethod';
|
||||
import { runInputMethodAsync } from '../inputMethod';
|
||||
import { runRegistrationUrlMethodAsync } from '../registrationUrlMethod';
|
||||
|
||||
jest.mock('prompts');
|
||||
jest.mock('../registrationUrlMethod');
|
||||
jest.mock('../developerPortalMethod');
|
||||
jest.mock('../inputMethod');
|
||||
jest.mock('../registrationUrlMethod');
|
||||
|
||||
beforeEach(() => {
|
||||
const promptsMock = jest.mocked(prompts);
|
||||
@@ -25,6 +29,8 @@ describe(DeviceCreateAction, () => {
|
||||
jest.mocked(prompts).mockImplementationOnce(async () => ({
|
||||
method: RegistrationMethod.WEBSITE,
|
||||
}));
|
||||
const appStoreApiMock = mock<AppStoreApi>();
|
||||
const appStoreApi = instance(appStoreApiMock);
|
||||
|
||||
const account: Account = {
|
||||
id: 'account_id',
|
||||
@@ -35,7 +41,7 @@ describe(DeviceCreateAction, () => {
|
||||
appleTeamIdentifier: 'ABC123Y',
|
||||
appleTeamName: 'John Doe (Individual)',
|
||||
};
|
||||
const action = new DeviceCreateAction(account, appleTeam);
|
||||
const action = new DeviceCreateAction(appStoreApi, account, appleTeam);
|
||||
await action.runAsync();
|
||||
|
||||
expect(runRegistrationUrlMethodAsync).toBeCalled();
|
||||
@@ -45,6 +51,8 @@ describe(DeviceCreateAction, () => {
|
||||
jest.mocked(prompts).mockImplementationOnce(async () => ({
|
||||
method: RegistrationMethod.INPUT,
|
||||
}));
|
||||
const appStoreApiMock = mock<AppStoreApi>();
|
||||
const appStoreApi = instance(appStoreApiMock);
|
||||
|
||||
const account: Account = {
|
||||
id: 'account_id',
|
||||
@@ -55,10 +63,32 @@ describe(DeviceCreateAction, () => {
|
||||
appleTeamIdentifier: 'ABC123Y',
|
||||
appleTeamName: 'John Doe (Individual)',
|
||||
};
|
||||
const action = new DeviceCreateAction(account, appleTeam);
|
||||
const action = new DeviceCreateAction(appStoreApi, account, appleTeam);
|
||||
await action.runAsync();
|
||||
|
||||
expect(runInputMethodAsync).toBeCalled();
|
||||
});
|
||||
|
||||
it('calls runDeveloperPortalMethodAsync if user chooses the developer portal method', async () => {
|
||||
jest.mocked(prompts).mockImplementationOnce(async () => ({
|
||||
method: RegistrationMethod.DEVELOPER_PORTAL,
|
||||
}));
|
||||
const appStoreApiMock = mock<AppStoreApi>();
|
||||
const appStoreApi = instance(appStoreApiMock);
|
||||
|
||||
const account: Account = {
|
||||
id: 'account_id',
|
||||
name: 'foobar',
|
||||
};
|
||||
const appleTeam = {
|
||||
id: 'apple-team-id',
|
||||
appleTeamIdentifier: 'ABC123Y',
|
||||
appleTeamName: 'John Doe (Individual)',
|
||||
};
|
||||
const action = new DeviceCreateAction(appStoreApi, account, appleTeam);
|
||||
await action.runAsync();
|
||||
|
||||
expect(runDeveloperPortalMethodAsync).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,20 +1,24 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import AppStoreApi from '../../../credentials/ios/appstore/AppStoreApi';
|
||||
import { AppleTeam } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { runDeveloperPortalMethodAsync } from './developerPortalMethod';
|
||||
import { runInputMethodAsync } from './inputMethod';
|
||||
import { runRegistrationUrlMethodAsync } from './registrationUrlMethod';
|
||||
|
||||
export enum RegistrationMethod {
|
||||
WEBSITE,
|
||||
INPUT,
|
||||
DEVELOPER_PORTAL,
|
||||
EXIT,
|
||||
}
|
||||
|
||||
export default class DeviceCreateAction {
|
||||
constructor(
|
||||
private appStoreApi: AppStoreApi,
|
||||
private account: Account,
|
||||
private appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName' | 'id'>
|
||||
) {}
|
||||
@@ -23,6 +27,8 @@ export default class DeviceCreateAction {
|
||||
const method = await this.askForRegistrationMethodAsync();
|
||||
if (method === RegistrationMethod.WEBSITE) {
|
||||
await runRegistrationUrlMethodAsync(this.account.id, this.appleTeam);
|
||||
} else if (method === RegistrationMethod.DEVELOPER_PORTAL) {
|
||||
await runDeveloperPortalMethodAsync(this.appStoreApi, this.account.id, this.appleTeam);
|
||||
} else if (method === RegistrationMethod.INPUT) {
|
||||
await runInputMethodAsync(this.account.id, this.appleTeam);
|
||||
} else if (method === RegistrationMethod.EXIT) {
|
||||
@@ -44,6 +50,12 @@ export default class DeviceCreateAction {
|
||||
)} - generates a registration URL to be opened on your devices`,
|
||||
value: RegistrationMethod.WEBSITE,
|
||||
},
|
||||
{
|
||||
title: `${chalk.bold(
|
||||
'Developer Portal'
|
||||
)} - import devices already registered on Apple Developer Portal`,
|
||||
value: RegistrationMethod.DEVELOPER_PORTAL,
|
||||
},
|
||||
{
|
||||
title: `${chalk.bold('Input')} - allows you to type in UDIDs (advanced option)`,
|
||||
value: RegistrationMethod.INPUT,
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import { Device, DeviceClass } from '@expo/apple-utils';
|
||||
|
||||
import { AppleDeviceMutation } from '../../../credentials/ios/api/graphql/mutations/AppleDeviceMutation';
|
||||
import {
|
||||
AppleDeviceFragmentWithAppleTeam,
|
||||
AppleDeviceQuery,
|
||||
} from '../../../credentials/ios/api/graphql/queries/AppleDeviceQuery';
|
||||
import AppStoreApi from '../../../credentials/ios/appstore/AppStoreApi';
|
||||
import { getRequestContext } from '../../../credentials/ios/appstore/authenticate';
|
||||
import { AuthCtx } from '../../../credentials/ios/appstore/authenticateTypes';
|
||||
import { AppleDeviceClass, AppleTeam } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { ora } from '../../../ora';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import chunk from '../../../utils/expodash/chunk';
|
||||
|
||||
const DEVICE_IMPORT_CHUNK_SIZE = 10;
|
||||
const DEVICE_CLASS_TO_GRAPHQL_TYPE: Partial<Record<DeviceClass, AppleDeviceClass>> = {
|
||||
[DeviceClass.IPAD]: AppleDeviceClass.Ipad,
|
||||
[DeviceClass.IPHONE]: AppleDeviceClass.Iphone,
|
||||
};
|
||||
|
||||
export async function runDeveloperPortalMethodAsync(
|
||||
appStoreApi: AppStoreApi,
|
||||
accountId: string,
|
||||
appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName' | 'id'>
|
||||
): Promise<void> {
|
||||
const appleAuthCtx = await appStoreApi.ensureAuthenticatedAsync();
|
||||
const unregisteredPortalDevices = await findUnregisteredPortalDevicesAsync(
|
||||
appleAuthCtx,
|
||||
accountId,
|
||||
appleTeam
|
||||
);
|
||||
if (unregisteredPortalDevices.length === 0) {
|
||||
Log.log('All your devices registered on Apple Developer Portal are already imported to EAS.');
|
||||
return;
|
||||
}
|
||||
const devicesToImport = await chooseDevicesToImportAsync(unregisteredPortalDevices);
|
||||
await importDevicesAsync(accountId, appleTeam, devicesToImport);
|
||||
}
|
||||
|
||||
async function importDevicesAsync(
|
||||
accountId: string,
|
||||
appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName' | 'id'>,
|
||||
devices: Device[]
|
||||
): Promise<void> {
|
||||
const spinner = ora(
|
||||
`Importing ${devices.length} Apple device${devices.length === 1 ? '' : 's'} to EAS`
|
||||
).start();
|
||||
const deviceChunks = chunk(devices, DEVICE_IMPORT_CHUNK_SIZE);
|
||||
try {
|
||||
for (const deviceChunk of deviceChunks) {
|
||||
await importDeviceChunkAsync(accountId, appleTeam, deviceChunk);
|
||||
}
|
||||
} catch (err) {
|
||||
spinner.fail();
|
||||
throw err;
|
||||
}
|
||||
spinner.succeed();
|
||||
}
|
||||
|
||||
async function importDeviceChunkAsync(
|
||||
accountId: string,
|
||||
appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName' | 'id'>,
|
||||
devices: Device[]
|
||||
): Promise<void> {
|
||||
const promises = devices.map(device => {
|
||||
return AppleDeviceMutation.createAppleDeviceAsync(
|
||||
{
|
||||
appleTeamId: appleTeam.id,
|
||||
identifier: device.attributes.udid,
|
||||
name: device.attributes.name,
|
||||
deviceClass: DEVICE_CLASS_TO_GRAPHQL_TYPE[device.attributes.deviceClass] ?? undefined,
|
||||
},
|
||||
accountId
|
||||
);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
async function findUnregisteredPortalDevicesAsync(
|
||||
appleAuthCtx: AuthCtx,
|
||||
accountId: string,
|
||||
appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName' | 'id'>
|
||||
): Promise<Device[]> {
|
||||
const expoRegisteredDevices = await AppleDeviceQuery.getAllByAppleTeamIdentifierAsync(
|
||||
accountId,
|
||||
appleTeam.appleTeamIdentifier,
|
||||
{ useCache: false }
|
||||
);
|
||||
const expoRegisteredDevicesByUdid = expoRegisteredDevices.reduce((acc, device) => {
|
||||
acc[device.identifier] = device;
|
||||
return acc;
|
||||
}, {} as Record<string, AppleDeviceFragmentWithAppleTeam>);
|
||||
|
||||
const portalDevices = await Device.getAllIOSProfileDevicesAsync(getRequestContext(appleAuthCtx));
|
||||
return portalDevices.filter(
|
||||
portalDevice =>
|
||||
!(portalDevice.attributes.udid in expoRegisteredDevicesByUdid) &&
|
||||
[DeviceClass.IPAD, DeviceClass.IPHONE].includes(portalDevice.attributes.deviceClass)
|
||||
);
|
||||
}
|
||||
|
||||
async function chooseDevicesToImportAsync(devices: Device[]): Promise<Device[]> {
|
||||
const { chosenDevices } = await promptAsync({
|
||||
type: 'multiselect',
|
||||
name: 'chosenDevices',
|
||||
message: 'Which devices do you want to import to EAS?',
|
||||
choices: devices.map(device => ({
|
||||
value: device,
|
||||
title: formatDeviceLabel(device),
|
||||
selected: true,
|
||||
})),
|
||||
});
|
||||
return chosenDevices;
|
||||
}
|
||||
|
||||
export function formatDeviceLabel(device: Device): string {
|
||||
const deviceDetails = formatDeviceDetails(device);
|
||||
return `${device.attributes.name} - ${device.attributes.udid})${
|
||||
deviceDetails !== '' ? ` - ${deviceDetails}` : ''
|
||||
}`;
|
||||
}
|
||||
|
||||
function formatDeviceDetails(device: Device): string {
|
||||
let details: string = device.attributes.deviceClass;
|
||||
if (device.attributes.model) {
|
||||
details = device.attributes.model;
|
||||
}
|
||||
return details === '' ? details : `(${details})`;
|
||||
}
|
||||
@@ -29,12 +29,12 @@ export default class DeviceManager {
|
||||
Log.addNewLineIfNone();
|
||||
|
||||
const account = await this.resolveAccountAsync();
|
||||
const { team } = await this.ctx.appStore.ensureAuthenticatedAsync();
|
||||
const appleAuthCtx = await this.ctx.appStore.ensureAuthenticatedAsync();
|
||||
const appleTeam = await ensureAppleTeamExistsAsync(account.id, {
|
||||
appleTeamIdentifier: team.id,
|
||||
appleTeamName: team.name,
|
||||
appleTeamIdentifier: appleAuthCtx.team.id,
|
||||
appleTeamName: appleAuthCtx.team.name,
|
||||
});
|
||||
const action = new DeviceCreateAction(account, appleTeam);
|
||||
const action = new DeviceCreateAction(this.ctx.appStore, account, appleTeam);
|
||||
await action.runAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
+213
-2
@@ -123,6 +123,8 @@ export type Account = {
|
||||
requiresAccessTokenForPushSecurity: Scalars['Boolean'];
|
||||
/** Snacks associated with this account */
|
||||
snacks: Array<Snack>;
|
||||
/** SSO configuration for this account */
|
||||
ssoConfiguration?: Maybe<AccountSsoConfiguration>;
|
||||
/** Subscription info visible to members that have VIEWER role */
|
||||
subscription?: Maybe<SubscriptionDetails>;
|
||||
/** @deprecated No longer needed */
|
||||
@@ -393,6 +395,90 @@ export type AccountQueryByNameArgs = {
|
||||
accountName: Scalars['String'];
|
||||
};
|
||||
|
||||
/** Auth configuration data for an SSO account. */
|
||||
export type AccountSsoConfiguration = {
|
||||
__typename?: 'AccountSSOConfiguration';
|
||||
authEndpoint?: Maybe<Scalars['String']>;
|
||||
authProtocol: AuthProtocolType;
|
||||
authProviderIdentifier: Scalars['String'];
|
||||
clientIdentifier: Scalars['String'];
|
||||
clientSecret: Scalars['String'];
|
||||
createdAt: Scalars['DateTime'];
|
||||
id: Scalars['ID'];
|
||||
issuer?: Maybe<Scalars['String']>;
|
||||
jwksEndpoint?: Maybe<Scalars['String']>;
|
||||
redirectUri: Scalars['String'];
|
||||
tokenEndpoint?: Maybe<Scalars['String']>;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
userInfoEndpoint?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type AccountSsoConfigurationData = {
|
||||
authEndpoint?: InputMaybe<Scalars['String']>;
|
||||
authProtocol: AuthProtocolType;
|
||||
authProviderIdentifier: Scalars['String'];
|
||||
clientIdentifier: Scalars['String'];
|
||||
clientSecret: Scalars['String'];
|
||||
issuer?: InputMaybe<Scalars['String']>;
|
||||
jwksEndpoint?: InputMaybe<Scalars['String']>;
|
||||
redirectUri: Scalars['String'];
|
||||
tokenEndpoint?: InputMaybe<Scalars['String']>;
|
||||
userInfoEndpoint?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type AccountSsoConfigurationMutation = {
|
||||
__typename?: 'AccountSSOConfigurationMutation';
|
||||
/** Create an AccountSSOConfiguration for an Account */
|
||||
createAccountSSOConfiguration: AccountSsoConfiguration;
|
||||
/** Delete an AccountSSOConfiguration */
|
||||
deleteAccountSSOConfiguration: DeleteAccountSsoConfigurationResult;
|
||||
/** Update an AccountSSOConfiguration */
|
||||
updateAccountSSOConfiguration: AccountSsoConfiguration;
|
||||
};
|
||||
|
||||
|
||||
export type AccountSsoConfigurationMutationCreateAccountSsoConfigurationArgs = {
|
||||
accountId: Scalars['ID'];
|
||||
accountSSOConfigurationData: AccountSsoConfigurationData;
|
||||
};
|
||||
|
||||
|
||||
export type AccountSsoConfigurationMutationDeleteAccountSsoConfigurationArgs = {
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
|
||||
export type AccountSsoConfigurationMutationUpdateAccountSsoConfigurationArgs = {
|
||||
accountSSOConfigurationData: AccountSsoConfigurationData;
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
/** Public auth configuration data for an SSO account. */
|
||||
export type AccountSsoConfigurationPublicData = {
|
||||
__typename?: 'AccountSSOConfigurationPublicData';
|
||||
authEndpoint?: Maybe<Scalars['String']>;
|
||||
authProtocol: AuthProtocolType;
|
||||
authProviderIdentifier: Scalars['String'];
|
||||
clientIdentifier: Scalars['String'];
|
||||
id: Scalars['ID'];
|
||||
issuer?: Maybe<Scalars['String']>;
|
||||
jwksEndpoint?: Maybe<Scalars['String']>;
|
||||
redirectUri: Scalars['String'];
|
||||
tokenEndpoint?: Maybe<Scalars['String']>;
|
||||
userInfoEndpoint?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type AccountSsoConfigurationPublicDataQuery = {
|
||||
__typename?: 'AccountSSOConfigurationPublicDataQuery';
|
||||
/** Get AccountSSOConfiguration public data by account name */
|
||||
publicDataByAccountName: AccountSsoConfigurationPublicData;
|
||||
};
|
||||
|
||||
|
||||
export type AccountSsoConfigurationPublicDataQueryPublicDataByAccountNameArgs = {
|
||||
accountName: Scalars['String'];
|
||||
};
|
||||
|
||||
export type AccountUsageMetric = {
|
||||
__typename?: 'AccountUsageMetric';
|
||||
id: Scalars['ID'];
|
||||
@@ -427,6 +513,7 @@ export type AccountUsageMetricsByBillingPeriodArgs = {
|
||||
|
||||
|
||||
export type AccountUsageMetricsMetricsForServiceMetricArgs = {
|
||||
filterParams?: InputMaybe<Scalars['JSONObject']>;
|
||||
granularity: UsageMetricsGranularity;
|
||||
serviceMetric: EasServiceMetric;
|
||||
timespan: UsageMetricsTimespan;
|
||||
@@ -708,6 +795,7 @@ export type AndroidJobInput = {
|
||||
type: BuildWorkflow;
|
||||
updates?: InputMaybe<BuildUpdatesInput>;
|
||||
username?: InputMaybe<Scalars['String']>;
|
||||
version?: InputMaybe<AndroidJobVersionInput>;
|
||||
};
|
||||
|
||||
export type AndroidJobKeystoreInput = {
|
||||
@@ -722,6 +810,10 @@ export type AndroidJobSecretsInput = {
|
||||
environmentSecrets?: InputMaybe<Scalars['JSONObject']>;
|
||||
};
|
||||
|
||||
export type AndroidJobVersionInput = {
|
||||
versionCode: Scalars['String'];
|
||||
};
|
||||
|
||||
export type AndroidKeystore = {
|
||||
__typename?: 'AndroidKeystore';
|
||||
account: Account;
|
||||
@@ -834,6 +926,7 @@ export type App = Project & {
|
||||
isLikedByMe: Scalars['Boolean'];
|
||||
/** @deprecated No longer supported */
|
||||
lastPublishedTime: Scalars['DateTime'];
|
||||
latestAppVersionByPlatformAndApplicationIdentifier?: Maybe<AppVersion>;
|
||||
latestReleaseForReleaseChannel?: Maybe<AppRelease>;
|
||||
/** ID of latest classic update release */
|
||||
latestReleaseId: Scalars['ID'];
|
||||
@@ -956,6 +1049,13 @@ export type AppIosAppCredentialsArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppLatestAppVersionByPlatformAndApplicationIdentifierArgs = {
|
||||
applicationIdentifier: Scalars['String'];
|
||||
platform: AppPlatform;
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppLatestReleaseForReleaseChannelArgs = {
|
||||
platform: AppPlatform;
|
||||
@@ -1198,6 +1298,52 @@ export enum AppStoreConnectUserRole {
|
||||
Unknown = 'UNKNOWN'
|
||||
}
|
||||
|
||||
/** Represents Play Store/App Store version of an application */
|
||||
export type AppVersion = {
|
||||
__typename?: 'AppVersion';
|
||||
/**
|
||||
* Store identifier for an application
|
||||
* - Android - applicationId
|
||||
* - iOS - bundle identifier
|
||||
*/
|
||||
applicationIdentifier: Scalars['String'];
|
||||
/**
|
||||
* Value that identifies build in a store (it's visible to developers, but not to end users)
|
||||
* - Android - versionCode in build.gradle ("android.versionCode" field in app.json)
|
||||
* - iOS - CFBundleVersion in Info.plist ("ios.buildNumber" field in app.json)
|
||||
*/
|
||||
buildVersion: Scalars['String'];
|
||||
id: Scalars['ID'];
|
||||
platform: AppPlatform;
|
||||
runtimeVersion?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* User-facing version in a store
|
||||
* - Android - versionName in build.gradle ("version" field in app.json)
|
||||
* - iOS - CFBundleShortVersionString in Info.plist ("version" field in app.json)
|
||||
*/
|
||||
storeVersion: Scalars['String'];
|
||||
};
|
||||
|
||||
export type AppVersionInput = {
|
||||
appId: Scalars['ID'];
|
||||
applicationIdentifier: Scalars['String'];
|
||||
buildVersion: Scalars['String'];
|
||||
platform: AppPlatform;
|
||||
runtimeVersion?: InputMaybe<Scalars['String']>;
|
||||
storeVersion: Scalars['String'];
|
||||
};
|
||||
|
||||
export type AppVersionMutation = {
|
||||
__typename?: 'AppVersionMutation';
|
||||
/** Create an app version */
|
||||
createAppVersion: AppVersion;
|
||||
};
|
||||
|
||||
|
||||
export type AppVersionMutationCreateAppVersionArgs = {
|
||||
appVersionInput: AppVersionInput;
|
||||
};
|
||||
|
||||
export type AppleAppIdentifier = {
|
||||
__typename?: 'AppleAppIdentifier';
|
||||
account: Account;
|
||||
@@ -1414,7 +1560,7 @@ export type ApplePushKey = {
|
||||
};
|
||||
|
||||
export type ApplePushKeyInput = {
|
||||
appleTeamId?: InputMaybe<Scalars['ID']>;
|
||||
appleTeamId: Scalars['ID'];
|
||||
keyIdentifier: Scalars['String'];
|
||||
keyP8: Scalars['String'];
|
||||
};
|
||||
@@ -1539,6 +1685,10 @@ export type AssetQueryMetadataArgs = {
|
||||
storageKeys: Array<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum AuthProtocolType {
|
||||
Oidc = 'OIDC'
|
||||
}
|
||||
|
||||
export type Billing = {
|
||||
__typename?: 'Billing';
|
||||
/** History of invoices */
|
||||
@@ -1567,6 +1717,7 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
|
||||
canRetry: Scalars['Boolean'];
|
||||
cancelingActor?: Maybe<Actor>;
|
||||
channel?: Maybe<Scalars['String']>;
|
||||
childBuild?: Maybe<Build>;
|
||||
completedAt?: Maybe<Scalars['DateTime']>;
|
||||
createdAt: Scalars['DateTime'];
|
||||
distribution?: Maybe<DistributionType>;
|
||||
@@ -1584,6 +1735,10 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
|
||||
iosEnterpriseProvisioning?: Maybe<BuildIosEnterpriseProvisioning>;
|
||||
isGitWorkingTreeDirty?: Maybe<Scalars['Boolean']>;
|
||||
logFiles: Array<Scalars['String']>;
|
||||
maxBuildTimeSeconds: Scalars['Int'];
|
||||
/** Retry time starts after completedAt */
|
||||
maxRetryTimeMinutes: Scalars['Int'];
|
||||
message?: Maybe<Scalars['String']>;
|
||||
metrics?: Maybe<BuildMetrics>;
|
||||
parentBuild?: Maybe<Build>;
|
||||
platform: AppPlatform;
|
||||
@@ -1741,6 +1896,7 @@ export type BuildMetadataInput = {
|
||||
gitCommitHash?: InputMaybe<Scalars['String']>;
|
||||
iosEnterpriseProvisioning?: InputMaybe<BuildIosEnterpriseProvisioning>;
|
||||
isGitWorkingTreeDirty?: InputMaybe<Scalars['Boolean']>;
|
||||
message?: InputMaybe<Scalars['String']>;
|
||||
reactNativeVersion?: InputMaybe<Scalars['String']>;
|
||||
releaseChannel?: InputMaybe<Scalars['String']>;
|
||||
runtimeVersion?: InputMaybe<Scalars['String']>;
|
||||
@@ -1817,7 +1973,8 @@ export type BuildParamsInput = {
|
||||
|
||||
export enum BuildPriority {
|
||||
High = 'HIGH',
|
||||
Normal = 'NORMAL'
|
||||
Normal = 'NORMAL',
|
||||
NormalPlus = 'NORMAL_PLUS'
|
||||
}
|
||||
|
||||
/** Publicly visible data for a Build. */
|
||||
@@ -2028,6 +2185,11 @@ export type DeleteAccountResult = {
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type DeleteAccountSsoConfigurationResult = {
|
||||
__typename?: 'DeleteAccountSSOConfigurationResult';
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type DeleteAndroidKeystoreResult = {
|
||||
__typename?: 'DeleteAndroidKeystoreResult';
|
||||
id: Scalars['ID'];
|
||||
@@ -2123,6 +2285,7 @@ export enum EasBuildDeprecationInfoType {
|
||||
export enum EasServiceMetric {
|
||||
AssetsRequests = 'ASSETS_REQUESTS',
|
||||
BandwidthUsage = 'BANDWIDTH_USAGE',
|
||||
Builds = 'BUILDS',
|
||||
ManifestRequests = 'MANIFEST_REQUESTS',
|
||||
UniqueUsers = 'UNIQUE_USERS'
|
||||
}
|
||||
@@ -2216,6 +2379,15 @@ export enum Feature {
|
||||
Teams = 'TEAMS'
|
||||
}
|
||||
|
||||
export type FutureSubscription = {
|
||||
__typename?: 'FutureSubscription';
|
||||
createdAt: Scalars['DateTime'];
|
||||
id: Scalars['ID'];
|
||||
meteredBillingStatus: MeteredBillingStatus;
|
||||
planId: Scalars['String'];
|
||||
startDate: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export type GetSignedAssetUploadSpecificationsResult = {
|
||||
__typename?: 'GetSignedAssetUploadSpecificationsResult';
|
||||
specifications: Array<Scalars['String']>;
|
||||
@@ -2488,6 +2660,7 @@ export type IosJobInput = {
|
||||
type: BuildWorkflow;
|
||||
updates?: InputMaybe<BuildUpdatesInput>;
|
||||
username?: InputMaybe<Scalars['String']>;
|
||||
version?: InputMaybe<IosJobVersionInput>;
|
||||
};
|
||||
|
||||
export type IosJobSecretsInput = {
|
||||
@@ -2501,6 +2674,10 @@ export type IosJobTargetCredentialsInput = {
|
||||
targetName: Scalars['String'];
|
||||
};
|
||||
|
||||
export type IosJobVersionInput = {
|
||||
buildNumber: Scalars['String'];
|
||||
};
|
||||
|
||||
/** @deprecated Use developmentClient option instead. */
|
||||
export enum IosManagedBuildType {
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT',
|
||||
@@ -2682,6 +2859,11 @@ export type MeMutationUpdateProfileArgs = {
|
||||
userData: UserDataInput;
|
||||
};
|
||||
|
||||
export type MeteredBillingStatus = {
|
||||
__typename?: 'MeteredBillingStatus';
|
||||
EAS_UPDATE: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type Offer = {
|
||||
__typename?: 'Offer';
|
||||
features?: Maybe<Array<Maybe<Feature>>>;
|
||||
@@ -2898,6 +3080,8 @@ export type RootMutation = {
|
||||
accessToken: AccessTokenMutation;
|
||||
/** Mutations that modify an Account */
|
||||
account: AccountMutation;
|
||||
/** Mutations that create, update, and delete an AccountSSOConfiguration */
|
||||
accountSSOConfiguration: AccountSsoConfigurationMutation;
|
||||
/** Mutations that modify the build credentials for an Android app */
|
||||
androidAppBuildCredentials: AndroidAppBuildCredentialsMutation;
|
||||
/** Mutations that modify the credentials for an Android app */
|
||||
@@ -2910,6 +3094,8 @@ export type RootMutation = {
|
||||
app?: Maybe<AppMutation>;
|
||||
/** Mutations that modify an App Store Connect Api Key */
|
||||
appStoreConnectApiKey: AppStoreConnectApiKeyMutation;
|
||||
/** Mutations that modify an AppVersion */
|
||||
appVersion: AppVersionMutation;
|
||||
/** Mutations that modify an Identifier for an iOS App */
|
||||
appleAppIdentifier: AppleAppIdentifierMutation;
|
||||
/** Mutations that modify an Apple Device */
|
||||
@@ -2985,6 +3171,8 @@ export type RootQuery = {
|
||||
_doNotUse?: Maybe<Scalars['String']>;
|
||||
/** Top-level query object for querying Accounts. */
|
||||
account: AccountQuery;
|
||||
/** Top-level query object for querying AccountSSOConfigurationPublicData */
|
||||
accountSSOConfigurationPublicData: AccountSsoConfigurationPublicDataQuery;
|
||||
/** Top-level query object for querying Actors. */
|
||||
actor: ActorQuery;
|
||||
/**
|
||||
@@ -3178,12 +3366,16 @@ export type Submission = ActivityTimelineProjectActivity & {
|
||||
archiveUrl?: Maybe<Scalars['String']>;
|
||||
canRetry: Scalars['Boolean'];
|
||||
cancelingActor?: Maybe<Actor>;
|
||||
childSubmission?: Maybe<Submission>;
|
||||
completedAt?: Maybe<Scalars['DateTime']>;
|
||||
createdAt: Scalars['DateTime'];
|
||||
error?: Maybe<SubmissionError>;
|
||||
id: Scalars['ID'];
|
||||
initiatingActor?: Maybe<Actor>;
|
||||
iosConfig?: Maybe<IosSubmissionConfig>;
|
||||
logsUrl?: Maybe<Scalars['String']>;
|
||||
/** Retry time starts after completedAt */
|
||||
maxRetryTimeMinutes: Scalars['Int'];
|
||||
parentSubmission?: Maybe<Submission>;
|
||||
platform: AppPlatform;
|
||||
status: SubmissionStatus;
|
||||
@@ -3279,8 +3471,10 @@ export type SubscriptionDetails = {
|
||||
cancelledAt?: Maybe<Scalars['DateTime']>;
|
||||
concurrencies?: Maybe<Concurrencies>;
|
||||
endedAt?: Maybe<Scalars['DateTime']>;
|
||||
futureSubscription?: Maybe<FutureSubscription>;
|
||||
id: Scalars['ID'];
|
||||
isDowngrading?: Maybe<Scalars['Boolean']>;
|
||||
meteredBillingStatus: MeteredBillingStatus;
|
||||
name?: Maybe<Scalars['String']>;
|
||||
nextInvoice?: Maybe<Scalars['DateTime']>;
|
||||
planId?: Maybe<Scalars['String']>;
|
||||
@@ -3475,6 +3669,7 @@ export type UsageMetricTotal = {
|
||||
|
||||
export enum UsageMetricType {
|
||||
Bandwidth = 'BANDWIDTH',
|
||||
Build = 'BUILD',
|
||||
Request = 'REQUEST',
|
||||
User = 'USER'
|
||||
}
|
||||
@@ -4330,6 +4525,13 @@ export type CreateAppMutationVariables = Exact<{
|
||||
|
||||
export type CreateAppMutation = { __typename?: 'RootMutation', app?: { __typename?: 'AppMutation', createApp: { __typename?: 'App', id: string } } | null };
|
||||
|
||||
export type CreateAppVersionMutationVariables = Exact<{
|
||||
appVersionInput: AppVersionInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateAppVersionMutation = { __typename?: 'RootMutation', appVersion: { __typename?: 'AppVersionMutation', createAppVersion: { __typename?: 'AppVersion', id: string } } };
|
||||
|
||||
export type CreateAndroidBuildMutationVariables = Exact<{
|
||||
appId: Scalars['ID'];
|
||||
job: AndroidJobInput;
|
||||
@@ -4448,6 +4650,15 @@ export type DeleteWebhookMutationVariables = Exact<{
|
||||
|
||||
export type DeleteWebhookMutation = { __typename?: 'RootMutation', webhook: { __typename?: 'WebhookMutation', deleteWebhook: { __typename?: 'DeleteWebhookResult', id: string } } };
|
||||
|
||||
export type LatestAppVersionQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
platform: AppPlatform;
|
||||
applicationIdentifier: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type LatestAppVersionQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, latestAppVersionByPlatformAndApplicationIdentifier?: { __typename?: 'AppVersion', id: string, storeVersion: string, buildVersion: string } | null } } };
|
||||
|
||||
export type ViewBranchQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import assert from 'assert';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
AppPlatform,
|
||||
CreateAppVersionMutation,
|
||||
CreateAppVersionMutationVariables,
|
||||
} from '../generated';
|
||||
|
||||
export const AppVersionMutation = {
|
||||
async createAppVersionAsync(appVersionInput: {
|
||||
appId: string;
|
||||
platform: AppPlatform;
|
||||
applicationIdentifier: string;
|
||||
storeVersion: string;
|
||||
buildVersion: string;
|
||||
runtimeVersion?: string;
|
||||
}): Promise<string> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateAppVersionMutation, CreateAppVersionMutationVariables>(
|
||||
gql`
|
||||
mutation CreateAppVersionMutation($appVersionInput: AppVersionInput!) {
|
||||
appVersion {
|
||||
createAppVersion(appVersionInput: $appVersionInput) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
appVersionInput,
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
const appVersionId = data.appVersion?.createAppVersion.id;
|
||||
assert(appVersionId, 'AppVersion ID must be defined');
|
||||
return appVersionId;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
AppPlatform,
|
||||
AppVersion,
|
||||
LatestAppVersionQuery,
|
||||
LatestAppVersionQueryVariables,
|
||||
} from '../generated';
|
||||
|
||||
export const AppVersionQuery = {
|
||||
async latestVersionAsync(
|
||||
appId: string,
|
||||
platform: AppPlatform,
|
||||
applicationIdentifier: string
|
||||
): Promise<Pick<AppVersion, 'storeVersion' | 'buildVersion'> | null> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<LatestAppVersionQuery, LatestAppVersionQueryVariables>(
|
||||
gql`
|
||||
query LatestAppVersion(
|
||||
$appId: String!
|
||||
$platform: AppPlatform!
|
||||
$applicationIdentifier: String!
|
||||
) {
|
||||
app {
|
||||
byId(appId: $appId) {
|
||||
id
|
||||
latestAppVersionByPlatformAndApplicationIdentifier(
|
||||
platform: $platform
|
||||
applicationIdentifier: $applicationIdentifier
|
||||
) {
|
||||
id
|
||||
storeVersion
|
||||
buildVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ appId, applicationIdentifier, platform },
|
||||
{
|
||||
additionalTypenames: ['App', 'AppVersion'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
return data.app.byId.latestAppVersionByPlatformAndApplicationIdentifier ?? null;
|
||||
},
|
||||
};
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,73 @@
|
||||
import { AppCategoryId, AppSubcategoryId } from '@expo/apple-utils';
|
||||
|
||||
import { AppleConfigReader } from '../reader';
|
||||
import { leastRestrictiveAdvisory, mostRestrictiveAdvisory } from './fixtures/ageRatingDeclaration';
|
||||
|
||||
describe('setAgeRating', () => {
|
||||
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();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { AppCategoryId, AppSubcategoryId } from '@expo/apple-utils';
|
||||
|
||||
import { AppleConfigWriter } from '../writer';
|
||||
import {
|
||||
emptyAdvisory,
|
||||
@@ -5,7 +7,7 @@ import {
|
||||
leastRestrictiveAdvisory,
|
||||
mostRestrictiveAdvisory,
|
||||
} from './fixtures/ageRatingDeclaration';
|
||||
import { primaryAndSecondaryCategory, secondaryOnlyCategory } from './fixtures/appInfo';
|
||||
import { makeCategoryInfo } from './fixtures/appInfo';
|
||||
import { dutchInfo, englishInfo } from './fixtures/appInfoLocalization';
|
||||
import { nameAndDemoReviewDetails, nameOnlyReviewDetails } from './fixtures/appStoreReviewDetail';
|
||||
import { automaticRelease, manualRelease, scheduledRelease } from './fixtures/appStoreVersion';
|
||||
@@ -73,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]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -78,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. */
|
||||
|
||||
@@ -64,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -47,6 +47,23 @@ export async function selectRequestedPlatformAsync(platform?: string): Promise<R
|
||||
return requestedPlatform;
|
||||
}
|
||||
|
||||
export async function selectPlatformAsync(platform?: string): Promise<Platform> {
|
||||
if (platform && Object.values(Platform).includes(platform.toLowerCase() as Platform)) {
|
||||
return platform.toLowerCase() as Platform;
|
||||
}
|
||||
|
||||
const { resolvedPlatform } = await promptAsync({
|
||||
type: 'select',
|
||||
message: 'Select platform',
|
||||
name: 'resolvedPlatform',
|
||||
choices: [
|
||||
{ title: 'Android', value: Platform.ANDROID },
|
||||
{ title: 'iOS', value: Platform.IOS },
|
||||
],
|
||||
});
|
||||
return resolvedPlatform;
|
||||
}
|
||||
|
||||
export function toPlatforms(requestedPlatform: RequestedPlatform): Platform[] {
|
||||
if (requestedPlatform === RequestedPlatform.All) {
|
||||
return [Platform.ANDROID, Platform.IOS];
|
||||
|
||||
@@ -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],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { getNextVersionCode, isValidVersionCode } from '../versions';
|
||||
|
||||
describe(isValidVersionCode, () => {
|
||||
test('isValidVersionCode()', () => {
|
||||
expect(isValidVersionCode(123)).toBe(true);
|
||||
expect(isValidVersionCode(12.3)).toBe(false);
|
||||
expect(isValidVersionCode(-123)).toBe(false);
|
||||
expect(isValidVersionCode(2000000000)).toBe(true);
|
||||
expect(isValidVersionCode(2000000001)).toBe(false);
|
||||
expect(isValidVersionCode('123')).toBe(true);
|
||||
expect(isValidVersionCode('12.3')).toBe(false);
|
||||
expect(isValidVersionCode('-123')).toBe(false);
|
||||
expect(isValidVersionCode('2000000000')).toBe(true);
|
||||
expect(isValidVersionCode('2000000001')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe(getNextVersionCode, () => {
|
||||
test('getNextVersionCode()', () => {
|
||||
expect(getNextVersionCode(123)).toBe(124);
|
||||
expect(() => getNextVersionCode(12.3)).toThrowError();
|
||||
expect(getNextVersionCode(1999999999)).toBe(2000000000);
|
||||
expect(() => getNextVersionCode(2000000000)).toThrowError();
|
||||
expect(getNextVersionCode('123')).toBe(124);
|
||||
expect(() => getNextVersionCode('12.3')).toThrowError();
|
||||
});
|
||||
});
|
||||
@@ -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) {
|
||||
@@ -155,10 +159,10 @@ export function warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject(
|
||||
): void {
|
||||
if (AndroidConfig.Package.getPackage(exp) && !warnPrinted) {
|
||||
Log.warn(
|
||||
`Specifying "android.package" in ${getProjectConfigDescription(
|
||||
`Specified value for "android.package" in ${getProjectConfigDescription(
|
||||
projectDir
|
||||
)} is deprecated for bare workflow projects.\n` +
|
||||
'EAS Build depends only on the value in the native code. Please remove the deprecated configuration.'
|
||||
)} is ignored because an ${chalk.bold('android')} directory was detected in the project.\n` +
|
||||
'EAS Build will use the value found in the native code.'
|
||||
);
|
||||
warnPrinted = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import assert from 'assert';
|
||||
|
||||
const MAX_VERSION_CODE = 2000000000;
|
||||
export const VERSION_CODE_REQUIREMENTS = `versionCode needs to be a positive integer smaller or equal to ${MAX_VERSION_CODE}`;
|
||||
|
||||
export function isValidVersionCode(versionCode: string | number): boolean {
|
||||
const numericVersionCode = typeof versionCode === 'string' ? Number(versionCode) : versionCode;
|
||||
return (
|
||||
Number.isInteger(numericVersionCode) &&
|
||||
numericVersionCode <= MAX_VERSION_CODE &&
|
||||
numericVersionCode > 0
|
||||
);
|
||||
}
|
||||
|
||||
export function getNextVersionCode(versionCode: string | number): number {
|
||||
assert(isValidVersionCode(versionCode), `Invalid versionCode ${versionCode}`);
|
||||
const numericVersionCode = typeof versionCode === 'string' ? Number(versionCode) : versionCode;
|
||||
if (numericVersionCode >= MAX_VERSION_CODE) {
|
||||
throw new Error(`Invalid value: ${VERSION_CODE_REQUIREMENTS}.`);
|
||||
}
|
||||
return numericVersionCode + 1;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
|
||||
import {
|
||||
ensureApplicationIdIsDefinedForManagedProjectAsync,
|
||||
getApplicationIdAsync,
|
||||
} from './android/applicationId';
|
||||
import { resolveGradleBuildContextAsync } from './android/gradle';
|
||||
import { getBundleIdentifierAsync } from './ios/bundleIdentifier';
|
||||
import { resolveXcodeBuildContextAsync } from './ios/scheme';
|
||||
import { findApplicationTarget, resolveTargetsAsync } from './ios/target';
|
||||
import { resolveWorkflowAsync } from './workflow';
|
||||
|
||||
export async function getApplicationIdentifierAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig,
|
||||
buildProfile: BuildProfile,
|
||||
platform: Platform
|
||||
): Promise<string> {
|
||||
if (platform === Platform.ANDROID) {
|
||||
const profile = buildProfile as BuildProfile<Platform.ANDROID>;
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
const gradleContext = await resolveGradleBuildContextAsync(projectDir, profile);
|
||||
|
||||
if (workflow === Workflow.MANAGED) {
|
||||
await ensureApplicationIdIsDefinedForManagedProjectAsync(projectDir, exp);
|
||||
}
|
||||
|
||||
return await getApplicationIdAsync(projectDir, exp, gradleContext);
|
||||
} else {
|
||||
const profile = buildProfile as BuildProfile<Platform.IOS>;
|
||||
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
|
||||
{ exp, projectDir, nonInteractive: false },
|
||||
profile
|
||||
);
|
||||
const targets = await resolveTargetsAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
xcodeBuildContext,
|
||||
env: profile.env,
|
||||
});
|
||||
const applicationTarget = findApplicationTarget(targets);
|
||||
return await getBundleIdentifierAsync(projectDir, exp, {
|
||||
targetName: applicationTarget.targetName,
|
||||
buildConfiguration: applicationTarget.buildConfiguration,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -41,8 +41,8 @@ export async function ensureProjectExistsAsync(projectInfo: ProjectInfo): Promis
|
||||
});
|
||||
|
||||
const spinner = ora(`Linking to project ${chalk.bold(projectFullName)}`).start();
|
||||
|
||||
const maybeId = await findProjectIdByAccountNameAndSlugNullableAsync(accountName, projectName);
|
||||
|
||||
if (maybeId) {
|
||||
spinner.succeed(`Linked to project ${chalk.bold(projectLink)}`);
|
||||
projectCache[projectFullName] = maybeId;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { getNextBuildNumber, isValidBuildNumber } from '../versions';
|
||||
|
||||
describe(isValidBuildNumber, () => {
|
||||
test('isValidBuildNumber()', () => {
|
||||
expect(isValidBuildNumber('1.2.3')).toBe(true);
|
||||
expect(isValidBuildNumber('1.2')).toBe(true);
|
||||
expect(isValidBuildNumber('1')).toBe(true);
|
||||
expect(isValidBuildNumber('1.2.3.4')).toBe(false);
|
||||
expect(isValidBuildNumber('1.2.-3')).toBe(false);
|
||||
expect(isValidBuildNumber('1.a.3')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe(getNextBuildNumber, () => {
|
||||
test('getNextBuildNumber', () => {
|
||||
expect(getNextBuildNumber('1.2.3')).toBe('1.2.4');
|
||||
expect(getNextBuildNumber('1.2')).toBe('1.3');
|
||||
expect(getNextBuildNumber('1')).toBe('2');
|
||||
expect(() => getNextBuildNumber('1.2.3.4')).toThrowError();
|
||||
});
|
||||
});
|
||||
@@ -152,10 +152,10 @@ export function warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject(
|
||||
): void {
|
||||
if (IOSConfig.BundleIdentifier.getBundleIdentifier(exp) && !warnPrinted) {
|
||||
Log.warn(
|
||||
`Specifying "ios.bundleIdentifier" in ${getProjectConfigDescription(
|
||||
`Specified value for "ios.bundleIdentifier" in ${getProjectConfigDescription(
|
||||
projectDir
|
||||
)} is deprecated for bare workflow projects.\n` +
|
||||
'EAS Build depends only on the value in the native code. Please remove the deprecated configuration.'
|
||||
)} is ignored because an ${chalk.bold('ios')} directory was detected in the project.\n` +
|
||||
'EAS Build will use the value found in the native code.'
|
||||
);
|
||||
warnPrinted = true;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { IOSConfig } from '@expo/config-plugins';
|
||||
import { IOSConfig, XcodeProject } from '@expo/config-plugins';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { JSONObject } from '@expo/json-file';
|
||||
import Joi from 'joi';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { Target } from '../../credentials/ios/types';
|
||||
import { resolveWorkflowAsync } from '../workflow';
|
||||
@@ -91,6 +92,7 @@ export async function resolveBareProjectTargetsAsync({
|
||||
const { buildScheme, buildConfiguration } = xcodeBuildContext;
|
||||
const result: Target[] = [];
|
||||
|
||||
const pbxProject = IOSConfig.XcodeUtils.getPbxproj(projectDir);
|
||||
const applicationTarget = await IOSConfig.Target.findApplicationTargetWithDependenciesAsync(
|
||||
projectDir,
|
||||
buildScheme
|
||||
@@ -108,6 +110,11 @@ export async function resolveBareProjectTargetsAsync({
|
||||
bundleIdentifier,
|
||||
buildConfiguration,
|
||||
entitlements: entitlements ?? {},
|
||||
buildSettings: resolveBareProjectBuildSettings(
|
||||
pbxProject,
|
||||
applicationTarget.name,
|
||||
buildConfiguration
|
||||
),
|
||||
});
|
||||
|
||||
const dependencies = await resolveBareProjectDependenciesAsync({
|
||||
@@ -116,6 +123,7 @@ export async function resolveBareProjectTargetsAsync({
|
||||
buildConfiguration,
|
||||
target: applicationTarget,
|
||||
bundleIdentifier,
|
||||
pbxProject,
|
||||
});
|
||||
if (dependencies.length > 0) {
|
||||
result.push(...dependencies);
|
||||
@@ -141,12 +149,14 @@ async function resolveBareProjectDependenciesAsync({
|
||||
buildConfiguration,
|
||||
target,
|
||||
bundleIdentifier,
|
||||
pbxProject,
|
||||
}: {
|
||||
exp: ExpoConfig;
|
||||
projectDir: string;
|
||||
buildConfiguration?: string;
|
||||
target: IOSConfig.Target.Target;
|
||||
bundleIdentifier: string;
|
||||
pbxProject: XcodeProject;
|
||||
}): Promise<Target[]> {
|
||||
const result: Target[] = [];
|
||||
|
||||
@@ -166,6 +176,11 @@ async function resolveBareProjectDependenciesAsync({
|
||||
bundleIdentifier: dependencyBundleIdentifier,
|
||||
parentBundleIdentifier: bundleIdentifier,
|
||||
entitlements: entitlements ?? {},
|
||||
buildSettings: resolveBareProjectBuildSettings(
|
||||
pbxProject,
|
||||
dependency.name,
|
||||
buildConfiguration
|
||||
),
|
||||
});
|
||||
const dependencyDependencies = await resolveBareProjectDependenciesAsync({
|
||||
exp,
|
||||
@@ -173,6 +188,7 @@ async function resolveBareProjectDependenciesAsync({
|
||||
buildConfiguration,
|
||||
target: dependency,
|
||||
bundleIdentifier: dependencyBundleIdentifier,
|
||||
pbxProject,
|
||||
});
|
||||
if (dependencyDependencies.length > 0) {
|
||||
result.push(...dependencyDependencies);
|
||||
@@ -198,3 +214,15 @@ export function findTargetByName(targets: Target[], name: string): Target {
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
function resolveBareProjectBuildSettings(
|
||||
project: XcodeProject,
|
||||
targetName: string,
|
||||
buildConfiguration?: string
|
||||
): XCBuildConfiguration['buildSettings'] {
|
||||
const xcBuildConfiguration = IOSConfig.Target.getXCBuildConfigurationFromPbxproj(project, {
|
||||
targetName,
|
||||
buildConfiguration,
|
||||
});
|
||||
return xcBuildConfiguration?.buildSettings ?? {};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import assert from 'assert';
|
||||
|
||||
export const BUILD_NUMBER_REQUIREMENTS = `buildNumber needs to consist only of up to 3 dot-separated positive integers`;
|
||||
|
||||
export function isValidBuildNumber(buildNumber: string): boolean {
|
||||
return !!buildNumber.match(/^\d+(\.\d+)?(\.\d+)?$/);
|
||||
}
|
||||
|
||||
export function getNextBuildNumber(buildNumber: string): string {
|
||||
assert(isValidBuildNumber(buildNumber), `Invalid buildNumber ${buildNumber}`);
|
||||
const comps = buildNumber.split('.');
|
||||
comps[comps.length - 1] = String(Number(comps[comps.length - 1]) + 1);
|
||||
return comps.join('.');
|
||||
}
|
||||
@@ -15,8 +15,6 @@ 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;
|
||||
@@ -163,6 +161,7 @@ export async function buildBundlesAsync({
|
||||
inputDir,
|
||||
'--experimental-bundle',
|
||||
'--non-interactive',
|
||||
'--dump-sourcemap',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -255,7 +254,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;
|
||||
@@ -266,7 +265,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 => {
|
||||
@@ -281,9 +279,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;
|
||||
@@ -291,32 +290,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,
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { AppVersionSource, EasJsonReader } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import Log from '../log';
|
||||
import { confirmAsync } from '../prompts';
|
||||
|
||||
export async function ensureRemoteVersionPolicyAsync(
|
||||
projectDir: string,
|
||||
easJsonReader: EasJsonReader
|
||||
): Promise<void> {
|
||||
const easJsonCliConfig = await easJsonReader.getCliConfigAsync();
|
||||
if (easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.log(
|
||||
`The app version source defines whether the app version is stored locally in your project source (e.g. in app.json, Info.plist, or build.gradle) or remotely on EAS servers and only applied to the project at build time. To use this command, you will need to enable the remote version policy by adding ${chalk.bold(
|
||||
'{"cli": { "appVersionSource": "remote" }}'
|
||||
)} in eas.json.`
|
||||
);
|
||||
// TODO: add link to docs
|
||||
const confirm = await confirmAsync({
|
||||
message: 'Do you want to set app version source to remote now?',
|
||||
});
|
||||
if (!confirm) {
|
||||
throw new Error('Aborting...');
|
||||
}
|
||||
|
||||
const easJsonPath = EasJsonReader.formatEasJsonPath(projectDir);
|
||||
const easJson = await fs.readJSON(easJsonPath);
|
||||
easJson.cli = { ...easJson?.cli, appVersionSource: AppVersionSource.REMOTE };
|
||||
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
|
||||
Log.withTick('Updated eas.json');
|
||||
}
|
||||
|
||||
export async function validateAppConfigForRemoteVersionPolicyAsync(exp: ExpoConfig): Promise<void> {
|
||||
if (typeof exp.runtimeVersion === 'object' && exp.runtimeVersion?.policy === 'nativeVersion') {
|
||||
throw new Error(
|
||||
`${chalk.bold('nativeVersion')} policy for ${chalk.bold(
|
||||
'runtimeVersion'
|
||||
)} is currently not supported when version source is set to remote. Switch policy e.g. to ${chalk.bold(
|
||||
'appVersion'
|
||||
)} or define version explicitly.`
|
||||
);
|
||||
}
|
||||
if (exp.ios?.buildNumber !== undefined) {
|
||||
throw new Error(
|
||||
`${chalk.bold(
|
||||
'ios.buildNumber'
|
||||
)} field in app config is not supported when version source is set to remote, remove it and re-run the command.`
|
||||
);
|
||||
}
|
||||
if (exp.android?.versionCode !== undefined) {
|
||||
throw new Error(
|
||||
`${chalk.bold(
|
||||
'android.versionCode'
|
||||
)} field in app config is not supported when version source is set to remote, remove it and re-run the command.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function getBuildVersionName(platform: Platform): string {
|
||||
if (platform === Platform.ANDROID) {
|
||||
return 'versionCode';
|
||||
} else {
|
||||
return 'buildNumber';
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { AppPlatform, BuildFragment, UploadSessionType } from '../../graphql/gen
|
||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
import { toAppPlatform } from '../../graphql/types/AppPlatform';
|
||||
import { confirmAsync, promptAsync } from '../../prompts';
|
||||
import { uploadAsync } from '../../uploads';
|
||||
import { uploadFileAtPathToS3Async } from '../../uploads';
|
||||
import {
|
||||
Archive,
|
||||
ArchiveSourceType,
|
||||
@@ -242,7 +242,10 @@ 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' });
|
||||
|
||||
jest
|
||||
.mocked(uploadFileAtPathToS3Async)
|
||||
.mockResolvedValueOnce({ url: ARCHIVE_URL, bucketKey: 'wat' });
|
||||
|
||||
const archive = await getArchiveAsync({
|
||||
...SOURCE_STUB_INPUT,
|
||||
@@ -250,7 +253,7 @@ describe(getArchiveAsync, () => {
|
||||
path,
|
||||
});
|
||||
|
||||
expect(uploadAsync).toBeCalledWith(
|
||||
expect(uploadFileAtPathToS3Async).toBeCalledWith(
|
||||
UploadSessionType.EasSubmitAppArchive,
|
||||
path,
|
||||
expect.anything()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { UploadSessionType } from '../../graphql/generated';
|
||||
import { uploadAsync } from '../../uploads';
|
||||
import { uploadFileAtPathToS3Async } from '../../uploads';
|
||||
import { createProgressTracker } from '../../utils/progress';
|
||||
|
||||
export async function isExistingFileAsync(filePath: string): Promise<boolean> {
|
||||
@@ -15,7 +15,7 @@ export async function isExistingFileAsync(filePath: string): Promise<boolean> {
|
||||
|
||||
export async function uploadAppArchiveAsync(path: string): Promise<string> {
|
||||
const fileSize = (await fs.stat(path)).size;
|
||||
const { url } = await uploadAsync(
|
||||
const { url } = await uploadFileAtPathToS3Async(
|
||||
UploadSessionType.EasSubmitAppArchive,
|
||||
path,
|
||||
createProgressTracker({
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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';
|
||||
@@ -8,14 +10,20 @@ import { UploadSessionType } from './graphql/generated';
|
||||
import { PresignedPost, UploadSessionMutation } from './graphql/mutations/UploadSessionMutation';
|
||||
import { ProgressHandler } from './utils/progress';
|
||||
|
||||
export async function uploadAsync(
|
||||
export async function uploadFileAtPathToS3Async(
|
||||
type: UploadSessionType,
|
||||
path: string,
|
||||
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 };
|
||||
}
|
||||
|
||||
@@ -23,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();
|
||||
@@ -55,14 +63,13 @@ export async function uploadWithPresignedPostAsync(
|
||||
try {
|
||||
const response = await uploadPromise;
|
||||
handleProgressEvent({ isComplete: true });
|
||||
return fixArchiveUrl(String(response.headers.get('location')));
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
handleProgressEvent({ isComplete: true, error });
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
const response = await uploadPromise;
|
||||
return fixArchiveUrl(String(response.headers.get('location')));
|
||||
return await uploadPromise;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +78,7 @@ export async function uploadWithPresignedPostAsync(
|
||||
* 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 fixArchiveUrl(archiveUrl: string): string {
|
||||
export function fixS3Url(archiveUrl: string): string {
|
||||
const parsed = new URL(archiveUrl);
|
||||
parsed.pathname = decodeURIComponent(parsed.pathname);
|
||||
return parsed.toString();
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import chunk from '../chunk';
|
||||
|
||||
describe(chunk, () => {
|
||||
it('works with an empty list', () => {
|
||||
expect(chunk([])).toEqual([]);
|
||||
});
|
||||
|
||||
it('works with list length undividable by chunk size', () => {
|
||||
expect(chunk([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
|
||||
});
|
||||
|
||||
it('works with list length dividable by chunk size', () => {
|
||||
expect(chunk([1, 2, 3, 4, 5, 6], 2)).toEqual([
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
[5, 6],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
export default function chunk<T>(list: T[], size = 1): T[][] {
|
||||
const result: T[][] = [];
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const isFirstElementInChunk = i % size === 0;
|
||||
if (isFirstElementInChunk) {
|
||||
result.push([list[i]]);
|
||||
} else {
|
||||
result[result.length - 1].push(list[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"description": "A library for interacting with eas.json",
|
||||
"version": "0.54.0",
|
||||
"version": "0.56.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/eas-build-job": "0.2.77",
|
||||
"@expo/eas-build-job": "0.2.82",
|
||||
"@expo/json-file": "8.2.36",
|
||||
"chalk": "4.1.2",
|
||||
"env-string": "1.0.1",
|
||||
@@ -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"
|
||||
@@ -38,5 +38,9 @@
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.6.0",
|
||||
"yarn": "1.22.19"
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export { AndroidReleaseStatus, AndroidReleaseTrack, SubmitProfile } from './submit/types';
|
||||
export { getDefaultProfile as getDefaultSubmitProfile } from './submit/resolver';
|
||||
export { EasJson, ProfileType } from './types';
|
||||
export { EasJson, ProfileType, AppVersionSource } from './types';
|
||||
export {
|
||||
AndroidVersionAutoIncrement,
|
||||
BuildProfile,
|
||||
|
||||
@@ -2,11 +2,13 @@ import Joi from 'joi';
|
||||
|
||||
import { BuildProfileSchema } from './build/schema';
|
||||
import { SubmitProfileSchema } from './submit/schema';
|
||||
import { AppVersionSource } from './types';
|
||||
|
||||
export const EasJsonSchema = Joi.object({
|
||||
cli: Joi.object({
|
||||
version: Joi.string(),
|
||||
requireCommit: Joi.boolean(),
|
||||
appVersionSource: Joi.string().valid(...Object.values(AppVersionSource)),
|
||||
}),
|
||||
build: Joi.object().pattern(Joi.string(), BuildProfileSchema),
|
||||
submit: Joi.object().pattern(Joi.string(), SubmitProfileSchema),
|
||||
|
||||
@@ -11,10 +11,16 @@ export enum CredentialsSource {
|
||||
REMOTE = 'remote',
|
||||
}
|
||||
|
||||
export enum AppVersionSource {
|
||||
LOCAL = 'local',
|
||||
REMOTE = 'remote',
|
||||
}
|
||||
|
||||
export interface EasJson {
|
||||
cli?: {
|
||||
version?: string;
|
||||
requireCommit?: boolean;
|
||||
appVersionSource?: AppVersionSource;
|
||||
};
|
||||
build?: { [profileName: string]: EasJsonBuildProfile };
|
||||
submit?: { [profileName: string]: EasJsonSubmitProfile };
|
||||
|
||||
File renamed without changes.
+16
-11
@@ -1,33 +1,38 @@
|
||||
{
|
||||
"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/node": "17.0.32",
|
||||
"@types/semver": "7.3.9",
|
||||
"ts-node": "10.7.0",
|
||||
"typescript": "4.6.4"
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@types/node": "18.0.4",
|
||||
"@types/semver": "7.3.10",
|
||||
"ts-node": "10.9.1",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.6.0",
|
||||
"yarn": "1.22.19"
|
||||
}
|
||||
}
|
||||
@@ -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
+53
-38
@@ -2,17 +2,30 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@cspotcode/source-map-consumer@0.8.0":
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
|
||||
integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==
|
||||
|
||||
"@cspotcode/[email protected]":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5"
|
||||
integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==
|
||||
"@cspotcode/source-map-support@^0.8.0":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
|
||||
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
|
||||
dependencies:
|
||||
"@cspotcode/source-map-consumer" "0.8.0"
|
||||
"@jridgewell/trace-mapping" "0.3.9"
|
||||
|
||||
"@jridgewell/resolve-uri@^3.0.3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
|
||||
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10":
|
||||
version "1.4.14"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||
|
||||
"@jridgewell/[email protected]":
|
||||
version "0.3.9"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
|
||||
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@tsconfig/node10@^1.0.7":
|
||||
version "1.0.8"
|
||||
@@ -46,7 +59,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==
|
||||
@@ -56,15 +76,15 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.3.tgz#7a8f2838603ea314d1d22bb3171d899e15c57bd5"
|
||||
integrity sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==
|
||||
|
||||
"@types/node@17.0.32":
|
||||
version "17.0.32"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.32.tgz#51d59d7a90ef2d0ae961791e0900cad2393a0149"
|
||||
integrity sha512-eAIcfAvhf/BkHcf4pkLJ7ECpBAhh9kcxRBpip9cTiO+hf+aJrsxYxBeS6OXvOd9WqNAJmavXVpZvY1rBjNsXmw==
|
||||
"@types/node@18.0.4":
|
||||
version "18.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.4.tgz#48aedbf35efb3af1248e4cd4d792c730290cd5d6"
|
||||
integrity sha512-M0+G6V0Y4YV8cqzHssZpaNCqvYwlCiulmm0PwpNLF55r/+cT8Ol42CHRU1SEaYFH2rTwiiE1aYg/2g2rrtGdPA==
|
||||
|
||||
"@types/[email protected].9":
|
||||
version "7.3.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc"
|
||||
integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==
|
||||
"@types/[email protected].10":
|
||||
version "7.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.10.tgz#5f19ee40cbeff87d916eedc8c2bfe2305d957f73"
|
||||
integrity sha512-zsv3fsC7S84NN6nPK06u79oWgrPVd0NvOyqgghV1haPaFcVxIrP4DLomRwGAXk0ui4HZA7mOcSFL98sMVW9viw==
|
||||
|
||||
acorn-walk@^8.1.1:
|
||||
version "8.2.0"
|
||||
@@ -119,10 +139,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 +161,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"
|
||||
@@ -153,12 +168,12 @@ [email protected]:
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
ts-node@10.7.0:
|
||||
version "10.7.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5"
|
||||
integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==
|
||||
ts-node@10.9.1:
|
||||
version "10.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
|
||||
integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
|
||||
dependencies:
|
||||
"@cspotcode/source-map-support" "0.7.0"
|
||||
"@cspotcode/source-map-support" "^0.8.0"
|
||||
"@tsconfig/node10" "^1.0.7"
|
||||
"@tsconfig/node12" "^1.0.7"
|
||||
"@tsconfig/node14" "^1.0.0"
|
||||
@@ -169,7 +184,7 @@ [email protected]:
|
||||
create-require "^1.1.0"
|
||||
diff "^4.0.1"
|
||||
make-error "^1.1.1"
|
||||
v8-compile-cache-lib "^3.0.0"
|
||||
v8-compile-cache-lib "^3.0.1"
|
||||
yn "3.1.1"
|
||||
|
||||
[email protected]:
|
||||
@@ -177,17 +192,17 @@ [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"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
|
||||
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
|
||||
|
||||
v8-compile-cache-lib@^3.0.0:
|
||||
v8-compile-cache-lib@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
|
||||
|
||||
@@ -1269,10 +1269,10 @@
|
||||
slugify "^1.3.4"
|
||||
sucrase "^3.20.0"
|
||||
|
||||
"@expo/[email protected].77":
|
||||
version "0.2.77"
|
||||
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-0.2.77.tgz#2ebdb3e6706c4020812e2e8e3d2e7da26e3c7eb1"
|
||||
integrity sha512-CFS/Uixa3XqV39vdoDnQP93kgd2EGf3gaOqLYl+9gnMCP9h2+Q9fIbfPw5DnFu2LEIyU+Xw5DtpkyTQEBjTLyA==
|
||||
"@expo/[email protected].82":
|
||||
version "0.2.82"
|
||||
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-0.2.82.tgz#b0f5ec0462314bbd94c3b126faf881beee84593a"
|
||||
integrity sha512-Yamyf0GZ/jjyvsgL4N7szhcht7T22SV6VwLBdddPYUWM53+9w70CRIxsCrmW6BPuW4nQUu0M+WoK5/fkYUd/zg==
|
||||
dependencies:
|
||||
joi "^17.4.2"
|
||||
semver "^7.3.5"
|
||||
@@ -8930,7 +8930,7 @@ lodash.without@^4.4.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac"
|
||||
integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=
|
||||
|
||||
lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.21, lodash@^4.7.0, lodash@~4.17.0:
|
||||
lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0, lodash@~4.17.0:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
@@ -12132,6 +12132,13 @@ ts-log@^2.2.3:
|
||||
resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.2.3.tgz#4da5640fe25a9fb52642cd32391c886721318efb"
|
||||
integrity sha512-XvB+OdKSJ708Dmf9ore4Uf/q62AYDTzFcAdxc8KNML1mmAWywRFVt/dn1KYJH8Agt5UJNujfM3znU5PxgAzA2w==
|
||||
|
||||
[email protected]:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-mockito/-/ts-mockito-2.6.1.tgz#bc9ee2619033934e6fad1c4455aca5b5ace34e73"
|
||||
integrity sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==
|
||||
dependencies:
|
||||
lodash "^4.17.5"
|
||||
|
||||
ts-node@^9:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.0.0.tgz#e7699d2a110cc8c0d3b831715e417688683460b3"
|
||||
@@ -12288,10 +12295,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