mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
175fc2b30e | ||
|
|
aec9347d69 | ||
|
|
91500d07c0 | ||
|
|
62ca14b504 | ||
|
|
9158a95d57 | ||
|
|
d0f3e387e7 | ||
|
|
158856f8ec | ||
|
|
1e59957753 | ||
|
|
f97d46040c | ||
|
|
d7485cb5d2 | ||
|
|
071fdb7f4a | ||
|
|
6dd96ffb20 | ||
|
|
f56fb0f7de | ||
|
|
7d28f6add9 | ||
|
|
eebef3e461 | ||
|
|
8336f630ea | ||
|
|
7d6bdbb6ed | ||
|
|
403b5c237f | ||
|
|
2367ed7911 |
No files matched your search
+28
-1
@@ -6,15 +6,42 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Retry graphQL call on network or transient server errors. ([#320](https://github.com/expo/eas-cli/pull/320) by [@jkhales](https://github.com/jkhales))
|
||||
- Display more friendly error messages when `eas submit` fails. ([#311](https://github.com/expo/eas-cli/pull/297) by [@barthap](https://github.com/barthap))
|
||||
- Add support for managing webhooks (new commands: `webhook:create`, `webhook:view`, `webhook:list`, `webhook:update`, and `webhook:delete`). ([#314](https://github.com/expo/eas-cli/pull/314) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix the issue where the first iOS build fails for a project without `ios.bundleIdentifier` set in `app.json`. ([#319](https://github.com/expo/eas-cli/pull/319) by [@dsokal](https://github.com/dsokal))
|
||||
- Bump @expo/config-plugins to fix `eas build:configure` on Windows
|
||||
- Fix provisioning profile validation to actually check status. ([#318](https://github.com/expo/eas-cli/pull/318) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Replace Credentials REST API calls with GraphQL counterparts, also improved DX. ([#293](https://github.com/expo/eas-cli/pull/293), [#299](https://github.com/expo/eas-cli/pull/299), [#301](https://github.com/expo/eas-cli/pull/301), [#317](https://github.com/expo/eas-cli/pull/317) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.8.1](https://github.com/expo/eas-cli/releases/tag/v0.8.1) - 2021-04-06
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix the issue where the build status never got updated when running `eas build`. ([#310](https://github.com/expo/eas-cli/pull/310) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.8.0](https://github.com/expo/eas-cli/releases/tag/v0.8.0) - 2021-04-06
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Change the way of disabling cache - add `cache.disabled` field. ([#295](https://github.com/expo/eas-cli/pull/295) by [@dsokal](https://github.com/dsokal))
|
||||
- `secrets:create` now uses flags rather than positional arguments ([#300](https://github.com/expo/eas-cli/pull/300) by [@fiberjw](https://github.com/fiberjw))
|
||||
- `secrets:create`'s `target` arg is now called `scope` ([#300](https://github.com/expo/eas-cli/pull/300) by [@fiberjw](https://github.com/fiberjw))
|
||||
- `secrets:list`'s `target` property is now called `scope` ([#300](https://github.com/expo/eas-cli/pull/300) by [@fiberjw](https://github.com/fiberjw))
|
||||
- `secrets:delete`'s `ID` arg is now optional ([#309](https://github.com/expo/eas-cli/pull/309) by [@fiberjw](https://github.com/fiberjw))
|
||||
- `secrets:delete`'s now allows users to choose secrets from a list ([#309](https://github.com/expo/eas-cli/pull/309) by [@fiberjw](https://github.com/fiberjw))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- `build:view` and `build:list` now showing the distribution type (store / internal) and release channel. ([#284](https://github.com/expo/eas-cli/pull/284) by [@vthibault](https://github.com/vthibault))
|
||||
|
||||
- Add analytics to EAS Build. ([#162](https://github.com/expo/eas-cli/pull/162) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Improve tar archive support in EAS Submit. ([#297](https://github.com/expo/eas-cli/pull/297) by [@barthap](https://github.com/barthap))
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020-present 650 Industries, Inc. (aka Expo)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.8.0"
|
||||
"version": "0.9.1"
|
||||
}
|
||||
+107
-22
@@ -48,9 +48,14 @@ eas --help COMMAND
|
||||
* [`eas device:view [UDID]`](#eas-deviceview-udid)
|
||||
* [`eas help [COMMAND]`](#eas-help-command)
|
||||
* [`eas secrets:create`](#eas-secretscreate)
|
||||
* [`eas secrets:delete ID`](#eas-secretsdelete-id)
|
||||
* [`eas secrets:delete [ID]`](#eas-secretsdelete-id)
|
||||
* [`eas secrets:list`](#eas-secretslist)
|
||||
* [`eas submit --platform=(android|ios)`](#eas-submit---platformandroidios)
|
||||
* [`eas webhook:create`](#eas-webhookcreate)
|
||||
* [`eas webhook:delete [ID]`](#eas-webhookdelete-id)
|
||||
* [`eas webhook:list`](#eas-webhooklist)
|
||||
* [`eas webhook:update`](#eas-webhookupdate)
|
||||
* [`eas webhook:view ID`](#eas-webhookview-id)
|
||||
|
||||
## `eas account:login`
|
||||
|
||||
@@ -64,7 +69,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [build/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/account/login.ts)_
|
||||
_See code: [build/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -78,7 +83,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [build/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/account/logout.ts)_
|
||||
_See code: [build/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -92,7 +97,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [build/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/account/view.ts)_
|
||||
_See code: [build/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -103,7 +108,7 @@ USAGE
|
||||
$ eas analytics [STATUS]
|
||||
```
|
||||
|
||||
_See code: [build/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/analytics.ts)_
|
||||
_See code: [build/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/analytics.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -122,7 +127,7 @@ OPTIONS
|
||||
--[no-]wait Wait for build(s) to complete
|
||||
```
|
||||
|
||||
_See code: [build/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/build/index.ts)_
|
||||
_See code: [build/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -133,7 +138,7 @@ USAGE
|
||||
$ eas build:cancel [BUILD_ID]
|
||||
```
|
||||
|
||||
_See code: [build/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/build/cancel.ts)_
|
||||
_See code: [build/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -148,7 +153,7 @@ OPTIONS
|
||||
--allow-experimental Enable experimental configuration steps.
|
||||
```
|
||||
|
||||
_See code: [build/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/build/configure.ts)_
|
||||
_See code: [build/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -164,7 +169,7 @@ OPTIONS
|
||||
--status=(in-queue|in-progress|errored|finished|canceled)
|
||||
```
|
||||
|
||||
_See code: [build/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/build/list.ts)_
|
||||
_See code: [build/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/build/list.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -175,7 +180,7 @@ USAGE
|
||||
$ eas build:view [BUILD_ID]
|
||||
```
|
||||
|
||||
_See code: [build/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/build/view.ts)_
|
||||
_See code: [build/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/build/view.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -186,7 +191,7 @@ USAGE
|
||||
$ eas credentials
|
||||
```
|
||||
|
||||
_See code: [build/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/credentials.ts)_
|
||||
_See code: [build/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/credentials.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -197,7 +202,7 @@ USAGE
|
||||
$ eas device:create
|
||||
```
|
||||
|
||||
_See code: [build/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/device/create.ts)_
|
||||
_See code: [build/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/device/create.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -211,7 +216,7 @@ OPTIONS
|
||||
--apple-team-id=apple-team-id
|
||||
```
|
||||
|
||||
_See code: [build/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/device/list.ts)_
|
||||
_See code: [build/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/device/list.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -222,7 +227,7 @@ USAGE
|
||||
$ eas device:view [UDID]
|
||||
```
|
||||
|
||||
_See code: [build/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/device/view.ts)_
|
||||
_See code: [build/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/device/view.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -255,15 +260,15 @@ OPTIONS
|
||||
--value=value Value of the secret
|
||||
```
|
||||
|
||||
_See code: [build/commands/secrets/create.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/secrets/create.ts)_
|
||||
_See code: [build/commands/secrets/create.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/secrets/create.ts)_
|
||||
|
||||
## `eas secrets:delete ID`
|
||||
## `eas secrets:delete [ID]`
|
||||
|
||||
Delete an environment secret by ID.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas secrets:delete ID
|
||||
$ eas secrets:delete [ID]
|
||||
|
||||
ARGUMENTS
|
||||
ID ID of the secret to delete
|
||||
@@ -272,7 +277,7 @@ DESCRIPTION
|
||||
Unsure where to find the secret's ID? Run eas secrets:list
|
||||
```
|
||||
|
||||
_See code: [build/commands/secrets/delete.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/secrets/delete.ts)_
|
||||
_See code: [build/commands/secrets/delete.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/secrets/delete.ts)_
|
||||
|
||||
## `eas secrets:list`
|
||||
|
||||
@@ -283,7 +288,7 @@ USAGE
|
||||
$ eas secrets:list
|
||||
```
|
||||
|
||||
_See code: [build/commands/secrets/list.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/secrets/list.ts)_
|
||||
_See code: [build/commands/secrets/list.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/secrets/list.ts)_
|
||||
|
||||
## `eas submit --platform=(android|ios)`
|
||||
|
||||
@@ -309,8 +314,7 @@ OPTIONS
|
||||
|
||||
iOS specific options=asc-app-id App Store Connect unique application Apple ID
|
||||
number. Providing this param results in skipping
|
||||
app creation step. Learn more:
|
||||
https://expo.fyi/asc-app-id
|
||||
app creation step. Learn more.
|
||||
|
||||
iOS specific options=bundle-identifier Your iOS Bundle Identifier (default:
|
||||
expo.ios.bundleIdentifier from app config)
|
||||
@@ -363,5 +367,86 @@ EXAMPLES
|
||||
and provide its App ID
|
||||
```
|
||||
|
||||
_See code: [build/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.8.0/build/commands/submit.ts)_
|
||||
_See code: [build/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/submit.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
Create a webhook on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:create
|
||||
|
||||
OPTIONS
|
||||
--event=(BUILD) [default: BUILD] Event type that triggers the webhook
|
||||
|
||||
--secret=secret Secret used to create a hash signature of the request payload, provided in the 'Expo-Signature'
|
||||
header.
|
||||
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [build/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
Delete a webhook on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:delete [ID]
|
||||
|
||||
ARGUMENTS
|
||||
ID ID of the webhook to delete
|
||||
```
|
||||
|
||||
_See code: [build/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
List webhooks on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:list
|
||||
|
||||
OPTIONS
|
||||
--event=(BUILD) Event type that triggers the webhook
|
||||
```
|
||||
|
||||
_See code: [build/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
Create a webhook on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:update
|
||||
|
||||
OPTIONS
|
||||
--event=(BUILD) [default: BUILD] Event type that triggers the webhook
|
||||
--id=id (required) Webhook ID
|
||||
|
||||
--secret=secret Secret used to create a hash signature of the request payload, provided in the 'Expo-Signature'
|
||||
header.
|
||||
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [build/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
View a webhook on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:view ID
|
||||
|
||||
ARGUMENTS
|
||||
ID ID of the webhook to view
|
||||
```
|
||||
|
||||
_See code: [build/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/webhook/view.ts)_
|
||||
<!-- commandsstop -->
|
||||
+1628
-1094
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.8.0",
|
||||
"version": "0.9.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
@@ -12,9 +12,9 @@
|
||||
"@amplitude/node": "^1.5.0",
|
||||
"@expo/apple-utils": "0.0.0-alpha.17",
|
||||
"@expo/config": "~3.3.19",
|
||||
"@expo/config-plugins": "1.0.21-alpha.0",
|
||||
"@expo/config-plugins": "1.0.24",
|
||||
"@expo/eas-build-job": "0.2.22",
|
||||
"@expo/eas-json": "^0.8.0",
|
||||
"@expo/eas-json": "^0.9.0",
|
||||
"@expo/json-file": "^8.2.24",
|
||||
"@expo/plist": "^0.0.11",
|
||||
"@expo/plugin-warn-if-update-available": "^1.7.0",
|
||||
@@ -25,7 +25,8 @@
|
||||
"@oclif/config": "^1",
|
||||
"@oclif/errors": "^1.3.3",
|
||||
"@oclif/plugin-help": "^3",
|
||||
"@urql/core": "^1.13.0",
|
||||
"@urql/core": "^2.0.0",
|
||||
"@urql/exchange-retry": "^0.2.0",
|
||||
"chalk": "^4.1.0",
|
||||
"cli-table3": "^0.6.0",
|
||||
"dateformat": "^3.0.3",
|
||||
|
||||
@@ -76,7 +76,7 @@ async function waitForBuildEndAsync(
|
||||
const builds: (BuildFragment | null)[] = await Promise.all(
|
||||
buildIds.map(async buildId => {
|
||||
try {
|
||||
return await BuildQuery.byIdAsync(buildId);
|
||||
return await BuildQuery.byIdAsync(buildId, { useCache: false });
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { ExpoConfig, getConfig } from '@expo/config';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
@@ -73,7 +73,7 @@ export default class Build extends Command {
|
||||
(flags.platform as RequestedPlatform | undefined) ?? (await promptForPlatformAsync());
|
||||
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
let { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
if (!(await isEasEnabledForProjectAsync(projectId))) {
|
||||
@@ -93,7 +93,7 @@ export default class Build extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
await ensureProjectConfiguredAsync(projectDir);
|
||||
exp = (await ensureProjectConfiguredAsync(projectDir)) ?? exp;
|
||||
|
||||
const commandCtx = await createCommandContextAsync({
|
||||
requestedPlatform: platform,
|
||||
@@ -189,9 +189,9 @@ async function promptForPlatformAsync(): Promise<RequestedPlatform> {
|
||||
return platform;
|
||||
}
|
||||
|
||||
async function ensureProjectConfiguredAsync(projectDir: string): Promise<void> {
|
||||
async function ensureProjectConfiguredAsync(projectDir: string): Promise<ExpoConfig | null> {
|
||||
if (await fs.pathExists(path.join(projectDir, 'eas.json'))) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
const confirm = await confirmAsync({
|
||||
message: 'This app is not set up for building with EAS. Set it up now?',
|
||||
@@ -207,6 +207,8 @@ async function ensureProjectConfiguredAsync(projectDir: string): Promise<void> {
|
||||
'Build process requires clean git working tree, please commit all your changes and run `eas build` again'
|
||||
);
|
||||
}
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
return exp;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Aborting, please run ${chalk.bold('eas build:configure')} or create eas.json (${learnMore(
|
||||
|
||||
@@ -3,14 +3,24 @@ import { Command } from '@oclif/command';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { EnvironmentSecretMutation } from '../../graphql/mutations/EnvironmentSecretMutation';
|
||||
import {
|
||||
EnvironmentSecretWithScope,
|
||||
EnvironmentSecretsQuery,
|
||||
} from '../../graphql/queries/EnvironmentSecretsQuery';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
isEasEnabledForProjectAsync,
|
||||
warnEasUnavailable,
|
||||
} from '../../project/isEasEnabledForProject';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { toggleConfirmAsync } from '../../prompts';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectAccountNameAsync,
|
||||
getProjectFullNameAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { EnvironmentSecretScope } from './create';
|
||||
|
||||
export default class EnvironmentSecretDelete extends Command {
|
||||
static description = `Delete an environment secret by ID.
|
||||
@@ -19,7 +29,7 @@ Unsure where to find the secret's ID? Run ${chalk.bold('eas secrets:list')}`;
|
||||
static args = [
|
||||
{
|
||||
name: 'id',
|
||||
required: true,
|
||||
required: false,
|
||||
description: `ID of the secret to delete`,
|
||||
},
|
||||
];
|
||||
@@ -30,6 +40,8 @@ Unsure where to find the secret's ID? Run ${chalk.bold('eas secrets:list')}`;
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectFullName = await getProjectFullNameAsync(exp);
|
||||
const projectAccountName = await getProjectAccountNameAsync(exp);
|
||||
|
||||
if (!(await isEasEnabledForProjectAsync(projectId))) {
|
||||
warnEasUnavailable();
|
||||
@@ -37,23 +49,50 @@ Unsure where to find the secret's ID? Run ${chalk.bold('eas secrets:list')}`;
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
let {
|
||||
args: { id },
|
||||
} = this.parse(EnvironmentSecretDelete);
|
||||
let secret: EnvironmentSecretWithScope | undefined;
|
||||
|
||||
if (!id) {
|
||||
const secrets = await EnvironmentSecretsQuery.allAsync(projectAccountName, projectFullName);
|
||||
|
||||
({ secret } = await promptAsync({
|
||||
type: 'autocomplete',
|
||||
name: 'secret',
|
||||
message: 'Pick the secret to be deleted:',
|
||||
choices: secrets.map(secret => ({
|
||||
title: `${secret.name} (${secret.scope})`,
|
||||
value: secret,
|
||||
})),
|
||||
}));
|
||||
|
||||
id = secret?.id;
|
||||
}
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.warn(
|
||||
`You are about to permamently delete secret with id: "${id}".\nThis action is irreversible.`
|
||||
`You are about to permamently delete secret${
|
||||
secret?.name ? ` "${secret?.name}"` : ''
|
||||
} with id: "${id}".\nThis action is irreversible.`
|
||||
);
|
||||
Log.newLine();
|
||||
const confirmed = await toggleConfirmAsync({ message: 'Are you sure you wish to proceed?' });
|
||||
const confirmed = await toggleConfirmAsync({
|
||||
message: `Are you sure you wish to proceed?${
|
||||
secret?.scope === EnvironmentSecretScope.ACCOUNT
|
||||
? ' This secret is applied across your whole account and may affect multiple apps.'
|
||||
: ''
|
||||
}`,
|
||||
});
|
||||
if (!confirmed) {
|
||||
Log.error(`Canceled deletion of secret with id: "${id}".`);
|
||||
Log.error(
|
||||
`Canceled deletion of secret${secret?.name ? ` "${secret?.name}"` : ''} with id: "${id}".`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await EnvironmentSecretMutation.delete(id);
|
||||
|
||||
Log.withTick(`️Deleted secret with id "${id}".`);
|
||||
Log.withTick(`️Deleted secret${secret?.name ? ` "${secret?.name}"` : ''} with id "${id}".`);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import chalk from 'chalk';
|
||||
import Table from 'cli-table3';
|
||||
import dateFormat from 'dateformat';
|
||||
|
||||
import { EnvironmentSecretFragment } from '../../graphql/generated';
|
||||
import { EnvironmentSecretsQuery } from '../../graphql/queries/EnvironmentSecretsQuery';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
@@ -18,7 +17,6 @@ import {
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { EnvironmentSecretScope } from './create';
|
||||
|
||||
export default class EnvironmentSecretsList extends Command {
|
||||
static description = 'Lists environment secrets available for your current app';
|
||||
@@ -43,15 +41,7 @@ export default class EnvironmentSecretsList extends Command {
|
||||
throw new Error("Please run this command inside your project's directory");
|
||||
}
|
||||
|
||||
const [accountSecrets, appSecrets] = await Promise.all([
|
||||
EnvironmentSecretsQuery.byAcccountNameAsync(projectAccountName),
|
||||
EnvironmentSecretsQuery.byAppFullNameAsync(projectFullName),
|
||||
]);
|
||||
|
||||
const secrets = [
|
||||
...appSecrets.map(s => ({ ...s, scope: EnvironmentSecretScope.PROJECT })),
|
||||
...accountSecrets.map(s => ({ ...s, scope: EnvironmentSecretScope.ACCOUNT })),
|
||||
] as (EnvironmentSecretFragment & { scope: EnvironmentSecretScope })[];
|
||||
const secrets = await EnvironmentSecretsQuery.allAsync(projectAccountName, projectFullName);
|
||||
|
||||
const table = new Table({
|
||||
head: ['Name', 'Scope', 'ID', 'Updated at'],
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import ora from 'ora';
|
||||
|
||||
import { WebhookType } from '../../graphql/generated';
|
||||
import { WebhookMutation } from '../../graphql/mutations/WebhookMutation';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { prepareInputParamsAsync } from '../../webhooks/input';
|
||||
|
||||
export default class WebhookCreate extends Command {
|
||||
static description = 'Create a webhook on the current project.';
|
||||
|
||||
static flags = {
|
||||
event: flags.enum({
|
||||
description: 'Event type that triggers the webhook',
|
||||
options: [WebhookType.Build],
|
||||
default: WebhookType.Build,
|
||||
}),
|
||||
url: flags.string({
|
||||
description: 'Webhook URL',
|
||||
}),
|
||||
secret: flags.string({
|
||||
description:
|
||||
"Secret used to create a hash signature of the request payload, provided in the 'Expo-Signature' header.",
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
await ensureLoggedInAsync();
|
||||
const { flags } = this.parse(WebhookCreate);
|
||||
const webhookInputParams = await prepareInputParamsAsync(flags);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
const spinner = ora('Creating a webhook').start();
|
||||
try {
|
||||
await WebhookMutation.createWebhookAsync(projectId, webhookInputParams);
|
||||
spinner.succeed('Successfully created a webhook');
|
||||
} catch (err) {
|
||||
spinner.fail('Failed to create a webhook');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Command } from '@oclif/command';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
import ora from 'ora';
|
||||
|
||||
import { WebhookFragment } from '../../graphql/generated';
|
||||
import { WebhookMutation } from '../../graphql/mutations/WebhookMutation';
|
||||
import { WebhookQuery } from '../../graphql/queries/WebhookQuery';
|
||||
import Log from '../../log';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { formatWebhook } from '../../webhooks/formatWebhook';
|
||||
|
||||
export default class WebhookDelete extends Command {
|
||||
static description = 'Delete a webhook on the current project.';
|
||||
|
||||
static args = [
|
||||
{
|
||||
name: 'ID',
|
||||
required: false,
|
||||
description: 'ID of the webhook to delete',
|
||||
},
|
||||
];
|
||||
|
||||
async run() {
|
||||
await ensureLoggedInAsync();
|
||||
let {
|
||||
args: { ID: webhookId },
|
||||
} = this.parse(WebhookDelete);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
let webhook: WebhookFragment | undefined =
|
||||
webhookId && (await WebhookQuery.byIdAsync(webhookId));
|
||||
if (!webhookId) {
|
||||
const webhooks = await fetchWebhooksByAppIdAsync(projectId);
|
||||
if (webhooks.length === 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
({ webhook } = await promptAsync({
|
||||
type: 'autocomplete',
|
||||
name: 'webhook',
|
||||
message: 'Pick the webhook to be deleted:',
|
||||
choices: webhooks.map(i => ({
|
||||
title: `${chalk.bold(i.url)} (Event: ${i.event}, ID: ${i.id})`,
|
||||
value: i,
|
||||
})),
|
||||
}));
|
||||
webhookId = nullthrows(webhook).id;
|
||||
}
|
||||
|
||||
assert(webhook, 'Webhook must be defined here');
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(formatWebhook(webhook));
|
||||
Log.newLine();
|
||||
Log.warn(`You are about to permamently delete this webhook.\nThis action is irreversible.`);
|
||||
Log.newLine();
|
||||
const confirmed = await toggleConfirmAsync({
|
||||
message: 'Are you sure you wish to proceed?',
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
Log.error(`Canceled deletion of the webhook`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const spinner = ora('Deleting the webhook').start();
|
||||
try {
|
||||
await WebhookMutation.deleteWebhookAsync(webhookId);
|
||||
spinner.succeed('Successfully deleted the webhook');
|
||||
} catch (err) {
|
||||
spinner.fail('Failed to delete the webhook');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchWebhooksByAppIdAsync(appId: string): Promise<WebhookFragment[]> {
|
||||
const spinner = ora('Fetching webhooks').start();
|
||||
try {
|
||||
const webhooks = await WebhookQuery.byAppIdAsync(appId);
|
||||
if (webhooks.length === 0) {
|
||||
spinner.fail('There are no webhooks on the project');
|
||||
return [];
|
||||
} else {
|
||||
spinner.succeed(`Successfully fetched ${webhooks.length} webhooks`);
|
||||
return webhooks;
|
||||
}
|
||||
} catch (err) {
|
||||
spinner.fail("Something went wrong and we couldn't fetch the webhook list");
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
|
||||
import { WebhookType } from '../../graphql/generated';
|
||||
import { WebhookQuery } from '../../graphql/queries/WebhookQuery';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectFullNameAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { formatWebhook } from '../../webhooks/formatWebhook';
|
||||
|
||||
export default class WebhookList extends Command {
|
||||
static description = 'List webhooks on the current project.';
|
||||
|
||||
static flags = {
|
||||
event: flags.enum({
|
||||
description: 'Event type that triggers the webhook',
|
||||
options: [WebhookType.Build],
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
await ensureLoggedInAsync();
|
||||
const {
|
||||
flags: { event },
|
||||
} = this.parse(WebhookList);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectFullName = await getProjectFullNameAsync(exp);
|
||||
|
||||
const spinner = ora(`Fetching the list of webhook on project ${projectFullName}`).start();
|
||||
try {
|
||||
const webhooks = await WebhookQuery.byAppIdAsync(projectId, event && { event });
|
||||
if (webhooks.length === 0) {
|
||||
spinner.fail(`There are no webhooks on project ${projectFullName}`);
|
||||
} else {
|
||||
spinner.succeed(`Found ${webhooks.length} webhooks on project ${projectFullName}`);
|
||||
const list = webhooks
|
||||
.map(webhook => formatWebhook(webhook))
|
||||
.join(`\n\n${chalk.dim('———')}\n\n`);
|
||||
Log.log(`\n${list}`);
|
||||
}
|
||||
} catch (err) {
|
||||
spinner.fail(
|
||||
`Something went wrong and we couldn't fetch the webhook list for the project ${projectFullName}`
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import pick from 'lodash/pick';
|
||||
import ora from 'ora';
|
||||
|
||||
import { WebhookType } from '../../graphql/generated';
|
||||
import { WebhookMutation } from '../../graphql/mutations/WebhookMutation';
|
||||
import { WebhookQuery } from '../../graphql/queries/WebhookQuery';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { prepareInputParamsAsync } from '../../webhooks/input';
|
||||
|
||||
export default class WebhookUpdate extends Command {
|
||||
static description = 'Create a webhook on the current project.';
|
||||
|
||||
static flags = {
|
||||
id: flags.string({
|
||||
description: 'Webhook ID',
|
||||
required: true,
|
||||
}),
|
||||
event: flags.enum({
|
||||
description: 'Event type that triggers the webhook',
|
||||
options: [WebhookType.Build],
|
||||
default: WebhookType.Build,
|
||||
}),
|
||||
url: flags.string({
|
||||
description: 'Webhook URL',
|
||||
}),
|
||||
secret: flags.string({
|
||||
description:
|
||||
"Secret used to create a hash signature of the request payload, provided in the 'Expo-Signature' header.",
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
await ensureLoggedInAsync();
|
||||
const { flags } = this.parse(WebhookUpdate);
|
||||
|
||||
const webhookId = flags.id;
|
||||
|
||||
const webhook = await WebhookQuery.byIdAsync(webhookId);
|
||||
const webhookInputParams = await prepareInputParamsAsync(
|
||||
pick(flags, ['event', 'url', 'secret']),
|
||||
webhook
|
||||
);
|
||||
|
||||
const spinner = ora('Updating a webhook').start();
|
||||
try {
|
||||
await WebhookMutation.updateWebhookAsync(webhookId, webhookInputParams);
|
||||
spinner.succeed('Successfully updated a webhook');
|
||||
} catch (err) {
|
||||
spinner.fail('Failed to update a webhook');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Command } from '@oclif/command';
|
||||
import ora from 'ora';
|
||||
|
||||
import { WebhookQuery } from '../../graphql/queries/WebhookQuery';
|
||||
import Log from '../../log';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { formatWebhook } from '../../webhooks/formatWebhook';
|
||||
|
||||
export default class WebhookView extends Command {
|
||||
static description = 'View a webhook on the current project.';
|
||||
|
||||
static args = [
|
||||
{
|
||||
name: 'ID',
|
||||
required: true,
|
||||
description: 'ID of the webhook to view',
|
||||
},
|
||||
];
|
||||
|
||||
async run() {
|
||||
await ensureLoggedInAsync();
|
||||
const {
|
||||
args: { ID: webhookId },
|
||||
} = this.parse(WebhookView);
|
||||
|
||||
const spinner = ora(`Fetching the webhook details for ID ${webhookId}`).start();
|
||||
try {
|
||||
const webhook = await WebhookQuery.byIdAsync(webhookId);
|
||||
spinner.succeed(`Found the webhook details`);
|
||||
Log.log(`\n${formatWebhook(webhook)}`);
|
||||
} catch (err) {
|
||||
spinner.fail(`Couldn't find the webhook with ID ${webhookId}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,10 @@ import {
|
||||
AppleDistributionCertificateFragment,
|
||||
AppleProvisioningProfileFragment,
|
||||
AppleTeamFragment,
|
||||
IosAppBuildCredentialsFragment,
|
||||
IosDistributionType,
|
||||
} from '../../graphql/generated';
|
||||
import { IosAppCredentialsWithBuildCredentialsQueryResult } from '../ios/api/graphql/queries/IosAppCredentialsQuery';
|
||||
import {
|
||||
DistributionCertificate,
|
||||
DistributionCertificateStoreInfo,
|
||||
@@ -107,6 +110,18 @@ export const testDistCertFragmentOneDependency: AppleDistributionCertificateFrag
|
||||
],
|
||||
};
|
||||
|
||||
export const testIosAppBuildCredentialsFragment: IosAppBuildCredentialsFragment = {
|
||||
id: 'test-ios-app-build-credentials-id',
|
||||
iosDistributionType: IosDistributionType.AppStore,
|
||||
distributionCertificate: testDistCertFragmentNoDependencies,
|
||||
provisioningProfile: testProvisioningProfileFragment,
|
||||
};
|
||||
|
||||
export const testIosAppCredentialsWithBuildCredentialsQueryResult: IosAppCredentialsWithBuildCredentialsQueryResult = {
|
||||
id: 'test-app-credential-id',
|
||||
iosAppBuildCredentialsArray: [testIosAppBuildCredentialsFragment],
|
||||
};
|
||||
|
||||
export const testDistCert: DistributionCertificate = {
|
||||
certP12: 'Y2VydHAxMg==',
|
||||
certPassword: 'test-password',
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import assert from 'assert';
|
||||
|
||||
import Log from '../../../log';
|
||||
import { Action, CredentialsManager } from '../../CredentialsManager';
|
||||
import { Context } from '../../context';
|
||||
import { askForUserProvidedAsync } from '../../utils/promptForCredentials';
|
||||
import { ProvisioningProfile } from '../appstore/Credentials.types';
|
||||
import { AppLookupParams, provisioningProfileSchema } from '../credentials';
|
||||
import { displayIosAppCredentials } from '../utils/printCredentials';
|
||||
import { generateProvisioningProfileAsync } from './ProvisioningProfileUtils';
|
||||
|
||||
export class CreateProvisioningProfileStandaloneManager implements Action {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
|
||||
await manager.runActionAsync(new CreateProvisioningProfile(this.app));
|
||||
|
||||
const appCredentials = await ctx.ios.getAppCredentialsAsync(this.app);
|
||||
displayIosAppCredentials(appCredentials);
|
||||
Log.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
export class CreateProvisioningProfile implements Action {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
|
||||
const provisioningProfile = await this.provideOrGenerateAsync(ctx);
|
||||
await ctx.ios.updateProvisioningProfileAsync(this.app, provisioningProfile);
|
||||
|
||||
Log.succeed('Created provisioning profile');
|
||||
}
|
||||
|
||||
private async provideOrGenerateAsync(ctx: Context): Promise<ProvisioningProfile> {
|
||||
const userProvided = await askForUserProvidedAsync(provisioningProfileSchema);
|
||||
if (userProvided) {
|
||||
// userProvided profiles don't come with ProvisioningProfileId's (only accessible from Apple Portal API)
|
||||
Log.warn('Provisioning profile: Unable to validate specified profile.');
|
||||
return userProvided;
|
||||
}
|
||||
const distCert = await ctx.ios.getDistributionCertificateAsync(this.app);
|
||||
assert(distCert, 'missing distribution certificate');
|
||||
return await generateProvisioningProfileAsync(ctx, this.app.bundleIdentifier, distCert);
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,8 @@ import { isCredentialsMap, readIosCredentialsAsync } from '../../credentialsJson
|
||||
import { AppLookupParams, IosAppCredentials, IosDistCredentials } from '../credentials';
|
||||
import { displayProjectCredentials } from '../utils/printCredentials';
|
||||
import { readAppleTeam } from '../utils/provisioningProfile';
|
||||
import { SetupProvisioningProfile } from './SetupProvisioningProfile';
|
||||
import { SetupAdhocProvisioningProfile } from './new/SetupAdhocProvisioningProfile';
|
||||
import { SetupProvisioningProfile } from './new/SetupProvisioningProfile';
|
||||
|
||||
type AppCredentialsAndDistCert = {
|
||||
appCredentials: IosAppCredentials;
|
||||
@@ -32,11 +32,11 @@ export class SetupBuildCredentials implements Action {
|
||||
|
||||
let iosAppBuildCredentials: IosAppBuildCredentialsFragment | null = null;
|
||||
try {
|
||||
const account = findAccountByName(ctx.user.accounts, this.app.accountName);
|
||||
if (!account) {
|
||||
throw new Error(`You do not have access to the ${this.app.accountName} account`);
|
||||
}
|
||||
if (this.distribution === 'internal') {
|
||||
const account = findAccountByName(ctx.user.accounts, this.app.accountName);
|
||||
if (!account) {
|
||||
throw new Error(`You do not have access to the ${this.app.accountName} account`);
|
||||
}
|
||||
const action = new SetupAdhocProvisioningProfile({
|
||||
account,
|
||||
projectName: this.app.projectName,
|
||||
@@ -45,7 +45,12 @@ export class SetupBuildCredentials implements Action {
|
||||
await manager.runActionAsync(action);
|
||||
iosAppBuildCredentials = action.iosAppBuildCredentials;
|
||||
} else {
|
||||
await manager.runActionAsync(new SetupProvisioningProfile(this.app));
|
||||
const setupProvisioningProfileAction = new SetupProvisioningProfile({
|
||||
account,
|
||||
projectName: this.app.projectName,
|
||||
bundleIdentifier: this.app.bundleIdentifier,
|
||||
});
|
||||
await setupProvisioningProfileAction.runAsync(manager, ctx);
|
||||
}
|
||||
} catch (error) {
|
||||
Log.error('Failed to setup credentials.');
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync, promptAsync } from '../../../prompts';
|
||||
import { Action, CredentialsManager } from '../../CredentialsManager';
|
||||
import { Context } from '../../context';
|
||||
import { AppLookupParams } from '../credentials';
|
||||
import { validateDistributionCertificateAsync } from '../validators/validateDistributionCertificate';
|
||||
import { CreateDistributionCertificate } from './CreateDistributionCertificate';
|
||||
import {
|
||||
formatDistributionCertificate,
|
||||
getValidDistCertsAsync,
|
||||
} from './DistributionCertificateUtils';
|
||||
import {
|
||||
UseExistingDistributionCertificate,
|
||||
UseSpecificDistributionCertificate,
|
||||
} from './UseDistributionCertificate';
|
||||
|
||||
export class SetupDistributionCertificateForApp implements Action {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
public async runAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
|
||||
if (await this.isCurrentCertificateValidAsync(ctx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(
|
||||
'Distribution Certificate is not configured correctly. Please run this command again in interactive mode.'
|
||||
);
|
||||
}
|
||||
|
||||
const existingCertificates = await getValidDistCertsAsync(ctx, this.app.accountName);
|
||||
|
||||
if (existingCertificates.length === 0) {
|
||||
await this.createNewCertificateAsync(manager, ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
// autoselect credentials if we find valid certs
|
||||
const autoselectedCertificate = existingCertificates[0];
|
||||
|
||||
const credentials = await ctx.ios.getAllCredentialsAsync(this.app.accountName);
|
||||
const usedByApps = credentials.appCredentials.filter(
|
||||
cred => cred.distCredentialsId === autoselectedCertificate.id
|
||||
);
|
||||
const confirm = await confirmAsync({
|
||||
message: `${formatDistributionCertificate(
|
||||
autoselectedCertificate,
|
||||
usedByApps,
|
||||
ctx.appStore.authCtx ? 'VALID' : 'UNKNOWN'
|
||||
)} \n Would you like to use this certificate?`,
|
||||
});
|
||||
if (!confirm) {
|
||||
await this.createOrReuseAsync(manager, ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.log(`Using Distribution Certificate: ${autoselectedCertificate.certId || '-----'}`);
|
||||
await manager.runActionAsync(
|
||||
new UseSpecificDistributionCertificate(this.app, autoselectedCertificate.id)
|
||||
);
|
||||
}
|
||||
|
||||
private async isCurrentCertificateValidAsync(ctx: Context): Promise<boolean> {
|
||||
const currentCertificate = await ctx.ios.getDistributionCertificateAsync(this.app);
|
||||
if (!currentCertificate) {
|
||||
return false;
|
||||
}
|
||||
if (ctx.appStore.authCtx) {
|
||||
const isValid = await validateDistributionCertificateAsync(ctx, currentCertificate);
|
||||
if (!isValid) {
|
||||
Log.warn("Current Distribution Certificate is no longer valid on Apple's server");
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async createNewCertificateAsync(
|
||||
manager: CredentialsManager,
|
||||
ctx: Context
|
||||
): Promise<void> {
|
||||
const action = new CreateDistributionCertificate(this.app.accountName);
|
||||
await manager.runActionAsync(action);
|
||||
await manager.runActionAsync(
|
||||
new UseSpecificDistributionCertificate(this.app, action.distCredentials.id)
|
||||
);
|
||||
}
|
||||
|
||||
private async createOrReuseAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
|
||||
const { action } = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'action',
|
||||
message: 'Select an iOS Distribution Certificate to use for code signing:',
|
||||
choices: [
|
||||
{
|
||||
title: '[Choose existing certificate] (Recommended)',
|
||||
value: 'CHOOSE_EXISTING',
|
||||
},
|
||||
{ title: '[Add a new certificate]', value: 'GENERATE' },
|
||||
],
|
||||
});
|
||||
|
||||
if (action === 'GENERATE') {
|
||||
await this.createNewCertificateAsync(manager, ctx);
|
||||
} else if (action === 'CHOOSE_EXISTING') {
|
||||
await manager.runActionAsync(new UseExistingDistributionCertificate(this.app));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
import assert from 'assert';
|
||||
import pick from 'lodash/pick';
|
||||
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync, promptAsync } from '../../../prompts';
|
||||
import { Action, CredentialsManager } from '../../CredentialsManager';
|
||||
import { Context } from '../../context';
|
||||
import { ProvisioningProfileStoreInfo } from '../appstore/Credentials.types';
|
||||
import { AppLookupParams, IosDistCredentials } from '../credentials';
|
||||
import {
|
||||
validateProvisioningProfileAsync,
|
||||
validateProvisioningProfileWithoutApple,
|
||||
} from '../validators/validateProvisioningProfile';
|
||||
import {
|
||||
ConfigureProvisioningProfile,
|
||||
UseExistingProvisioningProfile,
|
||||
} from './ConfigureProvisioningProfile';
|
||||
import { CreateProvisioningProfile } from './CreateProvisioningProfile';
|
||||
import { formatProvisioningProfileFromApple } from './ProvisioningProfileUtils';
|
||||
import { SetupDistributionCertificateForApp } from './SetupDistributionCertificate';
|
||||
|
||||
export class SetupProvisioningProfile implements Action {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
|
||||
await manager.runActionAsync(new SetupDistributionCertificateForApp(this.app));
|
||||
if (await this.isCurrentProfileValidAsync(ctx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(
|
||||
'Provisioning profile is not configured correctly. Please run this command again in interactive mode.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!ctx.appStore.authCtx) {
|
||||
await manager.runActionAsync(new CreateProvisioningProfile(this.app));
|
||||
return;
|
||||
}
|
||||
|
||||
const existingProfiles = await ctx.appStore.listProvisioningProfilesAsync(
|
||||
this.app.bundleIdentifier
|
||||
);
|
||||
|
||||
if (existingProfiles.length === 0) {
|
||||
await manager.runActionAsync(new CreateProvisioningProfile(this.app));
|
||||
return;
|
||||
}
|
||||
|
||||
const distCert = await ctx.ios.getDistributionCertificateAsync(this.app);
|
||||
assert(distCert, 'missing distribution certificate');
|
||||
|
||||
const autoselectedProfile = this.choosePreferred(existingProfiles, distCert);
|
||||
// autoselect credentials if we find valid certs
|
||||
|
||||
if (!ctx.nonInteractive) {
|
||||
const confirm = await confirmAsync({
|
||||
message: `${formatProvisioningProfileFromApple(
|
||||
autoselectedProfile
|
||||
)} \n Would you like to use this profile?`,
|
||||
});
|
||||
if (!confirm) {
|
||||
await this.createOrReuseAsync(manager, ctx);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Log.log(`Using provisioning profile: ${autoselectedProfile.provisioningProfileId}`);
|
||||
}
|
||||
|
||||
await ctx.ios.updateProvisioningProfileAsync(
|
||||
this.app,
|
||||
pick(autoselectedProfile, [
|
||||
'provisioningProfile',
|
||||
'provisioningProfileId',
|
||||
'teamId',
|
||||
'teamName',
|
||||
])
|
||||
);
|
||||
await manager.runActionAsync(new ConfigureProvisioningProfile(this.app));
|
||||
}
|
||||
|
||||
private async isCurrentProfileValidAsync(ctx: Context): Promise<boolean> {
|
||||
const currentProfile = await ctx.ios.getProvisioningProfileAsync(this.app);
|
||||
const currentCertificate = await ctx.ios.getDistributionCertificateAsync(this.app);
|
||||
if (!currentProfile || !currentCertificate) {
|
||||
return false;
|
||||
}
|
||||
if (!ctx.appStore.authCtx || !currentProfile.provisioningProfileId) {
|
||||
if (!currentProfile.provisioningProfileId) {
|
||||
Log.warn(
|
||||
"The provisioning profile we have on file cannot be validated on Apple's servers."
|
||||
);
|
||||
}
|
||||
const validationResult = validateProvisioningProfileWithoutApple(
|
||||
currentProfile,
|
||||
currentCertificate,
|
||||
this.app.bundleIdentifier
|
||||
);
|
||||
if (!validationResult.ok) {
|
||||
Log.warn(validationResult.error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const validationResult = await validateProvisioningProfileAsync(
|
||||
ctx,
|
||||
currentProfile,
|
||||
currentCertificate,
|
||||
this.app.bundleIdentifier
|
||||
);
|
||||
if (!validationResult.ok) {
|
||||
Log.warn(validationResult.error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private choosePreferred(
|
||||
profiles: ProvisioningProfileStoreInfo[],
|
||||
distCert: IosDistCredentials
|
||||
): ProvisioningProfileStoreInfo {
|
||||
// prefer the profile that already has the same dist cert associated with it
|
||||
const profileWithSameCert = profiles.find(profile =>
|
||||
profile.certificates.some(cert => cert.id === distCert.certId)
|
||||
);
|
||||
|
||||
// if not, just get an arbitrary profile
|
||||
return profileWithSameCert || profiles[0];
|
||||
}
|
||||
|
||||
private async createOrReuseAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
|
||||
const { action } = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'action',
|
||||
message: 'Select a provisioning profile:',
|
||||
choices: [
|
||||
{
|
||||
title: '[Choose existing provisioning profile] (Recommended)',
|
||||
value: 'CHOOSE_EXISTING',
|
||||
},
|
||||
{ title: '[Add a new provisioning profile]', value: 'GENERATE' },
|
||||
],
|
||||
});
|
||||
|
||||
if (action === 'GENERATE') {
|
||||
await manager.runActionAsync(new CreateProvisioningProfile(this.app));
|
||||
} else if (action === 'CHOOSE_EXISTING') {
|
||||
await manager.runActionAsync(new UseExistingProvisioningProfile(this.app));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import nullthrows from 'nullthrows';
|
||||
|
||||
import { AppleDistributionCertificateFragment } from '../../../../graphql/generated';
|
||||
import Log from '../../../../log';
|
||||
import { Action, CredentialsManager } from '../../../CredentialsManager';
|
||||
import { Context } from '../../../context';
|
||||
import { askForUserProvidedAsync } from '../../../utils/promptForCredentials';
|
||||
import { AppLookupParams } from '../../api/GraphqlClient';
|
||||
@@ -15,22 +14,13 @@ import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import { generateProvisioningProfileAsync } from '../ProvisioningProfileUtils';
|
||||
import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
|
||||
|
||||
export class CreateProvisioningProfile implements Action {
|
||||
private _provisioningProfile?: AppleProvisioningProfileMutationResult;
|
||||
export class CreateProvisioningProfile {
|
||||
constructor(
|
||||
private app: AppLookupParams,
|
||||
private distributionCertificate: AppleDistributionCertificateFragment
|
||||
) {}
|
||||
|
||||
public get provisioningProfile(): AppleProvisioningProfileMutationResult {
|
||||
assert(
|
||||
this._provisioningProfile,
|
||||
'provisioningProfile can be accessed only after calling .runAsync()'
|
||||
);
|
||||
return this._provisioningProfile;
|
||||
}
|
||||
|
||||
async runAsync(_manager: CredentialsManager, ctx: Context): Promise<void> {
|
||||
async runAsync(ctx: Context): Promise<AppleProvisioningProfileMutationResult> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new MissingCredentialsNonInteractiveError(
|
||||
'Creating Provisioning Profiles is only supported in interactive mode.'
|
||||
@@ -43,7 +33,7 @@ export class CreateProvisioningProfile implements Action {
|
||||
this.app,
|
||||
appleTeam
|
||||
);
|
||||
this._provisioningProfile = await ctx.newIos.createProvisioningProfileAsync(
|
||||
const provisioningProfileMutationResult = await ctx.newIos.createProvisioningProfileAsync(
|
||||
this.app,
|
||||
appleAppIdentifier,
|
||||
{
|
||||
@@ -52,6 +42,7 @@ export class CreateProvisioningProfile implements Action {
|
||||
}
|
||||
);
|
||||
Log.succeed('Created provisioning profile');
|
||||
return provisioningProfileMutationResult;
|
||||
}
|
||||
|
||||
private async provideOrGenerateAsync(
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import {
|
||||
AppleDistributionCertificateFragment,
|
||||
AppleProvisioningProfileFragment,
|
||||
IosAppBuildCredentialsFragment,
|
||||
IosDistributionType,
|
||||
} from '../../../../graphql/generated';
|
||||
import Log from '../../../../log';
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
import { CredentialsManager } from '../../../CredentialsManager';
|
||||
import { Context } from '../../../context';
|
||||
import { AppLookupParams } from '../../api/GraphqlClient';
|
||||
import { ProvisioningProfileStoreInfo } from '../../appstore/Credentials.types';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import { validateProvisioningProfileAsync } from '../../validators/validateProvisioningProfile';
|
||||
import { formatProvisioningProfileFromApple } from '../ProvisioningProfileUtils';
|
||||
import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
|
||||
import { ConfigureProvisioningProfile } from './ConfigureProvisioningProfile';
|
||||
import { CreateProvisioningProfile } from './CreateProvisioningProfile';
|
||||
import { SetupDistributionCertificate } from './SetupDistributionCertificate';
|
||||
|
||||
export class SetupProvisioningProfile {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async areBuildCredentialsSetupAsync(ctx: Context): Promise<boolean> {
|
||||
const buildCredentials = await this.getBuildCredentialsAsync(ctx);
|
||||
if (!buildCredentials) {
|
||||
return false;
|
||||
}
|
||||
const { distributionCertificate, provisioningProfile } = buildCredentials;
|
||||
if (!distributionCertificate || !provisioningProfile) {
|
||||
return false;
|
||||
}
|
||||
return await this.isCurrentProfileValidAsync(ctx, provisioningProfile, distributionCertificate);
|
||||
}
|
||||
|
||||
async getBuildCredentialsAsync(ctx: Context): Promise<IosAppBuildCredentialsFragment | null> {
|
||||
const appCredentials = await ctx.newIos.getIosAppCredentialsWithBuildCredentialsAsync(
|
||||
this.app,
|
||||
{
|
||||
iosDistributionType: IosDistributionType.AppStore,
|
||||
}
|
||||
);
|
||||
if (!appCredentials || appCredentials.iosAppBuildCredentialsArray.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const [buildCredentials] = appCredentials.iosAppBuildCredentialsArray;
|
||||
return buildCredentials;
|
||||
}
|
||||
|
||||
async assignNewAndDeleteOldProfileAsync(
|
||||
ctx: Context,
|
||||
distCert: AppleDistributionCertificateFragment,
|
||||
currentProfile: AppleProvisioningProfileFragment
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
const buildCredentials = await this.createAndAssignProfileAsync(ctx, distCert);
|
||||
// delete 'currentProfile' since its no longer valid
|
||||
await ctx.newIos.deleteProvisioningProfilesAsync([currentProfile.id]);
|
||||
return buildCredentials;
|
||||
}
|
||||
|
||||
async createAndAssignProfileAsync(
|
||||
ctx: Context,
|
||||
distCert: AppleDistributionCertificateFragment
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
const provisioningProfile = await new CreateProvisioningProfile(this.app, distCert).runAsync(
|
||||
ctx
|
||||
);
|
||||
return await this.assignBuildCredentialsAsync(ctx, distCert, provisioningProfile);
|
||||
}
|
||||
|
||||
async configureAndAssignProfileAsync(
|
||||
ctx: Context,
|
||||
distCert: AppleDistributionCertificateFragment,
|
||||
originalProvisioningProfile: AppleProvisioningProfileFragment
|
||||
): Promise<IosAppBuildCredentialsFragment | null> {
|
||||
const profileConfigurator = new ConfigureProvisioningProfile(
|
||||
this.app,
|
||||
distCert,
|
||||
originalProvisioningProfile
|
||||
);
|
||||
const updatedProvisioningProfile = await profileConfigurator.runAsync(ctx);
|
||||
if (!updatedProvisioningProfile) {
|
||||
return null;
|
||||
}
|
||||
return await this.assignBuildCredentialsAsync(ctx, distCert, updatedProvisioningProfile);
|
||||
}
|
||||
|
||||
async assignBuildCredentialsAsync(
|
||||
ctx: Context,
|
||||
distCert: AppleDistributionCertificateFragment,
|
||||
provisioningProfile: AppleProvisioningProfileFragment
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
const appleTeam = nullthrows(await resolveAppleTeamIfAuthenticatedAsync(ctx, this.app));
|
||||
const appleAppIdentifier = await ctx.newIos.createOrGetExistingAppleAppIdentifierAsync(
|
||||
this.app,
|
||||
appleTeam
|
||||
);
|
||||
return await ctx.newIos.createOrUpdateIosAppBuildCredentialsAsync(this.app, {
|
||||
appleTeam,
|
||||
appleAppIdentifierId: appleAppIdentifier.id,
|
||||
appleDistributionCertificateId: distCert.id,
|
||||
appleProvisioningProfileId: provisioningProfile.id,
|
||||
iosDistributionType: IosDistributionType.AppStore,
|
||||
});
|
||||
}
|
||||
|
||||
async getProvisioningProfileAsync(
|
||||
ctx: Context
|
||||
): Promise<AppleProvisioningProfileFragment | null> {
|
||||
const buildCredentials = await this.getBuildCredentialsAsync(ctx);
|
||||
if (!buildCredentials) {
|
||||
return null;
|
||||
}
|
||||
return buildCredentials.provisioningProfile ?? null;
|
||||
}
|
||||
|
||||
async runAsync(
|
||||
manager: CredentialsManager,
|
||||
ctx: Context
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
const areBuildCredentialsSetup = await this.areBuildCredentialsSetupAsync(ctx);
|
||||
if (areBuildCredentialsSetup) {
|
||||
return nullthrows(await this.getBuildCredentialsAsync(ctx));
|
||||
}
|
||||
if (ctx.nonInteractive) {
|
||||
throw new MissingCredentialsNonInteractiveError(
|
||||
'Provisioning profile is not configured correctly. Please run this command again in interactive mode.'
|
||||
);
|
||||
}
|
||||
|
||||
const distCertAction = new SetupDistributionCertificate(this.app, IosDistributionType.AppStore);
|
||||
await distCertAction.runAsync(manager, ctx);
|
||||
const distCert = distCertAction.distributionCertificate;
|
||||
|
||||
const currentProfile = await this.getProvisioningProfileAsync(ctx);
|
||||
if (!currentProfile) {
|
||||
return await this.createAndAssignProfileAsync(ctx, distCert);
|
||||
}
|
||||
|
||||
// See if the profile we have exists on the Apple Servers
|
||||
const existingProfiles = await ctx.appStore.listProvisioningProfilesAsync(
|
||||
this.app.bundleIdentifier
|
||||
);
|
||||
const currentProfileFromServer = this.getCurrentProfileStoreInfo(
|
||||
existingProfiles,
|
||||
currentProfile
|
||||
);
|
||||
if (!currentProfileFromServer) {
|
||||
return await this.assignNewAndDeleteOldProfileAsync(ctx, distCert, currentProfile);
|
||||
}
|
||||
|
||||
const confirm = await confirmAsync({
|
||||
message: `${formatProvisioningProfileFromApple(
|
||||
currentProfileFromServer
|
||||
)} \n Would you like to reuse the original profile?`,
|
||||
});
|
||||
if (!confirm) {
|
||||
return await this.assignNewAndDeleteOldProfileAsync(ctx, distCert, currentProfile);
|
||||
}
|
||||
|
||||
// If we get here, we've verified the current profile still exists on Apple
|
||||
// But something wasn't quite right, so we want to fix and update it
|
||||
const updatedProfile = await this.configureAndAssignProfileAsync(ctx, distCert, currentProfile);
|
||||
if (!updatedProfile) {
|
||||
// Something went wrong, so just create a new profile instead
|
||||
return await this.assignNewAndDeleteOldProfileAsync(ctx, distCert, currentProfile);
|
||||
}
|
||||
return updatedProfile;
|
||||
}
|
||||
|
||||
private async isCurrentProfileValidAsync(
|
||||
ctx: Context,
|
||||
currentProfile: AppleProvisioningProfileFragment,
|
||||
currentCertificate: AppleDistributionCertificateFragment
|
||||
): Promise<boolean> {
|
||||
const { provisioningProfile, developerPortalIdentifier } = currentProfile;
|
||||
const { certificateP12, certificatePassword } = currentCertificate;
|
||||
if (!provisioningProfile || !certificateP12 || !certificatePassword) {
|
||||
return false;
|
||||
}
|
||||
const validationResult = await validateProvisioningProfileAsync(
|
||||
ctx,
|
||||
{
|
||||
provisioningProfile,
|
||||
...(developerPortalIdentifier
|
||||
? { provisioningProfileId: developerPortalIdentifier }
|
||||
: null),
|
||||
},
|
||||
{
|
||||
certP12: certificateP12,
|
||||
certPassword: certificatePassword,
|
||||
},
|
||||
this.app.bundleIdentifier
|
||||
);
|
||||
if (!validationResult.ok) {
|
||||
Log.warn(validationResult.error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private getCurrentProfileStoreInfo(
|
||||
profiles: ProvisioningProfileStoreInfo[],
|
||||
currentProfile: AppleProvisioningProfileFragment
|
||||
): ProvisioningProfileStoreInfo | null {
|
||||
return (
|
||||
profiles.find(profile =>
|
||||
currentProfile.developerPortalIdentifier
|
||||
? currentProfile.developerPortalIdentifier === profile.provisioningProfileId
|
||||
: currentProfile.provisioningProfile === profile.provisioningProfile
|
||||
) ?? null
|
||||
);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
AppleDistributionCertificateFragment,
|
||||
AppleProvisioningProfileFragment,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { testProvisioningProfileFragment } from '../../../../__tests__/fixtures-ios';
|
||||
import { Context } from '../../../../context';
|
||||
import { AppLookupParams } from '../../../api/GraphqlClient';
|
||||
import { AppleProvisioningProfileMutationResult } from '../../../api/graphql/mutations/AppleProvisioningProfileMutation';
|
||||
|
||||
export class ConfigureProvisioningProfile {
|
||||
constructor(
|
||||
private app: AppLookupParams,
|
||||
private distributionCertificate: AppleDistributionCertificateFragment,
|
||||
private originalProvisioningProfile: AppleProvisioningProfileFragment
|
||||
) {}
|
||||
|
||||
public async runAsync(ctx: Context): Promise<AppleProvisioningProfileMutationResult | null> {
|
||||
return testProvisioningProfileFragment;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { AppleDistributionCertificateFragment } from '../../../../../graphql/generated';
|
||||
import { testProvisioningProfileFragment } from '../../../../__tests__/fixtures-ios';
|
||||
import { Context } from '../../../../context';
|
||||
import { AppLookupParams } from '../../../api/GraphqlClient';
|
||||
import { AppleProvisioningProfileMutationResult } from '../../../api/graphql/mutations/AppleProvisioningProfileMutation';
|
||||
|
||||
export class CreateProvisioningProfile {
|
||||
constructor(
|
||||
private app: AppLookupParams,
|
||||
private distributionCertificate: AppleDistributionCertificateFragment
|
||||
) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<AppleProvisioningProfileMutationResult> {
|
||||
return testProvisioningProfileFragment;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import {
|
||||
AppleDistributionCertificateFragment,
|
||||
IosDistributionType,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { Action, CredentialsManager } from '../../../../CredentialsManager';
|
||||
import { testDistCertFragmentOneDependency } from '../../../../__tests__/fixtures-ios';
|
||||
import { Context } from '../../../../context';
|
||||
import { AppLookupParams } from '../../../api/GraphqlClient';
|
||||
export class SetupDistributionCertificate implements Action {
|
||||
constructor(private app: AppLookupParams, private distributionType: IosDistributionType) {}
|
||||
|
||||
public get distributionCertificate(): AppleDistributionCertificateFragment {
|
||||
return testDistCertFragmentOneDependency;
|
||||
}
|
||||
|
||||
public async runAsync(_manager: CredentialsManager, _ctx: Context): Promise<void> {}
|
||||
}
|
||||
+3
-5
@@ -1,6 +1,6 @@
|
||||
import { confirmAsync } from '../../../../../prompts';
|
||||
import { getAppstoreMock, testAuthCtx } from '../../../../__tests__/fixtures-appstore';
|
||||
import { createCtxMock, createManagerMock } from '../../../../__tests__/fixtures-context';
|
||||
import { createCtxMock } from '../../../../__tests__/fixtures-context';
|
||||
import {
|
||||
testDistCertFragmentNoDependencies,
|
||||
testProvisioningProfile,
|
||||
@@ -13,7 +13,6 @@ jest.mock('../../../../../prompts');
|
||||
|
||||
describe('CreateProvisioningProfile', () => {
|
||||
it('creates a Provisioning Profile in Interactive Mode', async () => {
|
||||
const manager = createManagerMock();
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
@@ -28,7 +27,7 @@ describe('CreateProvisioningProfile', () => {
|
||||
appLookupParams,
|
||||
testDistCertFragmentNoDependencies
|
||||
);
|
||||
await createProvProfAction.runAsync(manager, ctx);
|
||||
await createProvProfAction.runAsync(ctx);
|
||||
|
||||
// expect provisioning profile to be created on expo servers
|
||||
expect((ctx.newIos.createProvisioningProfileAsync as any).mock.calls.length).toBe(1);
|
||||
@@ -36,7 +35,6 @@ describe('CreateProvisioningProfile', () => {
|
||||
expect((ctx.appStore.createProvisioningProfileAsync as any).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('errors in Non Interactive Mode', async () => {
|
||||
const manager = createManagerMock();
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
});
|
||||
@@ -45,7 +43,7 @@ describe('CreateProvisioningProfile', () => {
|
||||
appLookupParams,
|
||||
testDistCertFragmentNoDependencies
|
||||
);
|
||||
await expect(createProvProfAction.runAsync(manager, ctx)).rejects.toThrowError(
|
||||
await expect(createProvProfAction.runAsync(ctx)).rejects.toThrowError(
|
||||
MissingCredentialsNonInteractiveError
|
||||
);
|
||||
|
||||
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
import { confirmAsync } from '../../../../../prompts';
|
||||
import { getAppstoreMock, testAuthCtx } from '../../../../__tests__/fixtures-appstore';
|
||||
import { createCtxMock, createManagerMock } from '../../../../__tests__/fixtures-context';
|
||||
import {
|
||||
testAppleAppIdentifierFragment,
|
||||
testIosAppBuildCredentialsFragment,
|
||||
testIosAppCredentialsWithBuildCredentialsQueryResult,
|
||||
} from '../../../../__tests__/fixtures-ios';
|
||||
import { getNewIosApiMockWithoutCredentials } from '../../../../__tests__/fixtures-new-ios';
|
||||
import { ManageIosBeta } from '../../../../manager/ManageIosBeta';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import { validateProvisioningProfileAsync } from '../../../validators/validateProvisioningProfile';
|
||||
import { SetupProvisioningProfile } from '../SetupProvisioningProfile';
|
||||
jest.mock('../../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
jest.mock('../SetupDistributionCertificate');
|
||||
jest.mock('../ConfigureProvisioningProfile');
|
||||
jest.mock('../CreateProvisioningProfile');
|
||||
jest.mock('../../../validators/validateProvisioningProfile');
|
||||
|
||||
describe('SetupProvisioningProfile', () => {
|
||||
it('repairs existing Provisioning Profile with bad build credentials in Interactive Mode', async () => {
|
||||
(validateProvisioningProfileAsync as jest.Mock).mockImplementation(() => ({
|
||||
error: 'testing: everything is fine, ignore this',
|
||||
ok: false,
|
||||
}));
|
||||
const manager = createManagerMock();
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
listProvisioningProfilesAsync: jest.fn(() => [
|
||||
{
|
||||
provisioningProfileId:
|
||||
testIosAppCredentialsWithBuildCredentialsQueryResult.iosAppBuildCredentialsArray[0]
|
||||
.provisioningProfile.developerPortalIdentifier,
|
||||
},
|
||||
]),
|
||||
},
|
||||
newIos: {
|
||||
...getNewIosApiMockWithoutCredentials(),
|
||||
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
|
||||
() => testIosAppCredentialsWithBuildCredentialsQueryResult
|
||||
),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
createOrUpdateIosAppBuildCredentialsAsync: jest.fn(
|
||||
() => testIosAppBuildCredentialsFragment
|
||||
),
|
||||
},
|
||||
});
|
||||
const appLookupParams = ManageIosBeta.getAppLookupParamsFromContext(ctx);
|
||||
const setupProvisioningProfileAction = new SetupProvisioningProfile(appLookupParams);
|
||||
await setupProvisioningProfileAction.runAsync(manager, ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect((ctx.newIos.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect((ctx.newIos.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('sets up a new Provisioning Profile with bad build credentials in Interactive Mode', async () => {
|
||||
(validateProvisioningProfileAsync as jest.Mock).mockImplementation(() => ({
|
||||
error: 'testing: everything is fine, ignore this',
|
||||
ok: false,
|
||||
}));
|
||||
const manager = createManagerMock();
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
listProvisioningProfilesAsync: jest.fn(() => []),
|
||||
},
|
||||
newIos: {
|
||||
...getNewIosApiMockWithoutCredentials(),
|
||||
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
|
||||
() => testIosAppCredentialsWithBuildCredentialsQueryResult
|
||||
),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = ManageIosBeta.getAppLookupParamsFromContext(ctx);
|
||||
const setupProvisioningProfileAction = new SetupProvisioningProfile(appLookupParams);
|
||||
await setupProvisioningProfileAction.runAsync(manager, ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect((ctx.newIos.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile to be deleted on expo servers
|
||||
expect((ctx.newIos.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('skips setting up a Provisioning Profile with prior build credentials configured properly in Interactive Mode', async () => {
|
||||
(validateProvisioningProfileAsync as jest.Mock).mockImplementation(() => ({
|
||||
ok: true,
|
||||
}));
|
||||
const manager = createManagerMock();
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
//listProvisioningProfilesAsync: jest.fn(() => []),
|
||||
},
|
||||
newIos: {
|
||||
...getNewIosApiMockWithoutCredentials(),
|
||||
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
|
||||
() => testIosAppCredentialsWithBuildCredentialsQueryResult
|
||||
),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = ManageIosBeta.getAppLookupParamsFromContext(ctx);
|
||||
const setupProvisioningProfileAction = new SetupProvisioningProfile(appLookupParams);
|
||||
await setupProvisioningProfileAction.runAsync(manager, ctx);
|
||||
|
||||
// expect build credentials not to be created or updated on expo servers
|
||||
expect((ctx.newIos.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(0);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect((ctx.newIos.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('sets up a Provisioning Profile with no prior build credentials configured in Interactive Mode', async () => {
|
||||
const manager = createManagerMock();
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
},
|
||||
newIos: {
|
||||
...getNewIosApiMockWithoutCredentials(),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = ManageIosBeta.getAppLookupParamsFromContext(ctx);
|
||||
const setupProvisioningProfileAction = new SetupProvisioningProfile(appLookupParams);
|
||||
await setupProvisioningProfileAction.runAsync(manager, ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect((ctx.newIos.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect((ctx.newIos.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('errors in Non Interactive Mode', async () => {
|
||||
const manager = createManagerMock();
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
});
|
||||
const appLookupParams = ManageIosBeta.getAppLookupParamsFromContext(ctx);
|
||||
const setupProvisioningProfileAction = new SetupProvisioningProfile(appLookupParams);
|
||||
await expect(setupProvisioningProfileAction.runAsync(manager, ctx)).rejects.toThrowError(
|
||||
MissingCredentialsNonInteractiveError
|
||||
);
|
||||
|
||||
// expect build credentials not to be created or updated on expo servers
|
||||
expect((ctx.newIos.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(0);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect((ctx.newIos.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import { PlistArray, PlistObject } from '@expo/plist';
|
||||
import crypto from 'crypto';
|
||||
import minimatch from 'minimatch';
|
||||
|
||||
import Log from '../../../log';
|
||||
import { Context } from '../../context';
|
||||
import { DistributionCertificate, ProvisioningProfile } from '../appstore/Credentials.types';
|
||||
import { getP12CertFingerprint } from '../utils/p12Certificate';
|
||||
@@ -14,8 +15,8 @@ interface ValidationResult {
|
||||
|
||||
export async function validateProvisioningProfileAsync(
|
||||
ctx: Context,
|
||||
profile: ProvisioningProfile,
|
||||
distCert: DistributionCertificate,
|
||||
profile: Pick<ProvisioningProfile, 'provisioningProfile' | 'provisioningProfileId'>,
|
||||
distCert: Pick<DistributionCertificate, 'certP12' | 'certPassword'>,
|
||||
bundleIdentifier: string
|
||||
): Promise<ValidationResult> {
|
||||
const resultWithoutApple = validateProvisioningProfileWithoutApple(
|
||||
@@ -23,21 +24,33 @@ export async function validateProvisioningProfileAsync(
|
||||
distCert,
|
||||
bundleIdentifier
|
||||
);
|
||||
|
||||
// already failed, return early
|
||||
if (!resultWithoutApple.ok) {
|
||||
return resultWithoutApple;
|
||||
}
|
||||
|
||||
if (!ctx.appStore.authCtx) {
|
||||
Log.warn(
|
||||
"Skipping Provisioning Profile validation on Apple Servers because we aren't authenticated."
|
||||
);
|
||||
return resultWithoutApple;
|
||||
}
|
||||
|
||||
return await validateProvisioningProfileWithAppleAsync(ctx, profile, bundleIdentifier);
|
||||
}
|
||||
|
||||
async function validateProvisioningProfileWithAppleAsync(
|
||||
ctx: Context,
|
||||
profile: ProvisioningProfile,
|
||||
profile: Pick<ProvisioningProfile, 'provisioningProfile' | 'provisioningProfileId'>,
|
||||
bundleIdentifier: string
|
||||
): Promise<ValidationResult> {
|
||||
const profilesFromApple = await ctx.appStore.listProvisioningProfilesAsync(bundleIdentifier);
|
||||
|
||||
const configuredProfileFromApple = profilesFromApple.find(
|
||||
appleProfile => appleProfile.provisioningProfileId === profile.provisioningProfileId
|
||||
const configuredProfileFromApple = profilesFromApple.find(appleProfile =>
|
||||
profile.provisioningProfileId
|
||||
? appleProfile.provisioningProfileId === profile.provisioningProfileId
|
||||
: appleProfile.provisioningProfile === profile.provisioningProfile
|
||||
);
|
||||
if (!configuredProfileFromApple) {
|
||||
return {
|
||||
@@ -45,12 +58,18 @@ async function validateProvisioningProfileWithAppleAsync(
|
||||
ok: false,
|
||||
};
|
||||
}
|
||||
if (configuredProfileFromApple.status !== 'ACTIVE') {
|
||||
return {
|
||||
error: `Provisioning profile (id: ${profile.provisioningProfileId}) is no longer valid`,
|
||||
ok: false,
|
||||
};
|
||||
}
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
export function validateProvisioningProfileWithoutApple(
|
||||
provisioningProfile: ProvisioningProfile,
|
||||
distCert: DistributionCertificate,
|
||||
provisioningProfile: Pick<ProvisioningProfile, 'provisioningProfile' | 'provisioningProfileId'>,
|
||||
distCert: Pick<DistributionCertificate, 'certP12' | 'certPassword'>,
|
||||
bundleIdentifier: string
|
||||
): ValidationResult {
|
||||
try {
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import {
|
||||
CombinedError as GraphqlError,
|
||||
OperationResult,
|
||||
cacheExchange,
|
||||
createClient as createUrqlClient,
|
||||
dedupExchange,
|
||||
fetchExchange,
|
||||
} from '@urql/core';
|
||||
import { retryExchange } from '@urql/exchange-retry';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import { getExpoApiBaseUrl } from '../api';
|
||||
import Log from '../log';
|
||||
import { getAccessToken, getSessionSecret } from '../user/sessionStorage';
|
||||
|
||||
type AccessTokenHeaders = {
|
||||
@@ -18,6 +23,16 @@ type SessionHeaders = {
|
||||
|
||||
export const graphqlClient = createUrqlClient({
|
||||
url: getExpoApiBaseUrl() + '/--/graphql',
|
||||
exchanges: [
|
||||
dedupExchange,
|
||||
cacheExchange,
|
||||
retryExchange({
|
||||
maxDelayMs: 4000,
|
||||
retryIf: err =>
|
||||
!!(err && (err.networkError || err.graphQLErrors.some(e => e?.extensions?.isTransient))),
|
||||
}),
|
||||
fetchExchange,
|
||||
],
|
||||
// @ts-expect-error Type 'typeof fetch' is not assignable to type '(input: RequestInfo, init?: RequestInit | undefined) => Promise<Response>'.
|
||||
fetch,
|
||||
fetchOptions: (): { headers?: AccessTokenHeaders | SessionHeaders } => {
|
||||
@@ -45,6 +60,9 @@ export async function withErrorHandlingAsync<T>(promise: Promise<OperationResult
|
||||
const { data, error } = await promise;
|
||||
|
||||
if (error) {
|
||||
if (error.graphQLErrors.some(e => e?.extensions?.isTransient)) {
|
||||
Log.error(`We've encountered a transient error, please try again shortly.`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,8 @@ export type RootQuery = {
|
||||
* query object
|
||||
*/
|
||||
meActor?: Maybe<Actor>;
|
||||
/** Top-level query object for querying Webhooks. */
|
||||
webhook: WebhookQuery;
|
||||
};
|
||||
|
||||
|
||||
@@ -161,8 +163,8 @@ export type ActivityTimelineProjectActivity = {
|
||||
export type Actor = {
|
||||
id: Scalars['ID'];
|
||||
firstName?: Maybe<Scalars['String']>;
|
||||
created?: Maybe<Scalars['DateTime']>;
|
||||
isExpoAdmin?: Maybe<Scalars['Boolean']>;
|
||||
created: Scalars['DateTime'];
|
||||
isExpoAdmin: Scalars['Boolean'];
|
||||
/** Associated accounts */
|
||||
accounts: Array<Account>;
|
||||
/** Access Tokens belonging to this actor */
|
||||
@@ -189,19 +191,12 @@ export type Account = {
|
||||
__typename?: 'Account';
|
||||
id: Scalars['ID'];
|
||||
name: Scalars['String'];
|
||||
isCurrent?: Maybe<Scalars['Boolean']>;
|
||||
/** @deprecated Build packs are no longer supported */
|
||||
availableBuilds?: Maybe<Scalars['Int']>;
|
||||
unlimitedBuilds?: Maybe<Scalars['Boolean']>;
|
||||
/** @deprecated No longer needed */
|
||||
subscriptionChangesPending?: Maybe<Scalars['Boolean']>;
|
||||
/** @deprecated Build packs are no longer supported */
|
||||
willAutoRenewBuilds?: Maybe<Scalars['Boolean']>;
|
||||
pushSecurityEnabled?: Maybe<Scalars['Boolean']>;
|
||||
isCurrent: Scalars['Boolean'];
|
||||
pushSecurityEnabled: Scalars['Boolean'];
|
||||
createdAt: Scalars['DateTime'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
/** short lived property for test */
|
||||
offers?: Maybe<Array<Maybe<Offer>>>;
|
||||
/** Offers set on this account */
|
||||
offers?: Maybe<Array<Offer>>;
|
||||
/** Snacks associated with this account */
|
||||
snacks: Array<Snack>;
|
||||
/** Apps associated with this account */
|
||||
@@ -219,13 +214,6 @@ export type Account = {
|
||||
userInvitations: Array<UserInvitation>;
|
||||
/** Billing information */
|
||||
billing?: Maybe<Billing>;
|
||||
/**
|
||||
* Api tokens made for project
|
||||
* @deprecated Legacy access tokens are deprecated
|
||||
*/
|
||||
accessTokens: Array<Maybe<AccessToken>>;
|
||||
/** @deprecated Legacy access tokens are deprecated */
|
||||
requiresAccessTokenForPushSecurity: Scalars['Boolean'];
|
||||
/** iOS credentials for account */
|
||||
appleTeams: Array<AppleTeam>;
|
||||
appleAppIdentifiers: Array<AppleAppIdentifier>;
|
||||
@@ -235,6 +223,18 @@ export type Account = {
|
||||
appleDevices: Array<AppleDevice>;
|
||||
/** Environment secrets for an account */
|
||||
environmentSecrets: Array<EnvironmentSecret>;
|
||||
/** @deprecated Legacy access tokens are deprecated */
|
||||
accessTokens: Array<Maybe<AccessToken>>;
|
||||
/** @deprecated Legacy access tokens are deprecated */
|
||||
requiresAccessTokenForPushSecurity: Scalars['Boolean'];
|
||||
/** @deprecated See isCurrent */
|
||||
unlimitedBuilds: Scalars['Boolean'];
|
||||
/** @deprecated Build packs are no longer supported */
|
||||
availableBuilds?: Maybe<Scalars['Int']>;
|
||||
/** @deprecated No longer needed */
|
||||
subscriptionChangesPending?: Maybe<Scalars['Boolean']>;
|
||||
/** @deprecated Build packs are no longer supported */
|
||||
willAutoRenewBuilds?: Maybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -382,12 +382,12 @@ export type Project = {
|
||||
name: Scalars['String'];
|
||||
fullName: Scalars['String'];
|
||||
description: Scalars['String'];
|
||||
/** @deprecated Field no longer supported */
|
||||
iconUrl?: Maybe<Scalars['String']>;
|
||||
slug: Scalars['String'];
|
||||
username: Scalars['String'];
|
||||
updated: Scalars['DateTime'];
|
||||
published: Scalars['Boolean'];
|
||||
username: Scalars['String'];
|
||||
/** @deprecated Field no longer supported */
|
||||
iconUrl?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
@@ -397,52 +397,22 @@ export type App = Project & {
|
||||
name: Scalars['String'];
|
||||
fullName: Scalars['String'];
|
||||
description: Scalars['String'];
|
||||
slug: Scalars['String'];
|
||||
updated: Scalars['DateTime'];
|
||||
published: Scalars['Boolean'];
|
||||
ownerAccount: Account;
|
||||
githubUrl?: Maybe<Scalars['String']>;
|
||||
playStoreUrl?: Maybe<Scalars['String']>;
|
||||
appStoreUrl?: Maybe<Scalars['String']>;
|
||||
/** @deprecated Field no longer supported */
|
||||
iconUrl?: Maybe<Scalars['String']>;
|
||||
icon?: Maybe<AppIcon>;
|
||||
slug: Scalars['String'];
|
||||
sdkVersion: Scalars['String'];
|
||||
isDeprecated?: Maybe<Scalars['Boolean']>;
|
||||
username: Scalars['String'];
|
||||
updated: Scalars['DateTime'];
|
||||
pushSecurityEnabled?: Maybe<Scalars['Boolean']>;
|
||||
ownerAccount: Account;
|
||||
/** @deprecated Use 'privacySetting' instead. */
|
||||
privacy: Scalars['String'];
|
||||
isDeprecated: Scalars['Boolean'];
|
||||
privacySetting: AppPrivacy;
|
||||
latestReleaseId: Scalars['ID'];
|
||||
/** @deprecated 'likes' have been deprecated. */
|
||||
isLikedByMe: Scalars['Boolean'];
|
||||
published: Scalars['Boolean'];
|
||||
/** @deprecated 'likes' have been deprecated. */
|
||||
likeCount: Scalars['Int'];
|
||||
/** @deprecated 'likes' have been deprecated. */
|
||||
trendScore: Scalars['Float'];
|
||||
/** @deprecated 'likes' have been deprecated. */
|
||||
likedBy: Array<Maybe<User>>;
|
||||
/** @deprecated Field no longer supported */
|
||||
releases: Array<Maybe<AppRelease>>;
|
||||
pushSecurityEnabled: Scalars['Boolean'];
|
||||
/** (EAS Build) Builds associated with this app */
|
||||
builds: Array<Build>;
|
||||
buildJobs: Array<BuildJob>;
|
||||
/** @deprecated Field no longer supported */
|
||||
users?: Maybe<Array<Maybe<User>>>;
|
||||
/** @deprecated Field no longer supported */
|
||||
lastPublishedTime: Scalars['DateTime'];
|
||||
/** @deprecated Field no longer supported */
|
||||
packageUsername: Scalars['String'];
|
||||
/** @deprecated Field no longer supported */
|
||||
packageName: Scalars['String'];
|
||||
/**
|
||||
* Api tokens made for project
|
||||
* @deprecated Legacy access tokens are deprecated
|
||||
*/
|
||||
accessTokens: Array<Maybe<AccessToken>>;
|
||||
/** @deprecated Legacy access tokens are deprecated */
|
||||
requiresAccessTokenForPushSecurity: Scalars['Boolean'];
|
||||
/** iOS app credentials for the project */
|
||||
iosAppCredentials: Array<IosAppCredentials>;
|
||||
/** Android app credentials for the project */
|
||||
@@ -459,21 +429,36 @@ export type App = Project & {
|
||||
activityTimelineProjectActivities: Array<ActivityTimelineProjectActivity>;
|
||||
/** Environment secrets for an app */
|
||||
environmentSecrets: Array<EnvironmentSecret>;
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppLikedByArgs = {
|
||||
offset?: Maybe<Scalars['Int']>;
|
||||
limit?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppReleasesArgs = {
|
||||
platform: AppPlatform;
|
||||
offset?: Maybe<Scalars['Int']>;
|
||||
limit?: Maybe<Scalars['Int']>;
|
||||
/** Webhooks for an app */
|
||||
webhooks: Array<Webhook>;
|
||||
/** @deprecated Use ownerAccount.name instead */
|
||||
username: Scalars['String'];
|
||||
/** @deprecated Field no longer supported */
|
||||
iconUrl?: Maybe<Scalars['String']>;
|
||||
/** @deprecated Use 'privacySetting' instead. */
|
||||
privacy: Scalars['String'];
|
||||
/** @deprecated Field no longer supported */
|
||||
lastPublishedTime: Scalars['DateTime'];
|
||||
/** @deprecated Field no longer supported */
|
||||
packageUsername: Scalars['String'];
|
||||
/** @deprecated Field no longer supported */
|
||||
packageName: Scalars['String'];
|
||||
/** @deprecated Legacy access tokens are deprecated */
|
||||
accessTokens: Array<Maybe<AccessToken>>;
|
||||
/** @deprecated Legacy access tokens are deprecated */
|
||||
requiresAccessTokenForPushSecurity: Scalars['Boolean'];
|
||||
/** @deprecated 'likes' have been deprecated. */
|
||||
isLikedByMe: Scalars['Boolean'];
|
||||
/** @deprecated 'likes' have been deprecated. */
|
||||
likeCount: Scalars['Int'];
|
||||
/** @deprecated 'likes' have been deprecated. */
|
||||
trendScore: Scalars['Float'];
|
||||
/** @deprecated 'likes' have been deprecated. */
|
||||
likedBy: Array<Maybe<User>>;
|
||||
/** @deprecated Field no longer supported */
|
||||
users?: Maybe<Array<Maybe<User>>>;
|
||||
/** @deprecated Field no longer supported */
|
||||
releases: Array<Maybe<AppRelease>>;
|
||||
};
|
||||
|
||||
|
||||
@@ -544,6 +529,27 @@ export type AppEnvironmentSecretsArgs = {
|
||||
filterNames?: Maybe<Array<Scalars['String']>>;
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppWebhooksArgs = {
|
||||
filter?: Maybe<WebhookFilter>;
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppLikedByArgs = {
|
||||
offset?: Maybe<Scalars['Int']>;
|
||||
limit?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppReleasesArgs = {
|
||||
platform: AppPlatform;
|
||||
offset?: Maybe<Scalars['Int']>;
|
||||
limit?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
export type AppIcon = {
|
||||
__typename?: 'AppIcon';
|
||||
url: Scalars['String'];
|
||||
@@ -560,6 +566,46 @@ export enum AppPrivacy {
|
||||
Hidden = 'HIDDEN'
|
||||
}
|
||||
|
||||
export enum BuildStatus {
|
||||
InQueue = 'IN_QUEUE',
|
||||
InProgress = 'IN_PROGRESS',
|
||||
Errored = 'ERRORED',
|
||||
Finished = 'FINISHED',
|
||||
Canceled = 'CANCELED'
|
||||
}
|
||||
|
||||
export enum AppPlatform {
|
||||
Ios = 'IOS',
|
||||
Android = 'ANDROID'
|
||||
}
|
||||
|
||||
/** Represents an EAS Build */
|
||||
export type Build = ActivityTimelineProjectActivity & {
|
||||
__typename?: 'Build';
|
||||
id: Scalars['ID'];
|
||||
actor?: Maybe<Actor>;
|
||||
activityTimestamp: Scalars['DateTime'];
|
||||
project: Project;
|
||||
/** @deprecated User type is deprecated */
|
||||
initiatingUser?: Maybe<User>;
|
||||
initiatingActor?: Maybe<Actor>;
|
||||
artifacts?: Maybe<BuildArtifacts>;
|
||||
logFiles: Array<Scalars['String']>;
|
||||
updatedAt?: Maybe<Scalars['DateTime']>;
|
||||
createdAt?: Maybe<Scalars['DateTime']>;
|
||||
status: BuildStatus;
|
||||
expirationDate?: Maybe<Scalars['DateTime']>;
|
||||
platform: AppPlatform;
|
||||
appVersion?: Maybe<Scalars['String']>;
|
||||
sdkVersion?: Maybe<Scalars['String']>;
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
metrics?: Maybe<BuildMetrics>;
|
||||
distribution?: Maybe<DistributionType>;
|
||||
buildProfile?: Maybe<Scalars['String']>;
|
||||
gitCommitHash?: Maybe<Scalars['String']>;
|
||||
error?: Maybe<BuildError>;
|
||||
};
|
||||
|
||||
/** Represents a human (not robot) actor. */
|
||||
export type User = Actor & {
|
||||
__typename?: 'User';
|
||||
@@ -569,22 +615,17 @@ export type User = Actor & {
|
||||
firstName?: Maybe<Scalars['String']>;
|
||||
lastName?: Maybe<Scalars['String']>;
|
||||
fullName?: Maybe<Scalars['String']>;
|
||||
profilePhoto?: Maybe<Scalars['String']>;
|
||||
created?: Maybe<Scalars['DateTime']>;
|
||||
lastLogin?: Maybe<Scalars['DateTime']>;
|
||||
lastPasswordReset?: Maybe<Scalars['DateTime']>;
|
||||
profilePhoto: Scalars['String'];
|
||||
created: Scalars['DateTime'];
|
||||
industry?: Maybe<Scalars['String']>;
|
||||
location?: Maybe<Scalars['String']>;
|
||||
appCount?: Maybe<Scalars['Int']>;
|
||||
appCount: Scalars['Int'];
|
||||
githubUsername?: Maybe<Scalars['String']>;
|
||||
twitterUsername?: Maybe<Scalars['String']>;
|
||||
appetizeCode?: Maybe<Scalars['String']>;
|
||||
emailVerified?: Maybe<Scalars['Boolean']>;
|
||||
isOnboarded?: Maybe<Scalars['Boolean']>;
|
||||
isLegacy?: Maybe<Scalars['Boolean']>;
|
||||
wasLegacy?: Maybe<Scalars['Boolean']>;
|
||||
isEmailUnsubscribed?: Maybe<Scalars['Boolean']>;
|
||||
isExpoAdmin?: Maybe<Scalars['Boolean']>;
|
||||
emailVerified: Scalars['Boolean'];
|
||||
isEmailUnsubscribed: Scalars['Boolean'];
|
||||
isExpoAdmin: Scalars['Boolean'];
|
||||
isSecondFactorAuthenticationEnabled: Scalars['Boolean'];
|
||||
/** Get all certified second factor authentication methods */
|
||||
secondFactorDevices: Array<UserSecondFactorDevice>;
|
||||
@@ -596,11 +637,6 @@ export type User = Actor & {
|
||||
snacks: Array<Snack>;
|
||||
/** Apps this user has published */
|
||||
apps: Array<App>;
|
||||
/**
|
||||
* Likes this user has made
|
||||
* @deprecated 'likes' have been deprecated.
|
||||
*/
|
||||
likes?: Maybe<Array<Maybe<App>>>;
|
||||
/** Whether this user has any pending user invitations. Only resolves for the viewer. */
|
||||
hasPendingUserInvitations: Scalars['Boolean'];
|
||||
/** Pending UserInvitations for this user. Only resolves for the viewer. */
|
||||
@@ -610,6 +646,18 @@ export type User = Actor & {
|
||||
* Only resolves for the viewer.
|
||||
*/
|
||||
featureGates: Scalars['JSONObject'];
|
||||
/** @deprecated Field no longer supported */
|
||||
lastPasswordReset?: Maybe<Scalars['DateTime']>;
|
||||
/** @deprecated Field no longer supported */
|
||||
lastLogin?: Maybe<Scalars['DateTime']>;
|
||||
/** @deprecated Field no longer supported */
|
||||
isOnboarded?: Maybe<Scalars['Boolean']>;
|
||||
/** @deprecated Field no longer supported */
|
||||
isLegacy?: Maybe<Scalars['Boolean']>;
|
||||
/** @deprecated Field no longer supported */
|
||||
wasLegacy?: Maybe<Scalars['Boolean']>;
|
||||
/** @deprecated 'likes' have been deprecated. */
|
||||
likes?: Maybe<Array<Maybe<App>>>;
|
||||
};
|
||||
|
||||
|
||||
@@ -629,15 +677,15 @@ export type UserAppsArgs = {
|
||||
|
||||
|
||||
/** Represents a human (not robot) actor. */
|
||||
export type UserLikesArgs = {
|
||||
offset: Scalars['Int'];
|
||||
limit: Scalars['Int'];
|
||||
export type UserFeatureGatesArgs = {
|
||||
filter?: Maybe<Array<Scalars['String']>>;
|
||||
};
|
||||
|
||||
|
||||
/** Represents a human (not robot) actor. */
|
||||
export type UserFeatureGatesArgs = {
|
||||
filter?: Maybe<Array<Scalars['String']>>;
|
||||
export type UserLikesArgs = {
|
||||
offset: Scalars['Int'];
|
||||
limit: Scalars['Int'];
|
||||
};
|
||||
|
||||
/** A second factor device belonging to a User */
|
||||
@@ -706,59 +754,6 @@ export enum Role {
|
||||
}
|
||||
|
||||
|
||||
export enum AppPlatform {
|
||||
Ios = 'IOS',
|
||||
Android = 'ANDROID'
|
||||
}
|
||||
|
||||
export type AppRelease = {
|
||||
__typename?: 'AppRelease';
|
||||
id: Scalars['ID'];
|
||||
hash?: Maybe<Scalars['String']>;
|
||||
publishedTime?: Maybe<Scalars['DateTime']>;
|
||||
publishingUsername?: Maybe<Scalars['String']>;
|
||||
sdkVersion?: Maybe<Scalars['String']>;
|
||||
version?: Maybe<Scalars['String']>;
|
||||
s3Key?: Maybe<Scalars['String']>;
|
||||
s3Url?: Maybe<Scalars['String']>;
|
||||
manifest?: Maybe<Scalars['JSON']>;
|
||||
};
|
||||
|
||||
export enum BuildStatus {
|
||||
InQueue = 'IN_QUEUE',
|
||||
InProgress = 'IN_PROGRESS',
|
||||
Errored = 'ERRORED',
|
||||
Finished = 'FINISHED',
|
||||
Canceled = 'CANCELED'
|
||||
}
|
||||
|
||||
/** Represents an EAS Build */
|
||||
export type Build = ActivityTimelineProjectActivity & {
|
||||
__typename?: 'Build';
|
||||
id: Scalars['ID'];
|
||||
actor?: Maybe<Actor>;
|
||||
activityTimestamp: Scalars['DateTime'];
|
||||
project: Project;
|
||||
/** @deprecated User type is deprecated */
|
||||
initiatingUser?: Maybe<User>;
|
||||
initiatingActor?: Maybe<Actor>;
|
||||
artifacts?: Maybe<BuildArtifacts>;
|
||||
logFiles: Array<Scalars['String']>;
|
||||
updatedAt?: Maybe<Scalars['DateTime']>;
|
||||
createdAt?: Maybe<Scalars['DateTime']>;
|
||||
status: BuildStatus;
|
||||
expirationDate?: Maybe<Scalars['DateTime']>;
|
||||
platform: AppPlatform;
|
||||
appVersion?: Maybe<Scalars['String']>;
|
||||
sdkVersion?: Maybe<Scalars['String']>;
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
metrics?: Maybe<BuildMetrics>;
|
||||
distribution?: Maybe<DistributionType>;
|
||||
buildProfile?: Maybe<Scalars['String']>;
|
||||
gitCommitHash?: Maybe<Scalars['String']>;
|
||||
error?: Maybe<BuildError>;
|
||||
};
|
||||
|
||||
export type BuildArtifacts = {
|
||||
__typename?: 'BuildArtifacts';
|
||||
buildUrl?: Maybe<Scalars['String']>;
|
||||
@@ -806,6 +801,19 @@ export type BuildJob = ActivityTimelineProjectActivity & {
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type AppRelease = {
|
||||
__typename?: 'AppRelease';
|
||||
id: Scalars['ID'];
|
||||
hash?: Maybe<Scalars['String']>;
|
||||
publishedTime?: Maybe<Scalars['DateTime']>;
|
||||
publishingUsername?: Maybe<Scalars['String']>;
|
||||
sdkVersion?: Maybe<Scalars['String']>;
|
||||
version?: Maybe<Scalars['String']>;
|
||||
s3Key?: Maybe<Scalars['String']>;
|
||||
s3Url?: Maybe<Scalars['String']>;
|
||||
manifest?: Maybe<Scalars['JSON']>;
|
||||
};
|
||||
|
||||
export type BuildArtifact = {
|
||||
__typename?: 'BuildArtifact';
|
||||
url: Scalars['String'];
|
||||
@@ -1099,6 +1107,24 @@ export type EnvironmentSecret = {
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export type WebhookFilter = {
|
||||
event?: Maybe<WebhookType>;
|
||||
};
|
||||
|
||||
export enum WebhookType {
|
||||
Build = 'BUILD'
|
||||
}
|
||||
|
||||
export type Webhook = {
|
||||
__typename?: 'Webhook';
|
||||
id: Scalars['ID'];
|
||||
appId: Scalars['ID'];
|
||||
event: WebhookType;
|
||||
url: Scalars['String'];
|
||||
createdAt: Scalars['DateTime'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export type UserPermission = {
|
||||
__typename?: 'UserPermission';
|
||||
permissions: Array<Permission>;
|
||||
@@ -1491,6 +1517,16 @@ export type UserQueryByUsernameArgs = {
|
||||
username: Scalars['String'];
|
||||
};
|
||||
|
||||
export type WebhookQuery = {
|
||||
__typename?: 'WebhookQuery';
|
||||
byId: Webhook;
|
||||
};
|
||||
|
||||
|
||||
export type WebhookQueryByIdArgs = {
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type RootMutation = {
|
||||
__typename?: 'RootMutation';
|
||||
/**
|
||||
@@ -1549,6 +1585,8 @@ export type RootMutation = {
|
||||
emailSubscription: EmailSubscriptionMutation;
|
||||
/** Mutations that create and delete EnvironmentSecrets */
|
||||
environmentSecret: EnvironmentSecretMutation;
|
||||
/** Mutations that create, delete, update Webhooks */
|
||||
webhook: WebhookMutation;
|
||||
};
|
||||
|
||||
|
||||
@@ -2419,8 +2457,8 @@ export type Robot = Actor & {
|
||||
__typename?: 'Robot';
|
||||
id: Scalars['ID'];
|
||||
firstName?: Maybe<Scalars['String']>;
|
||||
created?: Maybe<Scalars['DateTime']>;
|
||||
isExpoAdmin?: Maybe<Scalars['Boolean']>;
|
||||
created: Scalars['DateTime'];
|
||||
isExpoAdmin: Scalars['Boolean'];
|
||||
/** Associated accounts */
|
||||
accounts: Array<Account>;
|
||||
/** Access Tokens belonging to this actor */
|
||||
@@ -2584,7 +2622,7 @@ export type UploadSession = {
|
||||
|
||||
|
||||
export type UploadSessionCreateUploadSessionArgs = {
|
||||
type?: Maybe<UploadSessionType>;
|
||||
type: UploadSessionType;
|
||||
};
|
||||
|
||||
export enum UploadSessionType {
|
||||
@@ -2926,6 +2964,44 @@ export type DeleteEnvironmentSecretResult = {
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type WebhookMutation = {
|
||||
__typename?: 'WebhookMutation';
|
||||
/** Create a Webhook */
|
||||
createWebhook: Webhook;
|
||||
/** Update a Webhook */
|
||||
updateWebhook: Webhook;
|
||||
/** Delete a Webhook */
|
||||
deleteWebhook: DeleteWebhookResult;
|
||||
};
|
||||
|
||||
|
||||
export type WebhookMutationCreateWebhookArgs = {
|
||||
appId: Scalars['String'];
|
||||
webhookInput: WebhookInput;
|
||||
};
|
||||
|
||||
|
||||
export type WebhookMutationUpdateWebhookArgs = {
|
||||
webhookId: Scalars['ID'];
|
||||
webhookInput: WebhookInput;
|
||||
};
|
||||
|
||||
|
||||
export type WebhookMutationDeleteWebhookArgs = {
|
||||
webhookId: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type WebhookInput = {
|
||||
url: Scalars['String'];
|
||||
secret: Scalars['String'];
|
||||
event: WebhookType;
|
||||
};
|
||||
|
||||
export type DeleteWebhookResult = {
|
||||
__typename?: 'DeleteWebhookResult';
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
export enum StandardOffer {
|
||||
/** $29 USD per month, 30 day trial */
|
||||
Default = 'DEFAULT',
|
||||
@@ -4091,6 +4167,58 @@ export type CreateUploadSessionMutation = (
|
||||
) }
|
||||
);
|
||||
|
||||
export type CreateWebhookMutationVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
webhookInput: WebhookInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateWebhookMutation = (
|
||||
{ __typename?: 'RootMutation' }
|
||||
& { webhook: (
|
||||
{ __typename?: 'WebhookMutation' }
|
||||
& { createWebhook: (
|
||||
{ __typename?: 'Webhook' }
|
||||
& Pick<Webhook, 'id'>
|
||||
& WebhookFragment
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type UpdateWebhookMutationVariables = Exact<{
|
||||
webhookId: Scalars['ID'];
|
||||
webhookInput: WebhookInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateWebhookMutation = (
|
||||
{ __typename?: 'RootMutation' }
|
||||
& { webhook: (
|
||||
{ __typename?: 'WebhookMutation' }
|
||||
& { updateWebhook: (
|
||||
{ __typename?: 'Webhook' }
|
||||
& Pick<Webhook, 'id'>
|
||||
& WebhookFragment
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type DeleteWebhookMutationVariables = Exact<{
|
||||
webhookId: Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteWebhookMutation = (
|
||||
{ __typename?: 'RootMutation' }
|
||||
& { webhook: (
|
||||
{ __typename?: 'WebhookMutation' }
|
||||
& { deleteWebhook: (
|
||||
{ __typename?: 'DeleteWebhookResult' }
|
||||
& Pick<DeleteWebhookResult, 'id'>
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type BuildsByIdQueryVariables = Exact<{
|
||||
buildId: Scalars['ID'];
|
||||
}>;
|
||||
@@ -4257,6 +4385,45 @@ export type CurrentUserQuery = (
|
||||
)> }
|
||||
);
|
||||
|
||||
export type WebhooksByAppIdQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
webhookFilter?: Maybe<WebhookFilter>;
|
||||
}>;
|
||||
|
||||
|
||||
export type WebhooksByAppIdQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
& Pick<App, 'id'>
|
||||
& { webhooks: Array<(
|
||||
{ __typename?: 'Webhook' }
|
||||
& Pick<Webhook, 'id'>
|
||||
& WebhookFragment
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
);
|
||||
|
||||
export type WebhookByIdQueryVariables = Exact<{
|
||||
webhookId: Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type WebhookByIdQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { webhook: (
|
||||
{ __typename?: 'WebhookQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'Webhook' }
|
||||
& Pick<Webhook, 'id'>
|
||||
& WebhookFragment
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type AppFragment = (
|
||||
{ __typename?: 'App' }
|
||||
& Pick<App, 'id' | 'fullName' | 'slug'>
|
||||
@@ -4295,6 +4462,11 @@ export type EnvironmentSecretFragment = (
|
||||
& Pick<EnvironmentSecret, 'id' | 'name' | 'createdAt'>
|
||||
);
|
||||
|
||||
export type WebhookFragment = (
|
||||
{ __typename?: 'Webhook' }
|
||||
& Pick<Webhook, 'id' | 'event' | 'url' | 'createdAt' | 'updatedAt'>
|
||||
);
|
||||
|
||||
export type AppleAppIdentifierFragment = (
|
||||
{ __typename?: 'AppleAppIdentifier' }
|
||||
& Pick<AppleAppIdentifier, 'id' | 'bundleIdentifier'>
|
||||
@@ -4342,7 +4514,7 @@ export type AppleDistributionCertificateFragment = (
|
||||
|
||||
export type AppleProvisioningProfileFragment = (
|
||||
{ __typename?: 'AppleProvisioningProfile' }
|
||||
& Pick<AppleProvisioningProfile, 'id' | 'expiration' | 'developerPortalIdentifier' | 'provisioningProfile'>
|
||||
& Pick<AppleProvisioningProfile, 'id' | 'expiration' | 'developerPortalIdentifier' | 'provisioningProfile' | 'updatedAt' | 'status'>
|
||||
& { appleTeam?: Maybe<(
|
||||
{ __typename?: 'AppleTeam' }
|
||||
& Pick<AppleTeam, 'id' | 'appleTeamIdentifier' | 'appleTeamName'>
|
||||
@@ -4367,21 +4539,12 @@ export type IosAppBuildCredentialsFragment = (
|
||||
& Pick<IosAppBuildCredentials, 'id' | 'iosDistributionType'>
|
||||
& { distributionCertificate?: Maybe<(
|
||||
{ __typename?: 'AppleDistributionCertificate' }
|
||||
& Pick<AppleDistributionCertificate, 'id' | 'certificateP12' | 'certificatePassword' | 'serialNumber' | 'developerPortalIdentifier' | 'validityNotBefore' | 'validityNotAfter' | 'updatedAt'>
|
||||
& { appleTeam?: Maybe<(
|
||||
{ __typename?: 'AppleTeam' }
|
||||
& Pick<AppleTeam, 'id' | 'appleTeamIdentifier' | 'appleTeamName'>
|
||||
)> }
|
||||
& Pick<AppleDistributionCertificate, 'id'>
|
||||
& AppleDistributionCertificateFragment
|
||||
)>, provisioningProfile?: Maybe<(
|
||||
{ __typename?: 'AppleProvisioningProfile' }
|
||||
& Pick<AppleProvisioningProfile, 'id' | 'expiration' | 'developerPortalIdentifier' | 'provisioningProfile' | 'updatedAt' | 'status'>
|
||||
& { appleDevices: Array<(
|
||||
{ __typename?: 'AppleDevice' }
|
||||
& Pick<AppleDevice, 'id' | 'identifier' | 'name' | 'model' | 'deviceClass'>
|
||||
)>, appleTeam?: Maybe<(
|
||||
{ __typename?: 'AppleTeam' }
|
||||
& Pick<AppleTeam, 'id' | 'appleTeamIdentifier' | 'appleTeamName'>
|
||||
)> }
|
||||
& Pick<AppleProvisioningProfile, 'id'>
|
||||
& AppleProvisioningProfileFragment
|
||||
)> }
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
CreateWebhookMutation,
|
||||
CreateWebhookMutationVariables,
|
||||
DeleteWebhookMutation,
|
||||
DeleteWebhookMutationVariables,
|
||||
UpdateWebhookMutation,
|
||||
UpdateWebhookMutationVariables,
|
||||
WebhookFragment,
|
||||
WebhookInput,
|
||||
} from '../generated';
|
||||
import { WebhookFragmentNode } from '../types/Webhook';
|
||||
|
||||
export const WebhookMutation = {
|
||||
async createWebhookAsync(appId: string, webhookInput: WebhookInput): Promise<WebhookFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateWebhookMutation, CreateWebhookMutationVariables>(
|
||||
gql`
|
||||
mutation CreateWebhookMutation($appId: String!, $webhookInput: WebhookInput!) {
|
||||
webhook {
|
||||
createWebhook(appId: $appId, webhookInput: $webhookInput) {
|
||||
id
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(WebhookFragmentNode)}
|
||||
`,
|
||||
{ appId, webhookInput }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.webhook.createWebhook;
|
||||
},
|
||||
async updateWebhookAsync(
|
||||
webhookId: string,
|
||||
webhookInput: WebhookInput
|
||||
): Promise<WebhookFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<UpdateWebhookMutation, UpdateWebhookMutationVariables>(
|
||||
gql`
|
||||
mutation UpdateWebhookMutation($webhookId: ID!, $webhookInput: WebhookInput!) {
|
||||
webhook {
|
||||
updateWebhook(webhookId: $webhookId, webhookInput: $webhookInput) {
|
||||
id
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(WebhookFragmentNode)}
|
||||
`,
|
||||
{ webhookId, webhookInput }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.webhook.updateWebhook;
|
||||
},
|
||||
async deleteWebhookAsync(webhookId: string): Promise<void> {
|
||||
await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<DeleteWebhookMutation, DeleteWebhookMutationVariables>(
|
||||
gql`
|
||||
mutation DeleteWebhookMutation($webhookId: ID!) {
|
||||
webhook {
|
||||
deleteWebhook(webhookId: $webhookId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ webhookId }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -26,7 +26,10 @@ type Filters = {
|
||||
type PendingBuildQueryResult = Pick<Build, 'id' | 'platform'>;
|
||||
|
||||
export const BuildQuery = {
|
||||
async byIdAsync(buildId: string): Promise<BuildFragment> {
|
||||
async byIdAsync(
|
||||
buildId: string,
|
||||
{ useCache = true }: { useCache?: boolean } = {}
|
||||
): Promise<BuildFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<BuildsByIdQuery, BuildsByIdQueryVariables>(
|
||||
@@ -41,7 +44,10 @@ export const BuildQuery = {
|
||||
}
|
||||
${print(BuildFragmentNode)}
|
||||
`,
|
||||
{ buildId }
|
||||
{ buildId },
|
||||
{
|
||||
requestPolicy: useCache ? 'cache-first' : 'network-only',
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { EnvironmentSecretScope } from '../../commands/secrets/create';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
EnvironmentSecretFragment,
|
||||
@@ -9,6 +10,10 @@ import {
|
||||
} from '../generated';
|
||||
import { EnvironmentSecretFragmentNode } from '../types/EnvironmentSecret';
|
||||
|
||||
export type EnvironmentSecretWithScope = EnvironmentSecretFragment & {
|
||||
scope: EnvironmentSecretScope;
|
||||
};
|
||||
|
||||
export const EnvironmentSecretsQuery = {
|
||||
async byAcccountNameAsync(accountName: string): Promise<EnvironmentSecretFragment[]> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
@@ -60,4 +65,18 @@ export const EnvironmentSecretsQuery = {
|
||||
|
||||
return data.app?.byFullName.environmentSecrets ?? [];
|
||||
},
|
||||
async allAsync(
|
||||
projectAccountName: string,
|
||||
projectFullName: string
|
||||
): Promise<EnvironmentSecretWithScope[]> {
|
||||
const [accountSecrets, appSecrets] = await Promise.all([
|
||||
this.byAcccountNameAsync(projectAccountName),
|
||||
this.byAppFullNameAsync(projectFullName),
|
||||
]);
|
||||
|
||||
return [
|
||||
...appSecrets.map(s => ({ ...s, scope: EnvironmentSecretScope.PROJECT })),
|
||||
...accountSecrets.map(s => ({ ...s, scope: EnvironmentSecretScope.ACCOUNT })),
|
||||
];
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
WebhookByIdQuery,
|
||||
WebhookByIdQueryVariables,
|
||||
WebhookFilter,
|
||||
WebhookFragment,
|
||||
WebhooksByAppIdQuery,
|
||||
WebhooksByAppIdQueryVariables,
|
||||
} from '../generated';
|
||||
import { WebhookFragmentNode } from '../types/Webhook';
|
||||
|
||||
export const WebhookQuery = {
|
||||
async byAppIdAsync(appId: string, webhookFilter?: WebhookFilter): Promise<WebhookFragment[]> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<WebhooksByAppIdQuery, WebhooksByAppIdQueryVariables>(
|
||||
gql`
|
||||
query WebhooksByAppId($appId: String!, $webhookFilter: WebhookFilter) {
|
||||
app {
|
||||
byId(appId: $appId) {
|
||||
id
|
||||
webhooks(filter: $webhookFilter) {
|
||||
id
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(WebhookFragmentNode)}
|
||||
`,
|
||||
{ appId, webhookFilter }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.app?.byId.webhooks ?? [];
|
||||
},
|
||||
async byIdAsync(webhookId: string): Promise<WebhookFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<WebhookByIdQuery, WebhookByIdQueryVariables>(
|
||||
gql`
|
||||
query WebhookById($webhookId: ID!) {
|
||||
webhook {
|
||||
byId(id: $webhookId) {
|
||||
id
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(WebhookFragmentNode)}
|
||||
`,
|
||||
{ webhookId }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.webhook.byId;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const WebhookFragmentNode = gql`
|
||||
fragment WebhookFragment on Webhook {
|
||||
id
|
||||
event
|
||||
url
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
`;
|
||||
@@ -6,6 +6,8 @@ export const AppleProvisioningProfileFragmentNode = gql`
|
||||
expiration
|
||||
developerPortalIdentifier
|
||||
provisioningProfile
|
||||
updatedAt
|
||||
status
|
||||
appleTeam {
|
||||
id
|
||||
appleTeamIdentifier
|
||||
|
||||
@@ -1,43 +1,22 @@
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { AppleDistributionCertificateFragmentNode } from './AppleDistributionCertificate';
|
||||
import { AppleProvisioningProfileFragmentNode } from './AppleProvisioningProfile';
|
||||
|
||||
export const IosAppBuildCredentialsFragmentNode = gql`
|
||||
fragment IosAppBuildCredentialsFragment on IosAppBuildCredentials {
|
||||
id
|
||||
iosDistributionType
|
||||
distributionCertificate {
|
||||
id
|
||||
certificateP12
|
||||
certificatePassword
|
||||
serialNumber
|
||||
developerPortalIdentifier
|
||||
validityNotBefore
|
||||
validityNotAfter
|
||||
updatedAt
|
||||
appleTeam {
|
||||
id
|
||||
appleTeamIdentifier
|
||||
appleTeamName
|
||||
}
|
||||
...AppleDistributionCertificateFragment
|
||||
}
|
||||
provisioningProfile {
|
||||
id
|
||||
expiration
|
||||
developerPortalIdentifier
|
||||
provisioningProfile
|
||||
updatedAt
|
||||
status
|
||||
appleDevices {
|
||||
id
|
||||
identifier
|
||||
name
|
||||
model
|
||||
deviceClass
|
||||
}
|
||||
appleTeam {
|
||||
id
|
||||
appleTeamIdentifier
|
||||
appleTeamName
|
||||
}
|
||||
...AppleProvisioningProfileFragment
|
||||
}
|
||||
}
|
||||
${print(AppleDistributionCertificateFragmentNode)}
|
||||
${print(AppleProvisioningProfileFragmentNode)}
|
||||
`;
|
||||
@@ -4,6 +4,8 @@ import { SubmissionError } from '../SubmissionService.types';
|
||||
enum SubmissionErrorCode {
|
||||
ARCHIVE_DOWNLOAD_NOT_FOUND_ERROR = 'SUBMISSION_SERVICE_COMMON_ARCHIVE_DOWNLOAD_NOT_FOUND_ERROR',
|
||||
ARCHIVE_DOWNLOAD_FORBIDDEN_ERROR = 'SUBMISSION_SERVICE_COMMON_ARCHIVE_DOWNLOAD_FORBIDDEN_ERROR',
|
||||
ARCHIVE_EXTRACT_NO_FILES_FOUND_ERROR = 'SUBMISSION_SERVICE_COMMON_ARCHIVE_EXTRACT_NO_FILES_FOUND_ERROR',
|
||||
ARCHIVE_EXTRACT_CORRUPT_ARCHIVE_ERROR = 'SUBMISSION_SERVICE_COMMON_ARCHIVE_EXTRACT_CORRUPT_ARCHIVE_ERROR',
|
||||
ANDROID_UNKNOWN_ERROR = 'SUBMISSION_SERVICE_ANDROID_UNKNOWN_ERROR',
|
||||
ANDROID_FIRST_UPLOAD_ERROR = 'SUBMISSION_SERVICE_ANDROID_FIRST_UPLOAD_ERROR',
|
||||
ANDROID_OLD_VERSION_CODE_ERROR = 'SUBMISSION_SERVICE_ANDROID_OLD_VERSION_CODE_ERROR',
|
||||
@@ -12,6 +14,7 @@ enum SubmissionErrorCode {
|
||||
IOS_UNKNOWN_ERROR = 'SUBMISSION_SERVICE_IOS_UNKNOWN_ERROR',
|
||||
IOS_MISSING_APP_ICON = 'SUBMISSION_SERVICE_IOS_MISSING_APP_ICON',
|
||||
IOS_INVALID_SIGNATURE = 'SUBMISSION_SERVICE_IOS_INVALID_SIGNATURE',
|
||||
IOS_INCORRECT_CREDENTIALS = 'SUBMISSION_SERVICE_IOS_INVALID_CREDENTIALS',
|
||||
}
|
||||
|
||||
const SubmissionErrorMessages: Record<SubmissionErrorCode, string> = {
|
||||
@@ -19,6 +22,11 @@ const SubmissionErrorMessages: Record<SubmissionErrorCode, string> = {
|
||||
"Failed to download the archive file (Response code: 404 Not Found). Please make sure the URL you've provided is correct.",
|
||||
[SubmissionErrorCode.ARCHIVE_DOWNLOAD_FORBIDDEN_ERROR]:
|
||||
'Failed to download the archive file (Response code: 403 Forbidden). This is most probably caused by trying to upload an expired build artifact. All Expo build artifacts expire after 30 days.',
|
||||
[SubmissionErrorCode.ARCHIVE_EXTRACT_CORRUPT_ARCHIVE_ERROR]:
|
||||
'The compressed archive is corrupt, in an unsupported format, or contains an invalid application format. Supported files include .apk, .aab, and .ipa files and one of these files compressed into a .tar.gz archive.',
|
||||
[SubmissionErrorCode.ARCHIVE_EXTRACT_NO_FILES_FOUND_ERROR]:
|
||||
"EAS Submit couldn't find a valid build artifact within provided compressed archive.\n" +
|
||||
'If you provide a tar.gz archive, it should contain at least one .apk/.aab/.ipa file, depending on the submission platform.',
|
||||
[SubmissionErrorCode.ANDROID_UNKNOWN_ERROR]:
|
||||
"We couldn't figure out what went wrong. Please see logs to learn more.",
|
||||
[SubmissionErrorCode.ANDROID_FIRST_UPLOAD_ERROR]:
|
||||
@@ -47,6 +55,8 @@ const SubmissionErrorMessages: Record<SubmissionErrorCode, string> = {
|
||||
'Your app signature seems to be invalid.\n' +
|
||||
"Please check your iOS Distribution Certificate and your app's Provisioning Profile.\n" +
|
||||
`${learnMore('https://docs.expo.io/distribution/app-signing')}`,
|
||||
[SubmissionErrorCode.IOS_INCORRECT_CREDENTIALS]:
|
||||
'Your Apple ID or app-specific password is incorrect. Please verify that you entered them correctly and try again.',
|
||||
};
|
||||
|
||||
export function printSubmissionError(error: SubmissionError): boolean {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { WebhookFragment } from '../graphql/generated';
|
||||
import formatFields from '../utils/formatFields';
|
||||
|
||||
export function formatWebhook(webhook: WebhookFragment): string {
|
||||
return formatFields([
|
||||
{ label: 'ID', value: webhook.id },
|
||||
{ label: 'Event', value: webhook.event },
|
||||
{ label: 'URL', value: webhook.url },
|
||||
{ label: 'Created at', value: new Date(webhook.createdAt).toLocaleString() },
|
||||
{ label: 'Updated at', value: new Date(webhook.updatedAt).toLocaleString() },
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import nullthrows from 'nullthrows';
|
||||
import { URL } from 'url';
|
||||
|
||||
import { WebhookFragment, WebhookInput, WebhookType } from '../graphql/generated';
|
||||
import Log from '../log';
|
||||
import { promptAsync } from '../prompts';
|
||||
|
||||
export async function prepareInputParamsAsync(
|
||||
{
|
||||
event,
|
||||
url: maybeUrl,
|
||||
secret: maybeSecret,
|
||||
}: {
|
||||
event: WebhookType;
|
||||
url?: string;
|
||||
secret?: string;
|
||||
},
|
||||
existingWebhook?: WebhookFragment
|
||||
): Promise<WebhookInput> {
|
||||
let url: string | undefined = maybeUrl;
|
||||
let secret: string | undefined = maybeSecret;
|
||||
|
||||
if (!url || !validateURL(url)) {
|
||||
const urlValidationMessage =
|
||||
'The provided webhook URL is invalid and must be an absolute URL, including a scheme.';
|
||||
if (url && !validateURL(url)) {
|
||||
Log.error(urlValidationMessage);
|
||||
}
|
||||
({ url } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'url',
|
||||
message: 'Webhook URL:',
|
||||
initial: url ? undefined : existingWebhook?.url,
|
||||
validate: (value: string) => validateURL(value) || urlValidationMessage,
|
||||
}));
|
||||
}
|
||||
|
||||
if (!secret || !validateSecret(secret)) {
|
||||
const secretValidationMessage =
|
||||
'Webhook secret has be at least 16 and not more than 1000 characters long';
|
||||
if (secret && !validateSecret(secret)) {
|
||||
Log.error(secretValidationMessage);
|
||||
}
|
||||
({ secret } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'secret',
|
||||
message: 'Webhook secret:',
|
||||
validate: (value: string) => validateSecret(value) || secretValidationMessage,
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
event,
|
||||
url: nullthrows(url),
|
||||
secret: nullthrows(secret),
|
||||
};
|
||||
}
|
||||
|
||||
export function validateURL(url: string): boolean {
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function validateSecret(secret: string): boolean {
|
||||
return secret.length >= 16 && secret.length <= 1000;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"description": "A library for interacting with the eas.json",
|
||||
"version": "0.8.0",
|
||||
"version": "0.9.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1125,24 +1125,24 @@
|
||||
"@babel/preset-env" "^7.4.4"
|
||||
"@babel/preset-typescript" "^7.3.3"
|
||||
|
||||
"@expo/[email protected]1-alpha.0":
|
||||
version "1.0.21-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-1.0.21-alpha.0.tgz#cc6d428809a8b1f483de1f4ca6b8219308bb3680"
|
||||
integrity sha512-npi9sttSQOxl/NWc/FSlIy+ws9HgUoX3vsbu+rgTmD2ARtF5D8x5NR5upnWWpwoq6ZXaYXwDXNcKZHWLGrCfjA==
|
||||
"@expo/[email protected]4":
|
||||
version "1.0.24"
|
||||
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-1.0.24.tgz#d04353500ca4ce6f6eb5f7058ac63e6f09e43486"
|
||||
integrity sha512-DB4CgkeZEKG2DWC0GzbLSHuWlxWZ6d3wE95Cjy6DOyR60cFyxo+1eriaxLaBCApX3KrFAdxJFsxvwK9zbCtuzQ==
|
||||
dependencies:
|
||||
"@expo/config-types" "^40.0.0-beta.2"
|
||||
"@expo/configure-splash-screen" "0.3.4"
|
||||
"@expo/image-utils" "0.3.10"
|
||||
"@expo/json-file" "8.2.28-alpha.0"
|
||||
"@expo/plist" "0.0.11"
|
||||
"@expo/image-utils" "0.3.12"
|
||||
"@expo/json-file" "8.2.28"
|
||||
"@expo/plist" "0.0.12"
|
||||
find-up "~5.0.0"
|
||||
fs-extra "9.0.0"
|
||||
getenv "0.7.0"
|
||||
getenv "^1.0.0"
|
||||
glob "7.1.6"
|
||||
resolve-from "^5.0.0"
|
||||
slash "^3.0.0"
|
||||
slugify "^1.3.4"
|
||||
xcode "^2.1.0"
|
||||
xcode "^3.0.1"
|
||||
xml2js "^0.4.23"
|
||||
|
||||
"@expo/config-types@^40.0.0-beta.2":
|
||||
@@ -1190,15 +1190,15 @@
|
||||
dependencies:
|
||||
"@hapi/joi" "^17.1.1"
|
||||
|
||||
"@expo/[email protected]0":
|
||||
version "0.3.10"
|
||||
resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.3.10.tgz#bdb9d6a2a7280680dd56d7f9dd283863b58d1e1e"
|
||||
integrity sha512-EebukeUnzyk4ts1E1vMQSb0p8otYqWKsZNDZEoqHtERhxMSO7WhQLqa7/z2kB/YMHRJjrhaa3Aa2X5zjYot1kA==
|
||||
"@expo/[email protected]2":
|
||||
version "0.3.12"
|
||||
resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.3.12.tgz#23a7dbf2cfe40fa9dd0c355fe7b4ca15ef83808e"
|
||||
integrity sha512-QMMAjyCJUFXI8b6AOrVC40gubkt++qYfRJdKgbegf9JHQNul/O4Q1ENWTgid+PH0EUqlMLbv6kJ6ai0ZjO08kw==
|
||||
dependencies:
|
||||
"@expo/spawn-async" "1.5.0"
|
||||
chalk "^4.0.0"
|
||||
fs-extra "9.0.0"
|
||||
getenv "0.7.0"
|
||||
getenv "^1.0.0"
|
||||
jimp "0.12.1"
|
||||
mime "^2.4.4"
|
||||
node-fetch "^2.6.0"
|
||||
@@ -1218,10 +1218,10 @@
|
||||
lodash "^4.17.15"
|
||||
write-file-atomic "^2.3.0"
|
||||
|
||||
"@expo/[email protected]-alpha.0":
|
||||
version "8.2.28-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.28-alpha.0.tgz#0e5280c82a17165be12c6a95cd95ec108c6dfe7b"
|
||||
integrity sha512-cCQdw/Nfw8doXjN3onvUnWkuJjtVxx2iUjSOLMydvgI87YpW3x05uUXOVs4P+77YFVoFS6xbki+fmKK2JSCf8w==
|
||||
"@expo/[email protected]":
|
||||
version "8.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.28.tgz#9218bb70ad12d8be03c6376990b01fbeb4a15b72"
|
||||
integrity sha512-C28McDuCIOmCmp6e2ZgIpNaT/fZG+L3/WFv5x2DCrooOQmamrkLSH/4Dib0NUmLgWVhgIhXL2hU4RB+LjzVvkQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "~7.10.4"
|
||||
fs-extra "9.0.0"
|
||||
@@ -1248,7 +1248,16 @@
|
||||
qqjs "^0.3.10"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@expo/[email protected]1", "@expo/plist@^0.0.11":
|
||||
"@expo/[email protected]2":
|
||||
version "0.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.0.12.tgz#59d95db7f8f3cd5427a210a0f442dc1616b270d7"
|
||||
integrity sha512-anGvLk58fxfeHY2PbtH79VxhrLDPGd+173pHYYXNg9HlfbrEVLI2Vo0ZBOrr/cYr7cgU5A/WNcMphRoO/KfYZQ==
|
||||
dependencies:
|
||||
base64-js "^1.2.3"
|
||||
xmlbuilder "^14.0.0"
|
||||
xmldom "~0.5.0"
|
||||
|
||||
"@expo/plist@^0.0.11":
|
||||
version "0.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.0.11.tgz#5d7900dc31df57d45a3524c061dde34aacc8dabf"
|
||||
integrity sha512-yza93QHDkbdkdwu/PXef0eJSCMkMNdrHujK5G1viZLaZt0Rxw2s+geTyjgJsYpwqQEAoOYVpKlVymOenK+bFQg==
|
||||
@@ -1352,24 +1361,7 @@
|
||||
"@graphql-codegen/plugin-helpers" "^1.18.2"
|
||||
tslib "~2.0.1"
|
||||
|
||||
"@graphql-codegen/plugin-helpers@^1.17.8", "@graphql-codegen/plugin-helpers@^1.17.9", "@graphql-codegen/plugin-helpers@^1.18.1":
|
||||
version "1.18.1"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.18.1.tgz#e89833cfc4b3359ed3616626e590a89077ed0da6"
|
||||
integrity sha512-jURyHDy8sUTUtRqF6U+akkPANmOYxJ7P47WExAknludiZCMOuoC6QUTxaHZcWNLCWsJFnKNta+oGHqXgkyA+VQ==
|
||||
dependencies:
|
||||
"@graphql-tools/utils" "^6"
|
||||
camel-case "4.1.1"
|
||||
common-tags "1.8.0"
|
||||
constant-case "3.0.3"
|
||||
import-from "3.0.0"
|
||||
lodash "~4.17.20"
|
||||
lower-case "2.0.1"
|
||||
param-case "3.0.3"
|
||||
pascal-case "3.1.1"
|
||||
tslib "~2.0.1"
|
||||
upper-case "2.0.1"
|
||||
|
||||
"@graphql-codegen/plugin-helpers@^1.18.2":
|
||||
"@graphql-codegen/plugin-helpers@^1.17.8", "@graphql-codegen/plugin-helpers@^1.17.9", "@graphql-codegen/plugin-helpers@^1.18.1", "@graphql-codegen/plugin-helpers@^1.18.2":
|
||||
version "1.18.2"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.18.2.tgz#57011076cb8b8f5d04d37d226a5eda300c01be94"
|
||||
integrity sha512-SvX+Ryq2naLcoD6jJNxtzc/moWTgHJ+X0KRfvhGWTa+xtFTS02i+PWOR89YYPcD8+LF6GmyFBjx2FmLCx4JwMg==
|
||||
@@ -1640,6 +1632,11 @@
|
||||
is-promise "4.0.0"
|
||||
tslib "~2.0.1"
|
||||
|
||||
"@graphql-typed-document-node/core@^3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.0.tgz#0eee6373e11418bfe0b5638f654df7a4ca6a3950"
|
||||
integrity sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg==
|
||||
|
||||
"@hapi/address@^4.0.1":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-4.1.0.tgz#d60c5c0d930e77456fdcde2598e77302e2955e1d"
|
||||
@@ -3539,11 +3536,20 @@
|
||||
dependencies:
|
||||
eslint-visitor-keys "^1.1.0"
|
||||
|
||||
"@urql/core@^1.13.0":
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@urql/core/-/core-1.13.0.tgz#811154b5951b4282dd698a8c9dff4ff3825ca215"
|
||||
integrity sha512-g7Kn2a0TDQotOqvGR7bRC+JqviA4TYtiDZH5M9Roszx7LiqZBkViSr5zmdwlNZjR4kSDn+kSXlCV+Ht0Uqls0Q==
|
||||
"@urql/core@>=1.15.0", "@urql/core@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@urql/core/-/core-2.0.0.tgz#b4936dd51fe84cb570506d9ee2579149689f7535"
|
||||
integrity sha512-Qj24CG8ullqZZsYmjrSH0JhH+nY7kj8GbVbA9si3KUjlYs75A/MBQU3i97j6oWyGldDBapyis2CfaQeXKbv8rA==
|
||||
dependencies:
|
||||
"@graphql-typed-document-node/core" "^3.1.0"
|
||||
wonka "^4.0.14"
|
||||
|
||||
"@urql/exchange-retry@^0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@urql/exchange-retry/-/exchange-retry-0.2.0.tgz#e90a99bf8280ad2b8926bea7f2157a6f59dc8aec"
|
||||
integrity sha512-eawDIkTSVudv+zMaOlm898UX9lkJnUry2PYqD7INeFWghkHmlIPm6wg5J/GBGAyFjqaOj1OWgAWYcu7sV4eljg==
|
||||
dependencies:
|
||||
"@urql/core" ">=1.15.0"
|
||||
wonka "^4.0.14"
|
||||
|
||||
"@vercel/ncc@^0.23.0":
|
||||
@@ -10771,7 +10777,7 @@ [email protected]:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
|
||||
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
|
||||
|
||||
[email protected], semver@^7.1.3, semver@^7.3.2:
|
||||
[email protected]:
|
||||
version "7.3.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
|
||||
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
|
||||
@@ -10781,7 +10787,7 @@ semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
semver@^7.3.4:
|
||||
semver@^7.1.3, semver@^7.3.2, semver@^7.3.4:
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
|
||||
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
|
||||
@@ -10862,7 +10868,7 @@ signedsource@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/signedsource/-/signedsource-1.0.0.tgz#1ddace4981798f93bd833973803d80d52e93ad6a"
|
||||
integrity sha1-HdrOSYF5j5O9gzlzgD2A1S6TrWo=
|
||||
|
||||
simple-plist@^1.0.0, simple-plist@^1.1.0:
|
||||
simple-plist@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.1.1.tgz#54367ca28bc5996a982c325c1c4a4c1a05f4047c"
|
||||
integrity sha512-pKMCVKvZbZTsqYR6RKgLfBHkh2cV89GXcA/0CVPje3sOiNOnXA8+rp/ciAMZ7JRaUdLzlEM6JFfUn+fS6Nt3hg==
|
||||
@@ -12386,15 +12392,7 @@ ws@^7.2.3:
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"
|
||||
integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==
|
||||
|
||||
xcode@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/xcode/-/xcode-2.1.0.tgz#bab64a7e954bb50ca8d19da7e09531c65a43ecfe"
|
||||
integrity sha512-uCrmPITrqTEzhn0TtT57fJaNaw8YJs1aCzs+P/QqxsDbvPZSv7XMPPwXrKvHtD6pLjBM/NaVwraWJm8q83Y4iQ==
|
||||
dependencies:
|
||||
simple-plist "^1.0.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
xcode@^3.0.0:
|
||||
xcode@^3.0.0, xcode@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xcode/-/xcode-3.0.1.tgz#3efb62aac641ab2c702458f9a0302696146aa53c"
|
||||
integrity sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==
|
||||
@@ -12462,6 +12460,11 @@ [email protected], xmldom@~0.1.31:
|
||||
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff"
|
||||
integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==
|
||||
|
||||
xmldom@~0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.5.0.tgz#193cb96b84aa3486127ea6272c4596354cb4962e"
|
||||
integrity sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==
|
||||
|
||||
xtend@^4.0.0, xtend@~4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
|
||||
Reference in new issue
Block a user