mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
def91af01f | ||
|
|
5eb0fb75ac | ||
|
|
de8c69f766 | ||
|
|
5879ac424d | ||
|
|
e30f62ec12 | ||
|
|
dc4e36e8a8 | ||
|
|
12ded8c9a9 | ||
|
|
7378c6fd0e | ||
|
|
77a8b08834 | ||
|
|
79db9eb482 | ||
|
|
59f6d36114 | ||
|
|
c36c26f5a9 | ||
|
|
d6692f20b5 | ||
|
|
6e37f596df | ||
|
|
d326891ef8 | ||
|
|
46a34327d9 | ||
|
|
97242af30f |
No files matched your search
@@ -159,7 +159,7 @@ jobs:
|
||||
git config --global user.email "[email protected]"
|
||||
git config --global user.name "Expo CI"
|
||||
git add CHANGELOG.md
|
||||
git commit -m "[skip ci] update CHANGELOG.md"
|
||||
git commit -m "update CHANGELOG.md"
|
||||
git push
|
||||
- name: Prepare changelog for Slack
|
||||
run: |
|
||||
|
||||
+20
-1
@@ -6,14 +6,33 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Enable auto-incrementing on Android. ([#645](https://github.com/expo/eas-cli/pull/645) by [@jkhales](https://github.com/jkhales))
|
||||
- Don't resolve the iOS builder image on the client side. EAS Build will use the appropriate iOS image for a given Expo SDK version unless the `image` is defined. This only applies to managed projects. ([#675](https://github.com/expo/eas-cli/pull/675) by [@wkozyra95](https://github.com/wkozyra95) + [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Integrate credentials service with Android submissions. ([#664](https://github.com/expo/eas-cli/pull/664) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add option to review ad-hoc devices when reusing provisioning profile. ([#673](https://github.com/expo/eas-cli/pull/673) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [0.30.1](https://github.com/expo/eas-cli/releases/tag/0.30.1) - 2021-10-06
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix `--json` flag when running EAS CLI on GitHub actions. ([#669](https://github.com/expo/eas-cli/pull/669) by [@dsokal](https://github.com/dsokal))
|
||||
- Fix `"ios: mods.ios.infoPlist: Failed to find Info.plist linked to Xcode project."` warning when running `eas build` in a managed project.([#670](https://github.com/expo/eas-cli/pull/670) by [@brentvatne](https://github.com/brentvatne))
|
||||
- Fix building monorepo projects on Windows. ([#671](https://github.com/expo/eas-cli/pull/671) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.30.0](https://github.com/expo/eas-cli/releases/tag/v0.30.0) - 2021-10-05
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Enable auto-incrementing on Android. ([#645](https://github.com/expo/eas-cli/pull/645) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Ports detection for Google Service Account Keys into the credentials service. ([#660](https://github.com/expo/eas-cli/pull/660) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Improve iOS credentials printing. ([#657](https://github.com/expo/eas-cli/pull/657) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Automate `eas-cli` releases. ([#668](https://github.com/expo/eas-cli/pull/668) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.30.0"
|
||||
"version": "0.31.0"
|
||||
}
|
||||
+2
-2
@@ -31,7 +31,7 @@
|
||||
"typescript": "4.4.2"
|
||||
},
|
||||
"volta": {
|
||||
"node": "16.8.0",
|
||||
"yarn": "1.22.11"
|
||||
"node": "16.10.0",
|
||||
"yarn": "1.22.15"
|
||||
}
|
||||
}
|
||||
+26
-26
@@ -73,7 +73,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/login.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/account/login.js)_
|
||||
_See code: [src/commands/account/login.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/account/login.js)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -87,7 +87,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/logout.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/account/logout.js)_
|
||||
_See code: [src/commands/account/logout.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/account/logout.js)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -101,7 +101,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/view.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/account/view.js)_
|
||||
_See code: [src/commands/account/view.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/account/view.js)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -112,7 +112,7 @@ USAGE
|
||||
$ eas analytics [STATUS]
|
||||
```
|
||||
|
||||
_See code: [src/commands/analytics.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/analytics.js)_
|
||||
_See code: [src/commands/analytics.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/analytics.js)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -145,7 +145,7 @@ OPTIONS
|
||||
--[no-]wait Wait for build(s) to complete
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/index.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/build/index.js)_
|
||||
_See code: [src/commands/build/index.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/build/index.js)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -156,7 +156,7 @@ USAGE
|
||||
$ eas build:cancel [BUILD_ID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/cancel.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/build/cancel.js)_
|
||||
_See code: [src/commands/build/cancel.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/build/cancel.js)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -170,7 +170,7 @@ OPTIONS
|
||||
-p, --platform=(android|ios|all) Platform to configure
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/configure.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/build/configure.js)_
|
||||
_See code: [src/commands/build/configure.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/build/configure.js)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -203,7 +203,7 @@ OPTIONS
|
||||
--status=(new|in-queue|in-progress|errored|finished|canceled)
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/list.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/build/list.js)_
|
||||
_See code: [src/commands/build/list.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/build/list.js)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -217,7 +217,7 @@ OPTIONS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/view.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/build/view.js)_
|
||||
_See code: [src/commands/build/view.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/build/view.js)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -232,7 +232,7 @@ OPTIONS
|
||||
--profile=profile
|
||||
```
|
||||
|
||||
_See code: [src/commands/config.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/config.js)_
|
||||
_See code: [src/commands/config.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/config.js)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -243,7 +243,7 @@ USAGE
|
||||
$ eas credentials
|
||||
```
|
||||
|
||||
_See code: [src/commands/credentials.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/credentials.js)_
|
||||
_See code: [src/commands/credentials.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/credentials.js)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -254,7 +254,7 @@ USAGE
|
||||
$ eas device:create
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/create.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/device/create.js)_
|
||||
_See code: [src/commands/device/create.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/device/create.js)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -268,7 +268,7 @@ OPTIONS
|
||||
--apple-team-id=apple-team-id
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/list.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/device/list.js)_
|
||||
_See code: [src/commands/device/list.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/device/list.js)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -279,7 +279,7 @@ USAGE
|
||||
$ eas device:view [UDID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/view.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/device/view.js)_
|
||||
_See code: [src/commands/device/view.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/device/view.js)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -290,7 +290,7 @@ USAGE
|
||||
$ eas diagnostics
|
||||
```
|
||||
|
||||
_See code: [src/commands/diagnostics.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/diagnostics.js)_
|
||||
_See code: [src/commands/diagnostics.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/diagnostics.js)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -318,7 +318,7 @@ USAGE
|
||||
$ eas project:info
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/info.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/project/info.js)_
|
||||
_See code: [src/commands/project/info.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/project/info.js)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -332,7 +332,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/init.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/project/init.js)_
|
||||
_See code: [src/commands/project/init.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/project/init.js)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
@@ -349,7 +349,7 @@ OPTIONS
|
||||
--value=value Value of the secret
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/create.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/secret/create.js)_
|
||||
_See code: [src/commands/secret/create.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/secret/create.js)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -366,7 +366,7 @@ DESCRIPTION
|
||||
Unsure where to find the secret's ID? Run eas secrets:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/delete.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/secret/delete.js)_
|
||||
_See code: [src/commands/secret/delete.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/secret/delete.js)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -377,7 +377,7 @@ USAGE
|
||||
$ eas secret:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/list.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/secret/list.js)_
|
||||
_See code: [src/commands/secret/list.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/secret/list.js)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -410,7 +410,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [src/commands/submit.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/submit.js)_
|
||||
_See code: [src/commands/submit.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/submit.js)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -429,7 +429,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/create.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/webhook/create.js)_
|
||||
_See code: [src/commands/webhook/create.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/webhook/create.js)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -443,7 +443,7 @@ ARGUMENTS
|
||||
ID ID of the webhook to delete
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/delete.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/webhook/delete.js)_
|
||||
_See code: [src/commands/webhook/delete.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/webhook/delete.js)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -457,7 +457,7 @@ OPTIONS
|
||||
--event=(BUILD) Event type that triggers the webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/list.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/webhook/list.js)_
|
||||
_See code: [src/commands/webhook/list.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/webhook/list.js)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -477,7 +477,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/update.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/webhook/update.js)_
|
||||
_See code: [src/commands/webhook/update.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/webhook/update.js)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -491,5 +491,5 @@ ARGUMENTS
|
||||
ID ID of the webhook to view
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/view.js](https://github.com/expo/eas-cli/blob/v0.30.0/packages/eas-cli/src/commands/webhook/view.js)_
|
||||
_See code: [src/commands/webhook/view.js](https://github.com/expo/eas-cli/blob/v0.31.0/packages/eas-cli/src/commands/webhook/view.js)_
|
||||
<!-- commandsstop -->
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.30.0",
|
||||
"version": "0.31.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.25",
|
||||
"@expo/config": "5.0.9",
|
||||
"@expo/config-plugins": "3.1.0",
|
||||
"@expo/eas-build-job": "0.2.48",
|
||||
"@expo/eas-json": "^0.30.0",
|
||||
"@expo/config": "6.0.2",
|
||||
"@expo/config-plugins": "4.0.2",
|
||||
"@expo/eas-build-job": "0.2.52",
|
||||
"@expo/eas-json": "^0.31.0",
|
||||
"@expo/json-file": "8.2.33",
|
||||
"@expo/pkcs12": "0.0.4",
|
||||
"@expo/plist": "0.0.14",
|
||||
"@expo/plugin-warn-if-update-available": "1.7.0",
|
||||
"@expo/prebuild-config": "2.1.0",
|
||||
"@expo/prebuild-config": "3.0.2",
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.0",
|
||||
"@expo/sdk-runtime-versions": "1.0.0",
|
||||
@@ -61,6 +61,7 @@
|
||||
"qrcode-terminal": "0.12.0",
|
||||
"resolve-from": "5.0.0",
|
||||
"semver": "7.3.5",
|
||||
"slash": "3.0.0",
|
||||
"strip-ansi": "6.0.0",
|
||||
"tar": "6.1.11",
|
||||
"terminal-link": "2.1.1",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Android, ArchiveSource, Job, Platform, sanitizeJob } from '@expo/eas-build-job';
|
||||
import { AndroidBuildProfile } from '@expo/eas-json';
|
||||
import path from 'path';
|
||||
import slash from 'slash';
|
||||
|
||||
import { AndroidCredentials } from '../../credentials/android/AndroidCredentialsProvider';
|
||||
import { getUsername } from '../../project/projectUtils';
|
||||
@@ -25,7 +26,8 @@ export async function prepareJobAsync(
|
||||
): Promise<Job> {
|
||||
const username = getUsername(ctx.exp, await ensureLoggedInAsync());
|
||||
const buildProfile: AndroidBuildProfile = ctx.buildProfile;
|
||||
const projectRootDirectory = path.relative(await vcs.getRootPathAsync(), ctx.projectDir) || '.';
|
||||
const projectRootDirectory =
|
||||
slash(path.relative(await vcs.getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
const { credentials } = jobData;
|
||||
const buildCredentials = credentials
|
||||
? {
|
||||
@@ -51,7 +53,7 @@ export async function prepareJobAsync(
|
||||
projectRootDirectory,
|
||||
projectArchive: jobData.projectArchive,
|
||||
builderEnvironment: {
|
||||
image: buildProfile.image ?? 'default',
|
||||
image: buildProfile.image,
|
||||
node: buildProfile.node,
|
||||
yarn: buildProfile.yarn,
|
||||
ndk: buildProfile.ndk,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ArchiveSource, Ios, Job, Platform, sanitizeJob } from '@expo/eas-build-job';
|
||||
import path from 'path';
|
||||
import semver from 'semver';
|
||||
import slash from 'slash';
|
||||
|
||||
import { IosCredentials, TargetCredentials } from '../../credentials/ios/types';
|
||||
import { getUsername } from '../../project/projectUtils';
|
||||
@@ -24,7 +24,8 @@ export async function prepareJobAsync(
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
jobData: JobData
|
||||
): Promise<Job> {
|
||||
const projectRootDirectory = path.relative(await vcs.getRootPathAsync(), ctx.projectDir) || '.';
|
||||
const projectRootDirectory =
|
||||
slash(path.relative(await vcs.getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
const username = getUsername(ctx.exp, await ensureLoggedInAsync());
|
||||
const buildCredentials: Ios.Job['secrets']['buildCredentials'] = {};
|
||||
if (jobData.credentials) {
|
||||
@@ -41,7 +42,7 @@ export async function prepareJobAsync(
|
||||
projectRootDirectory,
|
||||
distribution: ctx.buildProfile.simulator ? 'simulator' : ctx.buildProfile.distribution,
|
||||
builderEnvironment: {
|
||||
image: resolveImage(ctx),
|
||||
image: ctx.buildProfile.image,
|
||||
node: ctx.buildProfile.node,
|
||||
yarn: ctx.buildProfile.yarn,
|
||||
bundler: ctx.buildProfile.bundler,
|
||||
@@ -73,17 +74,6 @@ export async function prepareJobAsync(
|
||||
return sanitizeJob(job);
|
||||
}
|
||||
|
||||
function resolveImage(ctx: BuildContext<Platform.IOS>): Ios.BuilderEnvironment['image'] {
|
||||
// see https://linear.app/expo/issue/ENG-1396/make-default-image-dependent-on-sdk-version
|
||||
if (!ctx.buildProfile.image && ctx.exp.sdkVersion) {
|
||||
const majorSdkVersion = semver.major(ctx.exp.sdkVersion);
|
||||
if (majorSdkVersion <= 41) {
|
||||
return 'macos-catalina-10.15-xcode-12.4';
|
||||
}
|
||||
}
|
||||
return ctx.buildProfile.image ?? 'default';
|
||||
}
|
||||
|
||||
function prepareTargetCredentials(targetCredentials: TargetCredentials): Ios.TargetCredentials {
|
||||
return {
|
||||
provisioningProfileBase64: targetCredentials.provisioningProfile,
|
||||
|
||||
@@ -24,7 +24,9 @@ export function printLogsUrls(builds: BuildFragment[]): void {
|
||||
builds.forEach(build => {
|
||||
const logsUrl = getBuildLogsUrl(build);
|
||||
Log.log(
|
||||
`${appPlatformDisplayNames[build.platform]} build details: ${chalk.underline(logsUrl)}`
|
||||
`${appPlatformEmojis[build.platform]} ${
|
||||
appPlatformDisplayNames[build.platform]
|
||||
} build details: ${chalk.underline(logsUrl)}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ExpoConfig, getPackageJson } from '@expo/config';
|
||||
|
||||
export function isExpoUpdatesInstalled(projectDir: string): boolean {
|
||||
const packageJson = getPackageJson(projectDir);
|
||||
return packageJson.dependencies && 'expo-updates' in packageJson.dependencies;
|
||||
return !!(packageJson.dependencies && 'expo-updates' in packageJson.dependencies);
|
||||
}
|
||||
|
||||
export function ensureValidVersions(exp: ExpoConfig): void {
|
||||
|
||||
@@ -75,11 +75,7 @@ export default class BranchCreate extends EasCommand {
|
||||
flags,
|
||||
} = this.parse(BranchCreate);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const fullName = await getProjectFullNameAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
@@ -98,10 +98,7 @@ export default class BranchDelete extends EasCommand {
|
||||
flags: { json: jsonFlag },
|
||||
} = this.parse(BranchDelete);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const fullName = await getProjectFullNameAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
@@ -67,10 +67,7 @@ export default class BranchList extends EasCommand {
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = this.parse(BranchList);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const branches = await listBranchesAsync({ projectId });
|
||||
|
||||
@@ -162,11 +162,7 @@ export default class BranchPublish extends EasCommand {
|
||||
// If a group was specified, that means we are republishing it.
|
||||
republish = group ? true : republish;
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, {
|
||||
skipSDKVersionRequirement: true,
|
||||
isPublicConfig: true,
|
||||
|
||||
@@ -74,10 +74,7 @@ export default class BranchRename extends EasCommand {
|
||||
flags: { json: jsonFlag, from: currentName, to: newName },
|
||||
} = this.parse(BranchRename);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const fullName = await getProjectFullNameAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
@@ -90,11 +90,7 @@ export default class BranchView extends EasCommand {
|
||||
flags: { json: jsonFlag },
|
||||
} = this.parse(BranchView);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ export default class BuildCancel extends EasCommand {
|
||||
async runAsync(): Promise<void> {
|
||||
const { BUILD_ID: buildIdFromArg } = this.parse(BuildCancel).args;
|
||||
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectFullName = await getProjectFullNameAsync(exp);
|
||||
|
||||
@@ -31,7 +31,7 @@ export default class BuildConfigure extends EasCommand {
|
||||
|
||||
await configureAsync({
|
||||
platform,
|
||||
projectDir: (await findProjectRootAsync()) ?? process.cwd(),
|
||||
projectDir: await findProjectRootAsync(),
|
||||
});
|
||||
|
||||
Log.newLine();
|
||||
|
||||
@@ -143,7 +143,7 @@ export default class Build extends EasCommand {
|
||||
const flags = await this.sanitizeFlagsAsync(rawFlags);
|
||||
const { requestedPlatform } = flags;
|
||||
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const projectDir = await findProjectRootAsync();
|
||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||
|
||||
await vcs.ensureRepoExistsAsync();
|
||||
|
||||
@@ -76,7 +76,7 @@ export default class BuildList extends EasCommand {
|
||||
const graphqlBuildStatus = toGraphQLBuildStatus(buildStatus);
|
||||
const graphqlBuildDistribution = toGraphQLBuildDistribution(buildDistribution);
|
||||
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectName = await getProjectFullNameAsync(exp);
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class BuildView extends EasCommand {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectName = await getProjectFullNameAsync(exp);
|
||||
|
||||
@@ -84,10 +84,7 @@ export default class ChannelCreate extends EasCommand {
|
||||
flags: { json: jsonFlag },
|
||||
} = this.parse(ChannelCreate);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
|
||||
@@ -121,10 +121,7 @@ export default class ChannelEdit extends EasCommand {
|
||||
flags: { branch: branchName, json: jsonFlag },
|
||||
} = this.parse(ChannelEdit);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
|
||||
@@ -80,10 +80,7 @@ export default class ChannelList extends EasCommand {
|
||||
flags: { json: jsonFlag },
|
||||
} = this.parse(ChannelList);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
|
||||
@@ -346,10 +346,7 @@ export default class ChannelRollout extends EasCommand {
|
||||
flags: { branch: branchName, percent },
|
||||
} = this.parse(ChannelRollout);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const fullName = await getProjectFullNameAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
@@ -215,10 +215,7 @@ export default class ChannelView extends EasCommand {
|
||||
flags: { json: jsonFlag },
|
||||
} = this.parse(ChannelView);
|
||||
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export default class Config extends EasCommand {
|
||||
profile?: string;
|
||||
};
|
||||
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const projectDir = await findProjectRootAsync();
|
||||
await handleDeprecatedEasJsonAsync(projectDir, false);
|
||||
|
||||
const reader = new EasJsonReader(projectDir);
|
||||
|
||||
@@ -22,7 +22,7 @@ export default class BuildList extends EasCommand {
|
||||
async runAsync(): Promise<void> {
|
||||
let appleTeamIdentifier = this.parse(BuildList).flags['apple-team-id'];
|
||||
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const accountName = await getProjectAccountNameAsync(exp);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ If you are not sure what is the UDID of the device you are looking for, run:
|
||||
throw new Error('Device UDID is missing');
|
||||
}
|
||||
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const accountName = await getProjectAccountNameAsync(exp);
|
||||
|
||||
|
||||
@@ -34,10 +34,7 @@ export default class ProjectInfo extends EasCommand {
|
||||
static description = 'information about the current project';
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const { app } = await projectInfoByIdAsync(projectId);
|
||||
|
||||
@@ -10,10 +10,7 @@ export default class ProjectInit extends EasCommand {
|
||||
static aliases = ['init'];
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
|
||||
if (exp.extra?.eas?.projectId) {
|
||||
|
||||
@@ -50,7 +50,7 @@ export default class EnvironmentSecretCreate extends EasCommand {
|
||||
flags: { name, value: secretValue, scope, force },
|
||||
} = this.parse(EnvironmentSecretCreate);
|
||||
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const accountName = await getProjectAccountNameAsync(exp);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ Unsure where to find the secret's ID? Run ${chalk.bold('eas secrets:list')}`;
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectAccountName = await getProjectAccountNameAsync(exp);
|
||||
|
||||
@@ -20,7 +20,7 @@ export default class EnvironmentSecretList extends EasCommand {
|
||||
static description = 'Lists environment secrets available for your current app';
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectAccountName = await getProjectAccountNameAsync(exp);
|
||||
|
||||
@@ -93,7 +93,7 @@ See how to configure submits with eas.json: ${learnMore('https://docs.expo.dev/s
|
||||
const { flags: rawFlags } = this.parse(Submit);
|
||||
const flags = await this.sanitizeFlagsAsync(rawFlags);
|
||||
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
|
||||
@@ -30,10 +30,7 @@ export default class WebhookCreate extends EasCommand {
|
||||
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 projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
|
||||
@@ -29,10 +29,7 @@ export default class WebhookDelete extends EasCommand {
|
||||
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 projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
|
||||
@@ -29,10 +29,7 @@ export default class WebhookList extends EasCommand {
|
||||
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 projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectFullName = await getProjectFullNameAsync(exp);
|
||||
|
||||
+1
@@ -22,6 +22,7 @@ describe(CreateGoogleServiceAccountKey, () => {
|
||||
'/google-service-account-key.json': JSON.stringify({
|
||||
type: 'service_account',
|
||||
private_key: 'super secret',
|
||||
client_email: '[email protected]',
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ describe(SetupGoogleServiceAccountKey, () => {
|
||||
'/google-service-account-key.json': JSON.stringify({
|
||||
type: 'service_account',
|
||||
private_key: 'super secret',
|
||||
client_email: '[email protected]',
|
||||
}),
|
||||
});
|
||||
const ctx = createCtxMock({
|
||||
|
||||
@@ -19,6 +19,8 @@ export type KeystoreWithType = Keystore & {
|
||||
export type GoogleServiceAccountKey = {
|
||||
[key: string]: any;
|
||||
private_key: string;
|
||||
type: string;
|
||||
client_email: string;
|
||||
};
|
||||
|
||||
export type AndroidCredentials = {
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ beforeAll(() => {
|
||||
const mockDetectableServiceAccountJson = JSON.stringify({
|
||||
type: 'service_account',
|
||||
private_key: 'super secret',
|
||||
client_email: '[email protected]',
|
||||
});
|
||||
|
||||
vol.fromJSON({
|
||||
|
||||
@@ -13,6 +13,7 @@ import { GoogleServiceAccountKey } from '../credentials';
|
||||
export const MinimalGoogleServiceAccountKeySchema = Joi.object({
|
||||
type: Joi.string().required(),
|
||||
private_key: Joi.string().required(),
|
||||
client_email: Joi.string().required(),
|
||||
});
|
||||
|
||||
function fileIsServiceAccountKey(keyJsonPath: string): boolean {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
IosDistributionType,
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync, pressAnyKeyToContinueAsync } from '../../../prompts';
|
||||
import { confirmAsync, pressAnyKeyToContinueAsync, promptAsync } from '../../../prompts';
|
||||
import differenceBy from '../../../utils/expodash/differenceBy';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
@@ -23,6 +23,12 @@ import { assignBuildCredentialsAsync, getBuildCredentialsAsync } from './BuildCr
|
||||
import { chooseDevicesAsync, formatDeviceLabel } from './DeviceUtils';
|
||||
import { SetupDistributionCertificate } from './SetupDistributionCertificate';
|
||||
|
||||
enum ReuseAction {
|
||||
Yes,
|
||||
ShowDevices,
|
||||
No,
|
||||
}
|
||||
|
||||
export class SetupAdhocProvisioningProfile {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
@@ -184,10 +190,23 @@ export class SetupAdhocProvisioningProfile {
|
||||
);
|
||||
|
||||
if (allRegisteredDevicesAreProvisioned) {
|
||||
return await confirmAsync({
|
||||
message: `All your registered devices are present in the Provisioning Profile. Would you like to reuse it?`,
|
||||
initial: true,
|
||||
});
|
||||
const reuseAction = await this.promptForReuseActionAsync();
|
||||
if (reuseAction === ReuseAction.Yes) {
|
||||
return true;
|
||||
} else if (reuseAction === ReuseAction.No) {
|
||||
return false;
|
||||
} else {
|
||||
Log.newLine();
|
||||
Log.log('Devices registered in the Provisioning Profile:');
|
||||
for (const device of provisionedDevices) {
|
||||
Log.log(`- ${formatDeviceLabel(device)}`);
|
||||
}
|
||||
Log.newLine();
|
||||
return (
|
||||
(await this.promptForReuseActionAsync({ showShowDevicesOption: false })) ===
|
||||
ReuseAction.Yes
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const missingDevices = differenceBy(registeredAppleDevices, provisionedDevices, 'identifier');
|
||||
Log.warn(`The provisioning profile is missing the following devices:`);
|
||||
@@ -201,6 +220,36 @@ export class SetupAdhocProvisioningProfile {
|
||||
}
|
||||
}
|
||||
|
||||
private async promptForReuseActionAsync({
|
||||
showShowDevicesOption = true,
|
||||
} = {}): Promise<ReuseAction> {
|
||||
const { selected } = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'selected',
|
||||
message: `${
|
||||
showShowDevicesOption
|
||||
? 'All your registered devices are present in the Provisioning Profile. '
|
||||
: ''
|
||||
}Would you like to reuse the profile?`,
|
||||
choices: [
|
||||
{ title: 'Yes', value: ReuseAction.Yes },
|
||||
...(showShowDevicesOption
|
||||
? [
|
||||
{
|
||||
title: 'Show devices and ask me again',
|
||||
value: ReuseAction.ShowDevices,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: 'No, let me choose devices again',
|
||||
value: ReuseAction.No,
|
||||
},
|
||||
],
|
||||
});
|
||||
return selected;
|
||||
}
|
||||
|
||||
private async registerDevicesAsync(
|
||||
ctx: CredentialsContext,
|
||||
appleTeam: AppleTeamFragment
|
||||
|
||||
+4
@@ -12,7 +12,9 @@ Developer Portal ID 9839M6AY8W
|
||||
Apple Team 77KQ969CHE
|
||||
Updated 6 months ago
|
||||
|
||||
|
||||
App Store Configuration
|
||||
|
||||
Distribution Certificate
|
||||
Serial Number test-serial
|
||||
Expiration Date Wed, 13 Oct 2021 18:28:44 UTC
|
||||
@@ -26,7 +28,9 @@ Expiration Wed, 13 Oct 2021 18:28:44 UTC
|
||||
Apple Team test-apple-identifier
|
||||
Updated 5 months ago
|
||||
|
||||
|
||||
Ad Hoc Configuration
|
||||
|
||||
Distribution Certificate
|
||||
Serial Number test-serial
|
||||
Expiration Date Wed, 13 Oct 2021 18:28:44 UTC
|
||||
|
||||
@@ -128,11 +128,9 @@ export function displayProjectCredentials(
|
||||
const isMultitarget = targets.length > 1;
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
const fields = [
|
||||
{ label: 'Project Credentials Configuration', value: '' },
|
||||
{ label: 'Project', value: projectFullName },
|
||||
];
|
||||
|
||||
Log.log(chalk.cyan.bold('Project Credentials Configuration'));
|
||||
Log.newLine();
|
||||
const fields = [{ label: 'Project', value: projectFullName }];
|
||||
for (const [targetName, buildCredentials] of Object.entries(appBuildCredentials)) {
|
||||
if (isMultitarget) {
|
||||
fields.push({ label: '', value: '' });
|
||||
@@ -148,10 +146,12 @@ function displayIosAppBuildCredentials(
|
||||
buildCredentials: IosAppBuildCredentialsFragment,
|
||||
fields: { label: string; value: string }[]
|
||||
): void {
|
||||
fields.push({ label: '', value: '' });
|
||||
fields.push({
|
||||
label: `${prettyIosDistributionType(buildCredentials.iosDistributionType)} Configuration`,
|
||||
value: '',
|
||||
});
|
||||
fields.push({ label: '', value: '' });
|
||||
const maybeDistCert = buildCredentials.distributionCertificate;
|
||||
fields.push({ label: 'Distribution Certificate', value: '' });
|
||||
if (maybeDistCert) {
|
||||
@@ -192,9 +192,13 @@ function displayIosAppBuildCredentials(
|
||||
});
|
||||
}
|
||||
if (appleDevices && appleDevices.length > 0) {
|
||||
fields.push({ label: 'Provisioned devices', value: '' });
|
||||
for (const appleDevice of appleDevices) {
|
||||
fields.push({ label: ' -', value: formatAppleDevice(appleDevice) });
|
||||
const [firstAppleDevice, ...rest] = appleDevices;
|
||||
fields.push({
|
||||
label: 'Provisioned devices',
|
||||
value: `- ${formatAppleDevice(firstAppleDevice)}`,
|
||||
});
|
||||
for (const appleDevice of rest) {
|
||||
fields.push({ label: '', value: `- ${formatAppleDevice(appleDevice)}` });
|
||||
}
|
||||
}
|
||||
fields.push({ label: 'Updated', value: `${fromNow(new Date(updatedAt))} ago` });
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function createContextAsync({
|
||||
cwd?: string;
|
||||
user: Actor;
|
||||
}): Promise<DeviceManagerContext> {
|
||||
const projectDir = await findProjectRootAsync(cwd);
|
||||
const projectDir = await findProjectRootAsync({ cwd });
|
||||
let exp: ExpoConfig | null = null;
|
||||
if (projectDir) {
|
||||
const config = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
|
||||
@@ -26,15 +26,32 @@ describe(findProjectRootAsync, () => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
it('returns null if not inside the project directory', async () => {
|
||||
it('throws if not inside the project directory', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./README.md': '1',
|
||||
},
|
||||
'/app'
|
||||
);
|
||||
const projectRoot = await findProjectRootAsync('/app');
|
||||
expect(projectRoot).toBeNull();
|
||||
await expect(findProjectRootAsync({ cwd: '/app' })).rejects.toThrow(
|
||||
'Please run this command inside a project directory.'
|
||||
);
|
||||
});
|
||||
|
||||
it('defaults to process.cwd() if defaultToProcessCwd = true', async () => {
|
||||
const spy = jest.spyOn(process, 'cwd').mockReturnValue('/this/is/fake/process/cwd');
|
||||
try {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./README.md': '1',
|
||||
},
|
||||
'/app'
|
||||
);
|
||||
const projectDir = await findProjectRootAsync({ cwd: '/app', defaultToProcessCwd: true });
|
||||
expect(projectDir).toBe('/this/is/fake/process/cwd');
|
||||
} finally {
|
||||
spy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns the root directory of the project', async () => {
|
||||
@@ -46,7 +63,7 @@ describe(findProjectRootAsync, () => {
|
||||
},
|
||||
'/app'
|
||||
);
|
||||
const projectRoot = await findProjectRootAsync('/app/src');
|
||||
const projectRoot = await findProjectRootAsync({ cwd: '/app/src' });
|
||||
expect(projectRoot).toBe('/app');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,9 +52,23 @@ export async function getProjectAccountNameAsync(exp: ExpoConfig): Promise<strin
|
||||
return getProjectAccountName(exp, user);
|
||||
}
|
||||
|
||||
export async function findProjectRootAsync(cwd?: string): Promise<string | null> {
|
||||
export async function findProjectRootAsync({
|
||||
cwd,
|
||||
defaultToProcessCwd = false,
|
||||
}: {
|
||||
cwd?: string;
|
||||
defaultToProcessCwd?: boolean;
|
||||
} = {}): Promise<string> {
|
||||
const projectRootDir = await pkgDir(cwd);
|
||||
return projectRootDir ?? null;
|
||||
if (!projectRootDir) {
|
||||
if (!defaultToProcessCwd) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
} else {
|
||||
return process.cwd();
|
||||
}
|
||||
} else {
|
||||
return projectRootDir;
|
||||
}
|
||||
}
|
||||
|
||||
export async function setProjectIdAsync(
|
||||
@@ -129,7 +143,7 @@ export async function getProjectIdAsync(
|
||||
}
|
||||
|
||||
// Set the project ID if it is missing.
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
const projectDir = await findProjectRootAsync();
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
|
||||
@@ -144,12 +144,9 @@ export default class AndroidSubmitCommand {
|
||||
sourceType: ServiceAccountSourceType.path,
|
||||
path: serviceAccountKeyPath,
|
||||
});
|
||||
} else if (this.ctx.nonInteractive) {
|
||||
return result(new Error('Set serviceAccountKeyPath in the submit profile (eas.json).'));
|
||||
} else {
|
||||
return result({
|
||||
sourceType: ServiceAccountSourceType.detect,
|
||||
});
|
||||
}
|
||||
return result({
|
||||
sourceType: ServiceAccountSourceType.credentialsService,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import fs from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import {
|
||||
AndroidSubmissionConfigInput,
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
SubmissionFragment,
|
||||
} from '../../graphql/generated';
|
||||
import { SubmissionMutation } from '../../graphql/mutations/SubmissionMutation';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { Archive, ArchiveSource, getArchiveAsync } from '../ArchiveSource';
|
||||
import BaseSubmitter, { SubmissionInput } from '../BaseSubmitter';
|
||||
import {
|
||||
@@ -16,7 +17,11 @@ import {
|
||||
printSummary,
|
||||
} from '../utils/summary';
|
||||
import { AndroidPackageSource, getAndroidPackageAsync } from './AndroidPackageSource';
|
||||
import { ServiceAccountSource, getServiceAccountAsync } from './ServiceAccountSource';
|
||||
import {
|
||||
ServiceAccountKeyResult,
|
||||
ServiceAccountSource,
|
||||
getServiceAccountKeyResultAsync,
|
||||
} from './ServiceAccountSource';
|
||||
|
||||
export interface AndroidSubmissionOptions
|
||||
extends Pick<
|
||||
@@ -32,7 +37,7 @@ export interface AndroidSubmissionOptions
|
||||
interface ResolvedSourceOptions {
|
||||
androidPackage: string;
|
||||
archive: Archive;
|
||||
serviceAccountPath: string;
|
||||
serviceAccountKeyResult: ServiceAccountKeyResult;
|
||||
}
|
||||
|
||||
export default class AndroidSubmitter extends BaseSubmitter<
|
||||
@@ -73,19 +78,22 @@ export default class AndroidSubmitter extends BaseSubmitter<
|
||||
private async resolveSourceOptionsAsync(): Promise<ResolvedSourceOptions> {
|
||||
const androidPackage = await getAndroidPackageAsync(this.options.androidPackageSource);
|
||||
const archive = await getArchiveAsync(this.options.archiveSource);
|
||||
const serviceAccountPath = await getServiceAccountAsync(this.options.serviceAccountSource);
|
||||
const serviceAccountKeyResult = await getServiceAccountKeyResultAsync(
|
||||
this.ctx,
|
||||
this.options.serviceAccountSource,
|
||||
androidPackage
|
||||
);
|
||||
return {
|
||||
androidPackage,
|
||||
archive,
|
||||
serviceAccountPath,
|
||||
serviceAccountKeyResult,
|
||||
};
|
||||
}
|
||||
|
||||
private async formatSubmissionConfigAsync(
|
||||
options: AndroidSubmissionOptions,
|
||||
{ archive, androidPackage, serviceAccountPath }: ResolvedSourceOptions
|
||||
{ archive, androidPackage, serviceAccountKeyResult }: ResolvedSourceOptions
|
||||
): Promise<AndroidSubmissionConfigInput> {
|
||||
const serviceAccount = await fs.readFile(serviceAccountPath, 'utf-8');
|
||||
const { track, releaseStatus, changesNotSentForReview } = options;
|
||||
return {
|
||||
applicationIdentifier: androidPackage,
|
||||
@@ -93,13 +101,13 @@ export default class AndroidSubmitter extends BaseSubmitter<
|
||||
track,
|
||||
changesNotSentForReview,
|
||||
releaseStatus,
|
||||
googleServiceAccountKeyJson: serviceAccount,
|
||||
...serviceAccountKeyResult.result,
|
||||
};
|
||||
}
|
||||
|
||||
private prepareSummaryData(
|
||||
options: AndroidSubmissionOptions,
|
||||
{ archive, androidPackage, serviceAccountPath }: ResolvedSourceOptions
|
||||
{ archive, androidPackage, serviceAccountKeyResult }: ResolvedSourceOptions
|
||||
): SummaryData {
|
||||
const { projectId, track, releaseStatus, changesNotSentForReview } = options;
|
||||
|
||||
@@ -110,7 +118,7 @@ export default class AndroidSubmitter extends BaseSubmitter<
|
||||
track,
|
||||
changesNotSentForReview: changesNotSentForReview ?? undefined,
|
||||
releaseStatus: releaseStatus ?? undefined,
|
||||
serviceAccountPath,
|
||||
formattedServiceAccount: formatServiceAccountSummary(serviceAccountKeyResult),
|
||||
...formatArchiveSourceSummary(archive),
|
||||
};
|
||||
}
|
||||
@@ -119,9 +127,9 @@ export default class AndroidSubmitter extends BaseSubmitter<
|
||||
type SummaryData = {
|
||||
androidPackage: string;
|
||||
changesNotSentForReview?: boolean;
|
||||
formattedServiceAccount: string;
|
||||
projectId: string;
|
||||
releaseStatus?: SubmissionAndroidReleaseStatus;
|
||||
serviceAccountPath: string;
|
||||
track: SubmissionAndroidTrack;
|
||||
} & ArchiveSourceSummaryFields;
|
||||
|
||||
@@ -131,8 +139,43 @@ const SummaryHumanReadableKeys: Record<keyof SummaryData, string> = {
|
||||
archiveUrl: 'Download URL',
|
||||
changesNotSentForReview: 'Changes not sent for a review',
|
||||
formattedBuild: 'Build',
|
||||
formattedServiceAccount: 'Google Service Account Key',
|
||||
projectId: 'Project ID',
|
||||
releaseStatus: 'Release status',
|
||||
serviceAccountPath: 'Google Service Key',
|
||||
track: 'Release track',
|
||||
};
|
||||
|
||||
function formatServiceAccountSummary({ summary }: ServiceAccountKeyResult): string {
|
||||
const {
|
||||
email: serviceAccountEmail,
|
||||
path: serviceAccountKeyPath,
|
||||
source: serviceAccountKeySource,
|
||||
} = summary;
|
||||
|
||||
const fields = [
|
||||
{
|
||||
label: 'Key Source',
|
||||
value: serviceAccountKeySource,
|
||||
},
|
||||
{
|
||||
label: 'Key Path',
|
||||
value: serviceAccountKeyPath,
|
||||
},
|
||||
{
|
||||
label: 'Account E-mail',
|
||||
value: serviceAccountEmail,
|
||||
},
|
||||
];
|
||||
|
||||
const filteredFields = fields.filter(({ value }) => value !== undefined && value !== null) as {
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
|
||||
return (
|
||||
'\n' +
|
||||
formatFields(filteredFields, {
|
||||
labelFormat: label => ` ${chalk.dim(label)}:`,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
import glob from 'fast-glob';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { SetupGoogleServiceAccountKey } from '../../credentials/android/actions/SetupGoogleServiceAccountKey';
|
||||
import { readAndValidateServiceAccountKey } from '../../credentials/android/utils/googleServiceAccountKey';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { findProjectRootAsync } from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { filterAsync } from '../../utils/filterAsync';
|
||||
import { findAccountByName } from '../../user/Account';
|
||||
import { SubmissionContext } from '../context';
|
||||
import { isExistingFileAsync } from '../utils/files';
|
||||
|
||||
export enum ServiceAccountSourceType {
|
||||
path,
|
||||
prompt,
|
||||
detect,
|
||||
// credentialsService,
|
||||
// ...
|
||||
credentialsService,
|
||||
}
|
||||
|
||||
interface ServiceAccountSourceBase {
|
||||
@@ -30,66 +30,116 @@ interface ServiceAccountPromptSource extends ServiceAccountSourceBase {
|
||||
sourceType: ServiceAccountSourceType.prompt;
|
||||
}
|
||||
|
||||
interface ServiceAccountDetectSource extends ServiceAccountSourceBase {
|
||||
sourceType: ServiceAccountSourceType.detect;
|
||||
export interface ServiceAccountCredentialsServiceSource extends ServiceAccountSourceBase {
|
||||
sourceType: ServiceAccountSourceType.credentialsService;
|
||||
}
|
||||
|
||||
export type ServiceAccountKeyResult = {
|
||||
result: ServiceAccountKeyFile | ServiceAccountKeyFromExpoServers;
|
||||
summary: ServiceAccountKeySummary;
|
||||
};
|
||||
|
||||
type ServiceAccountKeySummary = {
|
||||
source: 'local' | 'EAS servers';
|
||||
path?: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
type ServiceAccountKeyFile = {
|
||||
googleServiceAccountKeyJson: string;
|
||||
};
|
||||
|
||||
type ServiceAccountKeyFromExpoServers = {
|
||||
googleServiceAccountKeyId: string;
|
||||
};
|
||||
|
||||
export type ServiceAccountSource =
|
||||
| ServiceAccountPathSource
|
||||
| ServiceAccountPromptSource
|
||||
| ServiceAccountDetectSource;
|
||||
| ServiceAccountCredentialsServiceSource;
|
||||
|
||||
export async function getServiceAccountAsync(source: ServiceAccountSource): Promise<string> {
|
||||
export async function getServiceAccountKeyResultAsync(
|
||||
ctx: SubmissionContext<Platform.ANDROID>,
|
||||
source: ServiceAccountSource,
|
||||
androidApplicationIdentifier: string
|
||||
): Promise<ServiceAccountKeyResult> {
|
||||
if (source.sourceType === ServiceAccountSourceType.credentialsService) {
|
||||
return await getServiceAccountFromCredentialsServiceAsync(ctx, androidApplicationIdentifier);
|
||||
} else {
|
||||
return await getServiceAccountLocallyAsync(source);
|
||||
}
|
||||
}
|
||||
|
||||
async function getServiceAccountLocallyAsync(
|
||||
source: ServiceAccountSource
|
||||
): Promise<ServiceAccountKeyResult> {
|
||||
const serviceAccountKeyPath = await getServiceAccountKeyPathAsync(source);
|
||||
const serviceAccountKey = readAndValidateServiceAccountKey(serviceAccountKeyPath);
|
||||
return {
|
||||
result: { googleServiceAccountKeyJson: await fs.readFile(serviceAccountKeyPath, 'utf-8') },
|
||||
summary: {
|
||||
source: 'local',
|
||||
path: serviceAccountKeyPath,
|
||||
email: serviceAccountKey.client_email,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function getServiceAccountKeyPathAsync(source: ServiceAccountSource): Promise<string> {
|
||||
switch (source.sourceType) {
|
||||
case ServiceAccountSourceType.path:
|
||||
return await handlePathSourceAsync(source);
|
||||
case ServiceAccountSourceType.prompt:
|
||||
return await handlePromptSourceAsync(source);
|
||||
case ServiceAccountSourceType.detect:
|
||||
return await handleDetectSourceAsync(source);
|
||||
case ServiceAccountSourceType.credentialsService: {
|
||||
throw new Error(`ServiceAccountSource ${source} does not return a path.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getServiceAccountFromCredentialsServiceAsync(
|
||||
ctx: SubmissionContext<Platform.ANDROID>,
|
||||
androidApplicationIdentifier: string
|
||||
): Promise<ServiceAccountKeyResult> {
|
||||
const appLookupParams = {
|
||||
account: nullthrows(
|
||||
findAccountByName(ctx.user.accounts, ctx.accountName),
|
||||
`You do not have access to account: ${ctx.accountName}`
|
||||
),
|
||||
projectName: ctx.projectName,
|
||||
androidApplicationIdentifier,
|
||||
};
|
||||
const setupGoogleServiceAccountKeyAction = new SetupGoogleServiceAccountKey(appLookupParams);
|
||||
const androidAppCredentials = await setupGoogleServiceAccountKeyAction.runAsync(
|
||||
ctx.credentialsCtx
|
||||
);
|
||||
const googleServiceAccountKey = nullthrows(
|
||||
androidAppCredentials.googleServiceAccountKeyForSubmissions,
|
||||
'Credentials Service must provide a valid GoogleServiceAccountKey'
|
||||
);
|
||||
|
||||
return {
|
||||
result: {
|
||||
googleServiceAccountKeyId: googleServiceAccountKey.id,
|
||||
},
|
||||
summary: {
|
||||
source: 'EAS servers',
|
||||
email: googleServiceAccountKey.clientEmail,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function handlePathSourceAsync(source: ServiceAccountPathSource): Promise<string> {
|
||||
if (!(await isExistingFileAsync(source.path))) {
|
||||
Log.warn(`File ${source.path} doesn't exist.`);
|
||||
return await getServiceAccountAsync({ sourceType: ServiceAccountSourceType.prompt });
|
||||
return await getServiceAccountKeyPathAsync({ sourceType: ServiceAccountSourceType.prompt });
|
||||
}
|
||||
return source.path;
|
||||
}
|
||||
|
||||
async function handleDetectSourceAsync(_source: ServiceAccountDetectSource): Promise<string> {
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const foundFilePaths = await glob('**/*.json', {
|
||||
cwd: projectDir,
|
||||
ignore: ['app.json', 'package*.json', 'tsconfig.json', 'node_modules'],
|
||||
});
|
||||
|
||||
const googleServiceFiles = await filterAsync(
|
||||
foundFilePaths.map(file => path.join(projectDir, file)),
|
||||
fileIsGoogleServicesAsync
|
||||
);
|
||||
|
||||
if (googleServiceFiles.length > 1) {
|
||||
const selectedPath = await displayPathChooserAsync(googleServiceFiles, projectDir);
|
||||
|
||||
if (selectedPath !== false) {
|
||||
return selectedPath;
|
||||
}
|
||||
} else if (googleServiceFiles.length === 1) {
|
||||
const [detectedPath] = googleServiceFiles;
|
||||
|
||||
if (await confirmDetectedPathAsync(detectedPath)) {
|
||||
return detectedPath;
|
||||
}
|
||||
}
|
||||
|
||||
return await getServiceAccountAsync({ sourceType: ServiceAccountSourceType.prompt });
|
||||
}
|
||||
|
||||
async function handlePromptSourceAsync(_source: ServiceAccountPromptSource): Promise<string> {
|
||||
const path = await askForServiceAccountPathAsync();
|
||||
return await getServiceAccountAsync({
|
||||
return await getServiceAccountKeyPathAsync({
|
||||
sourceType: ServiceAccountSourceType.path,
|
||||
path,
|
||||
});
|
||||
@@ -124,53 +174,3 @@ async function askForServiceAccountPathAsync(): Promise<string> {
|
||||
});
|
||||
return filePath;
|
||||
}
|
||||
|
||||
async function displayPathChooserAsync(
|
||||
paths: string[],
|
||||
projectDir: string
|
||||
): Promise<string | false> {
|
||||
const choices = paths.map<{ title: string; value: string | false }>(f => ({
|
||||
value: f,
|
||||
title: f.startsWith(projectDir) ? path.relative(projectDir, f) : f,
|
||||
}));
|
||||
|
||||
choices.push({
|
||||
title: 'None of the above',
|
||||
value: false,
|
||||
});
|
||||
|
||||
Log.log(
|
||||
'Multiple Google Service Account JSON keys have been found inside your project directory.'
|
||||
);
|
||||
const { selectedPath } = await promptAsync({
|
||||
name: 'selectedPath',
|
||||
type: 'select',
|
||||
message: 'Choose the key you want to use for this submission:',
|
||||
choices,
|
||||
});
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
return selectedPath;
|
||||
}
|
||||
|
||||
async function confirmDetectedPathAsync(path: string): Promise<boolean> {
|
||||
Log.log(`A Google Service Account JSON key has been found at\n ${chalk.underline(path)}`);
|
||||
const { confirmed } = await promptAsync({
|
||||
name: 'confirmed',
|
||||
type: 'confirm',
|
||||
message: 'Would you like to use this file?',
|
||||
initial: true,
|
||||
});
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
async function fileIsGoogleServicesAsync(path: string): Promise<boolean> {
|
||||
try {
|
||||
const jsonFile = await fs.readJson(path);
|
||||
return jsonFile.type === 'service_account';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,11 @@ describe(AndroidSubmitCommand, () => {
|
||||
|
||||
const fakeFiles: Record<string, string> = {
|
||||
'/apks/fake.apk': 'fake apk',
|
||||
'/google-service-account.json': JSON.stringify({ service: 'account' }),
|
||||
'/google-service-account.json': JSON.stringify({
|
||||
type: 'service_account',
|
||||
private_key: 'super secret',
|
||||
client_email: '[email protected]',
|
||||
}),
|
||||
};
|
||||
|
||||
const fakeBuildFragment: Partial<BuildFragment> = {
|
||||
|
||||
@@ -1,39 +1,70 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { AndroidReleaseStatus, AndroidReleaseTrack } from '@expo/eas-json';
|
||||
import { vol } from 'memfs';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { asMock } from '../../../__tests__/utils';
|
||||
import { findProjectRootAsync } from '../../../project/projectUtils';
|
||||
import { testAndroidAppCredentialsFragment } from '../../../credentials/__tests__/fixtures-android';
|
||||
import { jester as mockJester } from '../../../credentials/__tests__/fixtures-constants';
|
||||
import { SetupGoogleServiceAccountKey } from '../../../credentials/android/actions/SetupGoogleServiceAccountKey';
|
||||
import { createTestProject } from '../../../project/__tests__/project-utils';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { createSubmissionContextAsync } from '../../context';
|
||||
import {
|
||||
ServiceAccountSource,
|
||||
ServiceAccountSourceType,
|
||||
getServiceAccountAsync,
|
||||
getServiceAccountKeyPathAsync,
|
||||
getServiceAccountKeyResultAsync,
|
||||
} from '../ServiceAccountSource';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('../../../prompts');
|
||||
jest.mock('../../../project/projectUtils');
|
||||
jest.mock('../../../credentials/android/actions/SetupGoogleServiceAccountKey');
|
||||
jest.mock('../../../user/User', () => ({
|
||||
getUserAsync: jest.fn(() => mockJester),
|
||||
}));
|
||||
jest.mock('../../../user/Account', () => ({
|
||||
findAccountByName: jest.fn(() => mockJester.accounts[0]),
|
||||
}));
|
||||
const testProject = createTestProject(mockJester, {
|
||||
android: {
|
||||
package: 'com.expo.test.project',
|
||||
},
|
||||
});
|
||||
const mockManifest = { exp: testProject.appJSON.expo };
|
||||
jest.mock('@expo/config', () => ({
|
||||
getConfig: jest.fn(() => mockManifest),
|
||||
}));
|
||||
|
||||
describe(getServiceAccountAsync, () => {
|
||||
beforeAll(() => {
|
||||
const mockDetectableServiceAccountJson = JSON.stringify({
|
||||
type: 'service_account',
|
||||
});
|
||||
const projectId = uuidv4();
|
||||
const mockDetectableServiceAccountJson = JSON.stringify({
|
||||
type: 'service_account',
|
||||
private_key: 'super secret',
|
||||
client_email: '[email protected]',
|
||||
});
|
||||
|
||||
vol.fromJSON({
|
||||
'/google-service-account.json': JSON.stringify({ service: 'account' }),
|
||||
'/project_dir/subdir/service-account.json': mockDetectableServiceAccountJson,
|
||||
'/project_dir/another-service-account.json': mockDetectableServiceAccountJson,
|
||||
'/other_dir/invalid_file.txt': 'this is not even a JSON',
|
||||
});
|
||||
});
|
||||
afterAll(() => {
|
||||
vol.reset();
|
||||
beforeAll(() => {
|
||||
vol.fromJSON({
|
||||
'/google-service-account.json': mockDetectableServiceAccountJson,
|
||||
'/project_dir/subdir/service-account.json': mockDetectableServiceAccountJson,
|
||||
'/project_dir/another-service-account.json': mockDetectableServiceAccountJson,
|
||||
'/other_dir/invalid_file.txt': 'this is not even a JSON',
|
||||
});
|
||||
jest
|
||||
.spyOn(SetupGoogleServiceAccountKey.prototype, 'runAsync')
|
||||
.mockImplementation(async () => testAndroidAppCredentialsFragment);
|
||||
});
|
||||
afterAll(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
asMock(promptAsync).mockClear();
|
||||
});
|
||||
afterEach(() => {
|
||||
asMock(promptAsync).mockClear();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe(getServiceAccountKeyPathAsync, () => {
|
||||
describe('when source is ServiceAccountSourceType.path', () => {
|
||||
it("prompts for path if the provided file doesn't exist", async () => {
|
||||
asMock(promptAsync).mockImplementationOnce(() => ({
|
||||
@@ -43,7 +74,7 @@ describe(getServiceAccountAsync, () => {
|
||||
sourceType: ServiceAccountSourceType.path,
|
||||
path: '/doesnt-exist.json',
|
||||
};
|
||||
const serviceAccountPath = await getServiceAccountAsync(source);
|
||||
const serviceAccountPath = await getServiceAccountKeyPathAsync(source);
|
||||
expect(promptAsync).toHaveBeenCalled();
|
||||
expect(serviceAccountPath).toBe('/google-service-account.json');
|
||||
});
|
||||
@@ -53,7 +84,7 @@ describe(getServiceAccountAsync, () => {
|
||||
sourceType: ServiceAccountSourceType.path,
|
||||
path: '/google-service-account.json',
|
||||
};
|
||||
await getServiceAccountAsync(source);
|
||||
await getServiceAccountKeyPathAsync(source);
|
||||
expect(promptAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -62,7 +93,7 @@ describe(getServiceAccountAsync, () => {
|
||||
sourceType: ServiceAccountSourceType.path,
|
||||
path: '/google-service-account.json',
|
||||
};
|
||||
const serviceAccountPath = await getServiceAccountAsync(source);
|
||||
const serviceAccountPath = await getServiceAccountKeyPathAsync(source);
|
||||
expect(serviceAccountPath).toBe('/google-service-account.json');
|
||||
});
|
||||
});
|
||||
@@ -75,7 +106,7 @@ describe(getServiceAccountAsync, () => {
|
||||
const source: ServiceAccountSource = {
|
||||
sourceType: ServiceAccountSourceType.prompt,
|
||||
};
|
||||
const serviceAccountPath = await getServiceAccountAsync(source);
|
||||
const serviceAccountPath = await getServiceAccountKeyPathAsync(source);
|
||||
expect(promptAsync).toHaveBeenCalled();
|
||||
expect(serviceAccountPath).toBe('/google-service-account.json');
|
||||
});
|
||||
@@ -94,105 +125,105 @@ describe(getServiceAccountAsync, () => {
|
||||
const source: ServiceAccountSource = {
|
||||
sourceType: ServiceAccountSourceType.prompt,
|
||||
};
|
||||
const serviceAccountPath = await getServiceAccountAsync(source);
|
||||
const serviceAccountPath = await getServiceAccountKeyPathAsync(source);
|
||||
expect(promptAsync).toHaveBeenCalledTimes(3);
|
||||
expect(serviceAccountPath).toBe('/google-service-account.json');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when source is ServiceAccountSourceType.detect', () => {
|
||||
it('detects a single google-services file and prompts for confirmation', async () => {
|
||||
asMock(findProjectRootAsync).mockResolvedValueOnce('/project_dir/subdir'); // should find 1 file
|
||||
asMock(promptAsync).mockResolvedValueOnce({
|
||||
confirmed: true,
|
||||
});
|
||||
|
||||
const serviceAccountPath = await getServiceAccountAsync({
|
||||
sourceType: ServiceAccountSourceType.detect,
|
||||
});
|
||||
|
||||
expect(promptAsync).toHaveBeenCalledWith(expect.objectContaining({ type: 'confirm' }));
|
||||
expect(serviceAccountPath).toBe('/project_dir/subdir/service-account.json');
|
||||
describe(getServiceAccountKeyResultAsync, () => {
|
||||
it('returns a local Service Account Key file with a ServiceAccountSourceType.path source', async () => {
|
||||
const ctx = await createSubmissionContextAsync({
|
||||
platform: Platform.ANDROID,
|
||||
projectDir: testProject.projectRoot,
|
||||
projectId,
|
||||
archiveFlags: {
|
||||
url: 'http://expo.dev/fake.apk',
|
||||
},
|
||||
profile: {
|
||||
track: AndroidReleaseTrack.internal,
|
||||
releaseStatus: AndroidReleaseStatus.draft,
|
||||
changesNotSentForReview: false,
|
||||
},
|
||||
nonInteractive: true,
|
||||
});
|
||||
|
||||
it('falls back to prompt, when no valid files are found in the dir', async () => {
|
||||
asMock(findProjectRootAsync).mockResolvedValueOnce('/other_dir'); // no valid files in that dir
|
||||
asMock(promptAsync).mockResolvedValueOnce({
|
||||
filePath: '/google-service-account.json',
|
||||
});
|
||||
|
||||
const serviceAccountPath = await getServiceAccountAsync({
|
||||
sourceType: ServiceAccountSourceType.detect,
|
||||
});
|
||||
|
||||
expect(promptAsync).toHaveBeenCalledWith(expect.objectContaining({ type: 'text' }));
|
||||
expect(serviceAccountPath).toBe('/google-service-account.json');
|
||||
const source: ServiceAccountSource = {
|
||||
sourceType: ServiceAccountSourceType.path,
|
||||
path: '/project_dir/subdir/service-account.json',
|
||||
};
|
||||
const serviceAccountResult = await getServiceAccountKeyResultAsync(ctx, source, 'test.sdf');
|
||||
expect(serviceAccountResult).toMatchObject({
|
||||
result: {
|
||||
googleServiceAccountKeyJson: mockDetectableServiceAccountJson,
|
||||
},
|
||||
summary: {
|
||||
source: 'local',
|
||||
path: '/project_dir/subdir/service-account.json',
|
||||
email: '[email protected]serviceaccount.com',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to prompt, when user rejects to use detected file', async () => {
|
||||
asMock(findProjectRootAsync).mockResolvedValueOnce('/project_dir/subdir'); // should find 1 file
|
||||
asMock(promptAsync)
|
||||
.mockResolvedValueOnce({
|
||||
confirmed: false,
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
filePath: '/google-service-account.json',
|
||||
});
|
||||
const source: ServiceAccountSource = {
|
||||
sourceType: ServiceAccountSourceType.detect,
|
||||
};
|
||||
|
||||
const serviceAccountPath = await getServiceAccountAsync(source);
|
||||
|
||||
expect(promptAsync).toHaveBeenCalledTimes(2);
|
||||
expect(promptAsync).toHaveBeenCalledWith(expect.objectContaining({ type: 'confirm' }));
|
||||
expect(promptAsync).toHaveBeenLastCalledWith(expect.objectContaining({ type: 'text' }));
|
||||
expect(serviceAccountPath).toBe('/google-service-account.json');
|
||||
it('returns a local Service Account Key file with a ServiceAccountSourceType.prompt source', async () => {
|
||||
asMock(promptAsync).mockImplementationOnce(() => ({
|
||||
filePath: '/project_dir/subdir/service-account.json',
|
||||
}));
|
||||
const ctx = await createSubmissionContextAsync({
|
||||
platform: Platform.ANDROID,
|
||||
projectDir: testProject.projectRoot,
|
||||
projectId,
|
||||
archiveFlags: {
|
||||
url: 'http://expo.dev/fake.apk',
|
||||
},
|
||||
profile: {
|
||||
track: AndroidReleaseTrack.internal,
|
||||
releaseStatus: AndroidReleaseStatus.draft,
|
||||
changesNotSentForReview: false,
|
||||
},
|
||||
nonInteractive: true,
|
||||
});
|
||||
|
||||
it('displays a chooser, when multiple files are found', async () => {
|
||||
asMock(findProjectRootAsync).mockResolvedValueOnce('/project_dir'); // should find 2 files here
|
||||
asMock(promptAsync).mockResolvedValueOnce({
|
||||
selectedPath: '/project_dir/another-service-account.json',
|
||||
});
|
||||
|
||||
const serviceAccountPath = await getServiceAccountAsync({
|
||||
sourceType: ServiceAccountSourceType.detect,
|
||||
});
|
||||
|
||||
const expectedChoices = expect.arrayContaining([
|
||||
expect.objectContaining({ value: '/project_dir/another-service-account.json' }),
|
||||
expect.objectContaining({ value: '/project_dir/subdir/service-account.json' }),
|
||||
expect.objectContaining({ value: false }),
|
||||
]);
|
||||
|
||||
expect(promptAsync).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'select',
|
||||
choices: expectedChoices,
|
||||
})
|
||||
);
|
||||
expect(serviceAccountPath).toBe('/project_dir/another-service-account.json');
|
||||
const source: ServiceAccountSource = {
|
||||
sourceType: ServiceAccountSourceType.prompt,
|
||||
};
|
||||
const serviceAccountResult = await getServiceAccountKeyResultAsync(ctx, source, 'test.sdf');
|
||||
expect(serviceAccountResult).toMatchObject({
|
||||
result: {
|
||||
googleServiceAccountKeyJson: mockDetectableServiceAccountJson,
|
||||
},
|
||||
summary: {
|
||||
source: 'local',
|
||||
path: '/project_dir/subdir/service-account.json',
|
||||
email: '[email protected]',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to prompt, when user selects the "None of above"', async () => {
|
||||
asMock(findProjectRootAsync).mockResolvedValueOnce('/project_dir'); // should find 2 files here
|
||||
asMock(promptAsync)
|
||||
.mockResolvedValueOnce({
|
||||
selectedPath: false,
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
filePath: '/google-service-account.json',
|
||||
});
|
||||
|
||||
const serviceAccountPath = await getServiceAccountAsync({
|
||||
sourceType: ServiceAccountSourceType.detect,
|
||||
});
|
||||
|
||||
expect(promptAsync).toHaveBeenCalledTimes(2);
|
||||
expect(promptAsync).toHaveBeenCalledWith(expect.objectContaining({ type: 'select' }));
|
||||
expect(promptAsync).toHaveBeenLastCalledWith(expect.objectContaining({ type: 'text' }));
|
||||
expect(serviceAccountPath).toBe('/google-service-account.json');
|
||||
it('returns a remote Service Account Key file with a ServiceAccountSourceType.credentialService source', async () => {
|
||||
const ctx = await createSubmissionContextAsync({
|
||||
platform: Platform.ANDROID,
|
||||
projectDir: testProject.projectRoot,
|
||||
projectId,
|
||||
archiveFlags: {
|
||||
url: 'http://expo.dev/fake.apk',
|
||||
},
|
||||
profile: {
|
||||
track: AndroidReleaseTrack.internal,
|
||||
releaseStatus: AndroidReleaseStatus.draft,
|
||||
changesNotSentForReview: false,
|
||||
},
|
||||
nonInteractive: true,
|
||||
});
|
||||
const serviceAccountResult = await getServiceAccountKeyResultAsync(
|
||||
ctx,
|
||||
{
|
||||
sourceType: ServiceAccountSourceType.credentialsService,
|
||||
},
|
||||
'test.application.identiifer'
|
||||
);
|
||||
expect(serviceAccountResult).toMatchObject({
|
||||
result: { googleServiceAccountKeyId: 'test-id' },
|
||||
summary: { source: 'EAS servers', email: '[email protected]' },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,9 +4,12 @@ import { SubmitProfile } from '@expo/eas-json';
|
||||
|
||||
import { CredentialsContext } from '../credentials/context';
|
||||
import { getExpoConfig } from '../project/expoConfig';
|
||||
import { getProjectAccountName } from '../project/projectUtils';
|
||||
import { Actor } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
|
||||
export interface SubmissionContext<T extends Platform> {
|
||||
accountName: string;
|
||||
archiveFlags: SubmitArchiveFlags;
|
||||
credentialsCtx: CredentialsContext;
|
||||
exp: ExpoConfig;
|
||||
@@ -15,6 +18,8 @@ export interface SubmissionContext<T extends Platform> {
|
||||
profile: SubmitProfile<T>;
|
||||
projectDir: string;
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
user: Actor;
|
||||
}
|
||||
|
||||
export interface SubmitArchiveFlags {
|
||||
@@ -37,14 +42,19 @@ export async function createSubmissionContextAsync<T extends Platform>(params: {
|
||||
const { projectDir, nonInteractive } = params;
|
||||
const exp = getExpoConfig(projectDir, { env: params.env });
|
||||
const { env, ...rest } = params;
|
||||
const user = await ensureLoggedInAsync();
|
||||
const projectName = exp.slug;
|
||||
const accountName = getProjectAccountName(exp, user);
|
||||
let credentialsCtx: CredentialsContext | undefined = params.credentialsCtx;
|
||||
if (!credentialsCtx) {
|
||||
const user = await ensureLoggedInAsync();
|
||||
credentialsCtx = new CredentialsContext({ projectDir, user, exp, nonInteractive });
|
||||
}
|
||||
return {
|
||||
...rest,
|
||||
accountName,
|
||||
credentialsCtx,
|
||||
exp,
|
||||
projectName,
|
||||
user,
|
||||
};
|
||||
}
|
||||
@@ -9,9 +9,7 @@ export function enableJsonOutput(): void {
|
||||
return;
|
||||
}
|
||||
stdoutWrite = process.stdout.write;
|
||||
process.stdout.write = (...args: any) => {
|
||||
return process.stderr.write.call(null, args);
|
||||
};
|
||||
process.stdout.write = process.stderr.write.bind(process.stderr);
|
||||
}
|
||||
|
||||
export function printJsonOnlyOutput(value: object): void {
|
||||
@@ -20,9 +18,7 @@ export function printJsonOnlyOutput(value: object): void {
|
||||
process.stdout.write = stdoutWrite;
|
||||
Log.log(JSON.stringify(sanitizeValue(value), null, 2));
|
||||
} finally {
|
||||
process.stdout.write = (...args: any) => {
|
||||
return process.stderr.write.call(null, args);
|
||||
};
|
||||
process.stdout.write = process.stderr.write.bind(process.stderr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"description": "A library for interacting with the eas.json",
|
||||
"version": "0.30.0",
|
||||
"version": "0.31.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/eas-build-job": "0.2.48",
|
||||
"@expo/eas-build-job": "0.2.52",
|
||||
"@expo/json-file": "8.2.33",
|
||||
"env-string": "1.0.1",
|
||||
"fs-extra": "10.0.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "scripts",
|
||||
"private": true,
|
||||
"version": "0.30.0",
|
||||
"version": "0.31.0",
|
||||
"scripts": {
|
||||
"release-changelog": "ts-node src/changelog.ts"
|
||||
},
|
||||
|
||||
@@ -860,14 +860,15 @@
|
||||
mv "~2"
|
||||
safe-json-stringify "~1"
|
||||
|
||||
"@expo/config-plugins@3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-3.1.0.tgz#0752ff33c5eab21cf42034a44e79df97f0f867f8"
|
||||
integrity sha512-V5qxaxCAExBM0TXmbU1QKiZcAGP3ecu7KXede8vByT15cro5PkcWu2sSdJCYbHQ/gw6Vf/i8sr8gKlN8V8TSLg==
|
||||
"@expo/config-plugins@4.0.2":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-4.0.2.tgz#6c83ae373c12a240ccadc2322bd8267e4eb91f7b"
|
||||
integrity sha512-j8IOVH0yL7ZDYcoozdpQb3ZuwUM/4eZ1QWy4icovjlyXGtRFyJvWZitZkA/7HK1+0mS2ZNSW28elp+LVTX3UfA==
|
||||
dependencies:
|
||||
"@expo/config-types" "^42.0.0"
|
||||
"@expo/config-types" "^43.0.0"
|
||||
"@expo/json-file" "8.2.33"
|
||||
"@expo/plist" "0.0.14"
|
||||
"@expo/plist" "0.0.15"
|
||||
"@react-native/normalize-color" "^2.0.0"
|
||||
chalk "^4.1.2"
|
||||
debug "^4.3.1"
|
||||
find-up "~5.0.0"
|
||||
@@ -880,19 +881,19 @@
|
||||
xcode "^3.0.1"
|
||||
xml2js "^0.4.23"
|
||||
|
||||
"@expo/config-types@^42.0.0":
|
||||
version "42.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-42.0.0.tgz#3e3e125ec092c0c34dbfaf19be5480402de3d677"
|
||||
integrity sha512-Rj02OMZke2MrGa/1Y/EScmR7VuWbDEHPJyvfFyyLbadUt+Yv6isCdeFzDt71I7gJlPR9T4fzixeYLrtXXOTq0w==
|
||||
"@expo/config-types@^43.0.0":
|
||||
version "43.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-43.0.0.tgz#e283a4a1b72a655a6a2a745bd277caef58c8edb0"
|
||||
integrity sha512-GBbEtLdGX40DJ1wTixWtOHseupA5xWclV57KNZ58ebauWAOyK3Fyni7GD2OCOzxTIFi1nSjqgO9JlMp3ayP4Tw==
|
||||
|
||||
"@expo/config@5.0.9":
|
||||
version "5.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@expo/config/-/config-5.0.9.tgz#5221af5394599d861515ef8513731f21fbb322db"
|
||||
integrity sha512-eZj+cf03wkQQdHSpYvrmiqAsn2dJV10uhHIwXyeFBaFvhds0NgThOldJZfOppQ4QUaGobB/vaJ7UqUa3B0PCMw==
|
||||
"@expo/config@6.0.2":
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@expo/config/-/config-6.0.2.tgz#d778be85a6b9f9fdb1f453464ba4c3420ba410d1"
|
||||
integrity sha512-UjNlbs3gYfYnb+8If5dijepRz2+J8sXdmF9bvGQ2LUULdpO0Bx7EbLErctzrFXlK3j7jw1esPWWNfVq5wIlEwg==
|
||||
dependencies:
|
||||
"@babel/code-frame" "~7.10.4"
|
||||
"@expo/config-plugins" "3.1.0"
|
||||
"@expo/config-types" "^42.0.0"
|
||||
"@expo/config-plugins" "4.0.2"
|
||||
"@expo/config-types" "^43.0.0"
|
||||
"@expo/json-file" "8.2.33"
|
||||
getenv "^1.0.0"
|
||||
glob "7.1.6"
|
||||
@@ -902,12 +903,13 @@
|
||||
slugify "^1.3.4"
|
||||
sucrase "^3.20.0"
|
||||
|
||||
"@expo/[email protected].48":
|
||||
version "0.2.48"
|
||||
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-0.2.48.tgz#ebc2abea02cb3f23be9714318bea04515980b03d"
|
||||
integrity sha512-QWg44uXndNw3ZKrP+1FD2Li1VSQoSWn94iQ6j6lwMfVfFpp6DiiZ73HXf4Bxix52cl5DTmpaE7hxYyhxSvV0yA==
|
||||
"@expo/[email protected].52":
|
||||
version "0.2.52"
|
||||
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-0.2.52.tgz#889025a412c21e2c77a0c42d342916e2a2569a50"
|
||||
integrity sha512-1bfOp/dMOf3zbnfJnES3y2z/+pAwNUIIaicnMURY2JLars+3gkQOKm26nk+FnTTE/8QykmIocls6RhlPqCNnUg==
|
||||
dependencies:
|
||||
joi "^17.4.2"
|
||||
semver "^7.3.5"
|
||||
|
||||
"@expo/[email protected]":
|
||||
version "0.3.16"
|
||||
@@ -971,6 +973,15 @@
|
||||
base64-js "^1.2.3"
|
||||
xmlbuilder "^14.0.0"
|
||||
|
||||
"@expo/[email protected]":
|
||||
version "0.0.15"
|
||||
resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.0.15.tgz#41ef37b7bbe6b81c48bf4a5c359661c766bb9e90"
|
||||
integrity sha512-LDxiS0KNZAGJu4fIJhbEKczmb+zeftl1NU0LE0tj0mozoMI5HSKdMUchgvnBm35bwBl8ekKkAfJJ0ONxljWQjQ==
|
||||
dependencies:
|
||||
"@xmldom/xmldom" "~0.7.0"
|
||||
base64-js "^1.2.3"
|
||||
xmlbuilder "^14.0.0"
|
||||
|
||||
"@expo/[email protected]":
|
||||
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"
|
||||
@@ -986,19 +997,20 @@
|
||||
lodash.template "^4.4.0"
|
||||
semver "^7.3.2"
|
||||
|
||||
"@expo/prebuild-config@2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-2.1.0.tgz#d1dbead511b3959237e542e326a9db757e97fcab"
|
||||
integrity sha512-obpbnV0+Otv7Dbx8kkbSd62xL9HYZRDPdmdcVWuML7lv7Zo4r+OyS6vYpUmln9htp0gtjuc6+X9FiC74bbGkVA==
|
||||
"@expo/prebuild-config@3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-3.0.2.tgz#8b0fae285efbfede82d6b3aaee96ea8ea10307de"
|
||||
integrity sha512-iEKcjbbwrICRgqAPNcyI3g2MkZx3gkmD9KE7flfWJwC+6XzYC8DBz9vN5mVAu8GX3S9H16sC2tF4ZspVC6WHDg==
|
||||
dependencies:
|
||||
"@expo/config" "5.0.9"
|
||||
"@expo/config-plugins" "3.1.0"
|
||||
"@expo/config-types" "^42.0.0"
|
||||
"@expo/config" "6.0.2"
|
||||
"@expo/config-plugins" "4.0.2"
|
||||
"@expo/config-types" "^43.0.0"
|
||||
"@expo/image-utils" "0.3.16"
|
||||
"@expo/json-file" "8.2.33"
|
||||
debug "^4.3.1"
|
||||
fs-extra "^9.0.0"
|
||||
resolve-from "^5.0.0"
|
||||
semver "7.3.2"
|
||||
|
||||
"@expo/[email protected]":
|
||||
version "1.0.0"
|
||||
@@ -2607,6 +2619,11 @@
|
||||
dependencies:
|
||||
"@octokit/openapi-types" "^9.5.0"
|
||||
|
||||
"@react-native/normalize-color@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-native/normalize-color/-/normalize-color-2.0.0.tgz#da955909432474a9a0fe1cbffc66576a0447f567"
|
||||
integrity sha512-Wip/xsc5lw8vsBlmY2MO/gFLp3MvuZ2baBZjDeTjjndMgM0h5sxz7AZR62RDPGgstp8Np7JzjvVqVT7tpFZqsw==
|
||||
|
||||
"@samverschueren/stream-to-observable@^0.3.0":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz#a21117b19ee9be70c379ec1877537ef2e1c63301"
|
||||
@@ -9640,7 +9657,7 @@ sisteransi@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
|
||||
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
|
||||
|
||||
slash@^3.0.0:
|
||||
[email protected], slash@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
||||
|
||||
Reference in new issue
Block a user