mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbdf8a0ab1 | ||
|
|
4de958d636 | ||
|
|
5c57739511 | ||
|
|
0c38b98f63 | ||
|
|
0719abbd38 | ||
|
|
f0c958e58b | ||
|
|
607838c8e1 | ||
|
|
34ecd38304 |
No files matched your search
@@ -12,7 +12,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: 18
|
||||
- name: Checkout Pull Request
|
||||
run: hub pr checkout ${{ github.event.issue.number }}
|
||||
env:
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: 18
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: yarn build
|
||||
- name: Build tarballs
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: 18
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: yarn build
|
||||
- name: Build tarballs
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: 18
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: sudo apt-get install nsis
|
||||
- run: yarn build
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
scope: 'expo'
|
||||
node-version: '16'
|
||||
node-version: 18
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile --check-files
|
||||
- name: Build
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: 18
|
||||
- name: Install dependencies
|
||||
working-directory: ./scripts
|
||||
run: yarn install --frozen-lockfile --check-files
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- uses: preactjs/compressed-size-action@v2
|
||||
with:
|
||||
build-script: 'build:tarballs:linux'
|
||||
|
||||
@@ -10,9 +10,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: ['12', '14', '16']
|
||||
node: ['14', '16', '18']
|
||||
include:
|
||||
- node: '16'
|
||||
- node: '18'
|
||||
coverage: true
|
||||
name: Test with Node ${{ matrix.node }}
|
||||
steps:
|
||||
|
||||
@@ -12,6 +12,9 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Update dependencies. ([#1095](https://github.com/expo/eas-cli/pull/1095) by [@dsokal](https://github.com/dsokal))
|
||||
- Better spinner placement for multiple builds. ([#1105](https://github.com/expo/eas-cli/pull/1105) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.52.0](https://github.com/expo/eas-cli/releases/tag/v0.52.0) - 2022-04-26
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
+12
-12
@@ -17,22 +17,22 @@
|
||||
"clean": "lerna run clean && rm -rf node_modules coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "27.4.1",
|
||||
"@types/node": "16.7.8",
|
||||
"eslint": "8.12.0",
|
||||
"eslint-config-universe": "10.0.0",
|
||||
"@types/jest": "27.5.0",
|
||||
"@types/node": "17.0.32",
|
||||
"eslint": "8.15.0",
|
||||
"eslint-config-universe": "11.0.0",
|
||||
"eslint-plugin-async-protect": "1.1.0",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"jest": "27.5.1",
|
||||
"jest-watch-typeahead": "1.0.0",
|
||||
"jest": "28.1.0",
|
||||
"jest-watch-typeahead": "1.1.0",
|
||||
"lerna": "4.0.0",
|
||||
"oclif": "2.6.3",
|
||||
"prettier": "2.6.1",
|
||||
"ts-jest": "27.1.4",
|
||||
"typescript": "4.6.3"
|
||||
"oclif": "3.0.1",
|
||||
"prettier": "2.6.2",
|
||||
"ts-jest": "28.0.2",
|
||||
"typescript": "4.6.4"
|
||||
},
|
||||
"volta": {
|
||||
"node": "16.10.0",
|
||||
"yarn": "1.22.15"
|
||||
"node": "18.2.0",
|
||||
"yarn": "1.22.18"
|
||||
}
|
||||
}
|
||||
Generated
+559
-90
@@ -701,7 +701,7 @@
|
||||
},
|
||||
{
|
||||
"name": "billing",
|
||||
"description": "Billing information",
|
||||
"description": "Billing information. Only visible to members with the ADMIN or OWNER role.",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
@@ -1702,7 +1702,7 @@
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "chargeIdentifier",
|
||||
"name": "chargeID",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
@@ -1716,6 +1716,30 @@
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "reason",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
@@ -2088,15 +2112,15 @@
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "metric",
|
||||
"name": "id",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "EASServiceMetric",
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
@@ -2119,6 +2143,22 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "serviceMetric",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "EASServiceMetric",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "timestamp",
|
||||
"description": null,
|
||||
@@ -2157,11 +2197,151 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "AccountUsageMetricAndCost",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"description": "The limit, in units, allowed by this plan",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Float",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "metricType",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "UsageMetricType",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "serviceMetric",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "EASServiceMetric",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "totalCost",
|
||||
"description": "Total cost of this particular metric, in cents",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Float",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Float",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "AccountUsageMetrics",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "byBillingPeriod",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "date",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "DateTime",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "UsageMetricTotal",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "metricsForServiceMetric",
|
||||
"description": null,
|
||||
@@ -2183,7 +2363,7 @@
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "metric",
|
||||
"name": "serviceMetric",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
@@ -6485,15 +6665,15 @@
|
||||
"fields": [
|
||||
{
|
||||
"name": "colorPalette",
|
||||
"description": "Nullable color palette of the app icon. If null, color palette couldn't be retrieved from external service (imgix)",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "JSON",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
"isDeprecated": true,
|
||||
"deprecationReason": "No longer supported"
|
||||
},
|
||||
{
|
||||
"name": "originalUrl",
|
||||
@@ -6545,6 +6725,29 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "AppInfoInput",
|
||||
"description": null,
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "displayName",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "AppInput",
|
||||
@@ -6567,6 +6770,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "appInfo",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "AppInfoInput",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "privacy",
|
||||
"description": null,
|
||||
@@ -6683,6 +6898,55 @@
|
||||
"isDeprecated": true,
|
||||
"deprecationReason": "No longer supported"
|
||||
},
|
||||
{
|
||||
"name": "setAppInfo",
|
||||
"description": "Set display info for app",
|
||||
"args": [
|
||||
{
|
||||
"name": "appId",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "appInfo",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "AppInfoInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "App",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "setPushSecurityEnabled",
|
||||
"description": "Require api token to send push notifs for experience",
|
||||
@@ -13022,6 +13286,65 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Concurrencies",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "android",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "ios",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "total",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateAccessTokenInput",
|
||||
@@ -13126,6 +13449,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "archiveUrl",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "config",
|
||||
"description": null,
|
||||
@@ -13263,6 +13598,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "archiveUrl",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "config",
|
||||
"description": null,
|
||||
@@ -13296,77 +13643,6 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateSubmissionInput",
|
||||
"description": null,
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "appId",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "config",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "JSONObject",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "AppPlatform",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "submittedBuildId",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "CreateSubmissionResult",
|
||||
@@ -13992,11 +14268,29 @@
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "ASSETS_REQUESTS",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "BANDWIDTH_USAGE",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "MANIFEST_REQUESTS",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "UNIQUE_USERS",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
@@ -21089,6 +21383,34 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "archiveUrl",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "canRetry",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "cancelingActor",
|
||||
"description": null,
|
||||
@@ -21181,6 +21503,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "parentSubmission",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Submission",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"description": null,
|
||||
@@ -21429,9 +21763,13 @@
|
||||
"name": "submissionId",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
@@ -21517,18 +21855,18 @@
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "createSubmission",
|
||||
"description": "Create an EAS Submit submission",
|
||||
"name": "retrySubmission",
|
||||
"description": "Retry an EAS Submit submission",
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"name": "parentSubmissionId",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateSubmissionInput",
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
@@ -21546,8 +21884,8 @@
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": true,
|
||||
"deprecationReason": "Use createIosSubmission / createAndroidSubmission instead"
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
@@ -21687,6 +22025,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "concurrencies",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Concurrencies",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "endedAt",
|
||||
"description": null,
|
||||
@@ -22979,6 +23329,113 @@
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "UsageMetricTotal",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "billingPeriod",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "BillingPeriod",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "overageMetrics",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "AccountUsageMetricAndCost",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "planMetrics",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "AccountUsageMetricAndCost",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "totalCost",
|
||||
"description": "Total cost of overages, in cents",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Float",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "UsageMetricType",
|
||||
@@ -22987,11 +23444,23 @@
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "BANDWIDTH",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "REQUEST",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "USER",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
|
||||
@@ -8,31 +8,32 @@
|
||||
},
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/apple-utils": "0.0.0-alpha.31",
|
||||
"@expo/apple-utils": "0.0.0-alpha.32",
|
||||
"@expo/code-signing-certificates": "0.0.2",
|
||||
"@expo/config": "6.0.21",
|
||||
"@expo/config-plugins": "4.1.2",
|
||||
"@expo/config-types": "44.0.0",
|
||||
"@expo/eas-build-job": "0.2.71",
|
||||
"@expo/config": "6.0.23",
|
||||
"@expo/config-plugins": "4.1.4",
|
||||
"@expo/config-types": "45.0.0",
|
||||
"@expo/eas-build-job": "0.2.72",
|
||||
"@expo/eas-json": "0.52.0",
|
||||
"@expo/json-file": "8.2.34",
|
||||
"@expo/json-file": "8.2.36",
|
||||
"@expo/multipart-body-parser": "1.1.0",
|
||||
"@expo/pkcs12": "0.0.7",
|
||||
"@expo/plist": "0.0.17",
|
||||
"@expo/pkcs12": "0.0.8",
|
||||
"@expo/plist": "0.0.18",
|
||||
"@expo/plugin-warn-if-update-available": "2.1.0",
|
||||
"@expo/prebuild-config": "3.1.2",
|
||||
"@expo/prebuild-config": "3.1.6",
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.6.0",
|
||||
"@expo/timeago.js": "1.0.0",
|
||||
"@oclif/core": "1.6.4",
|
||||
"@oclif/plugin-autocomplete": "1.2.0",
|
||||
"@oclif/core": "1.8.0",
|
||||
"@oclif/plugin-autocomplete": "1.3.0",
|
||||
"@oclif/plugin-help": "5.1.12",
|
||||
"@urql/core": "2.4.3",
|
||||
"@urql/exchange-retry": "0.3.2",
|
||||
"@urql/core": "2.4.4",
|
||||
"@urql/exchange-retry": "0.3.3",
|
||||
"ajv": "^6.12.6",
|
||||
"chalk": "4.1.2",
|
||||
"cli-progress": "3.10.0",
|
||||
"cli-table3": "0.6.1",
|
||||
"cli-progress": "3.11.0",
|
||||
"cli-table3": "0.6.2",
|
||||
"dateformat": "4.6.3",
|
||||
"env-paths": "2.2.0",
|
||||
"envinfo": "7.8.1",
|
||||
@@ -40,12 +41,12 @@
|
||||
"fast-glob": "3.2.11",
|
||||
"figures": "3.2.0",
|
||||
"form-data": "4.0.0",
|
||||
"fs-extra": "10.0.1",
|
||||
"fs-extra": "10.1.0",
|
||||
"getenv": "1.0.0",
|
||||
"gradle-to-js": "2.0.0",
|
||||
"graphql": "16.3.0",
|
||||
"gradle-to-js": "2.0.1",
|
||||
"graphql": "16.5.0",
|
||||
"graphql-tag": "2.12.6",
|
||||
"https-proxy-agent": "5.0.0",
|
||||
"https-proxy-agent": "5.0.1",
|
||||
"ignore": "5.2.0",
|
||||
"indent-string": "4.0.0",
|
||||
"jks-js": "1.0.2",
|
||||
@@ -53,8 +54,8 @@
|
||||
"keychain": "1.3.0",
|
||||
"log-symbols": "4.1.0",
|
||||
"mime": "3.0.0",
|
||||
"minimatch": "3.0.5",
|
||||
"nanoid": "3.3.2",
|
||||
"minimatch": "5.0.1",
|
||||
"nanoid": "3.3.4",
|
||||
"node-fetch": "2.6.7",
|
||||
"node-forge": "1.3.1",
|
||||
"nullthrows": "1.1.1",
|
||||
@@ -63,12 +64,12 @@
|
||||
"prompts": "2.4.2",
|
||||
"qrcode-terminal": "0.12.0",
|
||||
"resolve-from": "5.0.0",
|
||||
"semver": "7.3.5",
|
||||
"semver": "7.3.7",
|
||||
"slash": "3.0.0",
|
||||
"strip-ansi": "6.0.0",
|
||||
"tar": "6.1.11",
|
||||
"terminal-link": "2.1.1",
|
||||
"tslib": "2.3.1",
|
||||
"tslib": "2.4.0",
|
||||
"turndown": "7.1.1",
|
||||
"untildify": "4.0.0",
|
||||
"uuid": "8.3.2",
|
||||
@@ -77,8 +78,8 @@
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "2.6.2",
|
||||
"@graphql-codegen/introspection": "2.1.1",
|
||||
"@graphql-codegen/typescript": "2.4.8",
|
||||
"@graphql-codegen/typescript-operations": "2.3.5",
|
||||
"@graphql-codegen/typescript": "2.4.11",
|
||||
"@graphql-codegen/typescript-operations": "2.4.0",
|
||||
"@types/cli-progress": "3.9.2",
|
||||
"@types/dateformat": "3.0.1",
|
||||
"@types/envinfo": "7.8.1",
|
||||
@@ -86,25 +87,25 @@
|
||||
"@types/getenv": "^1.0.0",
|
||||
"@types/mime": "2.0.3",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/node-forge": "1.0.1",
|
||||
"@types/node-forge": "1.0.2",
|
||||
"@types/prompts": "2.0.14",
|
||||
"@types/semver": "7.3.9",
|
||||
"@types/tar": "6.1.1",
|
||||
"@types/tough-cookie": "4.0.1",
|
||||
"@types/tough-cookie": "4.0.2",
|
||||
"@types/uuid": "8.3.4",
|
||||
"@types/wrap-ansi": "3.0.0",
|
||||
"axios": "0.26.1",
|
||||
"axios": "0.27.2",
|
||||
"eslint-plugin-graphql": "4.0.0",
|
||||
"form-data": "4.0.0",
|
||||
"memfs": "3.4.1",
|
||||
"memfs": "3.4.3",
|
||||
"mockdate": "3.0.5",
|
||||
"nock": "13.2.4",
|
||||
"tempy": "0.7.0",
|
||||
"ts-deepmerge": "2.0.1",
|
||||
"typescript": "4.6.3"
|
||||
"typescript": "4.6.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"files": [
|
||||
"/bin",
|
||||
|
||||
@@ -0,0 +1,928 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-06/schema",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"apple": {
|
||||
"AppleLocalizedPreviewFolder": {
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Path to folder containing preview files",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"AppleKidsAge": {
|
||||
"enum": ["FIVE_AND_UNDER", "SIX_TO_EIGHT", "NINE_TO_ELEVEN"]
|
||||
},
|
||||
"AppleRating": {
|
||||
"enum": ["NONE", "INFREQUENT_OR_MILD", "FREQUENT_OR_INTENSE"]
|
||||
},
|
||||
"AppleCategory": {
|
||||
"enum": [
|
||||
"FOOD_AND_DRINK",
|
||||
"BUSINESS",
|
||||
"EDUCATION",
|
||||
"SOCIAL_NETWORKING",
|
||||
"BOOKS",
|
||||
"SPORTS",
|
||||
"FINANCE",
|
||||
"REFERENCE",
|
||||
"GRAPHICS_AND_DESIGN",
|
||||
"DEVELOPER_TOOLS",
|
||||
"HEALTH_AND_FITNESS",
|
||||
"MUSIC",
|
||||
"WEATHER",
|
||||
"TRAVEL",
|
||||
"ENTERTAINMENT",
|
||||
"STICKERS",
|
||||
"GAMES",
|
||||
"LIFESTYLE",
|
||||
"MEDICAL",
|
||||
"MAGAZINES_AND_NEWSPAPERS",
|
||||
"UTILITIES",
|
||||
"SHOPPING",
|
||||
"PRODUCTIVITY",
|
||||
"NEWS",
|
||||
"PHOTO_AND_VIDEO",
|
||||
"NAVIGATION"
|
||||
]
|
||||
},
|
||||
"AppleSubcategory": {
|
||||
"enum": [
|
||||
"STICKERS_PLACES_AND_OBJECTS",
|
||||
"STICKERS_EMOJI_AND_EXPRESSIONS",
|
||||
"STICKERS_CELEBRATIONS",
|
||||
"STICKERS_CELEBRITIES",
|
||||
"STICKERS_MOVIES_AND_TV",
|
||||
"STICKERS_SPORTS_AND_ACTIVITIES",
|
||||
"STICKERS_EATING_AND_DRINKING",
|
||||
"STICKERS_CHARACTERS",
|
||||
"STICKERS_ANIMALS",
|
||||
"STICKERS_FASHION",
|
||||
"STICKERS_ART",
|
||||
"STICKERS_GAMING",
|
||||
"STICKERS_KIDS_AND_FAMILY",
|
||||
"STICKERS_PEOPLE",
|
||||
"STICKERS_MUSIC",
|
||||
"GAMES_SPORTS",
|
||||
"GAMES_WORD",
|
||||
"GAMES_MUSIC",
|
||||
"GAMES_ADVENTURE",
|
||||
"GAMES_ACTION",
|
||||
"GAMES_ROLE_PLAYING",
|
||||
"GAMES_CASUAL",
|
||||
"GAMES_BOARD",
|
||||
"GAMES_TRIVIA",
|
||||
"GAMES_CARD",
|
||||
"GAMES_PUZZLE",
|
||||
"GAMES_CASINO",
|
||||
"GAMES_STRATEGY",
|
||||
"GAMES_SIMULATION",
|
||||
"GAMES_RACING",
|
||||
"GAMES_FAMILY"
|
||||
]
|
||||
},
|
||||
"AppleIdfa": {
|
||||
"description": "Identifier for Advertisers",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"body": {
|
||||
"servesAds": false,
|
||||
"attributesAppInstallationToPreviousAd": false,
|
||||
"attributesActionWithPreviousAd": false,
|
||||
"honorsLimitedAdTracking": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"servesAds": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"attributesAppInstallationToPreviousAd": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"attributesActionWithPreviousAd": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"honorsLimitedAdTracking": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AppleInfo": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"title",
|
||||
"subtitle",
|
||||
"description",
|
||||
"keywords",
|
||||
"marketingUrl",
|
||||
"supportUrl",
|
||||
"privacyPolicyUrl"
|
||||
],
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"label": "Basic",
|
||||
"body": {
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Interactive",
|
||||
"description": "Creates localized app info",
|
||||
"body": {
|
||||
"title": "$1",
|
||||
"subtitle": "$2",
|
||||
"description": "$3",
|
||||
"keywords": [],
|
||||
"marketingUrl": "",
|
||||
"supportUrl": "",
|
||||
"privacyPolicyUrl": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"title": {
|
||||
"description": "Name of the app in the store. This should be similar to the installed app name.",
|
||||
"type": "string",
|
||||
"maxLength": 30,
|
||||
"meta": {
|
||||
"storeInfo": "The name will be reviewed before it is made available on the App Store."
|
||||
}
|
||||
},
|
||||
"subtitle": {
|
||||
"description": "Subtext for the app. Example: 'A Fun Game For Friends'",
|
||||
"type": "string",
|
||||
"maxLength": 30,
|
||||
"meta": {
|
||||
"storeInfo": "The subtitle will be reviewed before it is made available on the App Store."
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
},
|
||||
"maxLength": 4000,
|
||||
"description": "Main description of what the app does",
|
||||
"type": "string"
|
||||
},
|
||||
"promoText": {
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true,
|
||||
"aso": false
|
||||
},
|
||||
"maxLength": 170,
|
||||
"description": "Short tagline for the app",
|
||||
"type": "string"
|
||||
},
|
||||
"keywords": {
|
||||
"meta": {
|
||||
"versioned": true
|
||||
},
|
||||
"description": "List of keywords to help users find the app in the App Store",
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"maxLength": 100,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"privacyPolicyText": {
|
||||
"type": "string",
|
||||
"description": "Privacy policy for Apple TV"
|
||||
},
|
||||
"whatsNew": {
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
},
|
||||
"maxLength": 4000,
|
||||
"description": "Changes since the last public version",
|
||||
"type": "string"
|
||||
},
|
||||
"marketingUrl": {
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
},
|
||||
"description": "URL to the app marketing page",
|
||||
"type": "string"
|
||||
},
|
||||
"supportUrl": {
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
},
|
||||
"description": "URL to the app support page",
|
||||
"type": "string"
|
||||
},
|
||||
"privacyPolicyUrl": {
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true
|
||||
},
|
||||
"maxLength": 255,
|
||||
"description": "A URL that links to your privacy policy. A privacy policy is required for all apps.",
|
||||
"type": "string"
|
||||
},
|
||||
"privacyChoiceUrl": {
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true,
|
||||
"optional": true
|
||||
},
|
||||
"maxLength": 255,
|
||||
"description": "A URL where users can modify and delete the data collected from the app, or decide how their data is used and shared.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AppleAdvisory": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"label": "Minimum",
|
||||
"description": "The bare minimum rating, your app does nothing",
|
||||
"body": {
|
||||
"alcoholTobaccoOrDrugUseOrReferences": "NONE",
|
||||
"contests": "NONE",
|
||||
"gamblingSimulated": "NONE",
|
||||
"medicalOrTreatmentInformation": "NONE",
|
||||
"profanityOrCrudeHumor": "NONE",
|
||||
"sexualContentGraphicAndNudity": "NONE",
|
||||
"sexualContentOrNudity": "NONE",
|
||||
"horrorOrFearThemes": "NONE",
|
||||
"matureOrSuggestiveThemes": "NONE",
|
||||
"violenceCartoonOrFantasy": "NONE",
|
||||
"violenceRealisticProlongedGraphicOrSadistic": "NONE",
|
||||
"violenceRealistic": "NONE",
|
||||
"gamblingAndContests":false,
|
||||
"gambling": false,
|
||||
"kidsAgeBand": null,
|
||||
"seventeenPlus": false,
|
||||
"unrestrictedWebAccess": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Interactive",
|
||||
"description": "Step through options",
|
||||
"body": {
|
||||
"alcoholTobaccoOrDrugUseOrReferences": "${1|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"contests": "${2|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"gamblingSimulated": "${3|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"medicalOrTreatmentInformation": "${4|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"profanityOrCrudeHumor": "${5|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"sexualContentGraphicAndNudity": "${6|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"sexualContentOrNudity": "${7|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"horrorOrFearThemes": "${8|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"matureOrSuggestiveThemes": "${9|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"violenceCartoonOrFantasy": "${10|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"violenceRealisticProlongedGraphicOrSadistic": "${11|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"violenceRealistic": "${12|NONE,INFREQUENT_OR_MILD,FREQUENT_OR_INTENSE|}",
|
||||
"gamblingAndContests":false,
|
||||
"gambling": false,
|
||||
"kidsAgeBand": null,
|
||||
"seventeenPlus": false,
|
||||
"unrestrictedWebAccess": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"alcoholTobaccoOrDrugUseOrReferences": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"contests": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"gamblingAndContests": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"gambling": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"gamblingSimulated": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"kidsAgeBand": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/apple/AppleKidsAge"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"medicalOrTreatmentInformation": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"profanityOrCrudeHumor": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"sexualContentGraphicAndNudity": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"sexualContentOrNudity": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"seventeenPlus": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"horrorOrFearThemes": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"matureOrSuggestiveThemes": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"unrestrictedWebAccess": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"violenceCartoonOrFantasy": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"violenceRealisticProlongedGraphicOrSadistic": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
},
|
||||
"violenceRealistic": {
|
||||
"$ref": "#/definitions/apple/AppleRating"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AppleRelease": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"body": {
|
||||
"automaticRelease": true,
|
||||
"usesThirdPartyContent": false,
|
||||
"usesNonExemptEncryption": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"autoReleaseDate": {
|
||||
"description": "ISO Date to release the app automatically. Leave undefined to manually release the app to the public.",
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"body": "$CURRENT_YEAR-${1:$CURRENT_MONTH}-${2:$CURRENT_DATE}"
|
||||
}
|
||||
],
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
{
|
||||
"type": "number"
|
||||
}
|
||||
]
|
||||
},
|
||||
"isPhasedReleaseEnabled": {
|
||||
"type": "boolean",
|
||||
"description": "Release update over 7-day period instead of instantly.",
|
||||
"meta": {
|
||||
"storeInfo": "Phased release for automatic updates lets you gradually release this update over a 7-day period to users who have turned on automatic updates. Keep in mind that this version will still be available to all users as a manual update from the App Store. You can pause the phased release for up to 30 days or release this update to all users at any time. [Learn More](https://help.apple.com/app-store-connect/#/dev3d65fcee1)."
|
||||
}
|
||||
},
|
||||
"shouldResetRatings": {
|
||||
"description": "Should App Store ratings be reset for this version of the app.",
|
||||
"type": "boolean",
|
||||
"meta": {
|
||||
"storeInfo": "You can reset your app’s summary rating for all countries or regions when you release this version. Keep in mind that once this version is released, you won’t be able to restore the rating. Your app’s existing customer reviews will still appear on the App Store."
|
||||
}
|
||||
},
|
||||
"usesThirdPartyContent": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"automaticRelease": {
|
||||
"description": "Should the app automatically be released when approved by App Store review.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"usesNonExemptEncryption": {
|
||||
"description": "Alternative to setting `ITSAppUsesNonExemptEncryption` in the binary's `Info.plist`.",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AppleReview": {
|
||||
"type": "object",
|
||||
"description": "Info provided to the App Store review team to help them understand how to use your app.",
|
||||
"required": ["firstName", "lastName"],
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"body": {
|
||||
"firstName": "$1",
|
||||
"lastName": "$2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"demoUsername": {
|
||||
"description": "Optional username for testing the app.",
|
||||
"type": "string"
|
||||
},
|
||||
"demoPassword": {
|
||||
"description": "Optional password for testing the app.",
|
||||
"type": "string"
|
||||
},
|
||||
"attachment": {
|
||||
"description": "Local file path to upload to the review team.",
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"description": "Developer first name for the reviewer to contact.",
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"description": "Developer last name (surname) for the reviewer to contact.",
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"description": "Developer phone number for the reviewer to contact.",
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"description": "Developer email address for the reviewer to contact.",
|
||||
"type": "string",
|
||||
"format": "email"
|
||||
},
|
||||
"notes": {
|
||||
"description": "Any additional notes for the App Store reviewer.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AppleConfig": {
|
||||
"description": "Configuration that is specific to the Apple App Store.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"advisory": {
|
||||
"$ref": "#/definitions/apple/AppleAdvisory"
|
||||
},
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"maxItems": 2,
|
||||
"description": "App Store categories for the app. Can add up to two categories. STICKERS and GAMES categories can add an additional subcategory.",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/apple/AppleCategory"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"maxItems": 2,
|
||||
"items": [
|
||||
{
|
||||
"enum": ["GAMES"]
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"GAMES_SPORTS",
|
||||
"GAMES_WORD",
|
||||
"GAMES_MUSIC",
|
||||
"GAMES_ADVENTURE",
|
||||
"GAMES_ACTION",
|
||||
"GAMES_ROLE_PLAYING",
|
||||
"GAMES_CASUAL",
|
||||
"GAMES_BOARD",
|
||||
"GAMES_TRIVIA",
|
||||
"GAMES_CARD",
|
||||
"GAMES_PUZZLE",
|
||||
"GAMES_CASINO",
|
||||
"GAMES_STRATEGY",
|
||||
"GAMES_SIMULATION",
|
||||
"GAMES_RACING",
|
||||
"GAMES_FAMILY"
|
||||
]
|
||||
}
|
||||
],
|
||||
"additionalItems": false
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"maxItems": 2,
|
||||
"items": [
|
||||
{
|
||||
"enum": ["STICKERS"]
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"STICKERS_PLACES_AND_OBJECTS",
|
||||
"STICKERS_EMOJI_AND_EXPRESSIONS",
|
||||
"STICKERS_CELEBRATIONS",
|
||||
"STICKERS_CELEBRITIES",
|
||||
"STICKERS_MOVIES_AND_TV",
|
||||
"STICKERS_SPORTS_AND_ACTIVITIES",
|
||||
"STICKERS_EATING_AND_DRINKING",
|
||||
"STICKERS_CHARACTERS",
|
||||
"STICKERS_ANIMALS",
|
||||
"STICKERS_FASHION",
|
||||
"STICKERS_ART",
|
||||
"STICKERS_GAMING",
|
||||
"STICKERS_KIDS_AND_FAMILY",
|
||||
"STICKERS_PEOPLE",
|
||||
"STICKERS_MUSIC"
|
||||
]
|
||||
}
|
||||
],
|
||||
"additionalItems": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"copyright": {
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"body": "$CURRENT_YEAR ${1:name}"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"versioned": true,
|
||||
"liveEdits": true,
|
||||
"storeInfo": "The name of the person or entity that owns the exclusive rights to your app, preceded by the year the rights were obtained (for example, \"2008 Acme Inc.\"). Do not provide a URL."
|
||||
},
|
||||
"description": "The updated company copyright. Example: 2021 Evan Bacon",
|
||||
"type": "string"
|
||||
},
|
||||
"idfa": {
|
||||
"$ref": "#/definitions/apple/AppleIdfa"
|
||||
},
|
||||
"info": {
|
||||
"description": "Localized core app info",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ar-SA": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Arabic"
|
||||
},
|
||||
"ca": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Catalan"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Chinese (Simplified)"
|
||||
},
|
||||
"zh-Hant": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Chinese (Traditional)"
|
||||
},
|
||||
"hr": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Croatian"
|
||||
},
|
||||
"cs": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Czech"
|
||||
},
|
||||
"da": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Danish"
|
||||
},
|
||||
"nl-NL": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Dutch"
|
||||
},
|
||||
"en-AU": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "English (Australia)"
|
||||
},
|
||||
"en-CA": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "English (Canada)"
|
||||
},
|
||||
"en-GB": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "English (U.K.)"
|
||||
},
|
||||
"en-US": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "English (U.S.)"
|
||||
},
|
||||
"fi": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Finnish"
|
||||
},
|
||||
"fr-FR": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "French"
|
||||
},
|
||||
"fr-CA": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "French (Canada)"
|
||||
},
|
||||
"de-DE": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "German"
|
||||
},
|
||||
"el": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Greek"
|
||||
},
|
||||
"he": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Hebrew"
|
||||
},
|
||||
"hi": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Hindi"
|
||||
},
|
||||
"hu": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Hungarian"
|
||||
},
|
||||
"id": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Indonesian"
|
||||
},
|
||||
"it": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Italian"
|
||||
},
|
||||
"ja": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Japanese"
|
||||
},
|
||||
"ko": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Korean"
|
||||
},
|
||||
"ms": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Malay"
|
||||
},
|
||||
"no": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Norwegian"
|
||||
},
|
||||
"pl": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Polish"
|
||||
},
|
||||
"pt-BR": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Portuguese (Brazil)"
|
||||
},
|
||||
"pt-PT": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Portuguese (Portugal)"
|
||||
},
|
||||
"ro": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Romanian"
|
||||
},
|
||||
"ru": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Russian"
|
||||
},
|
||||
"sk": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Slovak"
|
||||
},
|
||||
"es-MX": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Spanish (Mexico)"
|
||||
},
|
||||
"es-ES": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Spanish (Spain)"
|
||||
},
|
||||
"sv": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Swedish"
|
||||
},
|
||||
"th": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Thai"
|
||||
},
|
||||
"tr": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Turkish"
|
||||
},
|
||||
"uk": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Ukrainian"
|
||||
},
|
||||
"vi": {
|
||||
"$ref": "#/definitions/apple/AppleInfo",
|
||||
"description": "Vietnamese"
|
||||
}
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Path to localized folders containing preview files",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Localized previews object. Ex: `\"en-US\": [\"image.png\"]`",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ar-SA": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Arabic"
|
||||
},
|
||||
"ca": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Catalan"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Chinese (Simplified)"
|
||||
},
|
||||
"zh-Hant": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Chinese (Traditional)"
|
||||
},
|
||||
"hr": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Croatian"
|
||||
},
|
||||
"cs": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Czech"
|
||||
},
|
||||
"da": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Danish"
|
||||
},
|
||||
"nl-NL": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Dutch"
|
||||
},
|
||||
"en-AU": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "English (Australia)"
|
||||
},
|
||||
"en-CA": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "English (Canada)"
|
||||
},
|
||||
"en-GB": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "English (U.K.)"
|
||||
},
|
||||
"en-US": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "English (U.S.)"
|
||||
},
|
||||
"fi": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Finnish"
|
||||
},
|
||||
"fr-FR": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "French"
|
||||
},
|
||||
"fr-CA": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "French (Canada)"
|
||||
},
|
||||
"de-DE": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "German"
|
||||
},
|
||||
"el": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Greek"
|
||||
},
|
||||
"he": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Hebrew"
|
||||
},
|
||||
"hi": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Hindi"
|
||||
},
|
||||
"hu": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Hungarian"
|
||||
},
|
||||
"id": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Indonesian"
|
||||
},
|
||||
"it": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Italian"
|
||||
},
|
||||
"ja": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Japanese"
|
||||
},
|
||||
"ko": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Korean"
|
||||
},
|
||||
"ms": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Malay"
|
||||
},
|
||||
"no": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Norwegian"
|
||||
},
|
||||
"pl": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Polish"
|
||||
},
|
||||
"pt-BR": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Portuguese (Brazil)"
|
||||
},
|
||||
"pt-PT": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Portuguese (Portugal)"
|
||||
},
|
||||
"ro": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Romanian"
|
||||
},
|
||||
"ru": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Russian"
|
||||
},
|
||||
"sk": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Slovak"
|
||||
},
|
||||
"es-MX": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Spanish (Mexico)"
|
||||
},
|
||||
"es-ES": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Spanish (Spain)"
|
||||
},
|
||||
"sv": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Swedish"
|
||||
},
|
||||
"th": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Thai"
|
||||
},
|
||||
"tr": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Turkish"
|
||||
},
|
||||
"uk": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Ukrainian"
|
||||
},
|
||||
"vi": {
|
||||
"$ref": "#/definitions/apple/AppleLocalizedPreviewFolder",
|
||||
"description": "Vietnamese"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"release": {
|
||||
"description": "Release strategy",
|
||||
"$ref": "#/definitions/apple/AppleRelease"
|
||||
},
|
||||
"review": {
|
||||
"description": "Info for the App Store reviewer",
|
||||
"$ref": "#/definitions/apple/AppleReview"
|
||||
},
|
||||
"priceTier": {
|
||||
"description": "App price tier. 0 is Free.",
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"configVersion": {
|
||||
"const": 0
|
||||
},
|
||||
"apple": {
|
||||
"$ref": "#/definitions/AppleConfig"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["configVersion", "apple"]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { logEvent } from './rudderstackClient';
|
||||
export type Event = BuildEvent | SubmissionEvent;
|
||||
export type Event = BuildEvent | SubmissionEvent | MetadataEvent;
|
||||
|
||||
export enum SubmissionEvent {
|
||||
SUBMIT_COMMAND = 'submit cli submit command',
|
||||
@@ -45,6 +45,11 @@ export enum BuildEvent {
|
||||
ANDROID_KEYSTORE_CREATE = 'build cli credentials keystore create',
|
||||
}
|
||||
|
||||
export enum MetadataEvent {
|
||||
APPLE_METADATA_DOWNLOAD = 'metadata cli download apple response',
|
||||
APPLE_METADATA_UPLOAD = 'metadata cli upload apple response',
|
||||
}
|
||||
|
||||
export class Analytics {
|
||||
static logEvent(name: Event, properties: Record<string, any>): void {
|
||||
logEvent(name, properties);
|
||||
|
||||
@@ -105,7 +105,7 @@ export async function maybeResolveVersionsAsync(
|
||||
appBuildVersion:
|
||||
resolveConfigValue(buildGradle, 'versionCode', parsedGradleCommand?.flavor) ?? '1',
|
||||
};
|
||||
} catch (err: any) {
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -231,16 +231,23 @@ export async function waitForBuildEndAsync(
|
||||
intervalSec = 10,
|
||||
} = {}
|
||||
): Promise<MaybeBuildFragment[]> {
|
||||
const b = `build${buildIds.length > 1 ? 's' : ''}`;
|
||||
Log.log(`Waiting for ${b} to complete. You can press Ctrl+C to exit.`);
|
||||
const spinner = ora(`Waiting for ${b} to complete.`).start();
|
||||
let spinner;
|
||||
let originalSpinnerText;
|
||||
if (buildIds.length === 1) {
|
||||
Log.log('Waiting for build to complete. You can press Ctrl+C to exit.');
|
||||
originalSpinnerText = 'Waiting for build to complete.';
|
||||
spinner = ora(originalSpinnerText).start();
|
||||
} else {
|
||||
originalSpinnerText = 'Waiting for builds to complete. You can press Ctrl+C to exit.';
|
||||
spinner = ora('Waiting for builds to complete. You can press Ctrl+C to exit.').start();
|
||||
}
|
||||
const endTime = new Date().getTime() + timeoutSec * 1000;
|
||||
while (new Date().getTime() <= endTime) {
|
||||
const builds = await getBuildsSafelyAsync(buildIds);
|
||||
const { refetch } =
|
||||
builds.length === 1
|
||||
? await handleSingleBuildProgressAsync({ build: builds[0], accountName }, { spinner })
|
||||
: await handleMultipleBuildsProgressAsync({ builds }, { spinner });
|
||||
: await handleMultipleBuildsProgressAsync({ builds }, { spinner, originalSpinnerText });
|
||||
if (!refetch) {
|
||||
return builds;
|
||||
}
|
||||
@@ -373,7 +380,7 @@ const platforms = [AppPlatform.Android, AppPlatform.Ios];
|
||||
|
||||
async function handleMultipleBuildsProgressAsync(
|
||||
{ builds: maybeBuilds }: { builds: MaybeBuildFragment[] },
|
||||
{ spinner }: { spinner: Ora }
|
||||
{ spinner, originalSpinnerText }: { spinner: Ora; originalSpinnerText: string }
|
||||
): Promise<BuildProgressResult> {
|
||||
const buildCount = maybeBuilds.length;
|
||||
const builds = maybeBuilds.filter<BuildFragment>(isBuildFragment);
|
||||
@@ -393,7 +400,7 @@ async function handleMultipleBuildsProgressAsync(
|
||||
}
|
||||
return { refetch: false };
|
||||
} else {
|
||||
spinner.text = formatPendingBuildsText(builds);
|
||||
spinner.text = formatPendingBuildsText(originalSpinnerText, builds);
|
||||
return { refetch: true };
|
||||
}
|
||||
}
|
||||
@@ -412,9 +419,10 @@ function formatSettledBuildsText(builds: BuildFragment[]): string {
|
||||
.join('\n ');
|
||||
}
|
||||
|
||||
function formatPendingBuildsText(builds: BuildFragment[]): string {
|
||||
return platforms
|
||||
.map(platform => {
|
||||
function formatPendingBuildsText(originalSpinnerText: string, builds: BuildFragment[]): string {
|
||||
return [
|
||||
originalSpinnerText,
|
||||
...platforms.map(platform => {
|
||||
const build = builds.find(build => build.platform === platform);
|
||||
const status = build ? statusToDisplayName[build.status] : 'unknown';
|
||||
let extraInfo = '';
|
||||
@@ -437,8 +445,8 @@ function formatPendingBuildsText(builds: BuildFragment[]): string {
|
||||
return `${appPlatformEmojis[platform]} ${
|
||||
appPlatformDisplayNames[platform]
|
||||
} build - status: ${chalk.bold(status)}${extraInfo}`;
|
||||
})
|
||||
.join('\n ');
|
||||
}),
|
||||
].join('\n ');
|
||||
}
|
||||
|
||||
function isBuildFragment(maybeBuild: MaybeBuildFragment): maybeBuild is BuildFragment {
|
||||
|
||||
@@ -107,7 +107,7 @@ describe(EasCommand.name, () => {
|
||||
await TestEasCommand.run().then(() => {
|
||||
throw new Error('foo');
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch {}
|
||||
|
||||
expect(flushAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -104,7 +104,7 @@ async function selectBuildToCancelAsync(
|
||||
async function ensureBuildExistsAsync(buildId: string): Promise<void> {
|
||||
try {
|
||||
await BuildQuery.byIdAsync(buildId);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
throw new Error(`Couldn't find a build matching the id ${buildId}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ export default class BuildInspect extends EasCommand {
|
||||
if (!flags.verbose) {
|
||||
Log.log(chalk.green('Build successful'));
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
if (!flags.verbose) {
|
||||
Log.error('Build failed');
|
||||
Log.error(`Re-run this command with ${chalk.bold('--verbose')} flag to see the logs`);
|
||||
|
||||
@@ -45,7 +45,7 @@ export function getBranchMapping(branchMappingString?: string): {
|
||||
let branchMapping: BranchMapping;
|
||||
try {
|
||||
branchMapping = JSON.parse(branchMappingString);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
throw new Error(`Could not parse branchMapping string into a JSON: "${branchMappingString}"`);
|
||||
}
|
||||
assert(branchMapping, 'Branch Mapping must be defined.');
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
|
||||
import EasCommand from '../commandUtils/EasCommand';
|
||||
import Log from '../log';
|
||||
import { createMetadataContextAsync } from '../metadata/context';
|
||||
import { handleMetadataError } from '../metadata/errors';
|
||||
import { uploadMetadataAsync } from '../metadata/upload';
|
||||
import { RequestedPlatform } from '../platform';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../project/projectUtils';
|
||||
|
||||
type RawCommandFlags = {
|
||||
platform?: string;
|
||||
profile?: string;
|
||||
'non-interactive': boolean;
|
||||
};
|
||||
|
||||
type CommandFlags = {
|
||||
requestedPlatforms: RequestedPlatform;
|
||||
profile?: string;
|
||||
nonInteractive: boolean;
|
||||
};
|
||||
|
||||
export default class Metadata extends EasCommand {
|
||||
static description = 'upload metadata configuration to the app stores';
|
||||
|
||||
static flags = {
|
||||
platform: Flags.enum({
|
||||
char: 'p',
|
||||
options: ['ios'],
|
||||
}),
|
||||
profile: Flags.string({
|
||||
description:
|
||||
'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
}),
|
||||
'non-interactive': Flags.boolean({
|
||||
default: false,
|
||||
description: 'Run command in non-interactive mode',
|
||||
}),
|
||||
};
|
||||
|
||||
static args = [];
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags: rawFlags } = await this.parse(Metadata);
|
||||
const flags = await this.sanitizeFlagsAsync(rawFlags);
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
await getProjectIdAsync(exp);
|
||||
|
||||
const metadataContext = await createMetadataContextAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
profileName: flags.profile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
});
|
||||
|
||||
try {
|
||||
Log.addNewLineIfNone();
|
||||
await uploadMetadataAsync(metadataContext);
|
||||
Log.addNewLineIfNone();
|
||||
Log.succeed(`Store has been updated with your ${metadataContext.metadataFile} configuration`);
|
||||
} catch (error: any) {
|
||||
handleMetadataError(error);
|
||||
}
|
||||
}
|
||||
|
||||
private async sanitizeFlagsAsync(flags: RawCommandFlags): Promise<CommandFlags> {
|
||||
const { platform, profile, 'non-interactive': nonInteractive } = flags;
|
||||
|
||||
if (!platform && nonInteractive) {
|
||||
Errors.error('--platform is required when building in non-interactive mode', { exit: 1 });
|
||||
}
|
||||
|
||||
return {
|
||||
// TODO: add support for multiple platforms, right now we only support ios
|
||||
requestedPlatforms: RequestedPlatform.Ios, // enforced by the flag options
|
||||
profile,
|
||||
nonInteractive,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { createMetadataContextAsync } from '../../metadata/context';
|
||||
import { downloadMetadataAsync } from '../../metadata/download';
|
||||
import { handleMetadataError } from '../../metadata/errors';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
|
||||
type RawCommandFlags = {
|
||||
platform?: string;
|
||||
profile?: string;
|
||||
'non-interactive': boolean;
|
||||
};
|
||||
|
||||
type CommandFlags = {
|
||||
requestedPlatforms: RequestedPlatform;
|
||||
profile?: string;
|
||||
nonInteractive: boolean;
|
||||
};
|
||||
|
||||
export default class MetadataConfigure extends EasCommand {
|
||||
static description = 'upload metadata configuration to the app stores';
|
||||
|
||||
static flags = {
|
||||
platform: Flags.enum({
|
||||
char: 'p',
|
||||
options: ['ios'],
|
||||
}),
|
||||
profile: Flags.string({
|
||||
description:
|
||||
'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
}),
|
||||
'non-interactive': Flags.boolean({
|
||||
default: false,
|
||||
description: 'Run command in non-interactive mode',
|
||||
}),
|
||||
};
|
||||
|
||||
static args = [];
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags: rawFlags } = await this.parse(MetadataConfigure);
|
||||
const flags = await this.sanitizeFlagsAsync(rawFlags);
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
await getProjectIdAsync(exp);
|
||||
|
||||
const metadataContext = await createMetadataContextAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
profileName: flags.profile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
});
|
||||
|
||||
try {
|
||||
Log.addNewLineIfNone();
|
||||
const filePath = await downloadMetadataAsync(metadataContext);
|
||||
Log.addNewLineIfNone();
|
||||
|
||||
Log.succeed('Your store configuration has been generated!');
|
||||
Log.log(filePath);
|
||||
Log.log(learnMore('https://docs.expo.dev/eas-metadata/introduction/'));
|
||||
} catch (error: any) {
|
||||
handleMetadataError(error);
|
||||
}
|
||||
}
|
||||
|
||||
private async sanitizeFlagsAsync(flags: RawCommandFlags): Promise<CommandFlags> {
|
||||
const { platform, profile, 'non-interactive': nonInteractive } = flags;
|
||||
|
||||
if (!platform && nonInteractive) {
|
||||
Errors.error('--platform is required when building in non-interactive mode', { exit: 1 });
|
||||
}
|
||||
|
||||
return {
|
||||
// TODO: add support for multiple platforms, right now we only support ios
|
||||
requestedPlatforms: RequestedPlatform.Ios, // enforced by the flag options
|
||||
profile,
|
||||
nonInteractive,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ function fileIsServiceAccountKey(keyJsonPath: string): boolean {
|
||||
try {
|
||||
readAndValidateServiceAccountKey(keyJsonPath);
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function keytoolCommandExistsAsync(): Promise<boolean> {
|
||||
try {
|
||||
await spawnAsync('keytool');
|
||||
return true;
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ function isPKCSKeystore(keystore: Keystore): boolean {
|
||||
try {
|
||||
parsePKCS12(keystore.keystore, keystore.keystorePassword);
|
||||
return true;
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ function isJKSKeystore(keystore: Keystore): boolean {
|
||||
try {
|
||||
jks.parseJks(Buffer.from(keystore.keystore, 'base64'), keystore.keystorePassword);
|
||||
return true;
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export class CredentialsContext {
|
||||
): ExpoConfig | null {
|
||||
try {
|
||||
return getExpoConfig(projectDir, { env });
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// ignore error, context might be created outside of expo project
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export async function readRawAsync(
|
||||
try {
|
||||
const credentialsJSONContents = await fs.readFile(credentialsJsonFilePath, 'utf8');
|
||||
return JSON.parse(credentialsJSONContents);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
throw new Error(
|
||||
`credentials.json must exist in the project root directory and contain a valid JSON`
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ describe(getCertData, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe(forge.pkcs12.pkcs12FromAsn1, () => {
|
||||
describe('forge.pkcs12.pkcs12FromAsn1', () => {
|
||||
describe('when using p12 created from keychain', () => {
|
||||
it('throws if empty string is passed', async () => {
|
||||
const p12Der = forge.util.decode64(testDistributionCertificateEmptyPasswordBase64);
|
||||
|
||||
@@ -31,7 +31,7 @@ export function parse(dataBase64: string): PlistObject {
|
||||
const buffer = Buffer.from(dataBase64, 'base64');
|
||||
const profile = buffer.toString('utf8');
|
||||
return plist.parse(profile) as PlistObject;
|
||||
} catch (error) {
|
||||
} catch {
|
||||
throw new Error('Provisioning profile is malformed');
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ function validateProvisioningProfileWithoutApple(
|
||||
Log.warn('Provisioning Profile has expired.');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
Log.warn('Provisioning Profile is malformed.');
|
||||
return false;
|
||||
}
|
||||
|
||||
+74
-22
@@ -94,7 +94,7 @@ export type Account = {
|
||||
apps: Array<App>;
|
||||
/** @deprecated Build packs are no longer supported */
|
||||
availableBuilds?: Maybe<Scalars['Int']>;
|
||||
/** Billing information */
|
||||
/** Billing information. Only visible to members with the ADMIN or OWNER role. */
|
||||
billing?: Maybe<Billing>;
|
||||
billingPeriod: BillingPeriod;
|
||||
/** Build Jobs associated with this account */
|
||||
@@ -338,7 +338,9 @@ export type AccountMutationRenameArgs = {
|
||||
|
||||
export type AccountMutationRequestRefundArgs = {
|
||||
accountID: Scalars['ID'];
|
||||
chargeIdentifier: Scalars['ID'];
|
||||
chargeID: Scalars['ID'];
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
reason?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -393,21 +395,40 @@ export type AccountQueryByNameArgs = {
|
||||
|
||||
export type AccountUsageMetric = {
|
||||
__typename?: 'AccountUsageMetric';
|
||||
metric: EasServiceMetric;
|
||||
id: Scalars['ID'];
|
||||
metricType: UsageMetricType;
|
||||
serviceMetric: EasServiceMetric;
|
||||
timestamp: Scalars['DateTime'];
|
||||
value: Scalars['Float'];
|
||||
};
|
||||
|
||||
export type AccountUsageMetricAndCost = {
|
||||
__typename?: 'AccountUsageMetricAndCost';
|
||||
id: Scalars['ID'];
|
||||
/** The limit, in units, allowed by this plan */
|
||||
limit: Scalars['Float'];
|
||||
metricType: UsageMetricType;
|
||||
serviceMetric: EasServiceMetric;
|
||||
/** Total cost of this particular metric, in cents */
|
||||
totalCost: Scalars['Float'];
|
||||
value: Scalars['Float'];
|
||||
};
|
||||
|
||||
export type AccountUsageMetrics = {
|
||||
__typename?: 'AccountUsageMetrics';
|
||||
byBillingPeriod: UsageMetricTotal;
|
||||
metricsForServiceMetric: Array<AccountUsageMetric>;
|
||||
};
|
||||
|
||||
|
||||
export type AccountUsageMetricsByBillingPeriodArgs = {
|
||||
date: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
|
||||
export type AccountUsageMetricsMetricsForServiceMetricArgs = {
|
||||
granularity: UsageMetricsGranularity;
|
||||
metric: EasServiceMetric;
|
||||
serviceMetric: EasServiceMetric;
|
||||
timespan: UsageMetricsTimespan;
|
||||
};
|
||||
|
||||
@@ -1002,15 +1023,20 @@ export type AppDataInput = {
|
||||
|
||||
export type AppIcon = {
|
||||
__typename?: 'AppIcon';
|
||||
/** Nullable color palette of the app icon. If null, color palette couldn't be retrieved from external service (imgix) */
|
||||
/** @deprecated No longer supported */
|
||||
colorPalette?: Maybe<Scalars['JSON']>;
|
||||
originalUrl: Scalars['String'];
|
||||
primaryColor?: Maybe<Scalars['String']>;
|
||||
url: Scalars['String'];
|
||||
};
|
||||
|
||||
export type AppInfoInput = {
|
||||
displayName?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type AppInput = {
|
||||
accountId: Scalars['ID'];
|
||||
appInfo?: InputMaybe<AppInfoInput>;
|
||||
privacy: AppPrivacy;
|
||||
projectName: Scalars['String'];
|
||||
};
|
||||
@@ -1021,6 +1047,8 @@ export type AppMutation = {
|
||||
createApp: App;
|
||||
/** @deprecated No longer supported */
|
||||
grantAccess?: Maybe<App>;
|
||||
/** Set display info for app */
|
||||
setAppInfo: App;
|
||||
/** Require api token to send push notifs for experience */
|
||||
setPushSecurityEnabled: App;
|
||||
};
|
||||
@@ -1037,6 +1065,12 @@ export type AppMutationGrantAccessArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type AppMutationSetAppInfoArgs = {
|
||||
appId: Scalars['ID'];
|
||||
appInfo: AppInfoInput;
|
||||
};
|
||||
|
||||
|
||||
export type AppMutationSetPushSecurityEnabledArgs = {
|
||||
appId: Scalars['ID'];
|
||||
pushSecurityEnabled: Scalars['Boolean'];
|
||||
@@ -1905,6 +1939,13 @@ export type CodeSigningInfoInput = {
|
||||
sig: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Concurrencies = {
|
||||
__typename?: 'Concurrencies';
|
||||
android: Scalars['Int'];
|
||||
ios: Scalars['Int'];
|
||||
total: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type CreateAccessTokenInput = {
|
||||
actorID: Scalars['ID'];
|
||||
note?: InputMaybe<Scalars['String']>;
|
||||
@@ -1920,6 +1961,7 @@ export type CreateAccessTokenResponse = {
|
||||
|
||||
export type CreateAndroidSubmissionInput = {
|
||||
appId: Scalars['ID'];
|
||||
archiveUrl?: InputMaybe<Scalars['String']>;
|
||||
config: AndroidSubmissionConfigInput;
|
||||
submittedBuildId?: InputMaybe<Scalars['ID']>;
|
||||
};
|
||||
@@ -1937,17 +1979,11 @@ export type CreateEnvironmentSecretInput = {
|
||||
|
||||
export type CreateIosSubmissionInput = {
|
||||
appId: Scalars['ID'];
|
||||
archiveUrl?: InputMaybe<Scalars['String']>;
|
||||
config: IosSubmissionConfigInput;
|
||||
submittedBuildId?: InputMaybe<Scalars['ID']>;
|
||||
};
|
||||
|
||||
export type CreateSubmissionInput = {
|
||||
appId: Scalars['ID'];
|
||||
config: Scalars['JSONObject'];
|
||||
platform: AppPlatform;
|
||||
submittedBuildId?: InputMaybe<Scalars['ID']>;
|
||||
};
|
||||
|
||||
export type CreateSubmissionResult = {
|
||||
__typename?: 'CreateSubmissionResult';
|
||||
/** Created submission */
|
||||
@@ -2057,7 +2093,10 @@ export enum EasBuildDeprecationInfoType {
|
||||
}
|
||||
|
||||
export enum EasServiceMetric {
|
||||
ManifestRequests = 'MANIFEST_REQUESTS'
|
||||
AssetsRequests = 'ASSETS_REQUESTS',
|
||||
BandwidthUsage = 'BANDWIDTH_USAGE',
|
||||
ManifestRequests = 'MANIFEST_REQUESTS',
|
||||
UniqueUsers = 'UNIQUE_USERS'
|
||||
}
|
||||
|
||||
export type EditUpdateBranchInput = {
|
||||
@@ -3108,6 +3147,8 @@ export type Submission = ActivityTimelineProjectActivity & {
|
||||
actor?: Maybe<Actor>;
|
||||
androidConfig?: Maybe<AndroidSubmissionConfig>;
|
||||
app: App;
|
||||
archiveUrl?: Maybe<Scalars['String']>;
|
||||
canRetry: Scalars['Boolean'];
|
||||
cancelingActor?: Maybe<Actor>;
|
||||
createdAt: Scalars['DateTime'];
|
||||
error?: Maybe<SubmissionError>;
|
||||
@@ -3115,6 +3156,7 @@ export type Submission = ActivityTimelineProjectActivity & {
|
||||
initiatingActor?: Maybe<Actor>;
|
||||
iosConfig?: Maybe<IosSubmissionConfig>;
|
||||
logsUrl?: Maybe<Scalars['String']>;
|
||||
parentSubmission?: Maybe<Submission>;
|
||||
platform: AppPlatform;
|
||||
status: SubmissionStatus;
|
||||
submittedBuild?: Maybe<Build>;
|
||||
@@ -3159,16 +3201,13 @@ export type SubmissionMutation = {
|
||||
createAndroidSubmission: CreateSubmissionResult;
|
||||
/** Create an iOS EAS Submit submission */
|
||||
createIosSubmission: CreateSubmissionResult;
|
||||
/**
|
||||
* Create an EAS Submit submission
|
||||
* @deprecated Use createIosSubmission / createAndroidSubmission instead
|
||||
*/
|
||||
createSubmission: CreateSubmissionResult;
|
||||
/** Retry an EAS Submit submission */
|
||||
retrySubmission: CreateSubmissionResult;
|
||||
};
|
||||
|
||||
|
||||
export type SubmissionMutationCancelSubmissionArgs = {
|
||||
submissionId?: InputMaybe<Scalars['ID']>;
|
||||
submissionId: Scalars['ID'];
|
||||
};
|
||||
|
||||
|
||||
@@ -3182,8 +3221,8 @@ export type SubmissionMutationCreateIosSubmissionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type SubmissionMutationCreateSubmissionArgs = {
|
||||
input: CreateSubmissionInput;
|
||||
export type SubmissionMutationRetrySubmissionArgs = {
|
||||
parentSubmissionId: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type SubmissionQuery = {
|
||||
@@ -3210,6 +3249,7 @@ export type SubscriptionDetails = {
|
||||
__typename?: 'SubscriptionDetails';
|
||||
addons: Array<AddonDetails>;
|
||||
cancelledAt?: Maybe<Scalars['DateTime']>;
|
||||
concurrencies?: Maybe<Concurrencies>;
|
||||
endedAt?: Maybe<Scalars['DateTime']>;
|
||||
id: Scalars['ID'];
|
||||
isDowngrading?: Maybe<Scalars['Boolean']>;
|
||||
@@ -3395,8 +3435,20 @@ export enum UploadSessionType {
|
||||
EasSubmitAppArchive = 'EAS_SUBMIT_APP_ARCHIVE'
|
||||
}
|
||||
|
||||
export type UsageMetricTotal = {
|
||||
__typename?: 'UsageMetricTotal';
|
||||
billingPeriod: BillingPeriod;
|
||||
id: Scalars['ID'];
|
||||
overageMetrics: Array<AccountUsageMetricAndCost>;
|
||||
planMetrics: Array<AccountUsageMetricAndCost>;
|
||||
/** Total cost of overages, in cents */
|
||||
totalCost: Scalars['Float'];
|
||||
};
|
||||
|
||||
export enum UsageMetricType {
|
||||
Request = 'REQUEST'
|
||||
Bandwidth = 'BANDWIDTH',
|
||||
Request = 'REQUEST',
|
||||
User = 'USER'
|
||||
}
|
||||
|
||||
export enum UsageMetricsGranularity {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { AgeRatingDeclaration, KidsAge } from '@expo/apple-utils';
|
||||
|
||||
import { AttributesOf } from '../../../../utils/asc';
|
||||
|
||||
export const kidsSixToEightAdvisory: AttributesOf<AgeRatingDeclaration> = {
|
||||
alcoholTobaccoOrDrugUseOrReferences: null,
|
||||
gamblingSimulated: null,
|
||||
gambling: null,
|
||||
contests: null,
|
||||
medicalOrTreatmentInformation: null,
|
||||
profanityOrCrudeHumor: null,
|
||||
sexualContentGraphicAndNudity: null,
|
||||
sexualContentOrNudity: null,
|
||||
horrorOrFearThemes: null,
|
||||
matureOrSuggestiveThemes: null,
|
||||
violenceCartoonOrFantasy: null,
|
||||
violenceRealisticProlongedGraphicOrSadistic: null,
|
||||
violenceRealistic: null,
|
||||
unrestrictedWebAccess: false,
|
||||
seventeenPlus: false,
|
||||
kidsAgeBand: KidsAge.SIX_TO_EIGHT,
|
||||
gamblingAndContests: false,
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { AppCategory, AppInfo, BundleIdPlatform } from '@expo/apple-utils';
|
||||
|
||||
import { AttributesOf } from '../../../../utils/asc';
|
||||
|
||||
export const primaryOnlyCategory: Pick<AttributesOf<AppInfo>, 'primaryCategory'> = {
|
||||
primaryCategory: new AppCategory({} as any, 'ENTERTAINMENT', {
|
||||
platforms: [BundleIdPlatform.IOS, BundleIdPlatform.MAC_OS],
|
||||
}),
|
||||
};
|
||||
|
||||
export const secondaryOnlyCategory: Pick<AttributesOf<AppInfo>, 'secondaryCategory'> = {
|
||||
secondaryCategory: new AppCategory({} as any, 'GAMES', {
|
||||
platforms: [BundleIdPlatform.IOS, BundleIdPlatform.MAC_OS],
|
||||
}),
|
||||
};
|
||||
|
||||
export const primaryAndSecondaryCategory: Pick<
|
||||
AttributesOf<AppInfo>,
|
||||
'primaryCategory' | 'secondaryCategory'
|
||||
> = {
|
||||
...primaryOnlyCategory,
|
||||
...secondaryOnlyCategory,
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { AppInfoLocalization } from '@expo/apple-utils';
|
||||
|
||||
import { AttributesOf } from '../../../../utils/asc';
|
||||
|
||||
export const englishInfo: AttributesOf<AppInfoLocalization> = {
|
||||
locale: 'en-US',
|
||||
name: 'Awesome test app',
|
||||
subtitle: 'This is just a test',
|
||||
privacyPolicyUrl: 'https://example.com/en/privacy-policy',
|
||||
privacyChoicesUrl: 'https://exmaple.com/en/privacy-choices',
|
||||
privacyPolicyText: 'This is some privacy policy text',
|
||||
};
|
||||
|
||||
export const dutchInfo: AttributesOf<AppInfoLocalization> = {
|
||||
locale: 'nl-NL',
|
||||
name: 'Geweldige test app',
|
||||
subtitle: 'Dit is maar een test',
|
||||
privacyPolicyUrl: 'https://example.com/nl/privacy-policy',
|
||||
privacyChoicesUrl: 'https://exmaple.com/nl/privacy-choices',
|
||||
privacyPolicyText: 'Dit is wat privacy policy tekst',
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { AppStoreState, AppStoreVersion, Platform, ReleaseType } from '@expo/apple-utils';
|
||||
|
||||
import { AttributesOf } from '../../../../utils/asc';
|
||||
|
||||
export const manualRelease: AttributesOf<AppStoreVersion> = {
|
||||
platform: Platform.IOS,
|
||||
versionString: '1.0.0',
|
||||
appStoreState: AppStoreState.WAITING_FOR_REVIEW,
|
||||
storeIcon: null,
|
||||
watchStoreIcon: null,
|
||||
copyright: '2022 - ACME',
|
||||
releaseType: ReleaseType.MANUAL,
|
||||
earliestReleaseDate: null,
|
||||
usesIdfa: null,
|
||||
isWatchOnly: false,
|
||||
downloadable: false,
|
||||
createdDate: '2022-05-23T00:00:00.000Z',
|
||||
};
|
||||
|
||||
export const automaticRelease: AttributesOf<AppStoreVersion> = {
|
||||
platform: Platform.IOS,
|
||||
versionString: '2.0.0',
|
||||
appStoreState: AppStoreState.WAITING_FOR_REVIEW,
|
||||
storeIcon: null,
|
||||
watchStoreIcon: null,
|
||||
copyright: '2022 - ACME',
|
||||
releaseType: ReleaseType.AFTER_APPROVAL,
|
||||
earliestReleaseDate: null,
|
||||
usesIdfa: null,
|
||||
isWatchOnly: false,
|
||||
downloadable: false,
|
||||
createdDate: '2022-05-23T00:00:00.000Z',
|
||||
};
|
||||
|
||||
export const scheduledRelease: AttributesOf<AppStoreVersion> = {
|
||||
platform: Platform.IOS,
|
||||
versionString: '3.0.0',
|
||||
appStoreState: AppStoreState.READY_FOR_SALE,
|
||||
storeIcon: null,
|
||||
watchStoreIcon: null,
|
||||
copyright: '2022 - ACME',
|
||||
releaseType: ReleaseType.SCHEDULED,
|
||||
earliestReleaseDate: '2022-05-29T00:00:00.000Z',
|
||||
usesIdfa: null,
|
||||
isWatchOnly: false,
|
||||
downloadable: false,
|
||||
createdDate: '2022-05-23T00:00:00.000Z',
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { AppStoreVersionLocalization } from '@expo/apple-utils';
|
||||
|
||||
import { AttributesOf } from '../../../../utils/asc';
|
||||
|
||||
export const englishVersion: AttributesOf<AppStoreVersionLocalization> = {
|
||||
locale: 'en-US',
|
||||
description: 'This is a description of this version',
|
||||
keywords: 'some, description',
|
||||
marketingUrl: 'https://example.com/en/marketing',
|
||||
promotionalText: 'This is some promotional text',
|
||||
supportUrl: 'https://example.com/en/support',
|
||||
whatsNew: 'Bugfixes and improvements',
|
||||
};
|
||||
|
||||
export const dutchVersion: AttributesOf<AppStoreVersionLocalization> = {
|
||||
locale: 'nl-NL',
|
||||
description: 'Dit is een beschrijving van deze versie',
|
||||
keywords: 'een, beschrijving',
|
||||
marketingUrl: 'https://example.com/nl/marketing',
|
||||
promotionalText: 'Dit is wat promotie tekst',
|
||||
supportUrl: 'https://example.com/nl/support',
|
||||
whatsNew: 'Beestreparaties en verbeteringen',
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
import { AppleConfigWriter } from '../writer';
|
||||
import { kidsSixToEightAdvisory } from './fixtures/ageRatingDeclaration';
|
||||
import { primaryAndSecondaryCategory, secondaryOnlyCategory } from './fixtures/appInfo';
|
||||
import { dutchInfo, englishInfo } from './fixtures/appInfoLocalization';
|
||||
import { automaticRelease, manualRelease, scheduledRelease } from './fixtures/appStoreVersion';
|
||||
import { dutchVersion, englishVersion } from './fixtures/appStoreVersionLocalization';
|
||||
|
||||
describe('toSchema', () => {
|
||||
it('returns object with apple schema', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
expect(writer.toSchema()).toMatchObject({
|
||||
configVersion: 0,
|
||||
apple: expect.any(Object),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setAgeRating', () => {
|
||||
it('modifies the advisory', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setAgeRating(kidsSixToEightAdvisory);
|
||||
expect(writer.schema.advisory).toMatchObject(kidsSixToEightAdvisory);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setInfoLocale', () => {
|
||||
it('creates and modifies the locale', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setInfoLocale(englishInfo);
|
||||
expect(writer.schema.info?.[englishInfo.locale]).toMatchObject({
|
||||
title: englishInfo.name,
|
||||
subtitle: englishInfo.subtitle,
|
||||
privacyPolicyUrl: englishInfo.privacyPolicyUrl,
|
||||
privacyPolicyText: englishInfo.privacyPolicyText,
|
||||
privacyChoicesUrl: englishInfo.privacyChoicesUrl,
|
||||
});
|
||||
});
|
||||
|
||||
it('modifies existing locales', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setInfoLocale(englishInfo);
|
||||
writer.setInfoLocale(dutchInfo);
|
||||
writer.setInfoLocale({
|
||||
...englishInfo,
|
||||
name: 'This is now different',
|
||||
privacyPolicyText: null,
|
||||
});
|
||||
|
||||
expect(writer.schema.info?.[dutchInfo.locale]).toHaveProperty('title', dutchInfo.name);
|
||||
expect(writer.schema.info?.[englishInfo.locale]).toMatchObject({
|
||||
title: 'This is now different',
|
||||
privacyPolicyText: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCategories', () => {
|
||||
it('modifies the categories', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setCategories(primaryAndSecondaryCategory as any);
|
||||
expect(writer.schema.categories).toHaveLength(2);
|
||||
expect((writer.schema.categories as any)[0]).toBe(
|
||||
primaryAndSecondaryCategory.primaryCategory?.id
|
||||
);
|
||||
expect((writer.schema.categories as any)[1]).toBe(
|
||||
primaryAndSecondaryCategory.secondaryCategory?.id
|
||||
);
|
||||
});
|
||||
|
||||
it('skips secondary category without primary category', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setCategories(secondaryOnlyCategory as any);
|
||||
expect(writer.schema.categories).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setVersion', () => {
|
||||
it('modifies the copyright', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setVersion(manualRelease);
|
||||
expect(writer.schema.copyright).toBe(manualRelease.copyright);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setVersionRelease', () => {
|
||||
it('modifies scheduled release', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setVersionRelease(scheduledRelease);
|
||||
expect(writer.schema.release).toMatchObject({
|
||||
autoReleaseDate: scheduledRelease.earliestReleaseDate,
|
||||
});
|
||||
});
|
||||
|
||||
it('modifies automatic release', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setVersionRelease(automaticRelease);
|
||||
expect(writer.schema.release).toMatchObject({
|
||||
automaticRelease: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('modifies manual release', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setVersionRelease(manualRelease);
|
||||
expect(writer.schema.release).toMatchObject({
|
||||
automaticRelease: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('overwrites all release fields', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setVersionRelease(scheduledRelease);
|
||||
writer.setVersionRelease(manualRelease);
|
||||
expect(writer.schema.release).not.toHaveProperty('autoReleaseDate');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setVersionLocale', () => {
|
||||
it('creates and modifies the locale', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setVersionLocale(englishVersion);
|
||||
expect(writer.schema.info?.[englishVersion.locale]).toMatchObject({
|
||||
description: englishVersion.description,
|
||||
keywords: englishVersion.keywords?.split(', '),
|
||||
releaseNotes: englishVersion.whatsNew,
|
||||
marketingUrl: englishVersion.marketingUrl,
|
||||
promoText: englishVersion.promotionalText,
|
||||
supportUrl: englishVersion.supportUrl,
|
||||
});
|
||||
});
|
||||
|
||||
it('modifies existing locales', () => {
|
||||
const writer = new AppleConfigWriter();
|
||||
writer.setVersionLocale(englishVersion);
|
||||
writer.setVersionLocale(dutchVersion);
|
||||
writer.setVersionLocale({
|
||||
...englishVersion,
|
||||
description: 'This is now different',
|
||||
whatsNew: null,
|
||||
});
|
||||
|
||||
expect(writer.schema.info?.[dutchVersion.locale]).toHaveProperty(
|
||||
'description',
|
||||
dutchVersion.description
|
||||
);
|
||||
expect(writer.schema.info?.[englishVersion.locale]).toMatchObject({
|
||||
description: 'This is now different',
|
||||
releaseNotes: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
import {
|
||||
AgeRatingDeclaration,
|
||||
AppInfoLocalization,
|
||||
AppStoreVersion,
|
||||
AppStoreVersionLocalization,
|
||||
CategoryIds,
|
||||
ReleaseType,
|
||||
} from '@expo/apple-utils';
|
||||
|
||||
import { unique } from '../../utils/array';
|
||||
import { AttributesOf } from '../../utils/asc';
|
||||
import { removeDatePrecision } from '../../utils/date';
|
||||
import { AppleMetadata } from '../types';
|
||||
|
||||
type PartialExcept<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>>;
|
||||
|
||||
// TODO: find out if we can move this to default JSON schema normalization
|
||||
export const DEFAULT_WHATSNEW = 'Bug fixes and improved stability';
|
||||
|
||||
/**
|
||||
* Deserializes the metadata config schema into attributes for different models.
|
||||
* This uses version 0 of the config schema.
|
||||
*/
|
||||
export class AppleConfigReader {
|
||||
constructor(public readonly schema: AppleMetadata) {}
|
||||
|
||||
getAgeRating(): Partial<AttributesOf<AgeRatingDeclaration>> | null {
|
||||
return this.schema.advisory || null;
|
||||
}
|
||||
|
||||
getLocales(): string[] {
|
||||
// TODO: filter "default" locales, add option to add non-localized info to the config
|
||||
return unique(Object.keys(this.schema.info || {}));
|
||||
}
|
||||
|
||||
getInfoLocale(
|
||||
locale: string
|
||||
): PartialExcept<AttributesOf<AppInfoLocalization>, 'locale' | 'name'> | null {
|
||||
const info = this.schema.info?.[locale];
|
||||
if (!info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
locale,
|
||||
name: info.title || 'no name provided',
|
||||
subtitle: info.subtitle,
|
||||
privacyChoicesUrl: info.privacyChoicesUrl,
|
||||
privacyPolicyText: info.privacyPolicyText,
|
||||
privacyPolicyUrl: info.privacyPolicyUrl,
|
||||
};
|
||||
}
|
||||
|
||||
getCategories(): CategoryIds | null {
|
||||
if (Array.isArray(this.schema.categories) && this.schema.categories.length > 0) {
|
||||
return {
|
||||
primaryCategory: this.schema.categories[0],
|
||||
secondaryCategory: this.schema.categories[1],
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
getVersion(): Partial<
|
||||
Omit<AttributesOf<AppStoreVersion>, 'releaseType' | 'earliestReleaseDate'>
|
||||
> | null {
|
||||
return this.schema.copyright ? { copyright: this.schema.copyright } : null;
|
||||
}
|
||||
|
||||
getVersionRelease(): Partial<
|
||||
Pick<AttributesOf<AppStoreVersion>, 'releaseType' | 'earliestReleaseDate'>
|
||||
> | null {
|
||||
const { release } = this.schema;
|
||||
|
||||
if (release?.autoReleaseDate) {
|
||||
return {
|
||||
releaseType: ReleaseType.SCHEDULED,
|
||||
// Convert time format to 2020-06-17T12:00:00-07:00
|
||||
earliestReleaseDate: removeDatePrecision(release.autoReleaseDate)?.toISOString() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
if (release?.automaticRelease === true) {
|
||||
return {
|
||||
releaseType: ReleaseType.AFTER_APPROVAL,
|
||||
earliestReleaseDate: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (release?.automaticRelease === false) {
|
||||
return {
|
||||
releaseType: ReleaseType.MANUAL,
|
||||
earliestReleaseDate: null,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
getVersionLocale(
|
||||
locale: string,
|
||||
context: { versionIsFirst: boolean }
|
||||
): Partial<AttributesOf<AppStoreVersionLocalization>> | null {
|
||||
const info = this.schema.info?.[locale];
|
||||
if (!info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
locale,
|
||||
description: info.description,
|
||||
keywords: info.keywords?.join(', '),
|
||||
// TODO: maybe move this to task logic, it's more an exception than data handling
|
||||
whatsNew: context.versionIsFirst ? undefined : info.releaseNotes || DEFAULT_WHATSNEW,
|
||||
marketingUrl: info.marketingUrl,
|
||||
promotionalText: info.promoText,
|
||||
supportUrl: info.supportUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
AgeRatingDeclaration,
|
||||
AppInfo,
|
||||
AppInfoLocalization,
|
||||
AppStoreVersion,
|
||||
AppStoreVersionLocalization,
|
||||
ReleaseType,
|
||||
} from '@expo/apple-utils';
|
||||
|
||||
import { AttributesOf } from '../../utils/asc';
|
||||
import { AppleMetadata } from '../types';
|
||||
|
||||
/**
|
||||
* Serializes the Apple ASC entities into the metadata configuration schema.
|
||||
* This uses version 0 of the config schema.
|
||||
*/
|
||||
export class AppleConfigWriter {
|
||||
constructor(public readonly schema: Partial<AppleMetadata> = {}) {}
|
||||
|
||||
/** Get the schema result to write it to the config file */
|
||||
toSchema(): { configVersion: number; apple: Partial<AppleMetadata> } {
|
||||
return {
|
||||
configVersion: 0,
|
||||
apple: this.schema,
|
||||
};
|
||||
}
|
||||
|
||||
setAgeRating(attributes: AttributesOf<AgeRatingDeclaration>): void {
|
||||
this.schema.advisory = attributes;
|
||||
}
|
||||
|
||||
setInfoLocale(attributes: AttributesOf<AppInfoLocalization>): void {
|
||||
this.schema.info = this.schema.info ?? {};
|
||||
const existing = this.schema.info[attributes.locale] ?? {};
|
||||
|
||||
this.schema.info[attributes.locale] = {
|
||||
...existing,
|
||||
title: attributes.name ?? 'no name provided',
|
||||
subtitle: optional(attributes.subtitle),
|
||||
privacyPolicyUrl: optional(attributes.privacyPolicyUrl),
|
||||
privacyPolicyText: optional(attributes.privacyPolicyText),
|
||||
privacyChoicesUrl: optional(attributes.privacyChoicesUrl),
|
||||
};
|
||||
}
|
||||
|
||||
setCategories({ primaryCategory, secondaryCategory }: AttributesOf<AppInfo>): void {
|
||||
this.schema.categories = [];
|
||||
|
||||
// TODO: see why these types are conflicting
|
||||
if (primaryCategory) {
|
||||
this.schema.categories.push(primaryCategory.id as any);
|
||||
if (secondaryCategory) {
|
||||
this.schema.categories.push(secondaryCategory.id as any);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setVersion(
|
||||
attributes: Omit<AttributesOf<AppStoreVersion>, 'releaseType' | 'earliestReleaseDate'>
|
||||
): void {
|
||||
this.schema.copyright = optional(attributes.copyright);
|
||||
}
|
||||
|
||||
setVersionRelease(
|
||||
attributes: Pick<AttributesOf<AppStoreVersion>, 'releaseType' | 'earliestReleaseDate'>
|
||||
): void {
|
||||
if (attributes.releaseType === ReleaseType.SCHEDULED) {
|
||||
this.schema.release = {
|
||||
autoReleaseDate: optional(attributes.earliestReleaseDate),
|
||||
};
|
||||
}
|
||||
|
||||
if (attributes.releaseType === ReleaseType.AFTER_APPROVAL) {
|
||||
this.schema.release = {
|
||||
automaticRelease: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (attributes.releaseType === ReleaseType.MANUAL) {
|
||||
this.schema.release = {
|
||||
automaticRelease: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
setVersionLocale(attributes: AttributesOf<AppStoreVersionLocalization>): void {
|
||||
this.schema.info = this.schema.info ?? {};
|
||||
const existing = this.schema.info[attributes.locale] ?? {};
|
||||
|
||||
this.schema.info[attributes.locale] = {
|
||||
...existing,
|
||||
description: optional(attributes.description),
|
||||
keywords: optional(attributes.keywords)
|
||||
?.split(',')
|
||||
.map(keyword => keyword.trim()),
|
||||
releaseNotes: optional(attributes.whatsNew),
|
||||
marketingUrl: optional(attributes.marketingUrl),
|
||||
promoText: optional(attributes.promotionalText),
|
||||
supportUrl: optional(attributes.supportUrl),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper function to convert `T | null` to `T | undefined` */
|
||||
function optional<T>(value: T | null): T | undefined {
|
||||
return value ?? undefined;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { App } from '@expo/apple-utils';
|
||||
|
||||
import type { AgeRatingData } from './tasks/age-rating';
|
||||
import type { AppInfoData } from './tasks/app-info';
|
||||
import type { AppVersionData } from './tasks/app-version';
|
||||
|
||||
/**
|
||||
* The fully prepared apple data, used within the `downloadAsync` or `uploadAsync` tasks.
|
||||
* It contains references to each individual models, to either upload or download App Store data.
|
||||
*/
|
||||
export type AppleData = { app: App } & AppInfoData & AppVersionData & AgeRatingData;
|
||||
|
||||
/**
|
||||
* The unprepared partial apple data, used within the `prepareAsync` tasks.
|
||||
* It contains a reference to the app, each task should populate the necessary data.
|
||||
* If an entity fails to prepare the data, individual tasks should raise errors about the missing data.
|
||||
*/
|
||||
export type PartialAppleData = { app: App } & Partial<Omit<AppleData, 'app'>>;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { AppleConfigReader } from './config/reader';
|
||||
import { AppleConfigWriter } from './config/writer';
|
||||
import { AppleData, PartialAppleData } from './data';
|
||||
|
||||
export abstract class AppleTask {
|
||||
/** Get a description from the task to use as section headings in the log */
|
||||
abstract name(): string;
|
||||
|
||||
/** Prepare the data from the App Store to start syncing with the store configuration */
|
||||
abstract prepareAsync(options: TaskPrepareOptions): Promise<void>;
|
||||
|
||||
/** Download all information from the App Store to generate the store configuration */
|
||||
abstract downloadAsync(options: TaskDownloadOptions): Promise<void>;
|
||||
|
||||
/** Upload all information from the store configuration to the App Store */
|
||||
abstract uploadAsync(options: TaskUploadOptions): Promise<void>;
|
||||
}
|
||||
|
||||
export type TaskPrepareOptions = {
|
||||
context: PartialAppleData;
|
||||
};
|
||||
|
||||
export type TaskDownloadOptions = {
|
||||
config: AppleConfigWriter;
|
||||
context: AppleData;
|
||||
};
|
||||
|
||||
export type TaskUploadOptions = {
|
||||
config: AppleConfigReader;
|
||||
context: AppleData;
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { getRequestClient } from '@expo/apple-utils';
|
||||
|
||||
export { default } from 'nock';
|
||||
|
||||
// Axios from apple-utils needs to be patched when we use Nock.
|
||||
// Instead of the default adapter, we need to use `axios/lib/adapters/http`
|
||||
// see: https://github.com/nock/nock#axios
|
||||
getRequestClient().defaults.adapter = require('axios/lib/adapters/http');
|
||||
@@ -0,0 +1,84 @@
|
||||
import { AgeRatingDeclaration, AppStoreVersion, Rating } from '@expo/apple-utils';
|
||||
import nock from 'nock';
|
||||
|
||||
import { AppleConfigReader } from '../../config/reader';
|
||||
import { AppleData } from '../../data';
|
||||
import { AgeRatingTask } from '../age-rating';
|
||||
import { requestContext } from './fixtures/requestContext';
|
||||
|
||||
jest.mock('../../../../ora');
|
||||
|
||||
describe(AgeRatingTask, () => {
|
||||
describe('preuploadAsync', () => {
|
||||
it('aborts when version is not loaded', async () => {
|
||||
const promise = new AgeRatingTask().prepareAsync({ context: {} as any });
|
||||
|
||||
await expect(promise).rejects.toThrow('not prepared');
|
||||
});
|
||||
|
||||
it('loads age rating from app version', async () => {
|
||||
const scope = nock('https://api.appstoreconnect.apple.com')
|
||||
.get(`/v1/${AppStoreVersion.type}/stub-id/ageRatingDeclaration`)
|
||||
.reply(200, require('./fixtures/appStoreVersions/get-ageRatingDeclaration-200.json'));
|
||||
|
||||
const context: any = {
|
||||
version: new AppStoreVersion(requestContext, 'stub-id', {} as any),
|
||||
};
|
||||
|
||||
await new AgeRatingTask().prepareAsync({ context });
|
||||
|
||||
expect(context.ageRating).toBeInstanceOf(AgeRatingDeclaration);
|
||||
expect(scope.isDone()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('uploadAsync', () => {
|
||||
it('aborts when age rating is not loaded', async () => {
|
||||
const promise = new AgeRatingTask().uploadAsync({
|
||||
config: new AppleConfigReader({}),
|
||||
context: { ageRating: undefined } as any,
|
||||
});
|
||||
|
||||
await expect(promise).rejects.toThrow('rating not initialized');
|
||||
});
|
||||
|
||||
it('skips updating age rating when not configured', async () => {
|
||||
const scope = nock('https://api.appstoreconnect.apple.com')
|
||||
.patch(`/v1/${AgeRatingDeclaration.type}/stub-id`)
|
||||
.reply(200, require('./fixtures/ageRatingDeclarations/patch-200.json'));
|
||||
|
||||
await new AgeRatingTask().uploadAsync({
|
||||
config: new AppleConfigReader({ advisory: undefined }),
|
||||
context: {
|
||||
ageRating: new AgeRatingDeclaration(requestContext, 'stub-id', {} as any),
|
||||
} as AppleData,
|
||||
});
|
||||
|
||||
expect(scope.isDone()).toBeFalsy();
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
it('updates age rating from config when configured', async () => {
|
||||
const scope = nock('https://api.appstoreconnect.apple.com')
|
||||
.patch(`/v1/${AgeRatingDeclaration.type}/stub-id`)
|
||||
.reply(200, require('./fixtures/ageRatingDeclarations/patch-200.json'));
|
||||
|
||||
const context = {
|
||||
ageRating: new AgeRatingDeclaration(requestContext, 'stub-id', {} as any),
|
||||
};
|
||||
|
||||
await new AgeRatingTask().uploadAsync({
|
||||
config: new AppleConfigReader({
|
||||
advisory: {
|
||||
// See fixture value for horrorOrFearThemes
|
||||
horrorOrFearThemes: Rating.INFREQUENT_OR_MILD,
|
||||
},
|
||||
}),
|
||||
context: context as AppleData,
|
||||
});
|
||||
|
||||
expect(context.ageRating.id).not.toMatch('stub-id');
|
||||
expect(scope.isDone()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import { App, AppCategoryId, AppInfo, AppInfoLocalization } from '@expo/apple-utils';
|
||||
import nock from 'nock';
|
||||
|
||||
import { AppleConfigReader } from '../../config/reader';
|
||||
import { AppleData, PartialAppleData } from '../../data';
|
||||
import { AppInfoTask } from '../app-info';
|
||||
import { requestContext } from './fixtures/requestContext';
|
||||
|
||||
jest.mock('../../../../ora');
|
||||
|
||||
describe(AppInfoTask, () => {
|
||||
describe('preuploadAsync', () => {
|
||||
it('loads editable app info and locales from app instance', async () => {
|
||||
const scopeInfo = nock('https://api.appstoreconnect.apple.com')
|
||||
.get(uri => uri.startsWith(`/v1/${App.type}/stub-id/${AppInfo.type}`)) // allow any query params
|
||||
.reply(200, require('./fixtures/apps/get-appInfos-200.json'));
|
||||
|
||||
const scopeLocales = nock('https://api.appstoreconnect.apple.com')
|
||||
.get(/\/v1\/appInfos\/.*\/appInfoLocalizations/) // allow any id from fixture
|
||||
.reply(200, require('./fixtures/appInfos/get-appInfoLocalizations-200.json'));
|
||||
|
||||
const context: PartialAppleData = {
|
||||
app: new App(requestContext, 'stub-id', {} as any),
|
||||
};
|
||||
|
||||
await new AppInfoTask().prepareAsync({ context });
|
||||
|
||||
expect(context.info).toBeInstanceOf(AppInfo);
|
||||
expect(context.infoLocales).toBeInstanceOf(Array);
|
||||
expect(scopeInfo.isDone()).toBeTruthy();
|
||||
expect(scopeLocales.isDone()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('uploadAsync', () => {
|
||||
it('aborts when app info is not loaded', async () => {
|
||||
const promise = new AppInfoTask().uploadAsync({
|
||||
config: new AppleConfigReader({}),
|
||||
context: { info: undefined } as any,
|
||||
});
|
||||
|
||||
await expect(promise).rejects.toThrow('info not initialized');
|
||||
});
|
||||
|
||||
it('skips updating categories when not configured', async () => {
|
||||
const scope = nock('https://api.appstoreconnect.apple.com')
|
||||
.patch(`/v1/${AppInfo.type}/stub-id`)
|
||||
.reply(200, require('./fixtures/appInfos/patch-200.json'));
|
||||
|
||||
await new AppInfoTask().uploadAsync({
|
||||
config: new AppleConfigReader({ categories: undefined }),
|
||||
context: {
|
||||
info: new AppInfo(requestContext, 'stub-id', {} as any),
|
||||
} as AppleData,
|
||||
});
|
||||
|
||||
expect(scope.isDone()).toBeFalsy();
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
it('skips updating localized info when not configured', async () => {
|
||||
const scope = nock('https://api.appstoreconnect.apple.com')
|
||||
.patch(`/v1/${AppInfoLocalization.type}/stub-id`)
|
||||
.reply(200, require('./fixtures/appInfos/patch-200.json'));
|
||||
|
||||
await new AppInfoTask().uploadAsync({
|
||||
config: new AppleConfigReader({ info: undefined }),
|
||||
context: {
|
||||
info: new AppInfo(requestContext, 'stub-id', {} as any),
|
||||
} as AppleData,
|
||||
});
|
||||
|
||||
expect(scope.isDone()).toBeFalsy();
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
it('updates categories and localized info', async () => {
|
||||
const updateCategoryScope = nock('https://api.appstoreconnect.apple.com')
|
||||
.patch(`/v1/${AppInfo.type}/stub-id`)
|
||||
.reply(200, require('./fixtures/appInfos/patch-200.json'));
|
||||
|
||||
const updateENInfoScope = nock('https://api.appstoreconnect.apple.com')
|
||||
.patch(`/v1/${AppInfoLocalization.type}/APP_INFO_LOCALE_1`) // see fixture ID
|
||||
.reply(200, require('./fixtures/appInfoLocalizations/patch-200.json'));
|
||||
|
||||
const updateNLInfoScope = nock('https://api.appstoreconnect.apple.com')
|
||||
.patch(`/v1/${AppInfoLocalization.type}/APP_INFO_LOCALE_2`) // see fixture ID
|
||||
.reply(200, require('./fixtures/appInfoLocalizations/patch-200.json'));
|
||||
|
||||
const fetchLocalizedInfoScope = nock('https://api.appstoreconnect.apple.com')
|
||||
.get(`/v1/${AppInfo.type}/APP_INFO_1/${AppInfoLocalization.type}`) // see fixture ID
|
||||
.twice()
|
||||
.reply(200, require('./fixtures/appInfos/get-appInfoLocalizations-200.json'));
|
||||
|
||||
await new AppInfoTask().uploadAsync({
|
||||
config: new AppleConfigReader({
|
||||
categories: [AppCategoryId.ENTERTAINMENT],
|
||||
info: {
|
||||
'en-US': { title: 'Hello' },
|
||||
'nl-NL': { title: 'Hallo' },
|
||||
},
|
||||
}),
|
||||
context: {
|
||||
info: new AppInfo(requestContext, 'stub-id', {} as any),
|
||||
} as AppleData,
|
||||
});
|
||||
|
||||
expect(updateCategoryScope.isDone()).toBeTruthy();
|
||||
expect(fetchLocalizedInfoScope.isDone()).toBeTruthy();
|
||||
expect(updateENInfoScope.isDone()).toBeTruthy();
|
||||
expect(updateNLInfoScope.isDone()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,176 @@
|
||||
import {
|
||||
App,
|
||||
AppStoreState,
|
||||
AppStoreVersion,
|
||||
AppStoreVersionLocalization,
|
||||
} from '@expo/apple-utils';
|
||||
import nock from 'nock';
|
||||
|
||||
import { AppleConfigReader } from '../../config/reader';
|
||||
import { AppleData, PartialAppleData } from '../../data';
|
||||
import { AppVersionTask } from '../app-version';
|
||||
import { requestContext } from './fixtures/requestContext';
|
||||
|
||||
jest.mock('../../../../ora');
|
||||
|
||||
describe(AppVersionTask, () => {
|
||||
describe('preuploadAsync', () => {
|
||||
it('loads live version', async () => {
|
||||
const scope = nock('https://api.appstoreconnect.apple.com')
|
||||
// Respond to app.getLiveAppStoreVersionAsync
|
||||
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
|
||||
.query((params: any) => params['filter[appStoreState]'] === AppStoreState.READY_FOR_SALE)
|
||||
.reply(200, require('./fixtures/apps/get-appStoreVersions-200.json'))
|
||||
// Respond to app.getAppStoreVersionsAsync
|
||||
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
|
||||
.query((params: any) => params['filter[appStoreState]'] !== AppStoreState.READY_FOR_SALE)
|
||||
.reply(200, require('./fixtures/apps/get-appStoreVersions-count-200.json'))
|
||||
// Respond to version.getLocalizationsAsync
|
||||
.get(`/v1/${AppStoreVersion.type}/APP_STORE_VERSION_1/${AppStoreVersionLocalization.type}`)
|
||||
.reply(
|
||||
200,
|
||||
require('./fixtures/appStoreVersions/get-appStoreVersionLocalizations-200.json')
|
||||
);
|
||||
|
||||
const context: PartialAppleData = {
|
||||
app: new App(requestContext, 'stub-id', {} as any),
|
||||
};
|
||||
|
||||
await new AppVersionTask({ editLive: true }).prepareAsync({ context });
|
||||
|
||||
expect(context.version).toBeInstanceOf(AppStoreVersion);
|
||||
expect(context.versionIsLive).toBeTruthy();
|
||||
expect(context.versionIsFirst).toBeTruthy();
|
||||
expect(scope.isDone()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('falls back to editable version if live version is not found', async () => {
|
||||
const scope = nock('https://api.appstoreconnect.apple.com')
|
||||
// Respond to app.getLiveAppStoreVersionAsync, failing with 404
|
||||
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
|
||||
.query((params: any) => params['filter[appStoreState]'] === AppStoreState.READY_FOR_SALE)
|
||||
.reply(404, {})
|
||||
// Respond to app.getEditAppStoreVersionAsync
|
||||
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
|
||||
.query((params: any) => params['filter[appStoreState]'] !== AppStoreState.READY_FOR_SALE)
|
||||
.reply(200, require('./fixtures/apps/get-appStoreVersions-count-200.json'))
|
||||
// Respond to app.getAppStoreVersionsAsync
|
||||
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
|
||||
.query(true)
|
||||
.reply(200, require('./fixtures/apps/get-appStoreVersions-count-200.json'))
|
||||
// Respond to version.getLocalizationsAsync
|
||||
.get(`/v1/${AppStoreVersion.type}/APP_STORE_VERSION_1/${AppStoreVersionLocalization.type}`)
|
||||
.reply(
|
||||
200,
|
||||
require('./fixtures/appStoreVersions/get-appStoreVersionLocalizations-200.json')
|
||||
);
|
||||
|
||||
const context: PartialAppleData = {
|
||||
app: new App(requestContext, 'stub-id', {} as any),
|
||||
};
|
||||
|
||||
await new AppVersionTask({ editLive: true }).prepareAsync({ context });
|
||||
|
||||
expect(context.version).toBeInstanceOf(AppStoreVersion);
|
||||
expect(context.versionIsLive).toBeFalsy();
|
||||
expect(context.versionIsFirst).toBeTruthy();
|
||||
expect(scope.isDone()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('loads last editable version', async () => {
|
||||
const scope = nock('https://api.appstoreconnect.apple.com')
|
||||
// Respond to app.getEditAppStoreVersionAsync
|
||||
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
|
||||
.query((params: any) => params['filter[appStoreState]'] !== AppStoreState.READY_FOR_SALE)
|
||||
.reply(200, require('./fixtures/apps/get-appStoreVersions-200.json'))
|
||||
// Respond to app.getAppStoreVersionsAsync
|
||||
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
|
||||
.query((params: any) => params['filter[appStoreState]'] !== AppStoreState.READY_FOR_SALE)
|
||||
.reply(200, require('./fixtures/apps/get-appStoreVersions-count-200.json'))
|
||||
// Respond to version.getLocalizationsAsync
|
||||
.get(`/v1/${AppStoreVersion.type}/APP_STORE_VERSION_1/${AppStoreVersionLocalization.type}`)
|
||||
.reply(
|
||||
200,
|
||||
require('./fixtures/appStoreVersions/get-appStoreVersionLocalizations-200.json')
|
||||
);
|
||||
|
||||
const context: PartialAppleData = {
|
||||
app: new App(requestContext, 'stub-id', {} as any),
|
||||
};
|
||||
|
||||
await new AppVersionTask().prepareAsync({ context });
|
||||
|
||||
expect(context.version).toBeInstanceOf(AppStoreVersion);
|
||||
expect(context.versionIsLive).toBeFalsy();
|
||||
expect(context.versionIsFirst).toBeTruthy();
|
||||
expect(scope.isDone()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('sets first version property for multiple versions', async () => {
|
||||
const scope = nock('https://api.appstoreconnect.apple.com')
|
||||
// Respond to app.getEditAppStoreVersionAsync
|
||||
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
|
||||
.query((params: any) => params['filter[appStoreState]'] !== AppStoreState.READY_FOR_SALE)
|
||||
.reply(200, require('./fixtures/apps/get-appStoreVersions-200.json'))
|
||||
// Respond to app.getAppStoreVersionsAsync
|
||||
.get(`/v1/${App.type}/stub-id/${AppStoreVersion.type}`)
|
||||
.query((params: any) => params['filter[appStoreState]'] !== AppStoreState.READY_FOR_SALE)
|
||||
.reply(200, require('./fixtures/apps/get-appStoreVersions-count-multiple-200.json'))
|
||||
// Respond to version.getLocalizationsAsync
|
||||
.get(`/v1/${AppStoreVersion.type}/APP_STORE_VERSION_1/${AppStoreVersionLocalization.type}`)
|
||||
.reply(
|
||||
200,
|
||||
require('./fixtures/appStoreVersions/get-appStoreVersionLocalizations-200.json')
|
||||
);
|
||||
|
||||
const context: PartialAppleData = {
|
||||
app: new App(requestContext, 'stub-id', {} as any),
|
||||
};
|
||||
|
||||
await new AppVersionTask().prepareAsync({ context });
|
||||
|
||||
expect(context.versionIsFirst).toBeFalsy();
|
||||
expect(scope.isDone()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('upload', () => {
|
||||
it('aborts when version is not loaded', async () => {
|
||||
const promise = new AppVersionTask().uploadAsync({
|
||||
context: {} as any,
|
||||
config: new AppleConfigReader({}),
|
||||
});
|
||||
|
||||
await expect(promise).rejects.toThrow('not initialized');
|
||||
});
|
||||
|
||||
it('updates version and release from config', async () => {
|
||||
const config = new AppleConfigReader({
|
||||
copyright: '2022 ACME',
|
||||
release: { automaticRelease: true },
|
||||
});
|
||||
|
||||
const attributes = { ...config.getVersion(), ...config.getVersionRelease() };
|
||||
const scope = nock('https://api.appstoreconnect.apple.com')
|
||||
// Respond to version.updateAsync
|
||||
.patch(`/v1/${AppStoreVersion.type}/APP_STORE_VERSION_1`)
|
||||
.reply(200, {
|
||||
data: {
|
||||
id: 'APP_STORE_VERSION_1',
|
||||
type: AppStoreVersion.type,
|
||||
attributes,
|
||||
},
|
||||
});
|
||||
|
||||
const context: PartialAppleData = {
|
||||
app: new App(requestContext, 'stub-id', {} as any),
|
||||
version: new AppStoreVersion(requestContext, 'APP_STORE_VERSION_1', {} as any),
|
||||
};
|
||||
|
||||
await new AppVersionTask().uploadAsync({ context: context as AppleData, config });
|
||||
|
||||
expect(context.version?.attributes).toMatchObject(attributes);
|
||||
expect(scope.isDone()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"data": {
|
||||
"type": "ageRatingDeclarations",
|
||||
"id": "AGE_RATING_1",
|
||||
"attributes": {
|
||||
"alcoholTobaccoOrDrugUseOrReferences": "NONE",
|
||||
"contests": "NONE",
|
||||
"gamblingAndContests": false,
|
||||
"gambling": false,
|
||||
"gamblingSimulated": "NONE",
|
||||
"kidsAgeBand": null,
|
||||
"medicalOrTreatmentInformation": "NONE",
|
||||
"profanityOrCrudeHumor": "NONE",
|
||||
"sexualContentGraphicAndNudity": "NONE",
|
||||
"sexualContentOrNudity": "NONE",
|
||||
"seventeenPlus": false,
|
||||
"horrorOrFearThemes": "INFREQUENT_OR_MILD",
|
||||
"matureOrSuggestiveThemes": "NONE",
|
||||
"unrestrictedWebAccess": false,
|
||||
"violenceCartoonOrFantasy": "NONE",
|
||||
"violenceRealisticProlongedGraphicOrSadistic": "NONE",
|
||||
"violenceRealistic": "NONE"
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/ageRatingDeclarations/AGE_RATING_1"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/ageRatingDeclarations/AGE_RATING_1"
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"data": {
|
||||
"type": "appInfoLocalizations",
|
||||
"id": "APP_INFO_LOCALE_1",
|
||||
"attributes": {
|
||||
"locale": "en-US",
|
||||
"name": "Rexpo Square App",
|
||||
"subtitle": "144 fps red squares",
|
||||
"privacyPolicyUrl": "https://github.com/bycedric/red-square-app",
|
||||
"privacyChoicesUrl": null,
|
||||
"privacyPolicyText": null
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfoLocalizations/APP_INFO_LOCALE_1"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfoLocalizations/APP_INFO_LOCALE_1"
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"type": "appInfoLocalizations",
|
||||
"id": "APP_INFO_LOCALE_1",
|
||||
"attributes": {
|
||||
"locale": "en-US",
|
||||
"name": "Rexpo Square App",
|
||||
"subtitle": "144 fps red squares",
|
||||
"privacyPolicyUrl": "https://github.com/bycedric/red-square-app",
|
||||
"privacyChoicesUrl": null,
|
||||
"privacyPolicyText": null
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfoLocalizations/APP_INFO_LOCALE_1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "appInfoLocalizations",
|
||||
"id": "APP_INFO_LOCALE_2",
|
||||
"attributes": {
|
||||
"locale": "nl-NL",
|
||||
"name": "Rexpo Vierkant App",
|
||||
"subtitle": "144 fps red vierkant",
|
||||
"privacyPolicyUrl": "https://github.com/bycedric/red-square-app",
|
||||
"privacyChoicesUrl": null,
|
||||
"privacyPolicyText": null
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfoLocalizations/APP_INFO_LOCALE_2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/a9c2ad6a-182f-4021-9456-6b609ebc5eab/appInfoLocalizations"
|
||||
},
|
||||
"meta": {
|
||||
"paging": {
|
||||
"total": 2,
|
||||
"limit": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"data": {
|
||||
"type": "appInfos",
|
||||
"id": "APP_INFO_1",
|
||||
"attributes": {
|
||||
"appStoreState": "PREPARE_FOR_SUBMISSION",
|
||||
"appStoreAgeRating": "NINE_PLUS",
|
||||
"brazilAgeRating": "TEN",
|
||||
"kidsAgeBand": null
|
||||
},
|
||||
"relationships": {
|
||||
"ageRatingDeclaration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/ageRatingDeclaration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/ageRatingDeclaration"
|
||||
}
|
||||
},
|
||||
"appInfoLocalizations": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/appInfoLocalizations",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/appInfoLocalizations"
|
||||
}
|
||||
},
|
||||
"primaryCategory": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/primaryCategory",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/primaryCategory"
|
||||
}
|
||||
},
|
||||
"primarySubcategoryOne": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/primarySubcategoryOne",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/primarySubcategoryOne"
|
||||
}
|
||||
},
|
||||
"primarySubcategoryTwo": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/primarySubcategoryTwo",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/primarySubcategoryTwo"
|
||||
}
|
||||
},
|
||||
"secondaryCategory": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/secondaryCategory",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/secondaryCategory"
|
||||
}
|
||||
},
|
||||
"secondarySubcategoryOne": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/secondarySubcategoryOne",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/secondarySubcategoryOne"
|
||||
}
|
||||
},
|
||||
"secondarySubcategoryTwo": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/secondarySubcategoryTwo",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/secondarySubcategoryTwo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1"
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"data": {
|
||||
"type": "ageRatingDeclarations",
|
||||
"id": "a9c2ad6a-182f-4021-9456-6b609ebc5eab",
|
||||
"attributes": {
|
||||
"alcoholTobaccoOrDrugUseOrReferences": "NONE",
|
||||
"contests": "NONE",
|
||||
"gamblingAndContests": false,
|
||||
"gambling": false,
|
||||
"gamblingSimulated": "NONE",
|
||||
"kidsAgeBand": null,
|
||||
"medicalOrTreatmentInformation": "NONE",
|
||||
"profanityOrCrudeHumor": "NONE",
|
||||
"sexualContentGraphicAndNudity": "NONE",
|
||||
"sexualContentOrNudity": "NONE",
|
||||
"seventeenPlus": false,
|
||||
"horrorOrFearThemes": "NONE",
|
||||
"matureOrSuggestiveThemes": "NONE",
|
||||
"unrestrictedWebAccess": false,
|
||||
"violenceCartoonOrFantasy": "NONE",
|
||||
"violenceRealisticProlongedGraphicOrSadistic": "NONE",
|
||||
"violenceRealistic": "NONE"
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/ageRatingDeclarations/a9c2ad6a-182f-4021-9456-6b609ebc5eab"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/949768f6-58e5-46b4-b046-23861f61831a/ageRatingDeclaration"
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"id": "9901eeb2-81e7-4562-9beb-c5f72010ab71",
|
||||
"status": "404",
|
||||
"code": "NOT_FOUND",
|
||||
"title": "The specified resource does not exist",
|
||||
"detail": "There is no resource of type 'appStoreVersions' with id 'non-existing'"
|
||||
}
|
||||
]
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"data": [{
|
||||
"type": "appStoreVersionLocalizations",
|
||||
"id": "4bad54bc-3810-401a-a7f4-b02ef8206c41",
|
||||
"attributes": {
|
||||
"description": "Some of the best apps",
|
||||
"locale": "nl-NL",
|
||||
"keywords": "meme, red, square, x2",
|
||||
"marketingUrl": "https://github.com/bycedric/red-square-app",
|
||||
"promotionalText": null,
|
||||
"supportUrl": "https://github.com/bycedric/red-square-app/issues",
|
||||
"whatsNew": null
|
||||
},
|
||||
"relationships": {
|
||||
"appScreenshotSets": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/4bad54bc-3810-401a-a7f4-b02ef8206c41/relationships/appScreenshotSets",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/4bad54bc-3810-401a-a7f4-b02ef8206c41/appScreenshotSets"
|
||||
}
|
||||
},
|
||||
"appPreviewSets": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/4bad54bc-3810-401a-a7f4-b02ef8206c41/relationships/appPreviewSets",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/4bad54bc-3810-401a-a7f4-b02ef8206c41/appPreviewSets"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/4bad54bc-3810-401a-a7f4-b02ef8206c41"
|
||||
}
|
||||
}, {
|
||||
"type": "appStoreVersionLocalizations",
|
||||
"id": "61153520-c5df-40a1-bb2b-e09cbfc33fe9",
|
||||
"attributes": {
|
||||
"description": "Some of the best apps",
|
||||
"locale": "en-US",
|
||||
"keywords": "meme, red, square, x2",
|
||||
"marketingUrl": "https://github.com/bycedric/red-square-app",
|
||||
"promotionalText": null,
|
||||
"supportUrl": "https://github.com/bycedric/red-square-app/issues",
|
||||
"whatsNew": null
|
||||
},
|
||||
"relationships": {
|
||||
"appScreenshotSets": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/61153520-c5df-40a1-bb2b-e09cbfc33fe9/relationships/appScreenshotSets",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/61153520-c5df-40a1-bb2b-e09cbfc33fe9/appScreenshotSets"
|
||||
}
|
||||
},
|
||||
"appPreviewSets": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/61153520-c5df-40a1-bb2b-e09cbfc33fe9/relationships/appPreviewSets",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/61153520-c5df-40a1-bb2b-e09cbfc33fe9/appPreviewSets"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersionLocalizations/61153520-c5df-40a1-bb2b-e09cbfc33fe9"
|
||||
}
|
||||
}],
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/949768f6-58e5-46b4-b046-23861f61831a/appStoreVersionLocalizations"
|
||||
},
|
||||
"meta": {
|
||||
"paging": {
|
||||
"total": 2,
|
||||
"limit": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"type": "appInfos",
|
||||
"id": "APP_INFO_1",
|
||||
"attributes": {
|
||||
"appStoreState": "PREPARE_FOR_SUBMISSION",
|
||||
"appStoreAgeRating": "NINE_PLUS",
|
||||
"brazilAgeRating": "TEN",
|
||||
"kidsAgeBand": null
|
||||
},
|
||||
"relationships": {
|
||||
"ageRatingDeclaration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/ageRatingDeclaration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/ageRatingDeclaration"
|
||||
}
|
||||
},
|
||||
"appInfoLocalizations": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/appInfoLocalizations",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/appInfoLocalizations"
|
||||
}
|
||||
},
|
||||
"primaryCategory": {
|
||||
"data": {
|
||||
"type": "appCategories",
|
||||
"id": "ENTERTAINMENT"
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/primaryCategory",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/primaryCategory"
|
||||
}
|
||||
},
|
||||
"primarySubcategoryOne": {
|
||||
"data": null,
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/primarySubcategoryOne",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/primarySubcategoryOne"
|
||||
}
|
||||
},
|
||||
"primarySubcategoryTwo": {
|
||||
"data": null,
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/primarySubcategoryTwo",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/primarySubcategoryTwo"
|
||||
}
|
||||
},
|
||||
"secondaryCategory": {
|
||||
"data": null,
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/secondaryCategory",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/secondaryCategory"
|
||||
}
|
||||
},
|
||||
"secondarySubcategoryOne": {
|
||||
"data": null,
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/secondarySubcategoryOne",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/secondarySubcategoryOne"
|
||||
}
|
||||
},
|
||||
"secondarySubcategoryTwo": {
|
||||
"data": null,
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/relationships/secondarySubcategoryTwo",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1/secondarySubcategoryTwo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appInfos/APP_INFO_1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"included": [
|
||||
{
|
||||
"type": "appCategories",
|
||||
"id": "ENTERTAINMENT",
|
||||
"attributes": {
|
||||
"platforms": [
|
||||
"IOS",
|
||||
"MAC_OS",
|
||||
"TV_OS"
|
||||
]
|
||||
},
|
||||
"relationships": {
|
||||
"subcategories": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appCategories/ENTERTAINMENT/relationships/subcategories",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appCategories/ENTERTAINMENT/subcategories"
|
||||
}
|
||||
},
|
||||
"parent": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appCategories/ENTERTAINMENT/relationships/parent",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appCategories/ENTERTAINMENT/parent"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appCategories/ENTERTAINMENT"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/apps/1611050397/appInfos?include=secondaryCategory%2CprimaryCategory%2CsecondarySubcategoryOne%2CprimarySubcategoryTwo%2CsecondarySubcategoryTwo%2CprimarySubcategoryOne"
|
||||
},
|
||||
"meta": {
|
||||
"paging": {
|
||||
"total": 1,
|
||||
"limit": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"id": "APP_INFO_0",
|
||||
"status": "404",
|
||||
"code": "NOT_FOUND",
|
||||
"title": "The specified resource does not exist",
|
||||
"detail": "There is no resource of type 'appInfo' with id 'non-existing'"
|
||||
}
|
||||
]
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"type": "appStoreVersions",
|
||||
"id": "APP_STORE_VERSION_1",
|
||||
"attributes": {
|
||||
"platform": "IOS",
|
||||
"versionString": "1.0",
|
||||
"appStoreState": "PREPARE_FOR_SUBMISSION",
|
||||
"storeIcon": null,
|
||||
"watchStoreIcon": null,
|
||||
"copyright": "2022 name",
|
||||
"releaseType": "SCHEDULED",
|
||||
"earliestReleaseDate": "2022-03-14T17:00:00-07:00",
|
||||
"usesIdfa": null,
|
||||
"isWatchOnly": false,
|
||||
"downloadable": true,
|
||||
"createdDate": "2022-02-21T09:26:24-08:00",
|
||||
"promotedDate": null
|
||||
},
|
||||
"relationships": {
|
||||
"ageRatingDeclaration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/ageRatingDeclaration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/ageRatingDeclaration"
|
||||
}
|
||||
},
|
||||
"appStoreVersionLocalizations": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionLocalizations",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionLocalizations"
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/build",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/build"
|
||||
}
|
||||
},
|
||||
"appStoreVersionPhasedRelease": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionPhasedRelease",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionPhasedRelease"
|
||||
}
|
||||
},
|
||||
"gameCenterConfiguration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/gameCenterConfiguration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/gameCenterConfiguration"
|
||||
}
|
||||
},
|
||||
"routingAppCoverage": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/routingAppCoverage",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/routingAppCoverage"
|
||||
}
|
||||
},
|
||||
"appStoreReviewDetail": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreReviewDetail",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreReviewDetail"
|
||||
}
|
||||
},
|
||||
"appStoreVersionSubmission": {
|
||||
"data": null,
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionSubmission",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionSubmission"
|
||||
}
|
||||
},
|
||||
"resetRatingsRequest": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/resetRatingsRequest",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/resetRatingsRequest"
|
||||
}
|
||||
},
|
||||
"idfaDeclaration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/idfaDeclaration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/idfaDeclaration"
|
||||
}
|
||||
},
|
||||
"appClipDefaultExperience": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appClipDefaultExperience",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appClipDefaultExperience"
|
||||
}
|
||||
},
|
||||
"appStoreVersionStateChanges": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionStateChanges",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionStateChanges"
|
||||
}
|
||||
},
|
||||
"appStoreVersionExperiments": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionExperiments",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionExperiments"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/apps/1611050397/appStoreVersions?include=appStoreVersionSubmission&filter%5Bplatform%5D=IOS&filter%5BappStoreState%5D=DEVELOPER_REJECTED%2CINVALID_BINARY%2CMETADATA_REJECTED%2CPREPARE_FOR_SUBMISSION%2CREJECTED%2CWAITING_FOR_REVIEW"
|
||||
},
|
||||
"meta": {
|
||||
"paging": {
|
||||
"total": 1,
|
||||
"limit": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"type": "appStoreVersions",
|
||||
"id": "APP_STORE_VERSION_1",
|
||||
"attributes": {
|
||||
"platform": "IOS",
|
||||
"versionString": "1.0",
|
||||
"appStoreState": "PREPARE_FOR_SUBMISSION",
|
||||
"storeIcon": null,
|
||||
"watchStoreIcon": null,
|
||||
"copyright": "2022 name",
|
||||
"releaseType": "SCHEDULED",
|
||||
"earliestReleaseDate": "2022-03-14T17:00:00-07:00",
|
||||
"usesIdfa": null,
|
||||
"isWatchOnly": false,
|
||||
"downloadable": true,
|
||||
"createdDate": "2022-02-21T09:26:24-08:00",
|
||||
"promotedDate": null
|
||||
},
|
||||
"relationships": {
|
||||
"ageRatingDeclaration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/ageRatingDeclaration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/ageRatingDeclaration"
|
||||
}
|
||||
},
|
||||
"appStoreVersionLocalizations": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionLocalizations",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionLocalizations"
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/build",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/build"
|
||||
}
|
||||
},
|
||||
"appStoreVersionPhasedRelease": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionPhasedRelease",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionPhasedRelease"
|
||||
}
|
||||
},
|
||||
"gameCenterConfiguration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/gameCenterConfiguration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/gameCenterConfiguration"
|
||||
}
|
||||
},
|
||||
"routingAppCoverage": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/routingAppCoverage",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/routingAppCoverage"
|
||||
}
|
||||
},
|
||||
"appStoreReviewDetail": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreReviewDetail",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreReviewDetail"
|
||||
}
|
||||
},
|
||||
"appStoreVersionSubmission": {
|
||||
"data": null,
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionSubmission",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionSubmission"
|
||||
}
|
||||
},
|
||||
"resetRatingsRequest": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/resetRatingsRequest",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/resetRatingsRequest"
|
||||
}
|
||||
},
|
||||
"idfaDeclaration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/idfaDeclaration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/idfaDeclaration"
|
||||
}
|
||||
},
|
||||
"appClipDefaultExperience": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appClipDefaultExperience",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appClipDefaultExperience"
|
||||
}
|
||||
},
|
||||
"appStoreVersionStateChanges": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionStateChanges",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionStateChanges"
|
||||
}
|
||||
},
|
||||
"appStoreVersionExperiments": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionExperiments",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionExperiments"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/apps/1611050397/appStoreVersions?include=appStoreVersionSubmission&filter%5Bplatform%5D=IOS&limit=2"
|
||||
},
|
||||
"meta": {
|
||||
"paging": {
|
||||
"total": 1,
|
||||
"limit": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"type": "appStoreVersions",
|
||||
"id": "APP_STORE_VERSION_1",
|
||||
"attributes": {
|
||||
"platform": "IOS",
|
||||
"versionString": "1.0",
|
||||
"appStoreState": "PREPARE_FOR_SUBMISSION",
|
||||
"storeIcon": null,
|
||||
"watchStoreIcon": null,
|
||||
"copyright": "2022 name",
|
||||
"releaseType": "SCHEDULED",
|
||||
"earliestReleaseDate": "2022-03-14T17:00:00-07:00",
|
||||
"usesIdfa": null,
|
||||
"isWatchOnly": false,
|
||||
"downloadable": true,
|
||||
"createdDate": "2022-02-21T09:26:24-08:00",
|
||||
"promotedDate": null
|
||||
},
|
||||
"relationships": {
|
||||
"ageRatingDeclaration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/ageRatingDeclaration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/ageRatingDeclaration"
|
||||
}
|
||||
},
|
||||
"appStoreVersionLocalizations": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionLocalizations",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionLocalizations"
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/build",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/build"
|
||||
}
|
||||
},
|
||||
"appStoreVersionPhasedRelease": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionPhasedRelease",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionPhasedRelease"
|
||||
}
|
||||
},
|
||||
"gameCenterConfiguration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/gameCenterConfiguration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/gameCenterConfiguration"
|
||||
}
|
||||
},
|
||||
"routingAppCoverage": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/routingAppCoverage",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/routingAppCoverage"
|
||||
}
|
||||
},
|
||||
"appStoreReviewDetail": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreReviewDetail",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreReviewDetail"
|
||||
}
|
||||
},
|
||||
"appStoreVersionSubmission": {
|
||||
"data": null,
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionSubmission",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionSubmission"
|
||||
}
|
||||
},
|
||||
"resetRatingsRequest": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/resetRatingsRequest",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/resetRatingsRequest"
|
||||
}
|
||||
},
|
||||
"idfaDeclaration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/idfaDeclaration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/idfaDeclaration"
|
||||
}
|
||||
},
|
||||
"appClipDefaultExperience": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appClipDefaultExperience",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appClipDefaultExperience"
|
||||
}
|
||||
},
|
||||
"appStoreVersionStateChanges": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionStateChanges",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionStateChanges"
|
||||
}
|
||||
},
|
||||
"appStoreVersionExperiments": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/relationships/appStoreVersionExperiments",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1/appStoreVersionExperiments"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP_STORE_VERSION_1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "appStoreVersions",
|
||||
"id": "APP-STORE-VERSION-2",
|
||||
"attributes": {
|
||||
"platform": "IOS",
|
||||
"versionString": "1.1",
|
||||
"appStoreState": "PREPARE_FOR_SUBMISSION",
|
||||
"storeIcon": null,
|
||||
"watchStoreIcon": null,
|
||||
"copyright": "2022 name",
|
||||
"releaseType": "SCHEDULED",
|
||||
"earliestReleaseDate": "2022-03-14T17:00:00-07:00",
|
||||
"usesIdfa": null,
|
||||
"isWatchOnly": false,
|
||||
"downloadable": true,
|
||||
"createdDate": "2022-02-21T09:26:24-08:00",
|
||||
"promotedDate": null
|
||||
},
|
||||
"relationships": {
|
||||
"ageRatingDeclaration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/ageRatingDeclaration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/ageRatingDeclaration"
|
||||
}
|
||||
},
|
||||
"appStoreVersionLocalizations": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/appStoreVersionLocalizations",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/appStoreVersionLocalizations"
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/build",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/build"
|
||||
}
|
||||
},
|
||||
"appStoreVersionPhasedRelease": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/appStoreVersionPhasedRelease",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/appStoreVersionPhasedRelease"
|
||||
}
|
||||
},
|
||||
"gameCenterConfiguration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/gameCenterConfiguration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/gameCenterConfiguration"
|
||||
}
|
||||
},
|
||||
"routingAppCoverage": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/routingAppCoverage",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/routingAppCoverage"
|
||||
}
|
||||
},
|
||||
"appStoreReviewDetail": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/appStoreReviewDetail",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/appStoreReviewDetail"
|
||||
}
|
||||
},
|
||||
"appStoreVersionSubmission": {
|
||||
"data": null,
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/appStoreVersionSubmission",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/appStoreVersionSubmission"
|
||||
}
|
||||
},
|
||||
"resetRatingsRequest": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/resetRatingsRequest",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/resetRatingsRequest"
|
||||
}
|
||||
},
|
||||
"idfaDeclaration": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/idfaDeclaration",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/idfaDeclaration"
|
||||
}
|
||||
},
|
||||
"appClipDefaultExperience": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/appClipDefaultExperience",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/appClipDefaultExperience"
|
||||
}
|
||||
},
|
||||
"appStoreVersionStateChanges": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/appStoreVersionStateChanges",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/appStoreVersionStateChanges"
|
||||
}
|
||||
},
|
||||
"appStoreVersionExperiments": {
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/relationships/appStoreVersionExperiments",
|
||||
"related": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2/appStoreVersionExperiments"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/appStoreVersions/APP-STORE-VERSION-2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "https://appstoreconnect.apple.com/iris/v1/apps/1611050397/appStoreVersions?include=appStoreVersionSubmission&filter%5Bplatform%5D=IOS&limit=2"
|
||||
},
|
||||
"meta": {
|
||||
"paging": {
|
||||
"total": 1,
|
||||
"limit": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/** A stubbed request context for the ASC API library */
|
||||
export const requestContext = {
|
||||
providerId: 1337,
|
||||
teamId: 'test-team-id',
|
||||
token: 'test-token',
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { AgeRatingDeclaration } from '@expo/apple-utils';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import Log from '../../../log';
|
||||
import { logAsync } from '../../utils/log';
|
||||
import { AppleTask, TaskDownloadOptions, TaskPrepareOptions, TaskUploadOptions } from '../task';
|
||||
|
||||
export type AgeRatingData = {
|
||||
/** The app age rating declaration for the app version */
|
||||
ageRating: AgeRatingDeclaration;
|
||||
};
|
||||
|
||||
export class AgeRatingTask extends AppleTask {
|
||||
name = (): string => 'age rating declarations';
|
||||
|
||||
async prepareAsync({ context }: TaskPrepareOptions): Promise<void> {
|
||||
assert(context.version, `App version information is not prepared, can't update age rating`);
|
||||
context.ageRating = (await context.version.getAgeRatingDeclarationAsync()) || undefined;
|
||||
}
|
||||
|
||||
async downloadAsync({ config, context }: TaskDownloadOptions): Promise<void> {
|
||||
if (context.ageRating) {
|
||||
config.setAgeRating(context.ageRating.attributes);
|
||||
}
|
||||
}
|
||||
|
||||
async uploadAsync({ config, context }: TaskUploadOptions): Promise<void> {
|
||||
assert(context.ageRating, `Age rating not initialized, can't update age rating`);
|
||||
|
||||
const ageRating = config.getAgeRating();
|
||||
if (!ageRating) {
|
||||
Log.log(chalk`{dim - Skipped age rating update, no advisory configured}`);
|
||||
} else {
|
||||
context.ageRating = await logAsync(() => context.ageRating.updateAsync(ageRating), {
|
||||
pending: 'Updating age rating declaration...',
|
||||
success: 'Updated age rating declaration',
|
||||
failure: 'Failed to update age rating declaration',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { AppInfo, AppInfoLocalization } from '@expo/apple-utils';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import Log from '../../../log';
|
||||
import { logAsync } from '../../utils/log';
|
||||
import { retryIfNullAsync } from '../../utils/retry';
|
||||
import { AppleTask, TaskDownloadOptions, TaskPrepareOptions, TaskUploadOptions } from '../task';
|
||||
|
||||
export type AppInfoData = {
|
||||
/** The current app info that should be edited */
|
||||
info: AppInfo;
|
||||
/** All info locales that are enabled */
|
||||
infoLocales: AppInfoLocalization[];
|
||||
};
|
||||
|
||||
export class AppInfoTask extends AppleTask {
|
||||
name = (): string => 'app information';
|
||||
|
||||
async prepareAsync({ context }: TaskPrepareOptions): Promise<void> {
|
||||
const info = await retryIfNullAsync(() => context.app.getEditAppInfoAsync());
|
||||
assert(info, 'Could not resolve the editable app info to update');
|
||||
|
||||
context.info = info;
|
||||
context.infoLocales = await info.getLocalizationsAsync();
|
||||
}
|
||||
|
||||
async downloadAsync({ config, context }: TaskDownloadOptions): Promise<void> {
|
||||
assert(context.info, `App info not initialized, can't download info`);
|
||||
|
||||
// TODO: see why this type mismatch occurs
|
||||
// @ts-ignore
|
||||
config.setCategories(context.info.attributes);
|
||||
|
||||
for (const locale of context.infoLocales) {
|
||||
config.setInfoLocale(locale.attributes);
|
||||
}
|
||||
}
|
||||
|
||||
async uploadAsync({ config, context }: TaskUploadOptions): Promise<void> {
|
||||
assert(context.info, `App info not initialized, can't update info`);
|
||||
|
||||
const categories = config.getCategories();
|
||||
if (!categories) {
|
||||
Log.log(chalk`{dim - Skipped app category update, not configured}`);
|
||||
} else {
|
||||
context.info = await logAsync(() => context.info.updateCategoriesAsync(categories), {
|
||||
pending: 'Updating app categories...',
|
||||
success: 'Updated app categories',
|
||||
failure: 'Failed updating app categories',
|
||||
});
|
||||
}
|
||||
|
||||
const locales = config.getLocales();
|
||||
if (locales.length <= 0) {
|
||||
Log.log(chalk`{dim - Skipped localized info update, no locales configured}`);
|
||||
} else {
|
||||
// BUG: new issue introduced in ASC 1.8.0, when creating version locales, info locales are also generated
|
||||
context.infoLocales = await logAsync(() => context.info.getLocalizationsAsync(), {
|
||||
pending: 'Reloading localized info...',
|
||||
success: 'Reloaded localized info',
|
||||
failure: 'Failed reloading localized info',
|
||||
});
|
||||
|
||||
for (const locale of locales) {
|
||||
const attributes = config.getInfoLocale(locale);
|
||||
if (!attributes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const model = context.infoLocales.find(model => model.attributes.locale === locale);
|
||||
await logAsync(
|
||||
() =>
|
||||
model
|
||||
? model.updateAsync(attributes)
|
||||
: context.info.createLocalizationAsync({ ...attributes, locale }),
|
||||
{
|
||||
pending: `${model ? 'Updating' : 'Creating'} localized info for ${locale}...`,
|
||||
success: `${model ? 'Updated' : 'Created'} localized info for ${locale}`,
|
||||
failure: `Failed ${model ? 'updating' : 'creating'} localized info for ${locale}`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
context.infoLocales = await context.info.getLocalizationsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { App, AppStoreVersion, AppStoreVersionLocalization, Platform } from '@expo/apple-utils';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import Log from '../../../log';
|
||||
import { logAsync } from '../../utils/log';
|
||||
import { retryIfNullAsync } from '../../utils/retry';
|
||||
import { AppleTask, TaskDownloadOptions, TaskPrepareOptions, TaskUploadOptions } from '../task';
|
||||
|
||||
export type AppVersionOptions = {
|
||||
/** If we should use the live version of the app (if available - defaults to false) */
|
||||
editLive: boolean;
|
||||
/** The platform to use (defaults to IOS) */
|
||||
platform: Platform;
|
||||
};
|
||||
|
||||
export type AppVersionData = {
|
||||
/** The current selected app store version to update */
|
||||
version: AppStoreVersion;
|
||||
/** If the current selected version is a live version, where not all properties are editable */
|
||||
versionIsLive: boolean;
|
||||
/** If the current selected version is the first version to be created */
|
||||
versionIsFirst: boolean;
|
||||
/** All version locales that should be, or are enabled */
|
||||
versionLocales: AppStoreVersionLocalization[];
|
||||
};
|
||||
|
||||
export class AppVersionTask extends AppleTask {
|
||||
private options: AppVersionOptions;
|
||||
|
||||
constructor(options: Partial<AppVersionOptions> = {}) {
|
||||
super();
|
||||
this.options = {
|
||||
platform: options.platform || Platform.IOS,
|
||||
editLive: options.editLive || false,
|
||||
};
|
||||
}
|
||||
|
||||
name = (): string => (this.options.editLive ? 'live app version' : 'editable app version');
|
||||
|
||||
async prepareAsync({ context }: TaskPrepareOptions): Promise<void> {
|
||||
const { version, versionIsFirst, versionIsLive } = await resolveVersionAsync(
|
||||
context.app,
|
||||
this.options
|
||||
);
|
||||
|
||||
assert(version, 'Could not resolve a live or editable app version');
|
||||
|
||||
context.version = version;
|
||||
context.versionIsFirst = versionIsFirst;
|
||||
context.versionIsLive = versionIsLive;
|
||||
context.versionLocales = await version.getLocalizationsAsync();
|
||||
}
|
||||
|
||||
async downloadAsync({ config, context }: TaskDownloadOptions): Promise<void> {
|
||||
assert(context.version, `App version not initialized, can't download version`);
|
||||
|
||||
config.setVersion(context.version.attributes);
|
||||
config.setVersionRelease(context.version.attributes);
|
||||
|
||||
for (const locale of context.versionLocales) {
|
||||
config.setVersionLocale(locale.attributes);
|
||||
}
|
||||
}
|
||||
|
||||
async uploadAsync({ config, context }: TaskUploadOptions): Promise<void> {
|
||||
assert(context.version, `App version not initialized, can't update version`);
|
||||
|
||||
const version = config.getVersion();
|
||||
const release = config.getVersionRelease();
|
||||
if (!version && !release) {
|
||||
Log.log(chalk`{dim - Skipped version and release update, not configured}`);
|
||||
} else {
|
||||
const description = [version && 'version info', release && 'release info']
|
||||
.filter(Boolean)
|
||||
.join(' and ');
|
||||
|
||||
context.version = await logAsync(
|
||||
() => context.version.updateAsync({ ...version, ...release }),
|
||||
{
|
||||
pending: `Updating ${description}...`,
|
||||
success: `Updated ${description}`,
|
||||
failure: `Failed updating ${description}`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const locales = config.getLocales();
|
||||
if (locales.length <= 0) {
|
||||
Log.log(chalk`{dim - Skipped localized version update, no locales configured}`);
|
||||
} else {
|
||||
for (const locale of locales) {
|
||||
const attributes = config.getVersionLocale(locale, context);
|
||||
if (!attributes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const oldModel = context.versionLocales.find(model => model.attributes.locale === locale);
|
||||
await logAsync(
|
||||
() =>
|
||||
oldModel
|
||||
? oldModel.updateAsync(attributes)
|
||||
: context.version.createLocalizationAsync({ ...attributes, locale }),
|
||||
{
|
||||
pending: `${oldModel ? 'Updating' : 'Creating'} localized version for ${locale}...`,
|
||||
success: `${oldModel ? 'Updated' : 'Created'} localized version for ${locale}`,
|
||||
failure: `Failed ${oldModel ? 'updating' : 'creating'} localized version for ${locale}`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
context.versionLocales = await context.version.getLocalizationsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the AppStoreVersion instance, either from live or editable version.
|
||||
* This also checks if this is the first version, which disallow release notes.
|
||||
*/
|
||||
async function resolveVersionAsync(
|
||||
app: App,
|
||||
{ editLive, platform }: AppVersionOptions
|
||||
): Promise<{
|
||||
version: AppStoreVersion | null;
|
||||
versionIsLive: boolean;
|
||||
versionIsFirst: boolean;
|
||||
}> {
|
||||
let version: AppStoreVersion | null = null;
|
||||
let versionIsLive = false;
|
||||
|
||||
if (editLive) {
|
||||
version = await app.getLiveAppStoreVersionAsync({ platform });
|
||||
versionIsLive = !!version;
|
||||
}
|
||||
|
||||
if (!version) {
|
||||
version = await retryIfNullAsync(() => app.getEditAppStoreVersionAsync({ platform }));
|
||||
}
|
||||
|
||||
const versions = await app.getAppStoreVersionsAsync({
|
||||
query: { limit: 2, filter: { platform } },
|
||||
});
|
||||
|
||||
return {
|
||||
version,
|
||||
versionIsLive,
|
||||
versionIsFirst: versions.length === 1,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { MetadataContext } from '../../context';
|
||||
import { AppleTask } from '../task';
|
||||
import { AgeRatingTask } from './age-rating';
|
||||
import { AppInfoTask } from './app-info';
|
||||
import { AppVersionTask } from './app-version';
|
||||
|
||||
/**
|
||||
* List of all eligible tasks to sync local store configuration to the App store.
|
||||
*/
|
||||
export function createAppleTasks(_ctx: MetadataContext): AppleTask[] {
|
||||
return [new AppVersionTask(), new AppInfoTask(), new AgeRatingTask()];
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { AgeRatingDeclarationProps, AppCategoryId, AppSubcategoryId } from '@expo/apple-utils';
|
||||
|
||||
export type AppleLocale = string;
|
||||
|
||||
export interface AppleMetadata {
|
||||
copyright?: string;
|
||||
info?: Record<AppleLocale, AppleInfo>;
|
||||
categories?: AppCategoryId[] | AppleCategory;
|
||||
release?: AppleRelease;
|
||||
advisory?: AppleAdvisory;
|
||||
preview?: Record<string, string[]>;
|
||||
review?: AppleReview;
|
||||
}
|
||||
|
||||
export type AppleAdvisory = Partial<AgeRatingDeclarationProps>;
|
||||
|
||||
export interface AppleCategory {
|
||||
category: AppCategoryId;
|
||||
subcategory?: AppSubcategoryId[];
|
||||
}
|
||||
|
||||
export interface AppleRelease {
|
||||
isPhasedReleaseEnabled?: boolean;
|
||||
shouldResetRatings?: boolean;
|
||||
autoReleaseDate?: number | string;
|
||||
automaticRelease?: boolean;
|
||||
usesThirdPartyContent?: boolean;
|
||||
/** Alternative to setting `ITSAppUsesNonExemptEncryption` in the binary's `Info.plist`. */
|
||||
usesNonExemptEncryption?: boolean;
|
||||
}
|
||||
|
||||
export interface AppleInfo {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
/** Does not effect ASO https://developer.apple.com/app-store/product-page/ */
|
||||
promoText?: string;
|
||||
description?: string;
|
||||
keywords?: string[];
|
||||
releaseNotes?: string;
|
||||
marketingUrl?: string;
|
||||
privacyPolicyUrl?: string;
|
||||
privacyPolicyText?: string;
|
||||
privacyChoicesUrl?: string;
|
||||
supportUrl?: string;
|
||||
}
|
||||
|
||||
export interface AppleReview {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
demoUsername?: string;
|
||||
demoPassword?: string;
|
||||
notes?: string;
|
||||
attachment?: string;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import Ajv from 'ajv';
|
||||
import assert from 'assert';
|
||||
|
||||
import { AppleConfigReader } from './apple/config/reader';
|
||||
import { AppleConfigWriter } from './apple/config/writer';
|
||||
import { Metadata } from './types';
|
||||
|
||||
/**
|
||||
* Run the JSON Schema validation to normalize defaults and flag early config errors.
|
||||
* This includes validating the known store limitations for every configurable property.
|
||||
*/
|
||||
export function validateConfig(config: unknown): {
|
||||
valid: boolean;
|
||||
errors: Ajv.ErrorObject[];
|
||||
} {
|
||||
const validator = new Ajv({ allErrors: true, useDefaults: true })
|
||||
.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'))
|
||||
.compile(require('../../schema/metadata-0.json'));
|
||||
|
||||
const valid = validator(config) as boolean;
|
||||
|
||||
return { valid, errors: validator.errors || [] };
|
||||
}
|
||||
|
||||
/** Create a versioned deserializer to fetch App Store data from the store configuration. */
|
||||
export function createAppleReader(config: Metadata): AppleConfigReader {
|
||||
assert(config.configVersion === 0, 'Unsupported store configuration version');
|
||||
assert(config.apple, 'No apple configuration found');
|
||||
return new AppleConfigReader(config.apple);
|
||||
}
|
||||
|
||||
/** Create the serializer to write the App Store to the store configuration. */
|
||||
export function createAppleWriter(): AppleConfigWriter {
|
||||
return new AppleConfigWriter();
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { App, Session } from '@expo/apple-utils';
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { IosSubmitProfile } from '@expo/eas-json/build/submit/types';
|
||||
import assert from 'assert';
|
||||
|
||||
import { CredentialsContext } from '../credentials/context';
|
||||
import { getRequestContext } from '../credentials/ios/appstore/authenticate';
|
||||
import { getExpoConfig } from '../project/expoConfig';
|
||||
import { getBundleIdentifierAsync } from '../project/ios/bundleIdentifier';
|
||||
import { Actor } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { getProfilesAsync } from '../utils/profiles';
|
||||
|
||||
export type MetadataContext = {
|
||||
/** Submission profile platform to use */
|
||||
platform: Platform.IOS;
|
||||
/** Submission profile name to use */
|
||||
profile: IosSubmitProfile;
|
||||
/** Configured store configuration file name (defaults to store.config.json) */
|
||||
metadataFile: string;
|
||||
/** Authenticated Expo account */
|
||||
user: Actor;
|
||||
/** The store credentials manager */
|
||||
credentialsCtx: CredentialsContext;
|
||||
/** The app bundle identifier to use for the stores */
|
||||
bundleIdentifier: string;
|
||||
/** Root of the Expo project directory */
|
||||
projectDir: string;
|
||||
/** Resolved Expo app configuration */
|
||||
exp: ExpoConfig;
|
||||
|
||||
/**
|
||||
* If we should run in non-interactive mode, as much as we can.
|
||||
* The store authentication requires a fully configured ASC environment, or interactive mode.
|
||||
*/
|
||||
nonInteractive: boolean;
|
||||
};
|
||||
|
||||
export type MetadataAppStoreAuthentication = {
|
||||
/** The root entity of the App store */
|
||||
app: App;
|
||||
/** The authentication state we used to fetch the root entity */
|
||||
auth: Partial<Session.AuthState>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Metadata uses the submission profile to find the configured metadata filename.
|
||||
* Note, only iOS is supported for metadata right now.
|
||||
*/
|
||||
export async function createMetadataContextAsync(params: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
profileName?: string;
|
||||
nonInteractive?: boolean;
|
||||
}): Promise<MetadataContext> {
|
||||
const submissionProfiles = await getProfilesAsync({
|
||||
type: 'submit',
|
||||
platforms: [Platform.IOS],
|
||||
projectDir: params.projectDir,
|
||||
profileName: params.profileName,
|
||||
});
|
||||
|
||||
const submissionProfile = submissionProfiles.find(profile => profile.platform === Platform.IOS);
|
||||
assert(
|
||||
submissionProfile,
|
||||
'Could not resolve the iOS submission profile, only iOS is supported for metadata'
|
||||
);
|
||||
const iosSubmissionProfile = submissionProfile.profile as IosSubmitProfile;
|
||||
|
||||
const exp = params.exp ?? getExpoConfig(params.projectDir);
|
||||
const user = await ensureLoggedInAsync({ nonInteractive: params.nonInteractive });
|
||||
const bundleIdentifier = await getBundleIdentifierAsync(params.projectDir, exp);
|
||||
|
||||
const credentialsCtx = new CredentialsContext({
|
||||
user,
|
||||
exp: params.exp,
|
||||
nonInteractive: params.nonInteractive,
|
||||
projectDir: params.projectDir,
|
||||
});
|
||||
|
||||
return {
|
||||
platform: Platform.IOS,
|
||||
profile: iosSubmissionProfile,
|
||||
metadataFile: iosSubmissionProfile.meta ?? 'store.config.json',
|
||||
user,
|
||||
credentialsCtx,
|
||||
bundleIdentifier,
|
||||
projectDir: params.projectDir,
|
||||
exp: params.exp,
|
||||
nonInteractive: params.nonInteractive ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* To start syncing ASC entities, we need access to the apple utils App instance.
|
||||
* This ensures we are authenticated and have the app instance ready to use.
|
||||
*/
|
||||
export async function ensureMetadataAppStoreAuthenticatedAsync({
|
||||
credentialsCtx,
|
||||
bundleIdentifier,
|
||||
}: MetadataContext): Promise<MetadataAppStoreAuthentication> {
|
||||
const authCtx = await credentialsCtx.appStore.ensureAuthenticatedAsync();
|
||||
assert(authCtx.authState, 'Failed to authenticate with App Store Connect');
|
||||
|
||||
// TODO: improve error handling by mentioning possible configuration errors
|
||||
const app = await App.findAsync(getRequestContext(authCtx), { bundleId: bundleIdentifier });
|
||||
assert(app, `Failed to load app "${bundleIdentifier}" from App Store Connect`);
|
||||
|
||||
return { app, auth: authCtx.authState };
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import { MetadataEvent } from '../analytics/events';
|
||||
import { confirmAsync } from '../prompts';
|
||||
import { AppleData } from './apple/data';
|
||||
import { createAppleTasks } from './apple/tasks';
|
||||
import { createAppleWriter } from './config';
|
||||
import { MetadataContext, ensureMetadataAppStoreAuthenticatedAsync } from './context';
|
||||
import { MetadataDownloadError, MetadataValidationError } from './errors';
|
||||
import { subscribeTelemetry } from './utils/telemetry';
|
||||
|
||||
/**
|
||||
* Generate a local store configuration from the stores.
|
||||
* Note, only App Store is supported at this time.
|
||||
*/
|
||||
export async function downloadMetadataAsync(metadataContext: MetadataContext): Promise<string> {
|
||||
const filePath = path.resolve(metadataContext.projectDir, metadataContext.metadataFile);
|
||||
const fileExists = await fs.pathExists(filePath);
|
||||
|
||||
if (fileExists && metadataContext.nonInteractive) {
|
||||
throw new MetadataValidationError(`Store configuration already exists at "${filePath}"`);
|
||||
} else if (fileExists) {
|
||||
const overwrite = await confirmAsync({
|
||||
message: `Do you want to overwrite the existing store configuration "${metadataContext.metadataFile}"?`,
|
||||
});
|
||||
if (!overwrite) {
|
||||
throw new MetadataValidationError(`Store configuration already exists at "${filePath}"`);
|
||||
}
|
||||
}
|
||||
|
||||
const { app, auth } = await ensureMetadataAppStoreAuthenticatedAsync(metadataContext);
|
||||
const { unsubscribeTelemetry, executionId } = subscribeTelemetry(
|
||||
MetadataEvent.APPLE_METADATA_DOWNLOAD,
|
||||
{ app, auth }
|
||||
);
|
||||
|
||||
const errors: Error[] = [];
|
||||
const config = createAppleWriter();
|
||||
const tasks = createAppleTasks(metadataContext);
|
||||
const taskCtx = { app };
|
||||
|
||||
for (const task of tasks) {
|
||||
try {
|
||||
await task.prepareAsync({ context: taskCtx });
|
||||
} catch (error: any) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
for (const task of tasks) {
|
||||
try {
|
||||
await task.downloadAsync({ config, context: taskCtx as AppleData });
|
||||
} catch (error: any) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeJson(filePath, config.toSchema(), { spaces: 2 });
|
||||
unsubscribeTelemetry();
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new MetadataDownloadError(errors, executionId);
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { ErrorObject } from 'ajv';
|
||||
|
||||
import Log, { link } from '../log';
|
||||
|
||||
/**
|
||||
* Before syncing data to the ASC API, we need to validate the metadata config.
|
||||
* This error represents unrecoverable issues before syncing that data,
|
||||
* and should contain useful information for the user to solve before trying again.
|
||||
*/
|
||||
export class MetadataValidationError extends Error {
|
||||
constructor(message?: string, public readonly errors?: ErrorObject[]) {
|
||||
super(message ?? 'Store configuration validation failed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a single entity failed to update, we don't block the other entities from uploading.
|
||||
* We still attempt to update the data in the stores as much as possible.
|
||||
* Because of that, we keep track of any errors encountered and throw this generic error.
|
||||
* It contains that list of encountered errors to present to the user.
|
||||
*/
|
||||
export class MetadataUploadError extends Error {
|
||||
constructor(public readonly errors: Error[], public readonly executionId: string) {
|
||||
super(
|
||||
`Store configuration upload encountered ${
|
||||
errors.length === 1 ? 'an error' : `${errors.length} errors`
|
||||
}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a single entity failed to download, we don't block the other entities from downloading.
|
||||
* We sill attempt to pull in the data from the stores as much as possible.
|
||||
* Because of that, we keep track of any errors envountered and throw this generic error.
|
||||
* It contains that list of encountered errors to present to the user.
|
||||
*/
|
||||
export class MetadataDownloadError extends Error {
|
||||
constructor(public readonly errors: Error[], public readonly executionId: string) {
|
||||
super(
|
||||
`Store configuration download encountered ${
|
||||
errors.length === 1 ? 'an error' : `${errors.length} errors`
|
||||
}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a thrown metadata error by informing the user what went wrong.
|
||||
* If a normal error is thrown, this method will re-throw that error to avoid consuming it.
|
||||
*/
|
||||
export function handleMetadataError(error: Error): void {
|
||||
if (error instanceof MetadataValidationError) {
|
||||
Log.error(error.message);
|
||||
Log.log(error.errors?.map(err => ` - ${err.dataPath} ${err.message}`).join('\n'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (error instanceof MetadataDownloadError || error instanceof MetadataUploadError) {
|
||||
Log.error(error.message);
|
||||
Log.log('Please check the logs for any configuration issues.');
|
||||
Log.log('If this issue persists, please open a new issue at:');
|
||||
// TODO: add execution ID to the issue template link
|
||||
Log.log(link('https://github.com/expo/eas-cli'));
|
||||
return;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { AppleMetadata } from './apple/types';
|
||||
|
||||
export interface Metadata {
|
||||
/** The store configuration version */
|
||||
configVersion: number;
|
||||
/** All App Store related configuration */
|
||||
apple?: AppleMetadata;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import { MetadataEvent } from '../analytics/events';
|
||||
import { AppleData } from './apple/data';
|
||||
import { createAppleTasks } from './apple/tasks';
|
||||
import { createAppleReader, validateConfig } from './config';
|
||||
import { MetadataContext, ensureMetadataAppStoreAuthenticatedAsync } from './context';
|
||||
import { MetadataUploadError, MetadataValidationError } from './errors';
|
||||
import { subscribeTelemetry } from './utils/telemetry';
|
||||
|
||||
/**
|
||||
* Sync a local store configuration with the stores.
|
||||
* Note, only App Store is supported at this time.
|
||||
*/
|
||||
export async function uploadMetadataAsync(metadataContext: MetadataContext): Promise<void> {
|
||||
const filePath = path.resolve(metadataContext.projectDir, metadataContext.metadataFile);
|
||||
if (!(await fs.pathExists(filePath))) {
|
||||
throw new MetadataValidationError(`Store configuration file not found "${filePath}"`);
|
||||
}
|
||||
|
||||
const { app, auth } = await ensureMetadataAppStoreAuthenticatedAsync(metadataContext);
|
||||
const { unsubscribeTelemetry, executionId } = subscribeTelemetry(
|
||||
MetadataEvent.APPLE_METADATA_UPLOAD,
|
||||
{ app, auth }
|
||||
);
|
||||
|
||||
const fileData = await fs.readJson(filePath);
|
||||
const { valid, errors: validationErrors } = validateConfig(fileData);
|
||||
if (!valid) {
|
||||
throw new MetadataValidationError(`Store configuration errors found`, validationErrors);
|
||||
}
|
||||
|
||||
const errors: Error[] = [];
|
||||
const config = createAppleReader(fileData);
|
||||
const tasks = createAppleTasks(metadataContext);
|
||||
const taskCtx = { app };
|
||||
|
||||
for (const task of tasks) {
|
||||
try {
|
||||
await task.prepareAsync({ context: taskCtx });
|
||||
} catch (error: any) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
for (const task of tasks) {
|
||||
try {
|
||||
await task.uploadAsync({ config, context: taskCtx as AppleData });
|
||||
} catch (error: any) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
unsubscribeTelemetry();
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new MetadataUploadError(errors, executionId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { App } from '@expo/apple-utils';
|
||||
|
||||
import { TelemetryContext, makeDataScrubber } from '../telemetry';
|
||||
|
||||
describe(makeDataScrubber, () => {
|
||||
const stub: TelemetryContext = {
|
||||
// Only the App ID is considered to be sensitive
|
||||
app: new App({} as any, 'SECRET_APP_ID', {} as any),
|
||||
// Only the token properties and user credentials are considered to be sensitive
|
||||
auth: {
|
||||
username: 'SECRET_USERNAME',
|
||||
password: 'SECRET_PASSWORD',
|
||||
context: {
|
||||
token: 'SECRET_TOKEN',
|
||||
teamId: 'SECRET_TEAM_ID',
|
||||
providerId: 1337,
|
||||
},
|
||||
} as any,
|
||||
};
|
||||
|
||||
it('scrubs the app.id', () => {
|
||||
expect(makeDataScrubber(stub)('some text SECRET_APP_ID')).toBe('some text {APPLE_APP_ID}');
|
||||
});
|
||||
|
||||
it('scrubs the auth.username', () => {
|
||||
expect(makeDataScrubber(stub)('some text SECRET_USERNAME')).toBe('some text {APPLE_USERNAME}');
|
||||
});
|
||||
|
||||
it('scrubs the auth.password', () => {
|
||||
expect(makeDataScrubber(stub)('SECRET_PASSWORD')).toBe('{APPLE_PASSWORD}');
|
||||
});
|
||||
|
||||
it('scrubs the auth.context.token', () => {
|
||||
expect(makeDataScrubber(stub)('some text SECRET_TOKEN')).toBe('some text {APPLE_TOKEN}');
|
||||
});
|
||||
|
||||
it('scrubs the auth.context.token when using token instances', () => {
|
||||
const token = { getToken: () => 'SECRET_TOKEN_INSTANCE' } as any;
|
||||
const scrubber = makeDataScrubber({
|
||||
...stub,
|
||||
auth: {
|
||||
...stub.auth,
|
||||
context: {
|
||||
...stub.auth.context,
|
||||
token,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(scrubber('some text SECRET_TOKEN_INSTANCE')).toBe('some text {APPLE_TOKEN}');
|
||||
});
|
||||
|
||||
it('scrubs the auth.context.teamId', () => {
|
||||
expect(makeDataScrubber(stub)('SECRET_TEAM_ID')).toBe('{APPLE_TEAM_ID}');
|
||||
});
|
||||
|
||||
it('scrubs the auth.context.teamId', () => {
|
||||
expect(makeDataScrubber(stub)('some provider 1337')).toBe('some provider {APPLE_PROVIDER_ID}');
|
||||
});
|
||||
|
||||
it('scrubber returns string representation of falsy values', () => {
|
||||
const scrubber = makeDataScrubber(stub);
|
||||
expect(scrubber(null)).toBe('null');
|
||||
expect(scrubber(undefined)).toBe('undefined');
|
||||
expect(scrubber(false)).toBe('false');
|
||||
});
|
||||
|
||||
it('scrubs multiple sensitive values at once', () => {
|
||||
expect(makeDataScrubber(stub)('SECRET_TOKEN SECRET_USERNAME SECRET_PASSWORD')).toBe(
|
||||
'{APPLE_TOKEN} {APPLE_USERNAME} {APPLE_PASSWORD}'
|
||||
);
|
||||
});
|
||||
|
||||
it('scrubs json and transforms it to string', () => {
|
||||
const scrubber = makeDataScrubber(stub);
|
||||
expect(scrubber({ foo: 'bar' })).toBe('{"foo":"bar"}');
|
||||
expect(scrubber({ value: 'SECRET_APP_ID' })).toBe('{"value":"{APPLE_APP_ID}"}');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
export function unique<T = any>(items: T[]): T[] {
|
||||
const set = new Set(items);
|
||||
return [...set];
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { ConnectModel } from '@expo/apple-utils';
|
||||
|
||||
/** Get the properties of a single App Store Connect entity */
|
||||
export type AttributesOf<T extends ConnectModel> = T['attributes'];
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Remove time precision from a date to avoid potential errors with the App Store.
|
||||
*
|
||||
* "status": "409",
|
||||
* "code": "ENTITY_ERROR.ATTRIBUTE.INVALID",
|
||||
* "title": "An attribute value is invalid.",
|
||||
* "detail": "The attribute 'earliestReleaseDate' only allows hour precision",
|
||||
* "source": {
|
||||
* "pointer": "/data/attributes/earliestReleaseDate"
|
||||
* }
|
||||
*/
|
||||
export function removeDatePrecision(date: any): null | Date {
|
||||
if (date) {
|
||||
try {
|
||||
const result = new Date(date);
|
||||
result.setMinutes(0);
|
||||
result.setSeconds(0);
|
||||
result.setMilliseconds(0);
|
||||
return result;
|
||||
} catch {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Ora, ora } from '../../ora';
|
||||
|
||||
type LogAsyncOptions = {
|
||||
/** If the spinner representing the async action should be hidden, e.g. for JSON output */
|
||||
hidden?: boolean;
|
||||
/** The message to display when the action is pending */
|
||||
pending: string;
|
||||
/** The message to display when the action succeeded */
|
||||
success: string;
|
||||
/** The message to display when the action failed */
|
||||
failure: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Log an asynchronous action using a spinner.
|
||||
*/
|
||||
export async function logAsync<T>(
|
||||
action: (spinner?: Ora) => Promise<T>,
|
||||
{ hidden, ...message }: LogAsyncOptions
|
||||
): Promise<T> {
|
||||
if (hidden) {
|
||||
return action();
|
||||
}
|
||||
|
||||
const spinner = ora(message.pending).start();
|
||||
try {
|
||||
const result = await action(spinner);
|
||||
spinner.succeed(message.success);
|
||||
return result;
|
||||
} catch (error) {
|
||||
spinner.fail(message.failure);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export async function waitAsync(duration: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, duration));
|
||||
}
|
||||
|
||||
type WithRetryOptions = {
|
||||
tries?: number;
|
||||
delay?: number;
|
||||
onRetry?: (triesLeft: number) => void;
|
||||
};
|
||||
|
||||
export async function retryIfNullAsync<T>(
|
||||
method: () => Promise<T | null>,
|
||||
options: WithRetryOptions = {}
|
||||
): Promise<T | null> {
|
||||
let { tries = 5, delay = 1000 } = options;
|
||||
|
||||
while (tries > 0) {
|
||||
const value = await method();
|
||||
if (value !== null) {
|
||||
return value;
|
||||
}
|
||||
tries--;
|
||||
options.onRetry?.(tries);
|
||||
await waitAsync(delay);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { App, Session, getRequestClient } from '@expo/apple-utils';
|
||||
import type { AxiosError } from 'axios';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { Analytics, MetadataEvent } from '../../analytics/events';
|
||||
|
||||
export type TelemetryContext = {
|
||||
app: App;
|
||||
auth: Partial<Session.AuthState>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Subscribe the telemetry to the ongoing metadata requests and responses.
|
||||
* When providing the app and auth info, we can scrub that data from the telemetry.
|
||||
* Returns an execution ID to group all events of a single run together, and a unsubscribe function.
|
||||
*/
|
||||
export function subscribeTelemetry(
|
||||
event: MetadataEvent,
|
||||
options: TelemetryContext
|
||||
): {
|
||||
/** Unsubscribe the telemetry from all apple-utils events */
|
||||
unsubscribeTelemetry: () => void;
|
||||
/** The unique id added to all telemetry events from a single execution */
|
||||
executionId: string;
|
||||
} {
|
||||
const executionId = uuidv4();
|
||||
const scrubber = makeDataScrubber(options);
|
||||
const { interceptors } = getRequestClient();
|
||||
|
||||
const responseInterceptorId = interceptors.response.use(
|
||||
response => {
|
||||
Analytics.logEvent(event, {
|
||||
executionId,
|
||||
type: 'response',
|
||||
phase: 'resolved',
|
||||
method: response.request.method.toUpperCase(),
|
||||
url: scrubber(response.request.path),
|
||||
status: String(response.status),
|
||||
statusText: scrubber(response.statusText),
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
Analytics.logEvent(event, {
|
||||
executionId,
|
||||
type: 'response',
|
||||
phase: 'rejected',
|
||||
method: error.request.method.toUpperCase(),
|
||||
url: scrubber(error.config.url),
|
||||
error: scrubber(error.message),
|
||||
status: String(error.response?.status),
|
||||
statusText: scrubber(error.response?.statusText),
|
||||
input: scrubber(error.config.data),
|
||||
output: scrubber(error.response?.data),
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
|
||||
function unsubscribeTelemetry(): void {
|
||||
interceptors.response.eject(responseInterceptorId);
|
||||
}
|
||||
|
||||
return { unsubscribeTelemetry, executionId };
|
||||
}
|
||||
|
||||
/** Exposed for testing */
|
||||
export function makeDataScrubber({ app, auth }: TelemetryContext): <T>(data: T) => string {
|
||||
const token = getAuthTokenString(auth);
|
||||
const patterns: Record<string, RegExp | null> = {
|
||||
APPLE_APP_ID: new RegExp(app.id, 'gi'),
|
||||
APPLE_USERNAME: auth.username ? new RegExp(auth.username, 'gi') : null,
|
||||
APPLE_PASSWORD: auth.password ? new RegExp(auth.password, 'gi') : null,
|
||||
APPLE_TOKEN: token ? new RegExp(token, 'gi') : null,
|
||||
APPLE_TEAM_ID: auth.context?.teamId ? new RegExp(auth.context.teamId, 'gi') : null,
|
||||
APPLE_PROVIDER_ID: auth.context?.providerId
|
||||
? new RegExp(String(auth.context.providerId), 'gi')
|
||||
: null,
|
||||
};
|
||||
|
||||
const iterator = Object.entries(patterns);
|
||||
|
||||
return function scrubber(data) {
|
||||
if (!data) {
|
||||
return String(data);
|
||||
}
|
||||
|
||||
let value = typeof data === 'object' ? JSON.stringify(data) : String(data);
|
||||
for (const [replacement, pattern] of iterator) {
|
||||
if (pattern) {
|
||||
value = value.replace(pattern, `{${replacement}}`);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
function getAuthTokenString(auth: TelemetryContext['auth']): string | null {
|
||||
if (!auth.context?.token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof auth.context.token === 'object') {
|
||||
return auth.context.token.getToken();
|
||||
}
|
||||
|
||||
return auth.context.token;
|
||||
}
|
||||
@@ -412,9 +412,9 @@ describe(uploadAssetsAsync, () => {
|
||||
return { specifications: ['{}', '{}', '{}'] };
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
// beforeEach(() => {
|
||||
// jest.useFakeTimers();
|
||||
// });
|
||||
it('throws an error if the upload exceeds TIMEOUT_LIMIT', async () => {
|
||||
jest.spyOn(PublishQuery, 'getAssetMetadataAsync').mockImplementation(async () => {
|
||||
const status = AssetMetadataStatus.DoesNotExist;
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function ensureApplicationIdIsDefinedForManagedProjectAsync(
|
||||
return await getApplicationIdAsync(projectDir, exp, {
|
||||
moduleName: gradleUtils.DEFAULT_MODULE_NAME,
|
||||
});
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return await configureApplicationIdAsync(projectDir, exp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function ensureBundleIdentifierIsDefinedForManagedProjectAsync(
|
||||
|
||||
try {
|
||||
return await getBundleIdentifierAsync(projectDir, exp);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return await configureBundleIdentifierAsync(projectDir, exp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ function validateUrl(url: string): boolean {
|
||||
? protocols.map(x => `${x.toLowerCase()}:`).includes(parsed.protocol)
|
||||
: false
|
||||
: true;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export async function isExistingFileAsync(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
const stats = await fs.stat(filePath);
|
||||
return stats.isFile();
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ function parseLogs(logs: string): LogLine[] {
|
||||
let parsedLine;
|
||||
try {
|
||||
parsedLine = JSON.parse(line);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
let level: LogLine['level'];
|
||||
|
||||
@@ -35,7 +35,7 @@ export async function readReleaseChannelSafelyAsync(projectDir: string): Promise
|
||||
androidManifest,
|
||||
AndroidConfig.Updates.Config.RELEASE_CHANNEL
|
||||
);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ export async function readChannelSafelyAsync(projectDir: string): Promise<string
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(stringifiedRequestHeaders)['expo-channel-name'] ?? null;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function readReleaseChannelSafelyAsync(projectDir: string): Promise
|
||||
try {
|
||||
const expoPlist = await readExpoPlistAsync(projectDir);
|
||||
return expoPlist[IOSConfig.Updates.Config.RELEASE_CHANNEL] ?? null;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export async function readChannelSafelyAsync(projectDir: string): Promise<string
|
||||
const expoPlist = await readExpoPlistAsync(projectDir);
|
||||
const updatesRequestHeaders = expoPlist['EXUpdatesRequestHeaders'];
|
||||
return updatesRequestHeaders['expo-channel-name'] ?? null;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export async function ensureLoggedInAsync({ nonInteractive = false } = {}): Prom
|
||||
let user: Actor | undefined;
|
||||
try {
|
||||
user = await getUserAsync();
|
||||
} catch (_) {}
|
||||
} catch {}
|
||||
|
||||
if (!user) {
|
||||
Log.warn('An Expo user account is required to proceed.');
|
||||
|
||||
@@ -143,7 +143,7 @@ export default class GitClient extends Client {
|
||||
public async getCommitHashAsync(): Promise<string | undefined> {
|
||||
try {
|
||||
return (await spawnAsync('git', ['rev-parse', 'HEAD'])).stdout.trim();
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -155,7 +155,7 @@ export default class GitClient extends Client {
|
||||
public async getBranchNameAsync(): Promise<string | null> {
|
||||
try {
|
||||
return (await spawnAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'])).stdout.trim();
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -163,7 +163,7 @@ export default class GitClient extends Client {
|
||||
public async getLastCommitMessageAsync(): Promise<string | null> {
|
||||
try {
|
||||
return (await spawnAsync('git', ['--no-pager', 'log', '-1', '--pretty=%B'])).stdout.trim();
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -187,7 +187,7 @@ export default class GitClient extends Client {
|
||||
try {
|
||||
await spawnAsync('git', ['check-ignore', '-q', filePath]);
|
||||
return true;
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -284,7 +284,7 @@ export async function isGitCaseSensitiveAsync(): Promise<boolean | undefined> {
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export async function doesGitRepoExistAsync(): Promise<boolean> {
|
||||
try {
|
||||
await spawnAsync('git', ['rev-parse', '--git-dir']);
|
||||
return true;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export function validateURL(url: string): boolean {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedParameters":true,
|
||||
"noUnusedLocals":true,
|
||||
"strict": true,
|
||||
"target": "ES2019",
|
||||
"outDir": "build",
|
||||
|
||||
@@ -5,23 +5,23 @@
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/eas-build-job": "0.2.71",
|
||||
"@expo/json-file": "8.2.34",
|
||||
"@expo/eas-build-job": "0.2.72",
|
||||
"@expo/json-file": "8.2.36",
|
||||
"chalk": "4.1.2",
|
||||
"env-string": "1.0.1",
|
||||
"fs-extra": "10.0.1",
|
||||
"fs-extra": "10.1.0",
|
||||
"joi": "17.6.0",
|
||||
"log-symbols": "4.1.0",
|
||||
"semver": "7.3.5",
|
||||
"tslib": "2.3.1"
|
||||
"semver": "7.3.7",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"memfs": "3.4.1",
|
||||
"typescript": "4.6.3"
|
||||
"memfs": "3.4.3",
|
||||
"typescript": "4.6.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"homepage": "https://github.com/expo/eas-cli",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -26,6 +26,7 @@ export const IosSubmitProfileSchema = Joi.object({
|
||||
companyName: Joi.string(),
|
||||
appName: Joi.string(),
|
||||
bundleIdentifier: Joi.string(),
|
||||
meta: Joi.string(),
|
||||
});
|
||||
|
||||
export const SubmitProfileSchema = Joi.object({
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface IosSubmitProfile {
|
||||
companyName?: string;
|
||||
appName?: string;
|
||||
bundleIdentifier?: string;
|
||||
meta?: string;
|
||||
}
|
||||
|
||||
export const IosSubmitProfileFieldsToEvaluate: (keyof IosSubmitProfile)[] = [
|
||||
|
||||
@@ -10,24 +10,24 @@
|
||||
"author": "Expo <[email protected]>",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"dateformat": "4.5.1",
|
||||
"fs-extra": "10.0.0",
|
||||
"fs-extra": "10.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"marked": "3.0.4",
|
||||
"nullthrows": "1.1.1",
|
||||
"semver": "7.3.5",
|
||||
"tslib": "2.3.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.175",
|
||||
"@types/node": "16.7.8",
|
||||
"@types/semver": "7.3.8",
|
||||
"ts-node": "10.2.1",
|
||||
"typescript": "4.5.2"
|
||||
"@types/lodash": "4.14.182",
|
||||
"@types/node": "17.0.32",
|
||||
"@types/semver": "7.3.9",
|
||||
"ts-node": "10.7.0",
|
||||
"typescript": "4.6.4"
|
||||
}
|
||||
}
|
||||
Generated
+43
-37
@@ -7,10 +7,10 @@
|
||||
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/source-map-support@0.6.1":
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz#118511f316e2e87ee4294761868e254d3da47960"
|
||||
integrity sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==
|
||||
"@cspotcode/source-map-support@0.7.0":
|
||||
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==
|
||||
dependencies:
|
||||
"@cspotcode/source-map-consumer" "0.8.0"
|
||||
|
||||
@@ -46,25 +46,25 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/[email protected]75":
|
||||
version "4.14.175"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.175.tgz#b78dfa959192b01fae0ad90e166478769b215f45"
|
||||
integrity sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==
|
||||
"@types/[email protected]82":
|
||||
version "4.14.182"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
|
||||
integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
|
||||
|
||||
"@types/node@*":
|
||||
version "16.10.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.3.tgz#7a8f2838603ea314d1d22bb3171d899e15c57bd5"
|
||||
integrity sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==
|
||||
|
||||
"@types/node@16.7.8":
|
||||
version "16.7.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.8.tgz#2448be5f24fe6b77114632b6350fcd219334651e"
|
||||
integrity sha512-8upnoQU0OPzbIkm+ZMM0zCeFCkw2s3mS0IWdx0+AAaWqm4fkBb0UJp8Edl7FVKRamYbpJC/aVsHpKWBIbiC7Zg==
|
||||
"@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/[email protected].8":
|
||||
version "7.3.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.8.tgz#508a27995498d7586dcecd77c25e289bfaf90c59"
|
||||
integrity sha512-D/2EJvAlCEtYFEYmmlGwbGXuK886HzyCc3nZX/tkFTQdEU8jZDAgiv08P162yB17y4ZXZoq7yFAnW4GDBb9Now==
|
||||
"@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==
|
||||
|
||||
acorn-walk@^8.1.1:
|
||||
version "8.2.0"
|
||||
@@ -96,10 +96,10 @@ diff@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
fs-extra@10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1"
|
||||
integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==
|
||||
fs-extra@10.1.0:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
||||
integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^6.0.1"
|
||||
@@ -146,19 +146,19 @@ [email protected]:
|
||||
resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1"
|
||||
integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==
|
||||
|
||||
[email protected].5:
|
||||
version "7.3.5"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
|
||||
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
|
||||
[email protected].7:
|
||||
version "7.3.7"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
|
||||
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
ts-node@10.2.1:
|
||||
version "10.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.2.1.tgz#4cc93bea0a7aba2179497e65bb08ddfc198b3ab5"
|
||||
integrity sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==
|
||||
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==
|
||||
dependencies:
|
||||
"@cspotcode/source-map-support" "0.6.1"
|
||||
"@cspotcode/source-map-support" "0.7.0"
|
||||
"@tsconfig/node10" "^1.0.7"
|
||||
"@tsconfig/node12" "^1.0.7"
|
||||
"@tsconfig/node14" "^1.0.0"
|
||||
@@ -169,23 +169,29 @@ [email protected]:
|
||||
create-require "^1.1.0"
|
||||
diff "^4.0.1"
|
||||
make-error "^1.1.1"
|
||||
v8-compile-cache-lib "^3.0.0"
|
||||
yn "3.1.1"
|
||||
|
||||
tslib@2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
||||
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
||||
tslib@2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
||||
|
||||
typescript@4.5.2:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998"
|
||||
integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==
|
||||
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==
|
||||
|
||||
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:
|
||||
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==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
|
||||
Reference in new issue
Block a user