mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddfe60a7b0 | ||
|
|
7dedeb904b | ||
|
|
4c02e0102b | ||
|
|
e91516fcc7 | ||
|
|
d01f6c0d8e | ||
|
|
1ce69cd7c9 | ||
|
|
83c10793ac | ||
|
|
a344c11ba1 | ||
|
|
c7d5a9a819 | ||
|
|
2f43ed94ab | ||
|
|
4a4ebf3cd6 | ||
|
|
18b832dbda | ||
|
|
1200e7fecd | ||
|
|
3cc6b73d3a | ||
|
|
4831ff186a | ||
|
|
a2d60a4a76 | ||
|
|
9f604777b9 | ||
|
|
fce9bda9bb | ||
|
|
4cc6745a0d | ||
|
|
67f0b806f4 | ||
|
|
9280dfb3ad | ||
|
|
2befbb0eba | ||
|
|
41359b7194 | ||
|
|
c57861a67d | ||
|
|
d31ece8793 | ||
|
|
4f7148b756 | ||
|
|
bc8a781d86 | ||
|
|
153a70fb85 | ||
|
|
2f93492fc3 | ||
|
|
0b39ff1457 | ||
|
|
f6817037ae | ||
|
|
216f97ea8a |
No files matched your search
@@ -21,6 +21,13 @@ body:
|
||||
label: Summary
|
||||
description: Describe the issue in 1 or 2 sentences
|
||||
placeholder: Clearly describe what the expected behavior is vs. what is actually happening. This should be as short as possible, while still communicating all the necessary information. If your summary is just 'X library/method isn't working', then you need to continue debugging yourself and provide more information.
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Managed or bare?
|
||||
description: See the [Workflows](https://docs.expo.dev/introduction/managed-vs-bare/) page for more information.
|
||||
placeholder: Is your project managed or bare? Bare - Android and/or iOS native projects exist in your repo; Managed - you don't have native code (custom or 3rd party) in your project.
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
|
||||
+37
-2
@@ -6,17 +6,52 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- [eas-cli] change eas update:publish to eas update. ([#830](https://github.com/expo/eas-cli/pull/830) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Pass build profile environment to credentials command. ([#828](https://github.com/expo/eas-cli/pull/828) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [0.39.0](https://github.com/expo/eas-cli/releases/tag/v0.39.0) - 2021-12-06
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Set runtime version in `eas update:configure`. ([#811](https://github.com/expo/eas-cli/pull/811) by [@jkhales](https://github.com/jkhales))
|
||||
- Print project workflow in `eas diagnostics`. ([#822](https://github.com/expo/eas-cli/pull/822) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix submit for multi-target projects where ascAppId is not specified. ([#809](https://github.com/expo/eas-cli/pull/809) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Fix building iOS projects with Apple Watch companion app. ([#812](https://github.com/expo/eas-cli/pull/812), [#817](https://github.com/expo/eas-cli/pull/817), [#821](https://github.com/expo/eas-cli/pull/821) by [@jkhales](https://github.com/jkhales) + [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Improve error message for missing ascAppId. ([#813](https://github.com/expo/eas-cli/pull/813) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Add `react-native` version to metadata. ([#823](https://github.com/expo/eas-cli/pull/823) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
## [0.38.3](https://github.com/expo/eas-cli/releases/tag/v0.38.3) - 2021-11-29
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Print warning about outdated version to stderr. ([#805](https://github.com/expo/eas-cli/pull/805) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Fix Apple sign in capabilities. ([#806](https://github.com/expo/eas-cli/pull/806) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.38.2](https://github.com/expo/eas-cli/releases/tag/v0.38.2) - 2021-11-26
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add support for extending submit profiles. ([#794](https://github.com/expo/eas-cli/pull/794) by [@dsokal](https://github.com/dsokal))
|
||||
- Add command: `eas update:configure`. ([#800](https://github.com/expo/eas-cli/pull/800) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix reading app version and app build version of iOS projects. ([#798](https://github.com/expo/eas-cli/pull/798) by [@dsokal](https://github.com/dsokal))
|
||||
- Fix push notifications entitlements. ([#801](https://github.com/expo/eas-cli/pull/801) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [0.38.1](https://github.com/expo/eas-cli/releases/tag/v0.38.1) - 2021-11-25
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.38.2"
|
||||
"version": "0.40.0"
|
||||
}
|
||||
@@ -7,9 +7,9 @@ EAS command line tool
|
||||
[](https://npmjs.org/package/eas-cli)
|
||||
[](https://github.com/expo/eas-cli/blob/main/package.json)
|
||||
|
||||
* [Installation](#installation)
|
||||
* [Usage](#usage)
|
||||
* [Commands](#commands)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Commands](#commands)
|
||||
|
||||
# Installation
|
||||
|
||||
@@ -73,6 +73,8 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
log out
|
||||
@@ -85,6 +87,8 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
show the username you are logged in as
|
||||
@@ -97,6 +101,8 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
view or change analytics settings
|
||||
@@ -106,9 +112,11 @@ USAGE
|
||||
$ eas analytics [STATUS]
|
||||
```
|
||||
|
||||
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
start a build
|
||||
Start a build
|
||||
|
||||
```
|
||||
USAGE
|
||||
@@ -138,6 +146,8 @@ OPTIONS
|
||||
--[no-]wait Wait for build(s) to complete
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
Cancel a build.
|
||||
@@ -147,6 +157,8 @@ USAGE
|
||||
$ eas build:cancel [BUILD_ID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
Configure the project to support EAS Build.
|
||||
@@ -159,6 +171,8 @@ OPTIONS
|
||||
-p, --platform=(android|ios|all) Platform to configure
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
list all builds for your project
|
||||
@@ -190,6 +204,8 @@ OPTIONS
|
||||
--status=(new|in-queue|in-progress|errored|finished|canceled)
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
view a build for your project
|
||||
@@ -202,6 +218,8 @@ OPTIONS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
show the eas.json config
|
||||
@@ -215,6 +233,8 @@ OPTIONS
|
||||
--profile=profile
|
||||
```
|
||||
|
||||
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
manage your credentials
|
||||
@@ -224,6 +244,8 @@ USAGE
|
||||
$ eas credentials
|
||||
```
|
||||
|
||||
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/credentials.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
register new Apple Devices to use for internal distribution
|
||||
@@ -233,6 +255,8 @@ USAGE
|
||||
$ eas device:create
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
list all registered devices for your account
|
||||
@@ -245,6 +269,8 @@ OPTIONS
|
||||
--apple-team-id=apple-team-id
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
view a device for your project
|
||||
@@ -254,6 +280,8 @@ USAGE
|
||||
$ eas device:view [UDID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
log environment info to the console
|
||||
@@ -263,6 +291,8 @@ USAGE
|
||||
$ eas diagnostics
|
||||
```
|
||||
|
||||
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
display help for eas
|
||||
@@ -289,6 +319,8 @@ USAGE
|
||||
$ eas project:info
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
create or link an EAS project
|
||||
@@ -301,6 +333,8 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
Create an environment secret on the current project or owner account.
|
||||
@@ -316,6 +350,8 @@ OPTIONS
|
||||
--value=value Value of the secret
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
Delete an environment secret by ID.
|
||||
@@ -331,6 +367,8 @@ DESCRIPTION
|
||||
Unsure where to find the secret's ID? Run eas secret:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
Lists environment secrets available for your current app
|
||||
@@ -340,9 +378,11 @@ USAGE
|
||||
$ eas secret:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
submit build archive to app store
|
||||
Submit build archive to App Store Connect
|
||||
|
||||
```
|
||||
USAGE
|
||||
@@ -372,6 +412,8 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
Create a webhook on the current project.
|
||||
@@ -389,6 +431,8 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
Delete a webhook on the current project.
|
||||
@@ -401,6 +445,8 @@ ARGUMENTS
|
||||
ID ID of the webhook to delete
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
List webhooks on the current project.
|
||||
@@ -413,6 +459,8 @@ OPTIONS
|
||||
--event=(BUILD|SUBMIT) Event type that triggers the webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
Create a webhook on the current project.
|
||||
@@ -431,6 +479,8 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
View a webhook on the current project.
|
||||
@@ -442,4 +492,6 @@ USAGE
|
||||
ARGUMENTS
|
||||
ID ID of the webhook to view
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
<!-- commandsstop -->
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.38.2",
|
||||
"version": "0.40.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
@@ -9,15 +9,15 @@
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/apple-utils": "0.0.0-alpha.26",
|
||||
"@expo/config": "6.0.9",
|
||||
"@expo/config-plugins": "4.0.9",
|
||||
"@expo/eas-build-job": "0.2.57",
|
||||
"@expo/eas-json": "^0.38.2",
|
||||
"@expo/config": "6.0.11",
|
||||
"@expo/config-plugins": "4.0.11",
|
||||
"@expo/eas-build-job": "0.2.61",
|
||||
"@expo/eas-json": "^0.40.0",
|
||||
"@expo/json-file": "8.2.34",
|
||||
"@expo/pkcs12": "0.0.6",
|
||||
"@expo/plist": "0.0.16",
|
||||
"@expo/plugin-warn-if-update-available": "1.7.0",
|
||||
"@expo/prebuild-config": "3.0.9",
|
||||
"@expo/plugin-warn-if-update-available": "1.7.1",
|
||||
"@expo/prebuild-config": "3.0.11",
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.5.0",
|
||||
@@ -95,7 +95,8 @@
|
||||
"mockdate": "^3.0.2",
|
||||
"nock": "^13.0.5",
|
||||
"tempy": "0.7.0",
|
||||
"ts-deepmerge": "1.0.8"
|
||||
"ts-deepmerge": "1.0.8",
|
||||
"typescript": "4.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
|
||||
@@ -49,6 +49,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
nonInteractive,
|
||||
projectDir,
|
||||
user,
|
||||
env: buildProfile.env,
|
||||
});
|
||||
|
||||
const devClientProperties = getDevClientEventProperties({
|
||||
@@ -60,7 +61,6 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
tracking_id: uuidv4(),
|
||||
platform,
|
||||
...(accountId && { account_id: accountId }),
|
||||
account_name: accountName,
|
||||
project_id: projectId,
|
||||
project_type: workflow,
|
||||
...devClientProperties,
|
||||
|
||||
@@ -3,7 +3,7 @@ import spawnAsync from '@expo/spawn-async';
|
||||
import semver from 'semver';
|
||||
|
||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||
const PLUGIN_PACKAGE_VERSION = '0.0.50';
|
||||
const PLUGIN_PACKAGE_VERSION = '0.0.54';
|
||||
|
||||
export async function runLocalBuildAsync(job: Job): Promise<void> {
|
||||
const { command, args } = await getCommandAndArgsAsync(job);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Updates } from '@expo/config-plugins';
|
||||
import { Metadata, Platform, sanitizeMetadata } from '@expo/eas-build-job';
|
||||
import { IosEnterpriseProvisioning } from '@expo/eas-json';
|
||||
import fs from 'fs-extra';
|
||||
import resolveFrom from 'resolve-from';
|
||||
|
||||
import Log from '../log';
|
||||
import { getUsername } from '../project/projectUtils';
|
||||
@@ -48,6 +50,7 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
credentialsSource: ctx.buildProfile.credentialsSource,
|
||||
sdkVersion: ctx.exp.sdkVersion,
|
||||
runtimeVersion: getRuntimeVersionNullable(ctx.exp, ctx.platform) ?? undefined,
|
||||
reactNativeVersion: await getReactNativeVersionAsync(ctx),
|
||||
...channelOrReleaseChannel,
|
||||
distribution,
|
||||
appName: ctx.exp.name,
|
||||
@@ -141,6 +144,19 @@ async function getNativeChannelAsync<T extends Platform>(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function getReactNativeVersionAsync(
|
||||
ctx: BuildContext<Platform>
|
||||
): Promise<string | undefined> {
|
||||
try {
|
||||
const reactNativePackageJsonPath = resolveFrom(ctx.projectDir, 'react-native/package.json');
|
||||
return (await fs.readJson(reactNativePackageJsonPath)).version;
|
||||
} catch (err) {
|
||||
Log.debug('Failed to resolve react-native version:');
|
||||
Log.debug(err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveIosEnterpriseProvisioning(
|
||||
ctx: BuildContext<Platform.IOS>
|
||||
): IosEnterpriseProvisioning | undefined {
|
||||
|
||||
@@ -1,493 +1,10 @@
|
||||
import { ExpoConfig, getConfig } from '@expo/config';
|
||||
import { Updates } from '@expo/config-plugins';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { flags } from '@oclif/command';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import dateFormat from 'dateformat';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
GetUpdateGroupAsyncQuery,
|
||||
Robot,
|
||||
RootQueryUpdatesByGroupArgs,
|
||||
Update,
|
||||
UpdateInfoGroup,
|
||||
User,
|
||||
ViewBranchQuery,
|
||||
} from '../../graphql/generated';
|
||||
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import {
|
||||
PublishPlatform,
|
||||
buildBundlesAsync,
|
||||
buildUnsortedUpdateInfoGroupAsync,
|
||||
collectAssetsAsync,
|
||||
uploadAssetsAsync,
|
||||
} from '../../project/publish';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { promptAsync, selectAsync } from '../../prompts';
|
||||
import { formatUpdate } from '../../update/utils';
|
||||
import uniqBy from '../../utils/expodash/uniqBy';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { createUpdateChannelOnAppAsync } from '../channel/create';
|
||||
import { createUpdateBranchOnAppAsync } from './create';
|
||||
import { listBranchesAsync } from './list';
|
||||
import { viewUpdateBranchAsync } from './view';
|
||||
|
||||
const EAS_UPDATE_URL = 'https://u.expo.dev';
|
||||
|
||||
export const defaultPublishPlatforms: PublishPlatform[] = ['android', 'ios'];
|
||||
type PlatformFlag = PublishPlatform | 'all';
|
||||
|
||||
async function getUpdateGroupAsync({
|
||||
group,
|
||||
}: RootQueryUpdatesByGroupArgs): Promise<GetUpdateGroupAsyncQuery['updatesByGroup']> {
|
||||
const { updatesByGroup } = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<GetUpdateGroupAsyncQuery, RootQueryUpdatesByGroupArgs>(
|
||||
gql`
|
||||
query getUpdateGroupAsync($group: ID!) {
|
||||
updatesByGroup(group: $group) {
|
||||
id
|
||||
group
|
||||
runtimeVersion
|
||||
manifestFragment
|
||||
platform
|
||||
message
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
group,
|
||||
},
|
||||
{ additionalTypenames: ['Update'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return updatesByGroup;
|
||||
}
|
||||
|
||||
async function ensureChannelExistsAsync({
|
||||
appId,
|
||||
branchId,
|
||||
channelName,
|
||||
}: {
|
||||
appId: string;
|
||||
branchId: string;
|
||||
channelName: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
await createUpdateChannelOnAppAsync({
|
||||
appId,
|
||||
channelName,
|
||||
branchId,
|
||||
});
|
||||
Log.withTick(
|
||||
`Created a channel: ${chalk.bold(channelName)} pointed at branch: ${chalk.bold(channelName)}.`
|
||||
);
|
||||
} catch (e: any) {
|
||||
const isIgnorableError =
|
||||
e.graphQLErrors?.length === 1 &&
|
||||
e.graphQLErrors[0].extensions.errorCode === 'CHANNEL_ALREADY_EXISTS';
|
||||
if (!isIgnorableError) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureBranchExistsAsync({
|
||||
appId,
|
||||
name: branchName,
|
||||
}: {
|
||||
appId: string;
|
||||
name: string;
|
||||
}): Promise<{
|
||||
id: string;
|
||||
updates: Exclude<
|
||||
Exclude<ViewBranchQuery['app'], null | undefined>['byId']['updateBranchByName'],
|
||||
null | undefined
|
||||
>['updates'];
|
||||
}> {
|
||||
const { app } = await viewUpdateBranchAsync({
|
||||
appId,
|
||||
name: branchName,
|
||||
});
|
||||
const updateBranch = app?.byId.updateBranchByName;
|
||||
if (updateBranch) {
|
||||
const { id, updates } = updateBranch;
|
||||
await ensureChannelExistsAsync({ appId, branchId: id, channelName: branchName });
|
||||
return { id, updates };
|
||||
}
|
||||
|
||||
const newUpdateBranch = await createUpdateBranchOnAppAsync({ appId, name: branchName });
|
||||
Log.withTick(`Created branch: ${chalk.bold(branchName)}`);
|
||||
await ensureChannelExistsAsync({ appId, branchId: newUpdateBranch.id, channelName: branchName });
|
||||
return { id: newUpdateBranch.id, updates: [] };
|
||||
}
|
||||
|
||||
export default class BranchPublish extends EasCommand {
|
||||
static description = 'deprecated, use "eas update"';
|
||||
static hidden = true;
|
||||
static description = 'Publish an update group to a branch.';
|
||||
|
||||
static args = [
|
||||
{
|
||||
name: 'name',
|
||||
description: 'Name of the branch to publish on',
|
||||
},
|
||||
];
|
||||
|
||||
static flags = {
|
||||
message: flags.string({
|
||||
description: 'short message describing the updates.',
|
||||
required: false,
|
||||
}),
|
||||
republish: flags.boolean({
|
||||
description: 'republish an update group',
|
||||
exclusive: ['input-dir', 'skip-bundler'],
|
||||
}),
|
||||
group: flags.string({
|
||||
description: 'update group to republish',
|
||||
exclusive: ['input-dir', 'skip-bundler'],
|
||||
}),
|
||||
'input-dir': flags.string({
|
||||
description: 'location of the bundle',
|
||||
default: 'dist',
|
||||
required: false,
|
||||
}),
|
||||
'skip-bundler': flags.boolean({
|
||||
description: `skip running Expo CLI to bundle the app before publishing`,
|
||||
default: false,
|
||||
}),
|
||||
platform: flags.enum({
|
||||
char: 'p',
|
||||
description: `only publish to a single platform`,
|
||||
options: [...defaultPublishPlatforms, 'all'],
|
||||
default: 'all',
|
||||
required: false,
|
||||
}),
|
||||
json: flags.boolean({
|
||||
description: `return a json with the new update group.`,
|
||||
default: false,
|
||||
}),
|
||||
auto: flags.boolean({
|
||||
description:
|
||||
'use the current git branch and commit message for the EAS branch and update message',
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
args: { name: branchName },
|
||||
flags: {
|
||||
json: jsonFlag,
|
||||
auto: autoFlag,
|
||||
message,
|
||||
republish,
|
||||
group,
|
||||
'input-dir': inputDir,
|
||||
'skip-bundler': skipBundler,
|
||||
},
|
||||
} = this.parse(BranchPublish);
|
||||
const platformFlag = this.parse(BranchPublish).flags.platform as PlatformFlag;
|
||||
// If a group was specified, that means we are republishing it.
|
||||
republish = group ? true : republish;
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, {
|
||||
skipSDKVersionRequirement: true,
|
||||
isPublicConfig: true,
|
||||
});
|
||||
|
||||
const runtimeVersions = await getRuntimeVersionObjectAsync(exp, platformFlag, projectDir);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
await checkEASUpdateURLIsSetAsync(exp);
|
||||
|
||||
if (!branchName && autoFlag) {
|
||||
branchName =
|
||||
(await getVcsClient().getBranchNameAsync()) ||
|
||||
`branch-${Math.random().toString(36).substr(2, 4)}`;
|
||||
}
|
||||
|
||||
if (!branchName) {
|
||||
const validationMessage = 'branch name may not be empty.';
|
||||
if (jsonFlag) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
|
||||
const branches = await listBranchesAsync({ projectId });
|
||||
branchName = await selectAsync<string>(
|
||||
'which branch would you like to publish on?',
|
||||
branches.map(branch => {
|
||||
return {
|
||||
title: `${branch.name} ${chalk.grey(
|
||||
`- current update: ${formatUpdate(branch.updates[0])}`
|
||||
)}`,
|
||||
value: branch.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
assert(branchName, 'branch name must be specified.');
|
||||
}
|
||||
|
||||
const { id: branchId, updates } = await ensureBranchExistsAsync({
|
||||
appId: projectId,
|
||||
name: branchName,
|
||||
});
|
||||
|
||||
let unsortedUpdateInfoGroups: UpdateInfoGroup = {};
|
||||
let oldMessage: string, oldRuntimeVersion: string;
|
||||
if (republish) {
|
||||
// If we are republishing, we don't need to worry about building the bundle or uploading the assets.
|
||||
// Instead we get the `updateInfoGroup` from the update we wish to republish.
|
||||
let updatesToRepublish: Pick<
|
||||
Update,
|
||||
'group' | 'message' | 'runtimeVersion' | 'manifestFragment' | 'platform'
|
||||
>[];
|
||||
if (group) {
|
||||
updatesToRepublish = await getUpdateGroupAsync({ group });
|
||||
} else {
|
||||
// Drop into interactive mode if the user has not specified an update group to republish.
|
||||
if (jsonFlag) {
|
||||
throw new Error('You must specify the update group to republish.');
|
||||
}
|
||||
|
||||
const updateGroups = uniqBy(updates, u => u.group)
|
||||
.filter(update => {
|
||||
// Only show groups that have updates on the specified platform(s).
|
||||
return platformFlag === 'all' || update.platform === platformFlag;
|
||||
})
|
||||
.map(update => ({
|
||||
title: formatUpdateTitle(update),
|
||||
value: update.group,
|
||||
}));
|
||||
if (updateGroups.length === 0) {
|
||||
throw new Error(
|
||||
`There are no updates on branch "${branchName}" published on the platform(s) ${platformFlag}. Did you mean to publish a new update instead?`
|
||||
);
|
||||
}
|
||||
|
||||
const selectedUpdateGroup = await selectAsync<string>(
|
||||
'which update would you like to republish?',
|
||||
updateGroups
|
||||
);
|
||||
updatesToRepublish = updates.filter(update => update.group === selectedUpdateGroup);
|
||||
}
|
||||
const updatesToRepublishFilteredByPlatform = updatesToRepublish.filter(
|
||||
// Only republish to the specified platforms
|
||||
update => platformFlag === 'all' || update.platform === platformFlag
|
||||
);
|
||||
if (updatesToRepublishFilteredByPlatform.length === 0) {
|
||||
throw new Error(
|
||||
`There are no updates on branch "${branchName}" published on the platform(s) "${platformFlag}" with group ID "${
|
||||
group ? group : updatesToRepublish[0].group
|
||||
}". Did you mean to publish a new update instead?`
|
||||
);
|
||||
}
|
||||
|
||||
let publicationPlatformMessage: string;
|
||||
if (platformFlag === 'all') {
|
||||
if (updatesToRepublishFilteredByPlatform.length !== defaultPublishPlatforms.length) {
|
||||
Log.warn(`You are republishing an update that wasn't published for all platforms.`);
|
||||
}
|
||||
publicationPlatformMessage = `The republished update will appear on the same plaforms it was originally published on: ${updatesToRepublishFilteredByPlatform
|
||||
.map(update => update.platform)
|
||||
.join(',')}`;
|
||||
} else {
|
||||
publicationPlatformMessage = `The republished update will appear only on: ${platformFlag}`;
|
||||
}
|
||||
Log.withTick(publicationPlatformMessage);
|
||||
|
||||
for (const update of updatesToRepublishFilteredByPlatform) {
|
||||
const { manifestFragment } = update;
|
||||
const platform = update.platform as PublishPlatform;
|
||||
|
||||
unsortedUpdateInfoGroups[platform] = JSON.parse(manifestFragment);
|
||||
}
|
||||
|
||||
// These are the same for each member of an update group
|
||||
group = updatesToRepublishFilteredByPlatform[0].group;
|
||||
oldMessage = updatesToRepublishFilteredByPlatform[0].message ?? '';
|
||||
oldRuntimeVersion = updatesToRepublishFilteredByPlatform[0].runtimeVersion;
|
||||
} else {
|
||||
// build bundle and upload assets for a new publish
|
||||
if (!skipBundler) {
|
||||
await buildBundlesAsync({ projectDir, inputDir });
|
||||
}
|
||||
|
||||
const assetSpinner = ora('Uploading assets...').start();
|
||||
try {
|
||||
const platforms = platformFlag === 'all' ? defaultPublishPlatforms : [platformFlag];
|
||||
const assets = await collectAssetsAsync({ inputDir: inputDir!, platforms });
|
||||
await uploadAssetsAsync(assets);
|
||||
unsortedUpdateInfoGroups = await buildUnsortedUpdateInfoGroupAsync(assets, exp);
|
||||
assetSpinner.succeed('Uploaded assets!');
|
||||
} catch (e) {
|
||||
assetSpinner.fail('Failed to upload assets');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!message && autoFlag) {
|
||||
message = (await getVcsClient().getLastCommitMessageAsync())?.trim();
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
const validationMessage = 'publish message may not be empty.';
|
||||
if (jsonFlag) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
({ publishMessage: message } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'publishMessage',
|
||||
message: `Please enter a publication message.`,
|
||||
initial: republish
|
||||
? `Republish "${oldMessage!}" - group: ${group}`
|
||||
: (await getVcsClient().getLastCommitMessageAsync())?.trim(),
|
||||
validate: value => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
|
||||
const runtimeToPlatformMapping: Record<string, string[]> = {};
|
||||
for (const runtime of new Set(Object.values(runtimeVersions))) {
|
||||
runtimeToPlatformMapping[runtime] = Object.entries(runtimeVersions)
|
||||
.filter(pair => pair[1] === runtime)
|
||||
.map(pair => pair[0]);
|
||||
}
|
||||
|
||||
// Sort the updates into different groups based on their platform specific runtime versions
|
||||
const updateGroups = Object.entries(runtimeToPlatformMapping).map(([runtime, platforms]) => {
|
||||
const localUpdateInfoGroup = Object.fromEntries(
|
||||
platforms.map(platform => [
|
||||
platform,
|
||||
unsortedUpdateInfoGroups[platform as keyof UpdateInfoGroup],
|
||||
])
|
||||
);
|
||||
|
||||
if (republish && !oldRuntimeVersion) {
|
||||
throw new Error(
|
||||
'Can not find the runtime version of the update group that is being republished.'
|
||||
);
|
||||
}
|
||||
return {
|
||||
branchId,
|
||||
updateInfoGroup: localUpdateInfoGroup,
|
||||
runtimeVersion: republish ? oldRuntimeVersion : runtime,
|
||||
message,
|
||||
};
|
||||
});
|
||||
let newUpdates;
|
||||
const publishSpinner = ora('Publishing...').start();
|
||||
try {
|
||||
newUpdates = await PublishMutation.publishUpdateGroupAsync(updateGroups);
|
||||
publishSpinner.succeed('Published!');
|
||||
} catch (e) {
|
||||
publishSpinner.fail('Failed to published updates');
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (jsonFlag) {
|
||||
Log.log(JSON.stringify(newUpdates));
|
||||
} else {
|
||||
if (new Set(newUpdates.map(update => update.group)).size > 1) {
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
'👉 Since multiple runtime versions are defined, multiple update groups have been published.'
|
||||
);
|
||||
}
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
for (const runtime of new Set(Object.values(runtimeVersions))) {
|
||||
const platforms = newUpdates
|
||||
.filter(update => update.runtimeVersion === runtime)
|
||||
.map(update => update.platform);
|
||||
const newUpdate = newUpdates.find(update => update.runtimeVersion === runtime);
|
||||
if (!newUpdate) {
|
||||
throw new Error(`Publish response is missing updates with runtime ${runtime}.`);
|
||||
}
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'branch', value: branchName },
|
||||
{ label: 'runtime version', value: runtime },
|
||||
{ label: 'platform', value: platforms.join(',') },
|
||||
{ label: 'update group ID', value: newUpdate.group },
|
||||
{ label: 'message', value: message },
|
||||
])
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getRuntimeVersionObjectAsync(
|
||||
exp: ExpoConfig,
|
||||
platformFlag: PlatformFlag,
|
||||
projectDir: string
|
||||
): Promise<Record<string, string>> {
|
||||
const platforms = (platformFlag === 'all' ? ['android', 'ios'] : [platformFlag]) as Platform[];
|
||||
|
||||
for (const platform of platforms) {
|
||||
const isPolicy = typeof (exp[platform]?.runtimeVersion ?? exp.runtimeVersion) === 'object';
|
||||
if (isPolicy) {
|
||||
const isManaged = (await resolveWorkflowAsync(projectDir, platform)) === Workflow.MANAGED;
|
||||
if (!isManaged) {
|
||||
throw new Error('Runtime version policies are only supported in the managed workflow.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
platforms.map(platform => [platform, Updates.getRuntimeVersion(exp, platform)])
|
||||
);
|
||||
}
|
||||
|
||||
function formatUpdateTitle(
|
||||
update: Exclude<
|
||||
Exclude<ViewBranchQuery['app'], null | undefined>['byId']['updateBranchByName'],
|
||||
null | undefined
|
||||
>['updates'][number]
|
||||
): string {
|
||||
const { message, createdAt, actor, runtimeVersion } = update;
|
||||
|
||||
let actorName: string;
|
||||
switch (actor?.__typename) {
|
||||
case 'User': {
|
||||
actorName = (actor as Pick<User, 'username' | 'id'>).username;
|
||||
break;
|
||||
}
|
||||
case 'Robot': {
|
||||
const { firstName, id } = actor as Pick<Robot, 'firstName' | 'id'>;
|
||||
actorName = firstName ?? `robot: ${id.slice(0, 4)}...`;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
actorName = 'unknown';
|
||||
}
|
||||
return `[${dateFormat(
|
||||
createdAt,
|
||||
'mmm dd HH:MM'
|
||||
)} by ${actorName}, runtimeVersion: ${runtimeVersion}] ${message}`;
|
||||
}
|
||||
|
||||
async function getEASUpdateURLAsync(exp: ExpoConfig): Promise<string> {
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
return new URL(projectId, EAS_UPDATE_URL).href;
|
||||
}
|
||||
|
||||
async function checkEASUpdateURLIsSetAsync(exp: ExpoConfig): Promise<void> {
|
||||
const configuredURL = exp.updates?.url;
|
||||
const expectedURL = await getEASUpdateURLAsync(exp);
|
||||
|
||||
if (configuredURL !== expectedURL) {
|
||||
throw new Error(
|
||||
`The update URL is incorrectly configured for EAS Update. Please set updates.url to ${expectedURL} in your app.json.`
|
||||
);
|
||||
throw new Error(BranchPublish.description);
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ function logSuccess(platform: RequestedPlatform): void {
|
||||
storesText = 'the Google Play Store';
|
||||
} else if (platform === 'ios') {
|
||||
platformsText = 'iOS project is';
|
||||
storesText = 'the Apple App Store';
|
||||
storesText = 'the Apple App Store Connect';
|
||||
}
|
||||
|
||||
Log.log(`🎉 Your ${platformsText} ready to build.
|
||||
|
||||
@@ -75,7 +75,7 @@ interface BuildFlags {
|
||||
}
|
||||
|
||||
export default class Build extends EasCommand {
|
||||
static description = 'start a build';
|
||||
static description = 'Start a build';
|
||||
|
||||
static flags = {
|
||||
platform: flags.enum({
|
||||
|
||||
@@ -89,7 +89,7 @@ export async function updateChannelBranchMappingAsync({
|
||||
);
|
||||
const channel = data.updateChannel.editUpdateChannel;
|
||||
if (!channel) {
|
||||
throw new Error(`Could not fine channel with id ${channelId}`);
|
||||
throw new Error(`Could not find a channel with id: ${channelId}`);
|
||||
}
|
||||
return data.updateChannel.editUpdateChannel!;
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ export function logChannelDetails(channel: {
|
||||
: []),
|
||||
formatUpdate(update),
|
||||
update?.runtimeVersion ?? 'N/A',
|
||||
update?.group ?? 'N/A,',
|
||||
update?.group ?? 'N/A',
|
||||
getPlatformsForGroup({
|
||||
updates: branch.updates,
|
||||
group: branch.updates[0]?.group,
|
||||
@@ -239,7 +239,7 @@ export default class ChannelView extends EasCommand {
|
||||
});
|
||||
const channel = getUpdateChannelByNameForAppresult.app?.byId.updateChannelByName;
|
||||
if (!channel) {
|
||||
throw new Error(`Could not fine channel with name ${channelName}`);
|
||||
throw new Error(`Could not find a channel with name: ${channelName}`);
|
||||
}
|
||||
|
||||
if (jsonFlag) {
|
||||
@@ -256,9 +256,7 @@ export default class ChannelView extends EasCommand {
|
||||
])
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
chalk`{bold Branches, pointed at by this channel, and their most recent update group:}`
|
||||
);
|
||||
Log.log(chalk`{bold Branches pointed at this channel and their most recent update group:}`);
|
||||
logChannelDetails(channel);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,10 @@
|
||||
import EasCommand from '../commandUtils/EasCommand';
|
||||
import { CredentialsContext } from '../credentials/context';
|
||||
import { SelectPlatform } from '../credentials/manager/SelectPlatform';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
|
||||
export default class Credentials extends EasCommand {
|
||||
static description = 'manage your credentials';
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const ctx = new CredentialsContext({
|
||||
projectDir: process.cwd(),
|
||||
user: await ensureLoggedInAsync(),
|
||||
});
|
||||
await new SelectPlatform().runAsync(ctx);
|
||||
await new SelectPlatform().runAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import envinfo from 'envinfo';
|
||||
|
||||
import EasCommand from '../commandUtils/EasCommand';
|
||||
import Log from '../log';
|
||||
import { findProjectRootAsync } from '../project/projectUtils';
|
||||
import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
|
||||
export default class Diagnostics extends EasCommand {
|
||||
@@ -32,6 +35,23 @@ export default class Diagnostics extends EasCommand {
|
||||
title: `EAS CLI ${easCliVersion} environment info`,
|
||||
}
|
||||
);
|
||||
Log.log(info);
|
||||
|
||||
Log.log(info.trimEnd());
|
||||
await this.printWorkflowAsync();
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
private async printWorkflowAsync(): Promise<void> {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const androidWorkflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
const iosWorkflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
|
||||
if (androidWorkflow === iosWorkflow) {
|
||||
Log.log(` Project workflow: ${androidWorkflow}`);
|
||||
} else {
|
||||
Log.log(` Project Workflow:`);
|
||||
Log.log(` Android: ${androidWorkflow}`);
|
||||
Log.log(` iOS: ${iosWorkflow}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ interface Flags {
|
||||
}
|
||||
|
||||
export default class Submit extends EasCommand {
|
||||
static description = `submit build archive to app store
|
||||
static description = `Submit build archive to App Store Connect
|
||||
See how to configure submits with eas.json: ${link('https://docs.expo.dev/submit/eas-json/')}`;
|
||||
static aliases = ['build:submit'];
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { ExpoConfig, getConfig, modifyConfigAsync } from '@expo/config';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
|
||||
const EAS_UPDATE_URL = 'https://u.expo.dev';
|
||||
const DEFAULT_RUNTIME_VERSION = { policy: 'sdkVersion' } as const;
|
||||
|
||||
export async function getEASUpdateURLAsync(exp: ExpoConfig): Promise<string> {
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
return new URL(projectId, EAS_UPDATE_URL).href;
|
||||
}
|
||||
|
||||
async function configureProjectForEASUpdateAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
): Promise<void> {
|
||||
const easUpdateURL = await getEASUpdateURLAsync(exp);
|
||||
const preexistingRuntimeVersion = exp.runtimeVersion;
|
||||
const result = await modifyConfigAsync(projectDir, {
|
||||
runtimeVersion: preexistingRuntimeVersion ?? DEFAULT_RUNTIME_VERSION,
|
||||
updates: { ...exp.updates, url: easUpdateURL },
|
||||
});
|
||||
|
||||
switch (result.type) {
|
||||
case 'success':
|
||||
if (exp.updates?.url) {
|
||||
if (exp.updates.url !== easUpdateURL) {
|
||||
Log.withTick(
|
||||
`Overwrote "${exp.updates?.url}" with "${easUpdateURL}" for the updates.url value in app.json`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Log.withTick(`Set updates.url value, to "${easUpdateURL}" in app.json`);
|
||||
}
|
||||
if (!preexistingRuntimeVersion) {
|
||||
Log.withTick(
|
||||
`Set runtimeVersion to "${JSON.stringify(DEFAULT_RUNTIME_VERSION)}" in app.json`
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'warn': {
|
||||
Log.addNewLineIfNone();
|
||||
Log.warn(
|
||||
`It looks like you are using a dynamic configuration! ${learnMore(
|
||||
'https://docs.expo.dev/workflow/configuration/#dynamic-configuration-with-appconfigjs)'
|
||||
)}`
|
||||
);
|
||||
Log.warn(
|
||||
`In order to finish configuring your project for EAS Update, you are going to need manually add the following to your app.config.js:\n${learnMore(
|
||||
'https://expo.fyi/eas-update-config.md'
|
||||
)}\n`
|
||||
);
|
||||
Log.log(
|
||||
chalk.bold(
|
||||
`{\n updates": {\n "url": "${easUpdateURL}"\n },\n "runtimeVersion": {\n "policy": "sdkVersion"\n }\n}`
|
||||
)
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
throw new Error(result.message);
|
||||
}
|
||||
case 'fail':
|
||||
throw new Error(result.message);
|
||||
default:
|
||||
throw new Error('Unexpected result type from modifyConfigAsync');
|
||||
}
|
||||
}
|
||||
|
||||
export default class UpdateConfigure extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'Configure the project to support EAS Update.';
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
Log.log(
|
||||
'💡 The following process will configure your project to run EAS Update. These changes only apply to your local project files and you can safely revert them at any time.'
|
||||
);
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, {
|
||||
skipSDKVersionRequirement: true,
|
||||
});
|
||||
|
||||
await configureProjectForEASUpdateAsync(projectDir, exp);
|
||||
|
||||
const hasAndroidNativeProject =
|
||||
(await resolveWorkflowAsync(projectDir, Platform.ANDROID)) === Workflow.GENERIC;
|
||||
const hasIosNativeProject =
|
||||
(await resolveWorkflowAsync(projectDir, Platform.IOS)) === Workflow.GENERIC;
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
if (hasAndroidNativeProject || hasIosNativeProject) {
|
||||
Log.log(
|
||||
`🧐 It seems you are on the bare workflow! Please also update your native files. You can do this by either running ${chalk.bold(
|
||||
'eas build:configure'
|
||||
)} or manually editing Expo.plist/AndroidManifest.xml. ${learnMore(
|
||||
'https://expo.fyi/eas-update-config.md#native-configuration'
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
Log.log(`🎉 Your app is configured to run EAS Update!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
import { ExpoConfig, getConfig } from '@expo/config';
|
||||
import { Updates } from '@expo/config-plugins';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { flags } from '@oclif/command';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import dateFormat from 'dateformat';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
GetUpdateGroupAsyncQuery,
|
||||
Robot,
|
||||
RootQueryUpdatesByGroupArgs,
|
||||
Update,
|
||||
UpdateInfoGroup,
|
||||
User,
|
||||
ViewBranchQuery,
|
||||
} from '../../graphql/generated';
|
||||
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import {
|
||||
PublishPlatform,
|
||||
buildBundlesAsync,
|
||||
buildUnsortedUpdateInfoGroupAsync,
|
||||
collectAssetsAsync,
|
||||
uploadAssetsAsync,
|
||||
} from '../../project/publish';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { promptAsync, selectAsync } from '../../prompts';
|
||||
import { formatUpdate } from '../../update/utils';
|
||||
import uniqBy from '../../utils/expodash/uniqBy';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { createUpdateBranchOnAppAsync } from '../branch/create';
|
||||
import { listBranchesAsync } from '../branch/list';
|
||||
import { viewUpdateBranchAsync } from '../branch/view';
|
||||
import { createUpdateChannelOnAppAsync } from '../channel/create';
|
||||
import { getEASUpdateURLAsync } from './configure';
|
||||
|
||||
export const defaultPublishPlatforms: PublishPlatform[] = ['android', 'ios'];
|
||||
type PlatformFlag = PublishPlatform | 'all';
|
||||
|
||||
async function getUpdateGroupAsync({
|
||||
group,
|
||||
}: RootQueryUpdatesByGroupArgs): Promise<GetUpdateGroupAsyncQuery['updatesByGroup']> {
|
||||
const { updatesByGroup } = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<GetUpdateGroupAsyncQuery, RootQueryUpdatesByGroupArgs>(
|
||||
gql`
|
||||
query getUpdateGroupAsync($group: ID!) {
|
||||
updatesByGroup(group: $group) {
|
||||
id
|
||||
group
|
||||
runtimeVersion
|
||||
manifestFragment
|
||||
platform
|
||||
message
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
group,
|
||||
},
|
||||
{ additionalTypenames: ['Update'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return updatesByGroup;
|
||||
}
|
||||
|
||||
async function ensureChannelExistsAsync({
|
||||
appId,
|
||||
branchId,
|
||||
channelName,
|
||||
}: {
|
||||
appId: string;
|
||||
branchId: string;
|
||||
channelName: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
await createUpdateChannelOnAppAsync({
|
||||
appId,
|
||||
channelName,
|
||||
branchId,
|
||||
});
|
||||
Log.withTick(
|
||||
`Created a channel: ${chalk.bold(channelName)} pointed at branch: ${chalk.bold(channelName)}.`
|
||||
);
|
||||
} catch (e: any) {
|
||||
const isIgnorableError =
|
||||
e.graphQLErrors?.length === 1 &&
|
||||
e.graphQLErrors[0].extensions.errorCode === 'CHANNEL_ALREADY_EXISTS';
|
||||
if (!isIgnorableError) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureBranchExistsAsync({
|
||||
appId,
|
||||
name: branchName,
|
||||
}: {
|
||||
appId: string;
|
||||
name: string;
|
||||
}): Promise<{
|
||||
id: string;
|
||||
updates: Exclude<
|
||||
Exclude<ViewBranchQuery['app'], null | undefined>['byId']['updateBranchByName'],
|
||||
null | undefined
|
||||
>['updates'];
|
||||
}> {
|
||||
const { app } = await viewUpdateBranchAsync({
|
||||
appId,
|
||||
name: branchName,
|
||||
});
|
||||
const updateBranch = app?.byId.updateBranchByName;
|
||||
if (updateBranch) {
|
||||
const { id, updates } = updateBranch;
|
||||
await ensureChannelExistsAsync({ appId, branchId: id, channelName: branchName });
|
||||
return { id, updates };
|
||||
}
|
||||
|
||||
const newUpdateBranch = await createUpdateBranchOnAppAsync({ appId, name: branchName });
|
||||
Log.withTick(`Created branch: ${chalk.bold(branchName)}`);
|
||||
await ensureChannelExistsAsync({ appId, branchId: newUpdateBranch.id, channelName: branchName });
|
||||
return { id: newUpdateBranch.id, updates: [] };
|
||||
}
|
||||
|
||||
export default class UpdatePublish extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'Publish an update group.';
|
||||
|
||||
static flags = {
|
||||
branch: flags.string({
|
||||
description: 'Branch to publish the update group on',
|
||||
required: false,
|
||||
}),
|
||||
message: flags.string({
|
||||
description: 'A short message describing the update',
|
||||
required: false,
|
||||
}),
|
||||
republish: flags.boolean({
|
||||
description: 'Republish an update group',
|
||||
exclusive: ['input-dir', 'skip-bundler'],
|
||||
}),
|
||||
group: flags.string({
|
||||
description: 'Update group to republish',
|
||||
exclusive: ['input-dir', 'skip-bundler'],
|
||||
}),
|
||||
'input-dir': flags.string({
|
||||
description: 'Location of the bundle',
|
||||
default: 'dist',
|
||||
required: false,
|
||||
}),
|
||||
'skip-bundler': flags.boolean({
|
||||
description: `Skip running Expo CLI to bundle the app before publishing`,
|
||||
default: false,
|
||||
}),
|
||||
platform: flags.enum({
|
||||
char: 'p',
|
||||
options: [...defaultPublishPlatforms, 'all'],
|
||||
default: 'all',
|
||||
required: false,
|
||||
}),
|
||||
json: flags.boolean({
|
||||
description: 'Enable JSON output, non-JSON messages will be printed to stderr',
|
||||
default: false,
|
||||
}),
|
||||
auto: flags.boolean({
|
||||
description:
|
||||
'Use the current git branch and commit message for the EAS branch and update message',
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
flags: {
|
||||
branch: branchName,
|
||||
json: jsonFlag,
|
||||
auto: autoFlag,
|
||||
message,
|
||||
republish,
|
||||
group,
|
||||
'input-dir': inputDir,
|
||||
'skip-bundler': skipBundler,
|
||||
},
|
||||
} = this.parse(UpdatePublish);
|
||||
const platformFlag = this.parse(UpdatePublish).flags.platform as PlatformFlag;
|
||||
// If a group was specified, that means we are republishing it.
|
||||
republish = group ? true : republish;
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, {
|
||||
skipSDKVersionRequirement: true,
|
||||
isPublicConfig: true,
|
||||
});
|
||||
|
||||
const runtimeVersions = await getRuntimeVersionObjectAsync(exp, platformFlag, projectDir);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
await checkEASUpdateURLIsSetAsync(exp);
|
||||
|
||||
if (!branchName && autoFlag) {
|
||||
branchName =
|
||||
(await getVcsClient().getBranchNameAsync()) ||
|
||||
`branch-${Math.random().toString(36).substr(2, 4)}`;
|
||||
}
|
||||
|
||||
if (!branchName) {
|
||||
const validationMessage = 'branch name may not be empty.';
|
||||
if (jsonFlag) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
|
||||
const branches = await listBranchesAsync({ projectId });
|
||||
branchName = await selectAsync<string>(
|
||||
'which branch would you like to publish on?',
|
||||
branches.map(branch => {
|
||||
return {
|
||||
title: `${branch.name} ${chalk.grey(
|
||||
`- current update: ${formatUpdate(branch.updates[0])}`
|
||||
)}`,
|
||||
value: branch.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
assert(branchName, 'branch name must be specified.');
|
||||
}
|
||||
|
||||
const { id: branchId, updates } = await ensureBranchExistsAsync({
|
||||
appId: projectId,
|
||||
name: branchName,
|
||||
});
|
||||
|
||||
let unsortedUpdateInfoGroups: UpdateInfoGroup = {};
|
||||
let oldMessage: string, oldRuntimeVersion: string;
|
||||
if (republish) {
|
||||
// If we are republishing, we don't need to worry about building the bundle or uploading the assets.
|
||||
// Instead we get the `updateInfoGroup` from the update we wish to republish.
|
||||
let updatesToRepublish: Pick<
|
||||
Update,
|
||||
'group' | 'message' | 'runtimeVersion' | 'manifestFragment' | 'platform'
|
||||
>[];
|
||||
if (group) {
|
||||
updatesToRepublish = await getUpdateGroupAsync({ group });
|
||||
} else {
|
||||
// Drop into interactive mode if the user has not specified an update group to republish.
|
||||
if (jsonFlag) {
|
||||
throw new Error('You must specify the update group to republish.');
|
||||
}
|
||||
|
||||
const updateGroups = uniqBy(updates, u => u.group)
|
||||
.filter(update => {
|
||||
// Only show groups that have updates on the specified platform(s).
|
||||
return platformFlag === 'all' || update.platform === platformFlag;
|
||||
})
|
||||
.map(update => ({
|
||||
title: formatUpdateTitle(update),
|
||||
value: update.group,
|
||||
}));
|
||||
if (updateGroups.length === 0) {
|
||||
throw new Error(
|
||||
`There are no updates on branch "${branchName}" published on the platform(s) ${platformFlag}. Did you mean to publish a new update instead?`
|
||||
);
|
||||
}
|
||||
|
||||
const selectedUpdateGroup = await selectAsync<string>(
|
||||
'which update would you like to republish?',
|
||||
updateGroups
|
||||
);
|
||||
updatesToRepublish = updates.filter(update => update.group === selectedUpdateGroup);
|
||||
}
|
||||
const updatesToRepublishFilteredByPlatform = updatesToRepublish.filter(
|
||||
// Only republish to the specified platforms
|
||||
update => platformFlag === 'all' || update.platform === platformFlag
|
||||
);
|
||||
if (updatesToRepublishFilteredByPlatform.length === 0) {
|
||||
throw new Error(
|
||||
`There are no updates on branch "${branchName}" published on the platform(s) "${platformFlag}" with group ID "${
|
||||
group ? group : updatesToRepublish[0].group
|
||||
}". Did you mean to publish a new update instead?`
|
||||
);
|
||||
}
|
||||
|
||||
let publicationPlatformMessage: string;
|
||||
if (platformFlag === 'all') {
|
||||
if (updatesToRepublishFilteredByPlatform.length !== defaultPublishPlatforms.length) {
|
||||
Log.warn(`You are republishing an update that wasn't published for all platforms.`);
|
||||
}
|
||||
publicationPlatformMessage = `The republished update will appear on the same plaforms it was originally published on: ${updatesToRepublishFilteredByPlatform
|
||||
.map(update => update.platform)
|
||||
.join(',')}`;
|
||||
} else {
|
||||
publicationPlatformMessage = `The republished update will appear only on: ${platformFlag}`;
|
||||
}
|
||||
Log.withTick(publicationPlatformMessage);
|
||||
|
||||
for (const update of updatesToRepublishFilteredByPlatform) {
|
||||
const { manifestFragment } = update;
|
||||
const platform = update.platform as PublishPlatform;
|
||||
|
||||
unsortedUpdateInfoGroups[platform] = JSON.parse(manifestFragment);
|
||||
}
|
||||
|
||||
// These are the same for each member of an update group
|
||||
group = updatesToRepublishFilteredByPlatform[0].group;
|
||||
oldMessage = updatesToRepublishFilteredByPlatform[0].message ?? '';
|
||||
oldRuntimeVersion = updatesToRepublishFilteredByPlatform[0].runtimeVersion;
|
||||
} else {
|
||||
if (!message && autoFlag) {
|
||||
message = (await getVcsClient().getLastCommitMessageAsync())?.trim();
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
const validationMessage = 'publish message may not be empty.';
|
||||
if (jsonFlag) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
({ publishMessage: message } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'publishMessage',
|
||||
message: `Please enter an update message.`,
|
||||
initial: republish
|
||||
? `Republish "${oldMessage!}" - group: ${group}`
|
||||
: (await getVcsClient().getLastCommitMessageAsync())?.trim(),
|
||||
validate: (value: any) => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
|
||||
// build bundle and upload assets for a new publish
|
||||
if (!skipBundler) {
|
||||
await buildBundlesAsync({ projectDir, inputDir });
|
||||
}
|
||||
|
||||
const assetSpinner = ora('Uploading assets...').start();
|
||||
try {
|
||||
const platforms = platformFlag === 'all' ? defaultPublishPlatforms : [platformFlag];
|
||||
const assets = await collectAssetsAsync({ inputDir: inputDir!, platforms });
|
||||
await uploadAssetsAsync(assets);
|
||||
unsortedUpdateInfoGroups = await buildUnsortedUpdateInfoGroupAsync(assets, exp);
|
||||
assetSpinner.succeed('Uploaded assets!');
|
||||
} catch (e) {
|
||||
assetSpinner.fail('Failed to upload assets');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const runtimeToPlatformMapping: Record<string, string[]> = {};
|
||||
for (const runtime of new Set(Object.values(runtimeVersions))) {
|
||||
runtimeToPlatformMapping[runtime] = Object.entries(runtimeVersions)
|
||||
.filter(pair => pair[1] === runtime)
|
||||
.map(pair => pair[0]);
|
||||
}
|
||||
|
||||
// Sort the updates into different groups based on their platform specific runtime versions
|
||||
const updateGroups = Object.entries(runtimeToPlatformMapping).map(([runtime, platforms]) => {
|
||||
const localUpdateInfoGroup = Object.fromEntries(
|
||||
platforms.map(platform => [
|
||||
platform,
|
||||
unsortedUpdateInfoGroups[platform as keyof UpdateInfoGroup],
|
||||
])
|
||||
);
|
||||
|
||||
if (republish && !oldRuntimeVersion) {
|
||||
throw new Error(
|
||||
'Can not find the runtime version of the update group that is being republished.'
|
||||
);
|
||||
}
|
||||
return {
|
||||
branchId,
|
||||
updateInfoGroup: localUpdateInfoGroup,
|
||||
runtimeVersion: republish ? oldRuntimeVersion : runtime,
|
||||
message,
|
||||
};
|
||||
});
|
||||
let newUpdates;
|
||||
const publishSpinner = ora('Publishing...').start();
|
||||
try {
|
||||
newUpdates = await PublishMutation.publishUpdateGroupAsync(updateGroups);
|
||||
publishSpinner.succeed('Published!');
|
||||
} catch (e) {
|
||||
publishSpinner.fail('Failed to published updates');
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (jsonFlag) {
|
||||
Log.log(JSON.stringify(newUpdates));
|
||||
} else {
|
||||
if (new Set(newUpdates.map(update => update.group)).size > 1) {
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
'👉 Since multiple runtime versions are defined, multiple update groups have been published.'
|
||||
);
|
||||
}
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
for (const runtime of new Set(Object.values(runtimeVersions))) {
|
||||
const platforms = newUpdates
|
||||
.filter(update => update.runtimeVersion === runtime)
|
||||
.map(update => update.platform);
|
||||
const newUpdate = newUpdates.find(update => update.runtimeVersion === runtime);
|
||||
if (!newUpdate) {
|
||||
throw new Error(`Publish response is missing updates with runtime ${runtime}.`);
|
||||
}
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'branch', value: branchName },
|
||||
{ label: 'runtime version', value: runtime },
|
||||
{ label: 'platform', value: platforms.join(', ') },
|
||||
{ label: 'update group ID', value: newUpdate.group },
|
||||
{ label: 'message', value: message! },
|
||||
])
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getRuntimeVersionObjectAsync(
|
||||
exp: ExpoConfig,
|
||||
platformFlag: PlatformFlag,
|
||||
projectDir: string
|
||||
): Promise<Record<string, string>> {
|
||||
const platforms = (platformFlag === 'all' ? ['android', 'ios'] : [platformFlag]) as Platform[];
|
||||
|
||||
for (const platform of platforms) {
|
||||
const isPolicy = typeof (exp[platform]?.runtimeVersion ?? exp.runtimeVersion) === 'object';
|
||||
if (isPolicy) {
|
||||
const isManaged = (await resolveWorkflowAsync(projectDir, platform)) === Workflow.MANAGED;
|
||||
if (!isManaged) {
|
||||
throw new Error('Runtime version policies are only supported in the managed workflow.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
platforms.map(platform => [platform, Updates.getRuntimeVersion(exp, platform)])
|
||||
);
|
||||
}
|
||||
|
||||
function formatUpdateTitle(
|
||||
update: Exclude<
|
||||
Exclude<ViewBranchQuery['app'], null | undefined>['byId']['updateBranchByName'],
|
||||
null | undefined
|
||||
>['updates'][number]
|
||||
): string {
|
||||
const { message, createdAt, actor, runtimeVersion } = update;
|
||||
|
||||
let actorName: string;
|
||||
switch (actor?.__typename) {
|
||||
case 'User': {
|
||||
actorName = (actor as Pick<User, 'username' | 'id'>).username;
|
||||
break;
|
||||
}
|
||||
case 'Robot': {
|
||||
const { firstName, id } = actor as Pick<Robot, 'firstName' | 'id'>;
|
||||
actorName = firstName ?? `robot: ${id.slice(0, 4)}...`;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
actorName = 'unknown';
|
||||
}
|
||||
return `[${dateFormat(
|
||||
createdAt,
|
||||
'mmm dd HH:MM'
|
||||
)} by ${actorName}, runtimeVersion: ${runtimeVersion}] ${message}`;
|
||||
}
|
||||
|
||||
async function checkEASUpdateURLIsSetAsync(exp: ExpoConfig): Promise<void> {
|
||||
const configuredURL = exp.updates?.url;
|
||||
const expectedURL = await getEASUpdateURLAsync(exp);
|
||||
|
||||
if (configuredURL !== expectedURL) {
|
||||
throw new Error(
|
||||
`The update URL is incorrectly configured for EAS Update. Please set updates.url to ${expectedURL} in your app.json.`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ExpoConfig, getConfig } from '@expo/config';
|
||||
import { Env } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import Log from '../log';
|
||||
@@ -27,6 +28,7 @@ export class CredentialsContext {
|
||||
nonInteractive?: boolean;
|
||||
projectDir: string;
|
||||
user: Actor;
|
||||
env?: Env;
|
||||
}
|
||||
) {
|
||||
this.projectDir = options.projectDir;
|
||||
@@ -35,11 +37,21 @@ export class CredentialsContext {
|
||||
|
||||
this.resolvedExp = options.exp;
|
||||
if (!this.resolvedExp) {
|
||||
try {
|
||||
this.resolvedExp = getExpoConfig(options.projectDir);
|
||||
} catch (error) {
|
||||
// ignore error, context might be created outside of expo project
|
||||
}
|
||||
this.resolvedExp =
|
||||
CredentialsContext.getExpoConfigInProject(this.projectDir, { env: options.env }) ??
|
||||
undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static getExpoConfigInProject(
|
||||
projectDir: string,
|
||||
{ env }: { env?: Env } = {}
|
||||
): ExpoConfig | null {
|
||||
try {
|
||||
return getExpoConfig(projectDir, { env });
|
||||
} catch (error) {
|
||||
// ignore error, context might be created outside of expo project
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,9 @@ async function promptForAscApiKeyAsync(ctx: CredentialsContext): Promise<Minimal
|
||||
|
||||
async function promptForKeyP8AndIdAsync(): Promise<Pick<AscApiKeyPath, 'keyP8Path' | 'keyId'>> {
|
||||
Log.log(
|
||||
chalk.bold('An App Store Connect Api key is required to upload your app to the Apple App Store')
|
||||
chalk.bold(
|
||||
'An App Store Connect Api key is required to upload your app to the Apple App Store Connect'
|
||||
)
|
||||
);
|
||||
Log.log(
|
||||
`If you're not sure what this is or how to create one, ${learnMore(
|
||||
|
||||
@@ -162,11 +162,13 @@ export async function ensureAppExistsAsync(
|
||||
}
|
||||
): Promise<App> {
|
||||
const context = getRequestContext(authCtx);
|
||||
const spinner = ora(`Linking to App Store ${chalk.dim(bundleIdentifier)}`).start();
|
||||
const spinner = ora(`Linking to App Store Connect ${chalk.dim(bundleIdentifier)}`).start();
|
||||
|
||||
let app = await App.findAsync(context, { bundleId: bundleIdentifier });
|
||||
if (!app) {
|
||||
spinner.text = `Creating App Store app ${chalk.bold(name)} ${chalk.dim(bundleIdentifier)}`;
|
||||
spinner.text = `Creating App Store Connect app ${chalk.bold(name)} ${chalk.dim(
|
||||
bundleIdentifier
|
||||
)}`;
|
||||
try {
|
||||
// Assert contract errors when the user needs to create an app.
|
||||
await assertContractMessagesAsync(context, spinner);
|
||||
@@ -194,6 +196,8 @@ export async function ensureAppExistsAsync(
|
||||
} else {
|
||||
// TODO: Update app name when API gives us that possibility
|
||||
}
|
||||
spinner.succeed(`Prepared App Store for ${chalk.bold(name)} ${chalk.dim(bundleIdentifier)}`);
|
||||
spinner.succeed(
|
||||
`Prepared App Store Connect for ${chalk.bold(name)} ${chalk.dim(bundleIdentifier)}`
|
||||
);
|
||||
return app;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { findAccountByName } from '../../user/Account';
|
||||
import { ensureActorHasUsername } from '../../user/actions';
|
||||
import { ensureActorHasUsername, ensureLoggedInAsync } from '../../user/actions';
|
||||
import { AssignFcm } from '../android/actions/AssignFcm';
|
||||
import { AssignGoogleServiceAccountKey } from '../android/actions/AssignGoogleServiceAccountKey';
|
||||
import {
|
||||
@@ -51,12 +51,18 @@ import {
|
||||
import { SelectBuildProfileFromEasJson } from './SelectBuildProfileFromEasJson';
|
||||
|
||||
export class ManageAndroid {
|
||||
constructor(private callingAction: Action) {}
|
||||
constructor(private callingAction: Action, private projectDir: string) {}
|
||||
|
||||
async runAsync(
|
||||
ctx: CredentialsContext,
|
||||
currentActions: ActionInfo[] = highLevelActions
|
||||
): Promise<void> {
|
||||
async runAsync(currentActions: ActionInfo[] = highLevelActions): Promise<void> {
|
||||
const hasProjectContext = !!CredentialsContext.getExpoConfigInProject(this.projectDir);
|
||||
const buildProfile = hasProjectContext
|
||||
? await new SelectBuildProfileFromEasJson(this.projectDir, Platform.ANDROID).runAsync()
|
||||
: null;
|
||||
const ctx = new CredentialsContext({
|
||||
projectDir: process.cwd(),
|
||||
user: await ensureLoggedInAsync(),
|
||||
env: buildProfile?.env,
|
||||
});
|
||||
const accountName = ctx.hasProjectContext
|
||||
? getProjectAccountName(ctx.exp, ctx.user)
|
||||
: ensureActorHasUsername(ctx.user);
|
||||
@@ -64,7 +70,12 @@ export class ManageAndroid {
|
||||
if (!account) {
|
||||
throw new Error(`You do not have access to account: ${accountName}`);
|
||||
}
|
||||
const { gradleContext } = await this.createProjectContextAsync(ctx);
|
||||
|
||||
let gradleContext;
|
||||
if (ctx.hasProjectContext) {
|
||||
assert(buildProfile, 'buildProfile must be defined in a project context');
|
||||
gradleContext = await this.createProjectContextAsync(ctx, buildProfile);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
@@ -140,30 +151,18 @@ export class ManageAndroid {
|
||||
}
|
||||
}
|
||||
|
||||
private async createProjectContextAsync(ctx: CredentialsContext): Promise<{
|
||||
gradleContext?: GradleBuildContext;
|
||||
buildProfile?: BuildProfile<Platform.ANDROID>;
|
||||
}> {
|
||||
if (!ctx.hasProjectContext) {
|
||||
return {};
|
||||
}
|
||||
|
||||
private async createProjectContextAsync(
|
||||
ctx: CredentialsContext,
|
||||
buildProfile: BuildProfile<Platform.ANDROID>
|
||||
): Promise<GradleBuildContext | undefined> {
|
||||
assert(ctx.hasProjectContext, 'createProjectContextAsync: must have project context.');
|
||||
const maybeProjectId = await promptToCreateProjectIfNotExistsAsync(ctx.exp);
|
||||
if (!maybeProjectId) {
|
||||
throw new Error(
|
||||
'Your project must be registered with EAS in order to use the credentials manager.'
|
||||
);
|
||||
}
|
||||
|
||||
const buildProfile = await new SelectBuildProfileFromEasJson(
|
||||
ctx.projectDir,
|
||||
Platform.ANDROID
|
||||
).runAsync(ctx);
|
||||
const gradleContext = await resolveGradleBuildContextAsync(ctx.projectDir, buildProfile);
|
||||
return {
|
||||
gradleContext,
|
||||
buildProfile,
|
||||
};
|
||||
return await resolveGradleBuildContextAsync(ctx.projectDir, buildProfile);
|
||||
}
|
||||
|
||||
private async runProjectSpecificActionAsync(
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from '../../project/projectUtils';
|
||||
import { confirmAsync, promptAsync, selectAsync } from '../../prompts';
|
||||
import { Account, findAccountByName } from '../../user/Account';
|
||||
import { ensureActorHasUsername } from '../../user/actions';
|
||||
import { ensureActorHasUsername, ensureLoggedInAsync } from '../../user/actions';
|
||||
import { CredentialsContext } from '../context';
|
||||
import {
|
||||
AppStoreApiKeyPurpose,
|
||||
@@ -59,12 +59,18 @@ import { SelectBuildProfileFromEasJson } from './SelectBuildProfileFromEasJson';
|
||||
import { SelectIosDistributionTypeGraphqlFromBuildProfile } from './SelectIosDistributionTypeGraphqlFromBuildProfile';
|
||||
|
||||
export class ManageIos {
|
||||
constructor(private callingAction: Action) {}
|
||||
constructor(private callingAction: Action, private projectDir: string) {}
|
||||
|
||||
async runAsync(
|
||||
ctx: CredentialsContext,
|
||||
currentActions: ActionInfo[] = highLevelActions
|
||||
): Promise<void> {
|
||||
async runAsync(currentActions: ActionInfo[] = highLevelActions): Promise<void> {
|
||||
const hasProjectContext = !!CredentialsContext.getExpoConfigInProject(this.projectDir);
|
||||
const buildProfile = hasProjectContext
|
||||
? await new SelectBuildProfileFromEasJson(this.projectDir, Platform.IOS).runAsync()
|
||||
: null;
|
||||
const ctx = new CredentialsContext({
|
||||
projectDir: process.cwd(),
|
||||
user: await ensureLoggedInAsync(),
|
||||
env: buildProfile?.env,
|
||||
});
|
||||
const buildCredentialsActions = getBuildCredentialsActions(ctx);
|
||||
const pushKeyActions = getPushKeyActions(ctx);
|
||||
const ascApiKeyActions = getAscApiKeyActions(ctx);
|
||||
@@ -78,7 +84,16 @@ export class ManageIos {
|
||||
if (!account) {
|
||||
throw new Error(`You do not have access to account: ${accountName}`);
|
||||
}
|
||||
const { app, targets, buildProfile } = await this.createProjectContextAsync(ctx, account);
|
||||
|
||||
let app = null;
|
||||
let targets = null;
|
||||
if (ctx.hasProjectContext) {
|
||||
assert(buildProfile, 'buildProfile must be defined in project context');
|
||||
const projectContext = await this.createProjectContextAsync(ctx, account, buildProfile);
|
||||
app = projectContext.app;
|
||||
targets = projectContext.targets;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
if (ctx.hasProjectContext) {
|
||||
@@ -154,20 +169,13 @@ export class ManageIos {
|
||||
|
||||
private async createProjectContextAsync(
|
||||
ctx: CredentialsContext,
|
||||
account: Account
|
||||
account: Account,
|
||||
buildProfile: BuildProfile<Platform.IOS>
|
||||
): Promise<{
|
||||
app: App | null;
|
||||
targets: Target[] | null;
|
||||
buildProfile: BuildProfile<Platform.IOS> | null;
|
||||
app: App;
|
||||
targets: Target[];
|
||||
}> {
|
||||
if (!ctx.hasProjectContext) {
|
||||
return {
|
||||
app: null,
|
||||
targets: null,
|
||||
buildProfile: null,
|
||||
};
|
||||
}
|
||||
|
||||
assert(ctx.hasProjectContext, 'createProjectContextAsync: must have project context.');
|
||||
const maybeProjectId = await promptToCreateProjectIfNotExistsAsync(ctx.exp);
|
||||
if (!maybeProjectId) {
|
||||
throw new Error(
|
||||
@@ -176,10 +184,6 @@ export class ManageIos {
|
||||
}
|
||||
|
||||
const app = { account, projectName: ctx.exp.slug };
|
||||
const buildProfile = await new SelectBuildProfileFromEasJson(
|
||||
ctx.projectDir,
|
||||
Platform.IOS
|
||||
).runAsync(ctx);
|
||||
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
|
||||
{
|
||||
projectDir: ctx.projectDir,
|
||||
@@ -195,7 +199,6 @@ export class ManageIos {
|
||||
return {
|
||||
app,
|
||||
targets,
|
||||
buildProfile,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { BuildProfile, EasJsonReader } from '@expo/eas-json';
|
||||
|
||||
import Log from '../../log';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { CredentialsContext } from '../context';
|
||||
|
||||
export class SelectBuildProfileFromEasJson<T extends Platform> {
|
||||
private easJsonReader: EasJsonReader;
|
||||
@@ -12,14 +11,14 @@ export class SelectBuildProfileFromEasJson<T extends Platform> {
|
||||
this.easJsonReader = new EasJsonReader(projectDir);
|
||||
}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<BuildProfile<T>> {
|
||||
const profileName = await this.getProfileNameFromEasConfigAsync(ctx);
|
||||
async runAsync(): Promise<BuildProfile<T>> {
|
||||
const profileName = await this.getProfileNameFromEasConfigAsync();
|
||||
const easConfig = await this.easJsonReader.getBuildProfileAsync<T>(this.platform, profileName);
|
||||
Log.succeed(`Using build profile: ${profileName}`);
|
||||
return easConfig;
|
||||
}
|
||||
|
||||
async getProfileNameFromEasConfigAsync(ctx: CredentialsContext): Promise<string> {
|
||||
async getProfileNameFromEasConfigAsync(): Promise<string> {
|
||||
const buildProfileNames = await this.easJsonReader.getBuildProfileNamesAsync();
|
||||
if (buildProfileNames.length === 0) {
|
||||
throw new Error(
|
||||
@@ -28,11 +27,6 @@ export class SelectBuildProfileFromEasJson<T extends Platform> {
|
||||
} else if (buildProfileNames.length === 1) {
|
||||
return buildProfileNames[0];
|
||||
}
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(
|
||||
'You have multiple build profiles. Please run this command in interactive mode.'
|
||||
);
|
||||
}
|
||||
const { profileName } = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'profileName',
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { CredentialsContext } from '../context';
|
||||
import { ManageAndroid } from './ManageAndroid';
|
||||
import { ManageIos } from './ManageIos';
|
||||
|
||||
export class SelectPlatform {
|
||||
async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
async runAsync(): Promise<void> {
|
||||
const { platform } = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'platform',
|
||||
@@ -16,8 +15,8 @@ export class SelectPlatform {
|
||||
});
|
||||
|
||||
if (platform === 'ios') {
|
||||
return await new ManageIos(new SelectPlatform()).runAsync(ctx);
|
||||
return await new ManageIos(new SelectPlatform(), process.cwd()).runAsync();
|
||||
}
|
||||
return await new ManageAndroid(new SelectPlatform()).runAsync(ctx);
|
||||
return await new ManageAndroid(new SelectPlatform(), process.cwd()).runAsync();
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import mockdate from 'mockdate';
|
||||
import path from 'path';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { defaultPublishPlatforms } from '../../commands/branch/publish';
|
||||
import { defaultPublishPlatforms } from '../../commands/update';
|
||||
import { AssetMetadataStatus } from '../../graphql/generated';
|
||||
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
|
||||
import { PublishQuery } from '../../graphql/queries/PublishQuery';
|
||||
|
||||
@@ -24,17 +24,57 @@ export async function resolveTargetsAsync(
|
||||
buildConfiguration,
|
||||
});
|
||||
|
||||
if (applicationTarget.dependencies && applicationTarget.dependencies.length > 0) {
|
||||
for (const dependency of applicationTarget.dependencies) {
|
||||
const dependencies = await resolveDependenciesAsync({
|
||||
exp,
|
||||
projectDir,
|
||||
buildConfiguration,
|
||||
target: applicationTarget,
|
||||
bundleIdentifier,
|
||||
});
|
||||
if (dependencies.length > 0) {
|
||||
result.push(...dependencies);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function resolveDependenciesAsync({
|
||||
exp,
|
||||
projectDir,
|
||||
buildConfiguration,
|
||||
target,
|
||||
bundleIdentifier,
|
||||
}: {
|
||||
exp: ExpoConfig;
|
||||
projectDir: string;
|
||||
buildConfiguration?: string;
|
||||
target: IOSConfig.Target.Target;
|
||||
bundleIdentifier: string;
|
||||
}): Promise<Target[]> {
|
||||
const result: Target[] = [];
|
||||
|
||||
if (target.dependencies && target.dependencies.length > 0) {
|
||||
for (const dependency of target.dependencies) {
|
||||
const dependencyBundleIdentifier = await getBundleIdentifierAsync(projectDir, exp, {
|
||||
targetName: dependency.name,
|
||||
buildConfiguration,
|
||||
});
|
||||
result.push({
|
||||
targetName: dependency.name,
|
||||
buildConfiguration,
|
||||
bundleIdentifier: await getBundleIdentifierAsync(projectDir, exp, {
|
||||
targetName: dependency.name,
|
||||
buildConfiguration,
|
||||
}),
|
||||
bundleIdentifier: dependencyBundleIdentifier,
|
||||
parentBundleIdentifier: bundleIdentifier,
|
||||
});
|
||||
const dependencyDependencies = await resolveDependenciesAsync({
|
||||
exp,
|
||||
projectDir,
|
||||
buildConfiguration,
|
||||
target: dependency,
|
||||
bundleIdentifier: dependencyBundleIdentifier,
|
||||
});
|
||||
if (dependencyDependencies.length > 0) {
|
||||
result.push(...dependencyDependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@ export async function createSubmissionContextAsync<T extends Platform>(params: {
|
||||
tracking_id: uuidv4(),
|
||||
platform: params.platform,
|
||||
...(accountId && { account_id: accountId }),
|
||||
account_name: accountName,
|
||||
project_id: params.projectId,
|
||||
};
|
||||
|
||||
|
||||
@@ -35,7 +35,10 @@ export async function ensureAppStoreConnectAppExistsAsync(
|
||||
const { appName, language } = ctx.profile;
|
||||
const options = {
|
||||
...ctx.profile,
|
||||
bundleIdentifier: await getBundleIdentifierAsync(ctx.projectDir, exp),
|
||||
bundleIdentifier:
|
||||
ctx.applicationIdentifierOverride ??
|
||||
ctx.profile.bundleIdentifier ??
|
||||
(await getBundleIdentifierAsync(ctx.projectDir, exp)),
|
||||
appName: appName ?? exp.name ?? (await promptForAppNameAsync()),
|
||||
language: sanitizeLanguage(language),
|
||||
};
|
||||
|
||||
@@ -200,7 +200,11 @@ export default class IosSubmitCommand {
|
||||
if (ascAppId) {
|
||||
return result(ascAppId);
|
||||
} else if (this.ctx.nonInteractive) {
|
||||
return result(new Error('Set ascAppId in the submit profile (eas.json).'));
|
||||
return result(
|
||||
new Error(
|
||||
'Set ascAppId in the submit profile (eas.json) or re-run this command in interactive mode.'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
Log.log(
|
||||
wrapAnsi(
|
||||
|
||||
@@ -8,7 +8,7 @@ import { sleepAsync } from '../../utils/promise';
|
||||
|
||||
const APP_STORE_NAMES: Record<AppPlatform, string> = {
|
||||
[AppPlatform.Android]: 'Google Play Store',
|
||||
[AppPlatform.Ios]: 'Apple App Store',
|
||||
[AppPlatform.Ios]: 'Apple App Store Connect',
|
||||
};
|
||||
|
||||
const CHECK_TIMEOUT_MS = 3600_000;
|
||||
|
||||
@@ -29,7 +29,7 @@ export function getPlatformsForGroup({
|
||||
return groupedUpdates[group]
|
||||
.map(update => update.platform)
|
||||
.sort()
|
||||
.join(',');
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
export function formatUpdate(update: FormatUpdateParameter): string {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"description": "A library for interacting with eas.json",
|
||||
"version": "0.38.2",
|
||||
"version": "0.40.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/eas-build-job": "0.2.57",
|
||||
"@expo/eas-build-job": "0.2.61",
|
||||
"@expo/json-file": "8.2.33",
|
||||
"chalk": "4.1.2",
|
||||
"env-string": "1.0.1",
|
||||
@@ -16,7 +16,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "9.0.12",
|
||||
"memfs": "3.2.2"
|
||||
"memfs": "3.2.2",
|
||||
"typescript": "4.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
|
||||
@@ -66,7 +66,10 @@ function addEntry(tokens: markdown.Tokens, entry: Entry): void {
|
||||
}
|
||||
|
||||
function formatEntry({ message, prNumber, author }: Entry): string {
|
||||
const messageWithDot = message.endsWith('.') ? message : `${message}.`;
|
||||
const capitalizedMessage = `${message[0].toUpperCase()}${message.slice(1)}`;
|
||||
const messageWithDot = capitalizedMessage.endsWith('.')
|
||||
? capitalizedMessage
|
||||
: `${capitalizedMessage}.`;
|
||||
return `${messageWithDot} ([#${prNumber}](https://github.com/expo/eas-cli/pull/${prNumber}) by [@${author}](https://github.com/${author}))`;
|
||||
}
|
||||
|
||||
|
||||
@@ -848,14 +848,15 @@
|
||||
mv "~2"
|
||||
safe-json-stringify "~1"
|
||||
|
||||
"@expo/[email protected].9":
|
||||
version "4.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-4.0.9.tgz#52c5bee8411185fc4a340072fbed3584d0409234"
|
||||
integrity sha512-3wv0uhG5U5O4BGX2qm4gjFK3jvYVTy0raFsR6B/2zoBNgD3lXF8Ae5osVN118aKNZb2B7SMx3V7McunZhf7RXg==
|
||||
"@expo/[email protected].11":
|
||||
version "4.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-4.0.11.tgz#2c11c63ec0d3f7a56d6cb45c8340a1546f3cfc5c"
|
||||
integrity sha512-hoQq+B1NlIqWeSLKbQQ19g2vzqKhXh2glRwRFCGvqKT2/UaweFU5w7jetNPOBlVIV1+TpQmhLNbTYako6Z3vnQ==
|
||||
dependencies:
|
||||
"@expo/config-types" "^43.0.1"
|
||||
"@expo/json-file" "8.2.34"
|
||||
"@expo/plist" "0.0.16"
|
||||
"@expo/sdk-runtime-versions" "^1.0.0"
|
||||
"@react-native/normalize-color" "^2.0.0"
|
||||
chalk "^4.1.2"
|
||||
debug "^4.3.1"
|
||||
@@ -874,13 +875,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-43.0.1.tgz#3e047dccb371741a540980eaff26fb0c95039c30"
|
||||
integrity sha512-EtllpCGDdB/UdwAIs5YXJwBLpbFQNdlLLrxIvoILA9cXrpQMWkeDCT9lQPJzFRMFcLUaMuGvkzX2tR4tx5EQFQ==
|
||||
|
||||
"@expo/[email protected].9":
|
||||
version "6.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@expo/config/-/config-6.0.9.tgz#d2f700e067581f7fb088c4e811b38351ac57e151"
|
||||
integrity sha512-UJs8b5SSU9dl4VrX+MiSJcSwUVt7u6oUfNQDJ6VIWu03lDkNKtrk64GeSmUqHKsFd6iFXaMxtvkTBVmWWM6NaA==
|
||||
"@expo/[email protected].11":
|
||||
version "6.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@expo/config/-/config-6.0.11.tgz#a00815ac29c13dfb069441dff6f0b56da69baa2f"
|
||||
integrity sha512-ohmm/fjpqL3mk1Tj9BqmeoFuGNQxfMm2cQs0JHqBCXTjBUtprrH1j6wHdKR2ctNWQxpOlStPkC6CgPv+bd1+GA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "~7.10.4"
|
||||
"@expo/config-plugins" "4.0.9"
|
||||
"@expo/config-plugins" "4.0.11"
|
||||
"@expo/config-types" "^43.0.1"
|
||||
"@expo/json-file" "8.2.34"
|
||||
getenv "^1.0.0"
|
||||
@@ -891,10 +892,10 @@
|
||||
slugify "^1.3.4"
|
||||
sucrase "^3.20.0"
|
||||
|
||||
"@expo/[email protected].57":
|
||||
version "0.2.57"
|
||||
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-0.2.57.tgz#260a686a919e3b311975a850d36d381643433e1e"
|
||||
integrity sha512-q+u/2pCx60onMEHaQxstEP88H6vEgcFhSRY5IflErFtOpgcN9gA6ZemBRnxYzC9OS8T1AF6zJqzdeIjMANjPrw==
|
||||
"@expo/[email protected].61":
|
||||
version "0.2.61"
|
||||
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-0.2.61.tgz#85aae117f285e05f7d17c3a759fad73b6a503455"
|
||||
integrity sha512-8lv6FInuv/yV0dYSI5QRxpTXJzQizyGnAORIHFeaAdUPTkjjsGSEuqY8ynQV0pXxLcJ79DLQqxZ8QDjglevWpA==
|
||||
dependencies:
|
||||
joi "^17.4.2"
|
||||
semver "^7.3.5"
|
||||
@@ -970,10 +971,10 @@
|
||||
base64-js "^1.2.3"
|
||||
xmlbuilder "^14.0.0"
|
||||
|
||||
"@expo/[email protected].0":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@expo/plugin-warn-if-update-available/-/plugin-warn-if-update-available-1.7.0.tgz#d47fb15c9b0c8af91b20c9472e5f848972b472d8"
|
||||
integrity sha512-cOEVg43RusKOY/14SFmN+HCZllR+UEAr6Nv4kTI80vRpclSqn4WQaAqgSjZx5+i2cdVDfv9FeQwLdDwcmbkIIw==
|
||||
"@expo/[email protected].1":
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@expo/plugin-warn-if-update-available/-/plugin-warn-if-update-available-1.7.1.tgz#26e5dbf685801beb1e732dfa663944d280116e98"
|
||||
integrity sha512-Fo4s9EDliMTBnfaoHOXBvHLQxrHQq34RqmfzZ3qQUPEOCT0hpElEcSXa2qOM0K0WPJzhciMGIEk2ZWhZeTVWxg==
|
||||
dependencies:
|
||||
"@oclif/command" "^1.6.0"
|
||||
"@oclif/config" "^1.12.8"
|
||||
@@ -985,13 +986,13 @@
|
||||
lodash.template "^4.4.0"
|
||||
semver "^7.3.2"
|
||||
|
||||
"@expo/[email protected].9":
|
||||
version "3.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-3.0.9.tgz#c9a7034d4299bd7b3bf36c17efbdf1772899c7fe"
|
||||
integrity sha512-j4cHyZe04WijAyH2sJS2pH8DltqPDVQczN6brqFiuRktyIAroqh4ZMY1fgpSweX18LTXmGF9d1fG1aXC+N64YQ==
|
||||
"@expo/[email protected].11":
|
||||
version "3.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-3.0.11.tgz#362d647b695bc377d8a5dc95788b2b3e00754f5a"
|
||||
integrity sha512-RVtbTVgHGh2JSCng9xuvJAbvBW3pAWUvJLAbEkNqV/+mb2uPjDumXtyxcUfHSNX0Yjbtxw6LG61v6SNzGkfWlA==
|
||||
dependencies:
|
||||
"@expo/config" "6.0.9"
|
||||
"@expo/config-plugins" "4.0.9"
|
||||
"@expo/config" "6.0.11"
|
||||
"@expo/config-plugins" "4.0.11"
|
||||
"@expo/config-types" "^43.0.1"
|
||||
"@expo/image-utils" "0.3.18"
|
||||
"@expo/json-file" "8.2.34"
|
||||
@@ -1019,6 +1020,11 @@
|
||||
remove-trailing-slash "^0.1.0"
|
||||
uuid "^8.3.2"
|
||||
|
||||
"@expo/sdk-runtime-versions@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz#d7ebd21b19f1c6b0395e50d78da4416941c57f7c"
|
||||
integrity sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==
|
||||
|
||||
"@expo/[email protected]":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@expo/spawn-async/-/spawn-async-1.5.0.tgz#799827edd8c10ef07eb1a2ff9dcfe081d596a395"
|
||||
|
||||
Reference in new issue
Block a user