mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a730d70d6 | ||
|
|
41f58e5e52 | ||
|
|
34af57f585 | ||
|
|
4674cbebc5 | ||
|
|
69add6f523 | ||
|
|
b70e874135 | ||
|
|
03fa29dd5a | ||
|
|
9e467b900e | ||
|
|
6e1c7dd91e | ||
|
|
9812406c18 | ||
|
|
cf11ae93af | ||
|
|
14756ad39b | ||
|
|
1486b481f6 | ||
|
|
84d82e9a59 | ||
|
|
c7ce4a8670 | ||
|
|
fe0a3b4c41 | ||
|
|
0bce278bd4 |
No files matched your search
+2
-1
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['universe/node'],
|
||||
plugins: ['async-protect'],
|
||||
plugins: ['async-protect', 'node'],
|
||||
rules: {
|
||||
'no-console': 'warn',
|
||||
'no-constant-condition': ['warn', { checkLoops: false }],
|
||||
@@ -41,6 +41,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'async-protect/async-suffix': 'error',
|
||||
'node/no-sync': 'error',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
||||
+30
-2
@@ -8,13 +8,41 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- More upload support for Google Service Account Keys. ([#649](https://github.com/expo/eas-cli/pull/649) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Allow the user to assign an existing Google Service Account Key to their project. ([#650](https://github.com/expo/eas-cli/pull/650) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix double prompt for Apple credentials when running `eas build --auto-submit`. ([#654](https://github.com/expo/eas-cli/pull/654) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Always use async `fs` functions. ([#652](https://github.com/expo/eas-cli/pull/652) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.29.0](https://github.com/expo/eas-cli/releases/tag/v0.29.0) - 2021-09-28
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Added upload support for Google Service Account Keys. ([#642](https://github.com/expo/eas-cli/pull/642) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add Xcode 13 image. ([#651](https://github.com/expo/eas-cli/pull/651) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Credentials manager: stop prompting for an Android build profile every iteration. ([#641](https://github.com/expo/eas-cli/pull/641) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Don't display prompt in non-interactive mode when the metro config seems invalid. ([#644](https://github.com/expo/eas-cli/pull/644) by [@dsokal](https://github.com/dsokal))
|
||||
- Read versions from correct `Info.plist`. ([#635](https://github.com/expo/eas-cli/pull/635) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Move away from weird convention of exports. ([#647](https://github.com/expo/eas-cli/pull/647) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.28.2](https://github.com/expo/eas-cli/releases/tag/v0.28.2) - 2021-09-23
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Use `log-symbols` to unify green ticks style. ([#639](https://github.com/expo/eas-cli/pull/639) by [@dsokal](https://github.com/dsokal))
|
||||
- Fix `eas build --auto-submit` in fresh projects. ([#640](https://github.com/expo/eas-cli/pull/640) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [0.28.1](https://github.com/expo/eas-cli/releases/tag/v0.28.1) - 2021-09-22
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.28.2"
|
||||
"version": "0.29.1"
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-universe": "8.0.0",
|
||||
"eslint-plugin-async-protect": "1.1.0",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"jest": "27.1.0",
|
||||
"jest-watch-typeahead": "0.6.4",
|
||||
"lerna": "4.0.0",
|
||||
|
||||
+26
-26
@@ -73,7 +73,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/login.js](https://github.com/expo/eas-cli/blob/v0.28.2/packages/eas-cli/src/commands/account/login.js)_
|
||||
_See code: [src/commands/account/login.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/account/logout.js)_
|
||||
_See code: [src/commands/account/logout.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/account/view.js)_
|
||||
_See code: [src/commands/account/view.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/analytics.js)_
|
||||
_See code: [src/commands/analytics.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/build/index.js)_
|
||||
_See code: [src/commands/build/index.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/build/cancel.js)_
|
||||
_See code: [src/commands/build/cancel.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/build/configure.js)_
|
||||
_See code: [src/commands/build/configure.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/build/list.js)_
|
||||
_See code: [src/commands/build/list.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/build/view.js)_
|
||||
_See code: [src/commands/build/view.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/config.js)_
|
||||
_See code: [src/commands/config.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/credentials.js)_
|
||||
_See code: [src/commands/credentials.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/device/create.js)_
|
||||
_See code: [src/commands/device/create.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/device/list.js)_
|
||||
_See code: [src/commands/device/list.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/device/view.js)_
|
||||
_See code: [src/commands/device/view.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/diagnostics.js)_
|
||||
_See code: [src/commands/diagnostics.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/project/info.js)_
|
||||
_See code: [src/commands/project/info.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/project/init.js)_
|
||||
_See code: [src/commands/project/init.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/secret/create.js)_
|
||||
_See code: [src/commands/secret/create.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/secret/delete.js)_
|
||||
_See code: [src/commands/secret/delete.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/secret/list.js)_
|
||||
_See code: [src/commands/secret/list.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/submit.js)_
|
||||
_See code: [src/commands/submit.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/webhook/create.js)_
|
||||
_See code: [src/commands/webhook/create.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/webhook/delete.js)_
|
||||
_See code: [src/commands/webhook/delete.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/webhook/list.js)_
|
||||
_See code: [src/commands/webhook/list.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/webhook/update.js)_
|
||||
_See code: [src/commands/webhook/update.js](https://github.com/expo/eas-cli/blob/v0.29.1/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.28.2/packages/eas-cli/src/commands/webhook/view.js)_
|
||||
_See code: [src/commands/webhook/view.js](https://github.com/expo/eas-cli/blob/v0.29.1/packages/eas-cli/src/commands/webhook/view.js)_
|
||||
<!-- commandsstop -->
|
||||
@@ -1,8 +1,10 @@
|
||||
import { fs } from 'memfs';
|
||||
|
||||
// needed because of a weird bug with tempy (dependency of @expo/config-plugins)
|
||||
// eslint-disable-next-line node/no-sync
|
||||
fs.mkdirSync('/tmp');
|
||||
if (process.env.TMPDIR) {
|
||||
// eslint-disable-next-line node/no-sync
|
||||
fs.mkdirSync(process.env.TMPDIR, { recursive: true });
|
||||
}
|
||||
|
||||
|
||||
Generated
+2067
-607
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.28.2",
|
||||
"version": "0.29.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
@@ -11,15 +11,15 @@
|
||||
"@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.46",
|
||||
"@expo/eas-json": "^0.28.2",
|
||||
"@expo/eas-build-job": "0.2.48",
|
||||
"@expo/eas-json": "^0.29.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/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.0.7",
|
||||
"@expo/rudder-sdk-node": "1.1.0",
|
||||
"@expo/sdk-runtime-versions": "1.0.0",
|
||||
"@expo/spawn-async": "1.5.0",
|
||||
"@expo/timeago.js": "1.0.0",
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
const fs = require('fs');
|
||||
const { promises: fs } = require('fs');
|
||||
|
||||
// Patch `oclif-dev readme` path and link generation
|
||||
let readmeContent = fs.readFileSync('README.md', 'utf8');
|
||||
readmeContent = readmeContent.replace(/\[build\//g, '[src/');
|
||||
readmeContent = readmeContent.replace(/build\/commands/g, 'packages/eas-cli/src/commands');
|
||||
fs.writeFileSync('README.md', readmeContent);
|
||||
(async () => {
|
||||
// Patch `oclif-dev readme` path and link generation
|
||||
let readmeContent = await fs.readFile('README.md', 'utf8');
|
||||
readmeContent = readmeContent.replace(/\[build\//g, '[src/');
|
||||
readmeContent = readmeContent.replace(/build\/commands/g, 'packages/eas-cli/src/commands');
|
||||
await fs.writeFile('README.md', readmeContent);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Patched README path generation');
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Patched README path generation');
|
||||
})();
|
||||
@@ -25,7 +25,7 @@ interface AppJSON {
|
||||
};
|
||||
}
|
||||
|
||||
function createTestProject(
|
||||
export function createTestProject(
|
||||
user: Actor,
|
||||
appJSONExtraData?: Record<string, object | string>
|
||||
): MockProject {
|
||||
@@ -58,5 +58,3 @@ function createTestProject(
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { createTestProject };
|
||||
@@ -65,7 +65,6 @@ export async function initAsync(): Promise<void> {
|
||||
flushInterval: 300,
|
||||
}
|
||||
);
|
||||
rudderstackClient.logger.silent = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +86,7 @@ export async function setUserDataAsync(userId: string, traits: Record<string, an
|
||||
|
||||
export async function flushAsync(): Promise<void> {
|
||||
if (rudderstackClient) {
|
||||
rudderstackClient.flush();
|
||||
await rudderstackClient.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,13 +113,11 @@ function ensureUserIdentified(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rudderstackClient) {
|
||||
rudderstackClient.identify({
|
||||
userId: identifyData.userId,
|
||||
anonymousId: identifyData.deviceId,
|
||||
traits: identifyData.traits,
|
||||
});
|
||||
}
|
||||
rudderstackClient.identify({
|
||||
userId: identifyData.userId,
|
||||
anonymousId: identifyData.deviceId,
|
||||
traits: identifyData.traits,
|
||||
});
|
||||
userIdentified = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,17 +6,17 @@ import { readChannelSafelyAsync } from '../UpdatesModule';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
afterAll(() => {
|
||||
afterAll(async () => {
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.removeSync(os.tmpdir());
|
||||
await fs.remove(os.tmpdir());
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
vol.reset();
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.mkdirpSync(os.tmpdir());
|
||||
await fs.mkdirp(os.tmpdir());
|
||||
});
|
||||
|
||||
const testChannel = 'test-channel';
|
||||
|
||||
@@ -9,17 +9,17 @@ import { maybeResolveVersionsAsync } from '../version';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
afterAll(() => {
|
||||
afterAll(async () => {
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.removeSync(os.tmpdir());
|
||||
await fs.remove(os.tmpdir());
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
vol.reset();
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.mkdirpSync(os.tmpdir());
|
||||
await fs.mkdirp(os.tmpdir());
|
||||
});
|
||||
|
||||
describe(maybeResolveVersionsAsync, () => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import nullthrows from 'nullthrows';
|
||||
import AndroidCredentialsProvider, {
|
||||
AndroidCredentials,
|
||||
} from '../../credentials/android/AndroidCredentialsProvider';
|
||||
import { createCredentialsContextAsync } from '../../credentials/context';
|
||||
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
@@ -110,22 +109,16 @@ async function ensureAndroidCredentialsAsync(
|
||||
ctx.exp,
|
||||
gradleContext
|
||||
);
|
||||
const provider = new AndroidCredentialsProvider(
|
||||
await createCredentialsContextAsync(ctx.projectDir, {
|
||||
exp: ctx.exp,
|
||||
nonInteractive: ctx.nonInteractive,
|
||||
}),
|
||||
{
|
||||
app: {
|
||||
account: nullthrows(
|
||||
findAccountByName(ctx.user.accounts, ctx.accountName),
|
||||
`You do not have access to account: ${ctx.accountName}`
|
||||
),
|
||||
projectName: ctx.projectName,
|
||||
androidApplicationIdentifier,
|
||||
},
|
||||
}
|
||||
);
|
||||
const provider = new AndroidCredentialsProvider(ctx.credentialsCtx, {
|
||||
app: {
|
||||
account: nullthrows(
|
||||
findAccountByName(ctx.user.accounts, ctx.accountName),
|
||||
`You do not have access to account: ${ctx.accountName}`
|
||||
),
|
||||
projectName: ctx.projectName,
|
||||
androidApplicationIdentifier,
|
||||
},
|
||||
});
|
||||
const { credentialsSource } = ctx.buildProfile;
|
||||
logCredentialsSource(credentialsSource, Platform.ANDROID);
|
||||
return {
|
||||
|
||||
@@ -5,6 +5,7 @@ import JsonFile from '@expo/json-file';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { CredentialsContext } from '../credentials/context';
|
||||
import { RequestedPlatform } from '../platform';
|
||||
import { getExpoConfig } from '../project/expoConfig';
|
||||
import { getProjectAccountName, getProjectIdAsync } from '../project/projectUtils';
|
||||
@@ -31,6 +32,7 @@ export interface BuildContext<T extends Platform> {
|
||||
buildProfile: BuildProfile<T>;
|
||||
buildProfileName: string;
|
||||
clearCache: boolean;
|
||||
credentialsCtx: CredentialsContext;
|
||||
exp: ExpoConfig;
|
||||
local: boolean;
|
||||
nonInteractive: boolean;
|
||||
@@ -69,9 +71,16 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
const accountName = getProjectAccountName(exp, user);
|
||||
const projectName = exp.slug;
|
||||
const projectId = await getProjectIdAsync(exp, { env: buildProfile.env });
|
||||
|
||||
const workflow = await resolveWorkflowAsync(projectDir, platform);
|
||||
const accountId = findAccountByName(user.accounts, accountName)?.id;
|
||||
|
||||
const credentialsCtx = new CredentialsContext({
|
||||
exp,
|
||||
nonInteractive,
|
||||
projectDir,
|
||||
user,
|
||||
});
|
||||
|
||||
const devClientProperties = getDevClientEventProperties({
|
||||
platform,
|
||||
projectDir,
|
||||
@@ -87,11 +96,13 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
...devClientProperties,
|
||||
};
|
||||
Analytics.logEvent(Event.BUILD_COMMAND, trackingCtx);
|
||||
|
||||
return {
|
||||
accountName,
|
||||
buildProfile,
|
||||
buildProfileName,
|
||||
clearCache,
|
||||
credentialsCtx,
|
||||
exp,
|
||||
local,
|
||||
nonInteractive,
|
||||
|
||||
@@ -19,7 +19,11 @@ export async function configureUpdatesAsync(projectDir: string, exp: ExpoConfig)
|
||||
projectDir,
|
||||
xcodeProject
|
||||
);
|
||||
await fs.writeFile(IOSConfig.Paths.getPBXProjectPath(projectDir), xcodeProject.writeSync());
|
||||
await fs.writeFile(
|
||||
IOSConfig.Paths.getPBXProjectPath(projectDir),
|
||||
// eslint-disable-next-line node/no-sync
|
||||
xcodeProject.writeSync()
|
||||
);
|
||||
}
|
||||
|
||||
let expoPlist = await readExpoPlistAsync(projectDir);
|
||||
|
||||
@@ -6,17 +6,17 @@ import { readChannelSafelyAsync } from '../UpdatesModule';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
afterAll(() => {
|
||||
afterAll(async () => {
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.removeSync(os.tmpdir());
|
||||
await fs.remove(os.tmpdir());
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
vol.reset();
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.mkdirpSync(os.tmpdir());
|
||||
await fs.mkdirp(os.tmpdir());
|
||||
});
|
||||
|
||||
const testChannel = 'test-channel';
|
||||
|
||||
@@ -10,29 +10,43 @@ import {
|
||||
bumpVersionAsync,
|
||||
bumpVersionInAppJsonAsync,
|
||||
evaluateTemplateString,
|
||||
getInfoPlistPath,
|
||||
readBuildNumberAsync,
|
||||
readShortVersionAsync,
|
||||
} from '../version';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
afterAll(() => {
|
||||
afterAll(async () => {
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.removeSync(os.tmpdir());
|
||||
await fs.remove(os.tmpdir());
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
vol.reset();
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.mkdirpSync(os.tmpdir());
|
||||
await fs.mkdirp(os.tmpdir());
|
||||
});
|
||||
|
||||
describe(evaluateTemplateString, () => {
|
||||
it('evaluates the template string', () => {
|
||||
it('evaluates the template string when value is a number', () => {
|
||||
expect(evaluateTemplateString('$(BLAH_BLAH)', { BLAH_BLAH: 123 })).toBe('123');
|
||||
});
|
||||
it('evaluates the template string when value is a string', () => {
|
||||
expect(evaluateTemplateString('$(BLAH_BLAH)', { BLAH_BLAH: '123' })).toBe('123');
|
||||
});
|
||||
it('evaluates the template string when template is not the only element', () => {
|
||||
expect(evaluateTemplateString('before$(BLAH_BLAH)after', { BLAH_BLAH: '123' })).toBe(
|
||||
'before123after'
|
||||
);
|
||||
});
|
||||
it('evaluates the template string when template value is double quoted', () => {
|
||||
expect(evaluateTemplateString('before$(BLAH_BLAH)after', { BLAH_BLAH: '"123"' })).toBe(
|
||||
'before123after'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// generic workflow
|
||||
@@ -44,6 +58,7 @@ describe(bumpVersionAsync, () => {
|
||||
bumpStrategy: BumpStrategy.BUILD_NUMBER,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
buildSettings: {},
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
@@ -58,6 +73,30 @@ describe(bumpVersionAsync, () => {
|
||||
expect(infoPlist['CFBundleVersion']).toBe('2');
|
||||
});
|
||||
|
||||
it('bumps expo.ios.buildNumber and CFBundleVersion for non default Info.plist location', async () => {
|
||||
const fakeExp = initGenericProject({ infoPlistName: 'Info2.plist' });
|
||||
|
||||
await bumpVersionAsync({
|
||||
bumpStrategy: BumpStrategy.BUILD_NUMBER,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
buildSettings: {
|
||||
INFOPLIST_FILE: '$(SRCROOT)/myproject/Info2.plist',
|
||||
},
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
const infoPlist = (await readPlistAsync(
|
||||
'/repo/ios/myproject/Info2.plist'
|
||||
)) as IOSConfig.InfoPlist;
|
||||
expect(fakeExp.version).toBe('1.0.0');
|
||||
expect(fakeExp.ios?.buildNumber).toBe('2');
|
||||
expect(appJSON.expo.version).toBe('1.0.0');
|
||||
expect(appJSON.expo.ios.buildNumber).toBe('2');
|
||||
expect(infoPlist['CFBundleShortVersionString']).toBe('1.0.0');
|
||||
expect(infoPlist['CFBundleVersion']).toBe('2');
|
||||
});
|
||||
|
||||
it('bumps expo.version and CFBundleShortVersionString when strategy = BumpStrategy.SHORT_VERSION', async () => {
|
||||
const fakeExp = initGenericProject();
|
||||
|
||||
@@ -65,6 +104,7 @@ describe(bumpVersionAsync, () => {
|
||||
bumpStrategy: BumpStrategy.SHORT_VERSION,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
buildSettings: {},
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
@@ -86,6 +126,7 @@ describe(bumpVersionAsync, () => {
|
||||
bumpStrategy: BumpStrategy.NOOP,
|
||||
projectDir: '/repo',
|
||||
exp: fakeExp,
|
||||
buildSettings: {},
|
||||
});
|
||||
|
||||
const appJSON = await fs.readJSON('/repo/app.json');
|
||||
@@ -197,10 +238,59 @@ describe(readShortVersionAsync, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe(getInfoPlistPath, () => {
|
||||
it('returns default path if INFOPLIST_FILE is not specified', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/testapp/Info.plist': '',
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
const plistPath = getInfoPlistPath('/repo', {});
|
||||
expect(plistPath).toBe('/repo/ios/testapp/Info.plist');
|
||||
});
|
||||
it('returns INFOPLIST_FILE if specified', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/testapp/Info.plist': '',
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
const plistPath = getInfoPlistPath('/repo', { INFOPLIST_FILE: './qwert/NotInfo.plist' });
|
||||
expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist');
|
||||
});
|
||||
it('evaluates SRCROOT in Info.plist', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/testapp/Info.plist': '',
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
const plistPath = getInfoPlistPath('/repo', {
|
||||
INFOPLIST_FILE: '$(SRCROOT)/qwert/NotInfo.plist',
|
||||
});
|
||||
expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist');
|
||||
});
|
||||
it('evaluates BuildSettings in Info.plist', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/testapp/Info.plist': '',
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
const plistPath = getInfoPlistPath('/repo', {
|
||||
INFOPLIST_FILE: '$(SRCROOT)/qwert/$(TARGET_NAME).plist',
|
||||
TARGET_NAME: 'NotInfo',
|
||||
});
|
||||
expect(plistPath).toBe('/repo/ios/qwert/NotInfo.plist');
|
||||
});
|
||||
});
|
||||
|
||||
function initGenericProject({
|
||||
shortVersion = '1.0.0',
|
||||
version = '1',
|
||||
}: { shortVersion?: string; version?: string } = {}): ExpoConfig {
|
||||
infoPlistName = 'Info.plist',
|
||||
}: { shortVersion?: string; version?: string; infoPlistName?: string } = {}): ExpoConfig {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./app.json': JSON.stringify({
|
||||
@@ -211,7 +301,7 @@ function initGenericProject({
|
||||
},
|
||||
},
|
||||
}),
|
||||
'./ios/myproject/Info.plist': `<?xml version="1.0" encoding="UTF-8"?>
|
||||
[`./ios/myproject/${infoPlistName}`]: `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { IOSConfig } from '@expo/config-plugins';
|
||||
import { Ios, Job, Metadata, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { IosCredentials } from '../../credentials/ios/types';
|
||||
import { IosCredentials, Target } from '../../credentials/ios/types';
|
||||
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
|
||||
import { ensureBundleIdentifierIsDefinedForManagedProjectAsync } from '../../project/ios/bundleIdentifier';
|
||||
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
|
||||
@@ -16,6 +17,12 @@ import { ensureIosCredentialsAsync } from './credentials';
|
||||
import { transformJob } from './graphql';
|
||||
import { prepareJobAsync } from './prepareJob';
|
||||
|
||||
interface BuildConfiguration {
|
||||
targetName?: string;
|
||||
buildConfiguration?: string;
|
||||
buildSettings: XCBuildConfiguration['buildSettings'];
|
||||
}
|
||||
|
||||
export async function prepareIosBuildAsync(
|
||||
ctx: BuildContext<Platform.IOS>
|
||||
): Promise<BuildRequestSender> {
|
||||
@@ -43,6 +50,7 @@ export async function prepareIosBuildAsync(
|
||||
},
|
||||
xcodeBuildContext
|
||||
);
|
||||
const buildConfiguration = resolveBuildConfiguration(ctx, targets);
|
||||
|
||||
return await prepareBuildRequestForPlatformAsync({
|
||||
ctx,
|
||||
@@ -54,24 +62,11 @@ export async function prepareIosBuildAsync(
|
||||
projectDir: ctx.projectDir,
|
||||
exp: ctx.exp,
|
||||
buildProfile,
|
||||
buildSettings: buildConfiguration.buildSettings,
|
||||
});
|
||||
},
|
||||
getMetadataContext: (): IosMetadataContext => {
|
||||
if (ctx.workflow === Workflow.MANAGED) {
|
||||
return { buildSettings: {} };
|
||||
}
|
||||
const applicationTarget = findApplicationTarget(targets);
|
||||
const project = IOSConfig.XcodeUtils.getPbxproj(ctx.projectDir);
|
||||
const xcBuildConfiguration = IOSConfig.Target.getXCBuildConfigurationFromPbxproj(project, {
|
||||
targetName: applicationTarget.targetName,
|
||||
buildConfiguration: applicationTarget.buildConfiguration,
|
||||
});
|
||||
const buildSettings = xcBuildConfiguration?.buildSettings ?? {};
|
||||
return {
|
||||
buildSettings,
|
||||
targetName: applicationTarget.targetName,
|
||||
buildConfiguration: applicationTarget.buildConfiguration,
|
||||
};
|
||||
return buildConfiguration;
|
||||
},
|
||||
prepareJobAsync: async (
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
@@ -94,3 +89,24 @@ export async function prepareIosBuildAsync(
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function resolveBuildConfiguration(
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
targets: Target[]
|
||||
): BuildConfiguration {
|
||||
if (ctx.workflow === Workflow.MANAGED) {
|
||||
return { buildSettings: {} };
|
||||
}
|
||||
const applicationTarget = findApplicationTarget(targets);
|
||||
const project = IOSConfig.XcodeUtils.getPbxproj(ctx.projectDir);
|
||||
const xcBuildConfiguration = IOSConfig.Target.getXCBuildConfigurationFromPbxproj(project, {
|
||||
targetName: applicationTarget.targetName,
|
||||
buildConfiguration: applicationTarget.buildConfiguration,
|
||||
});
|
||||
const buildSettings = xcBuildConfiguration?.buildSettings ?? {};
|
||||
return {
|
||||
buildSettings,
|
||||
targetName: applicationTarget.targetName,
|
||||
buildConfiguration: applicationTarget.buildConfiguration,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { IosBuildProfile, VersionAutoIncrement } from '@expo/eas-json';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import Log from '../../log';
|
||||
import {
|
||||
@@ -31,10 +32,12 @@ export async function validateAndSyncProjectConfigurationAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
buildProfile,
|
||||
buildSettings,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
buildProfile: IosBuildProfile;
|
||||
buildSettings: XCBuildConfiguration['buildSettings'];
|
||||
}): Promise<void> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
const { autoIncrement } = buildProfile;
|
||||
@@ -44,7 +47,7 @@ export async function validateAndSyncProjectConfigurationAsync({
|
||||
if (isExpoUpdatesInstalled(projectDir)) {
|
||||
await syncUpdatesConfigurationAsync(projectDir, exp);
|
||||
}
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, buildSettings });
|
||||
} else {
|
||||
await bumpVersionInAppJsonAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
|
||||
import { createCredentialsContextAsync } from '../../credentials/context';
|
||||
import IosCredentialsProvider from '../../credentials/ios/IosCredentialsProvider';
|
||||
import { getAppFromContext } from '../../credentials/ios/actions/BuildCredentialsUtils';
|
||||
import { resolveEntitlementsJsonAsync } from '../../credentials/ios/appstore/entitlements';
|
||||
@@ -17,13 +16,8 @@ export async function ensureIosCredentialsAsync(
|
||||
return;
|
||||
}
|
||||
|
||||
const credentialsCtx = await createCredentialsContextAsync(buildCtx.projectDir, {
|
||||
exp: buildCtx.exp,
|
||||
nonInteractive: buildCtx.nonInteractive,
|
||||
});
|
||||
|
||||
const provider = new IosCredentialsProvider(credentialsCtx, {
|
||||
app: getAppFromContext(credentialsCtx),
|
||||
const provider = new IosCredentialsProvider(buildCtx.credentialsCtx, {
|
||||
app: getAppFromContext(buildCtx.credentialsCtx),
|
||||
targets,
|
||||
iosCapabilitiesOptions: {
|
||||
entitlements: await resolveEntitlementsJsonAsync(buildCtx.projectDir, buildCtx.workflow),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IOSConfig } from '@expo/config-plugins';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
import path from 'path';
|
||||
import semver from 'semver';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
@@ -22,19 +23,21 @@ export async function bumpVersionAsync({
|
||||
bumpStrategy,
|
||||
projectDir,
|
||||
exp,
|
||||
buildSettings,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
bumpStrategy: BumpStrategy;
|
||||
buildSettings: XCBuildConfiguration['buildSettings'];
|
||||
}): Promise<void> {
|
||||
if (bumpStrategy === BumpStrategy.NOOP) {
|
||||
return;
|
||||
}
|
||||
ensureStaticConfigExists(projectDir);
|
||||
const infoPlist = await readInfoPlistAsync(projectDir);
|
||||
const infoPlist = await readInfoPlistAsync(projectDir, buildSettings);
|
||||
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
|
||||
Log.log('Updated versions in app.json');
|
||||
await writeVersionsToInfoPlistAsync({ projectDir, exp, infoPlist });
|
||||
await writeVersionsToInfoPlistAsync({ projectDir, exp, infoPlist, buildSettings });
|
||||
Log.log('Synchronized versions with Info.plist');
|
||||
}
|
||||
|
||||
@@ -110,7 +113,7 @@ export async function readShortVersionAsync(
|
||||
): Promise<string | undefined> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const infoPlist = await readInfoPlistAsync(projectDir);
|
||||
const infoPlist = await readInfoPlistAsync(projectDir, buildSettings);
|
||||
return (
|
||||
infoPlist.CFBundleShortVersionString &&
|
||||
evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings)
|
||||
@@ -127,7 +130,7 @@ export async function readBuildNumberAsync(
|
||||
): Promise<string | undefined> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const infoPlist = await readInfoPlistAsync(projectDir);
|
||||
const infoPlist = await readInfoPlistAsync(projectDir, buildSettings);
|
||||
return (
|
||||
infoPlist.CFBundleVersion && evaluateTemplateString(infoPlist.CFBundleVersion, buildSettings)
|
||||
);
|
||||
@@ -155,30 +158,56 @@ async function writeVersionsToInfoPlistAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
infoPlist,
|
||||
buildSettings,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
infoPlist: IOSConfig.InfoPlist;
|
||||
buildSettings: XCBuildConfiguration['buildSettings'];
|
||||
}): Promise<IOSConfig.InfoPlist> {
|
||||
let updatedInfoPlist = IOSConfig.Version.setVersion(exp, infoPlist);
|
||||
updatedInfoPlist = IOSConfig.Version.setBuildNumber(exp, updatedInfoPlist);
|
||||
await writeInfoPlistAsync({ projectDir, infoPlist: updatedInfoPlist });
|
||||
await writeInfoPlistAsync({ projectDir, infoPlist: updatedInfoPlist, buildSettings });
|
||||
return updatedInfoPlist;
|
||||
}
|
||||
|
||||
async function readInfoPlistAsync(projectDir: string): Promise<IOSConfig.InfoPlist> {
|
||||
const infoPlistPath = IOSConfig.Paths.getInfoPlistPath(projectDir);
|
||||
export function getInfoPlistPath(
|
||||
projectDir: string,
|
||||
buildSettings: XCBuildConfiguration['buildSettings']
|
||||
): string {
|
||||
if (buildSettings.INFOPLIST_FILE) {
|
||||
const infoPlistFile = buildSettings.INFOPLIST_FILE.startsWith('"')
|
||||
? buildSettings.INFOPLIST_FILE.slice(1, -1)
|
||||
: buildSettings.INFOPLIST_FILE;
|
||||
const iosDir = path.join(projectDir, 'ios');
|
||||
const plistPath = evaluateTemplateString(infoPlistFile, {
|
||||
...buildSettings,
|
||||
SRCROOT: iosDir,
|
||||
});
|
||||
return path.isAbsolute(plistPath) ? plistPath : path.resolve(iosDir, plistPath);
|
||||
} else {
|
||||
return IOSConfig.Paths.getInfoPlistPath(projectDir);
|
||||
}
|
||||
}
|
||||
|
||||
async function readInfoPlistAsync(
|
||||
projectDir: string,
|
||||
buildSettings: XCBuildConfiguration['buildSettings']
|
||||
): Promise<IOSConfig.InfoPlist> {
|
||||
const infoPlistPath = getInfoPlistPath(projectDir, buildSettings);
|
||||
return (await readPlistAsync(infoPlistPath)) as IOSConfig.InfoPlist;
|
||||
}
|
||||
|
||||
async function writeInfoPlistAsync({
|
||||
projectDir,
|
||||
infoPlist,
|
||||
buildSettings,
|
||||
}: {
|
||||
projectDir: string;
|
||||
infoPlist: IOSConfig.InfoPlist;
|
||||
buildSettings: XCBuildConfiguration['buildSettings'];
|
||||
}): Promise<void> {
|
||||
const infoPlistPath = IOSConfig.Paths.getInfoPlistPath(projectDir);
|
||||
const infoPlistPath = getInfoPlistPath(projectDir, buildSettings);
|
||||
await writePlistAsync(infoPlistPath, infoPlist);
|
||||
}
|
||||
|
||||
@@ -190,5 +219,12 @@ function ensureStaticConfigExists(projectDir: string): void {
|
||||
}
|
||||
|
||||
export function evaluateTemplateString(s: string, vars: Record<string, any>): string {
|
||||
return s.replace(/\$\((\w+)\)/g, (match, key) => (vars.hasOwnProperty(key) ? vars[key] : match));
|
||||
return s.replace(/\$\((\w+)\)/g, (match, key) => {
|
||||
if (vars.hasOwnProperty(key)) {
|
||||
const value = String(vars[key]);
|
||||
return value.startsWith('"') ? value.slice(1, -1) : value;
|
||||
} else {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asMock } from '../../__tests__/utils';
|
||||
import { flushAsync, initAsync, logEvent } from '../../analytics';
|
||||
import { jester as mockJester } from '../../credentials/__tests__/fixtures-constants';
|
||||
import { getUserAsync } from '../../user/User';
|
||||
@@ -31,11 +32,15 @@ afterAll(() => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
(getUserAsync as jest.Mock).mockReset().mockImplementation(() => mockJester);
|
||||
(ensureLoggedInAsync as jest.Mock).mockReset().mockImplementation(() => mockJester);
|
||||
(initAsync as jest.Mock).mockReset();
|
||||
(flushAsync as jest.Mock).mockReset();
|
||||
(logEvent as jest.Mock).mockReset();
|
||||
asMock(getUserAsync)
|
||||
.mockReset()
|
||||
.mockImplementation(() => mockJester);
|
||||
asMock(ensureLoggedInAsync)
|
||||
.mockReset()
|
||||
.mockImplementation(() => mockJester);
|
||||
asMock(initAsync).mockReset();
|
||||
asMock(flushAsync).mockReset();
|
||||
asMock(logEvent).mockReset();
|
||||
});
|
||||
|
||||
describe(EasCommand.name, () => {
|
||||
@@ -54,7 +59,7 @@ describe(EasCommand.name, () => {
|
||||
}, 15_000);
|
||||
|
||||
it('ensures the user data is read from cache', async () => {
|
||||
(TestEasCommand.prototype.authValue as jest.Mock).mockImplementationOnce(() => false);
|
||||
asMock(TestEasCommand.prototype.authValue).mockImplementationOnce(() => false);
|
||||
|
||||
await TestEasCommand.run();
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
PublishPlatform,
|
||||
buildBundlesAsync,
|
||||
buildUnsortedUpdateInfoGroupAsync,
|
||||
collectAssets,
|
||||
collectAssetsAsync,
|
||||
uploadAssetsAsync,
|
||||
} from '../../project/publish';
|
||||
import { promptAsync, selectAsync } from '../../prompts';
|
||||
@@ -290,7 +290,7 @@ export default class BranchPublish extends EasCommand {
|
||||
const assetSpinner = ora('Uploading assets...').start();
|
||||
try {
|
||||
const platforms = platformFlag === 'all' ? defaultPublishPlatforms : [platformFlag];
|
||||
const assets = collectAssets({ inputDir: inputDir!, platforms });
|
||||
const assets = await collectAssetsAsync({ inputDir: inputDir!, platforms });
|
||||
await uploadAssetsAsync(assets);
|
||||
unsortedUpdateInfoGroups = await buildUnsortedUpdateInfoGroupAsync(assets, exp);
|
||||
assetSpinner.succeed('Uploaded assets!');
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { flags } from '@oclif/command';
|
||||
import { error } from '@oclif/errors';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { prepareAndroidBuildAsync } from '../../build/android/build';
|
||||
import { BuildRequestSender, waitForBuildEndAsync } from '../../build/build';
|
||||
@@ -18,7 +19,13 @@ import { ensureExpoDevClientInstalledForDevClientBuildsAsync } from '../../build
|
||||
import { printBuildResults, printLogsUrls } from '../../build/utils/printBuildInfo';
|
||||
import { ensureRepoIsCleanAsync } from '../../build/utils/repository';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { BuildFragment, BuildStatus, SubmissionFragment } from '../../graphql/generated';
|
||||
import { CredentialsContext } from '../../credentials/context';
|
||||
import {
|
||||
AppPlatform,
|
||||
BuildFragment,
|
||||
BuildStatus,
|
||||
SubmissionFragment,
|
||||
} from '../../graphql/generated';
|
||||
import { toAppPlatform, toPlatform } from '../../graphql/types/AppPlatform';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
@@ -35,7 +42,7 @@ import {
|
||||
import { validateMetroConfigForManagedWorkflowAsync } from '../../project/metroConfig';
|
||||
import { findProjectRootAsync } from '../../project/projectUtils';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { createSubmissionContext } from '../../submit/context';
|
||||
import { createSubmissionContextAsync } from '../../submit/context';
|
||||
import {
|
||||
submitAsync,
|
||||
waitToCompleteAsync as waitForSubmissionsToCompleteAsync,
|
||||
@@ -153,8 +160,9 @@ export default class Build extends EasCommand {
|
||||
});
|
||||
|
||||
const startedBuilds: BuildFragment[] = [];
|
||||
const buildCtxByPlatform: { [p in AppPlatform]?: BuildContext<Platform> } = {};
|
||||
for (const platform of platforms) {
|
||||
const maybeBuild = await this.prepareAndStartBuildAsync({
|
||||
const { build: maybeBuild, buildCtx } = await this.prepareAndStartBuildAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
flags,
|
||||
@@ -163,6 +171,7 @@ export default class Build extends EasCommand {
|
||||
if (maybeBuild) {
|
||||
startedBuilds.push(maybeBuild);
|
||||
}
|
||||
buildCtxByPlatform[toAppPlatform(platform)] = buildCtx;
|
||||
}
|
||||
if (flags.local) {
|
||||
return;
|
||||
@@ -176,10 +185,11 @@ export default class Build extends EasCommand {
|
||||
if (flags.autoSubmit) {
|
||||
for (const build of startedBuilds) {
|
||||
const submission = await this.prepareAndStartSubmissionAsync({
|
||||
projectDir,
|
||||
build,
|
||||
credentialsCtx: nullthrows(buildCtxByPlatform[build.platform]?.credentialsCtx),
|
||||
flags,
|
||||
moreBuilds: startedBuilds.length > 1,
|
||||
projectDir,
|
||||
});
|
||||
submissions.push(submission);
|
||||
}
|
||||
@@ -253,17 +263,17 @@ export default class Build extends EasCommand {
|
||||
};
|
||||
}
|
||||
|
||||
private async prepareAndStartBuildAsync({
|
||||
private async prepareAndStartBuildAsync<T extends Platform>({
|
||||
projectDir,
|
||||
platform,
|
||||
flags,
|
||||
moreBuilds,
|
||||
}: {
|
||||
projectDir: string;
|
||||
platform: Platform;
|
||||
platform: T;
|
||||
flags: BuildFlags;
|
||||
moreBuilds: boolean;
|
||||
}): Promise<BuildFragment | undefined> {
|
||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<T> }> {
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const buildProfile = await easJsonReader.readBuildProfileAsync(platform, flags.profile);
|
||||
|
||||
@@ -297,7 +307,11 @@ export default class Build extends EasCommand {
|
||||
error(EAS_UNAVAILABLE_MESSAGE, { exit: 1 });
|
||||
}
|
||||
|
||||
return await this.startBuildAsync(buildCtx);
|
||||
const build = await this.startBuildAsync(buildCtx);
|
||||
return {
|
||||
build,
|
||||
buildCtx,
|
||||
};
|
||||
}
|
||||
|
||||
private async startBuildAsync(ctx: BuildContext<Platform>): Promise<BuildFragment | undefined> {
|
||||
@@ -311,21 +325,23 @@ export default class Build extends EasCommand {
|
||||
}
|
||||
|
||||
private async prepareAndStartSubmissionAsync({
|
||||
projectDir,
|
||||
build,
|
||||
credentialsCtx,
|
||||
flags,
|
||||
moreBuilds,
|
||||
projectDir,
|
||||
}: {
|
||||
projectDir: string;
|
||||
build: BuildFragment;
|
||||
credentialsCtx: CredentialsContext;
|
||||
flags: BuildFlags;
|
||||
moreBuilds: boolean;
|
||||
projectDir: string;
|
||||
}): Promise<SubmissionFragment> {
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
const platform = toPlatform(build.platform);
|
||||
const buildProfile = await easJsonReader.readBuildProfileAsync(platform, flags.profile);
|
||||
const submitProfile = await easJsonReader.readSubmitProfileAsync(platform, flags.submitProfile);
|
||||
const submissionCtx = createSubmissionContext({
|
||||
const submissionCtx = await createSubmissionContextAsync({
|
||||
platform,
|
||||
projectDir,
|
||||
projectId: build.project.id,
|
||||
@@ -333,6 +349,7 @@ export default class Build extends EasCommand {
|
||||
archiveFlags: { id: build.id },
|
||||
nonInteractive: flags.nonInteractive,
|
||||
env: buildProfile.env,
|
||||
credentialsCtx,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import EasCommand from '../commandUtils/EasCommand';
|
||||
import { createCredentialsContextAsync } from '../credentials/context';
|
||||
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 = await createCredentialsContextAsync(process.cwd(), {});
|
||||
const ctx = new CredentialsContext({
|
||||
projectDir: process.cwd(),
|
||||
user: await ensureLoggedInAsync(),
|
||||
});
|
||||
await new SelectPlatform().runAsync(ctx);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from '../platform';
|
||||
import { isEasEnabledForProjectAsync, warnEasUnavailable } from '../project/isEasEnabledForProject';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../project/projectUtils';
|
||||
import { SubmitArchiveFlags, createSubmissionContext } from '../submit/context';
|
||||
import { SubmitArchiveFlags, createSubmissionContextAsync } from '../submit/context';
|
||||
import { submitAsync, waitToCompleteAsync } from '../submit/submit';
|
||||
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
|
||||
|
||||
@@ -108,7 +108,7 @@ See how to configure submits with eas.json: ${learnMore('https://docs.expo.dev/s
|
||||
const submissions: SubmissionFragment[] = [];
|
||||
for (const platform of platforms) {
|
||||
const profile = await easJsonReader.readSubmitProfileAsync(platform, flags.profile);
|
||||
const ctx = createSubmissionContext({
|
||||
const ctx = await createSubmissionContextAsync({
|
||||
platform,
|
||||
projectDir,
|
||||
projectId,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
AndroidKeystoreType,
|
||||
AppFragment,
|
||||
CommonAndroidAppCredentialsFragment,
|
||||
GoogleServiceAccountKeyFragment,
|
||||
} from '../../graphql/generated';
|
||||
import * as AndroidGraphqlClient from '../android/api/GraphqlClient';
|
||||
import { Keystore } from '../android/credentials';
|
||||
@@ -68,6 +69,16 @@ export const testLegacyAndroidFcmFragment: AndroidFcmFragment = {
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
export const testGoogleServiceAccountKeyFragment: GoogleServiceAccountKeyFragment = {
|
||||
id: 'test-id',
|
||||
projectIdentifier: 'sdf.sdf.sdf',
|
||||
privateKeyIdentifier: 'test-private-key-identifier',
|
||||
clientEmail: '[email protected]',
|
||||
clientIdentifier: 'test-client-identifier',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
export const testJksAndroidKeystoreFragment: AndroidKeystoreFragment = {
|
||||
id: 'test-id',
|
||||
type: AndroidKeystoreType.Jks,
|
||||
@@ -113,6 +124,7 @@ export const testAndroidAppCredentialsFragment: CommonAndroidAppCredentialsFragm
|
||||
isLegacy: false,
|
||||
app: testAppFragment,
|
||||
androidFcm: testLegacyAndroidFcmFragment,
|
||||
googleServiceAccountKeyForSubmissions: testGoogleServiceAccountKeyFragment,
|
||||
androidAppBuildCredentialsList: [testLegacyAndroidBuildCredentialsFragment],
|
||||
};
|
||||
|
||||
@@ -130,6 +142,8 @@ export function getNewAndroidApiMock(): { [key in keyof typeof AndroidGraphqlCli
|
||||
getAndroidAppBuildCredentialsByNameAsync: jest.fn(),
|
||||
createOrUpdateAndroidAppBuildCredentialsByNameAsync: jest.fn(),
|
||||
createKeystoreAsync: jest.fn(),
|
||||
createGoogleServiceAccountKeyAsync: jest.fn(),
|
||||
getGoogleServiceAccountKeysForAccountAsync: jest.fn(),
|
||||
createFcmAsync: jest.fn(),
|
||||
deleteKeystoreAsync: jest.fn(),
|
||||
deleteFcmAsync: jest.fn(),
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import merge from 'ts-deepmerge';
|
||||
|
||||
import { Context } from '../context';
|
||||
import { CredentialsContext } from '../context';
|
||||
import { getNewAndroidApiMock } from './fixtures-android';
|
||||
import { getAppstoreMock } from './fixtures-appstore';
|
||||
import { testAppJson, testUsername } from './fixtures-constants';
|
||||
import { getNewIosApiMock } from './fixtures-ios';
|
||||
|
||||
export function createCtxMock(mockOverride: Record<string, any> = {}): Context {
|
||||
export function createCtxMock(mockOverride: Record<string, any> = {}): CredentialsContext {
|
||||
const defaultMock = {
|
||||
ios: getNewIosApiMock(),
|
||||
android: getNewAndroidApiMock(),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CredentialsSource } from '@expo/eas-json';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { AndroidAppBuildCredentialsFragment } from '../../graphql/generated';
|
||||
import { Context } from '../context';
|
||||
import { CredentialsContext } from '../context';
|
||||
import * as credentialsJsonReader from '../credentialsJson/read';
|
||||
import { SetupBuildCredentials } from './actions/SetupBuildCredentials';
|
||||
import { AppLookupParams } from './api/GraphqlClient';
|
||||
@@ -20,7 +20,7 @@ interface Options {
|
||||
export default class AndroidCredentialsProvider {
|
||||
public readonly platform = Platform.ANDROID;
|
||||
|
||||
constructor(private ctx: Context, private options: Options) {}
|
||||
constructor(private ctx: CredentialsContext, private options: Options) {}
|
||||
|
||||
public async getCredentialsAsync(
|
||||
src: CredentialsSource.LOCAL | CredentialsSource.REMOTE
|
||||
|
||||
@@ -3,14 +3,14 @@ import {
|
||||
CommonAndroidAppCredentialsFragment,
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
|
||||
export class AssignFcm {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
public async runAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
fcm: AndroidFcmFragment
|
||||
): Promise<CommonAndroidAppCredentialsFragment> {
|
||||
const appCredentials =
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
CommonAndroidAppCredentialsFragment,
|
||||
GoogleServiceAccountKeyFragment,
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
|
||||
export class AssignGoogleServiceAccountKey {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
public async runAsync(
|
||||
ctx: CredentialsContext,
|
||||
googleServiceAccountKey: GoogleServiceAccountKeyFragment
|
||||
): Promise<CommonAndroidAppCredentialsFragment> {
|
||||
const appCredentials =
|
||||
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(this.app);
|
||||
const updatedAppCredentials = await ctx.android.updateAndroidAppCredentialsAsync(
|
||||
appCredentials,
|
||||
{
|
||||
googleServiceAccountKeyForSubmissionsId: googleServiceAccountKey.id,
|
||||
}
|
||||
);
|
||||
Log.succeed(
|
||||
`Google Service Account Key assigned to ${this.app.androidApplicationIdentifier} for submissions`
|
||||
);
|
||||
return updatedAppCredentials;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { GradleBuildContext } from '../../../project/android/gradle';
|
||||
import { getProjectAccountName, getProjectConfigDescription } from '../../../project/projectUtils';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { findAccountByName } from '../../../user/Account';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
|
||||
/**
|
||||
@@ -16,7 +16,7 @@ import { AppLookupParams } from '../api/GraphqlClient';
|
||||
* EAS credentials set up yet
|
||||
*/
|
||||
export async function canCopyLegacyCredentialsAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
app: AppLookupParams
|
||||
): Promise<boolean> {
|
||||
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(app);
|
||||
@@ -30,7 +30,7 @@ export async function canCopyLegacyCredentialsAsync(
|
||||
}
|
||||
|
||||
export async function promptUserAndCopyLegacyCredentialsAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
app: AppLookupParams
|
||||
): Promise<void> {
|
||||
assert(
|
||||
@@ -85,7 +85,7 @@ export async function promptUserAndCopyLegacyCredentialsAsync(
|
||||
}
|
||||
|
||||
export async function getAppLookupParamsFromContextAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
gradleContext?: GradleBuildContext
|
||||
): Promise<AppLookupParams> {
|
||||
ctx.ensureProjectContext();
|
||||
@@ -113,7 +113,7 @@ export async function getAppLookupParamsFromContextAsync(
|
||||
}
|
||||
|
||||
export async function createOrUpdateDefaultAndroidAppBuildCredentialsAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
appLookupParams: AppLookupParams,
|
||||
{
|
||||
androidKeystoreId,
|
||||
|
||||
@@ -2,12 +2,12 @@ import { AndroidFcmFragment, AndroidFcmVersion } from '../../../graphql/generate
|
||||
import Log from '../../../log';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
|
||||
export class CreateFcm {
|
||||
constructor(private account: Account) {}
|
||||
|
||||
public async runAsync(ctx: Context): Promise<AndroidFcmFragment> {
|
||||
public async runAsync(ctx: CredentialsContext): Promise<AndroidFcmFragment> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`FCM API Key cannot be uploaded in non-interactive mode.`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { GoogleServiceAccountKeyFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { GoogleServiceAccountKey } from '../credentials';
|
||||
import { readAndValidateServiceAccountKey } from '../utils/googleServiceAccountKey';
|
||||
|
||||
export class CreateGoogleServiceAccountKey {
|
||||
constructor(private account: Account) {}
|
||||
|
||||
public async runAsync(ctx: CredentialsContext): Promise<GoogleServiceAccountKeyFragment> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`New Google Service Account Key cannot be created in non-interactive mode.`);
|
||||
}
|
||||
const jsonKeyObject = await this.provideAsync();
|
||||
const gsaKeyFragment = await ctx.android.createGoogleServiceAccountKeyAsync(
|
||||
this.account,
|
||||
jsonKeyObject
|
||||
);
|
||||
Log.succeed('Uploaded Google Service Account Key.');
|
||||
return gsaKeyFragment;
|
||||
}
|
||||
|
||||
private async provideAsync(): Promise<GoogleServiceAccountKey> {
|
||||
try {
|
||||
const { keyJsonPath } = await promptAsync([
|
||||
{
|
||||
type: 'text',
|
||||
name: 'keyJsonPath',
|
||||
message: 'Path to Google Service Account Key JSON file:',
|
||||
validate: (value: string) => value.length > 0 || "Path can't be empty",
|
||||
},
|
||||
]);
|
||||
return readAndValidateServiceAccountKey(keyJsonPath);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return await this.provideAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { AndroidKeystoreFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { getProjectIdAsync } from '../../../project/projectUtils';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { askForUserProvidedAsync } from '../../utils/promptForCredentials';
|
||||
import { KeystoreWithType, keystoreSchema } from '../credentials';
|
||||
import { generateRandomKeystoreAsync } from '../utils/keystore';
|
||||
@@ -11,7 +11,7 @@ import { getKeystoreWithType, validateKeystore } from '../utils/keystoreNew';
|
||||
export class CreateKeystore {
|
||||
constructor(private account: Account) {}
|
||||
|
||||
public async runAsync(ctx: Context): Promise<AndroidKeystoreFragment> {
|
||||
public async runAsync(ctx: CredentialsContext): Promise<AndroidKeystoreFragment> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`New keystore cannot be created in non-interactive mode.`);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { AndroidAppBuildCredentialsFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import { maybeRenameExistingFileAsync } from '../../../utils/files';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams, formatProjectFullName } from '../api/GraphqlClient';
|
||||
import { displayAndroidKeystore } from '../utils/printCredentials';
|
||||
|
||||
@@ -19,7 +19,7 @@ export class DownloadKeystore {
|
||||
constructor(private options: DownloadKeystoreOptions) {}
|
||||
|
||||
public async runAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
buildCredentials: AndroidAppBuildCredentialsFragment
|
||||
): Promise<void> {
|
||||
const keystore = buildCredentials.androidKeystore;
|
||||
@@ -65,7 +65,7 @@ export class BackupKeystore {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
public async runAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
buildCredentials: AndroidAppBuildCredentialsFragment
|
||||
): Promise<void> {
|
||||
if (!buildCredentials.androidKeystore) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams, formatProjectFullName } from '../api/GraphqlClient';
|
||||
|
||||
export class RemoveFcm {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<void> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(
|
||||
"Deleting an FCM Api Key is a destructive operation. Start the CLI without the '--non-interactive' flag to delete the credentials."
|
||||
|
||||
@@ -3,7 +3,7 @@ import chalk from 'chalk';
|
||||
import { AndroidAppBuildCredentialsFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { BackupKeystore } from './DownloadKeystore';
|
||||
|
||||
@@ -11,7 +11,7 @@ export class RemoveKeystore {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
buildCredentials: AndroidAppBuildCredentialsFragment
|
||||
): Promise<void> {
|
||||
if (ctx.nonInteractive) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { ora } from '../../../ora';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import {
|
||||
@@ -28,7 +28,7 @@ interface Options {
|
||||
export class SetupBuildCredentials {
|
||||
constructor(private options: Options) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<AndroidAppBuildCredentialsFragment> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<AndroidAppBuildCredentialsFragment> {
|
||||
const { app, name: maybeName } = this.options;
|
||||
|
||||
if (!ctx.nonInteractive) {
|
||||
@@ -62,7 +62,7 @@ export class SetupBuildCredentials {
|
||||
name,
|
||||
keystore,
|
||||
}: {
|
||||
ctx: Context;
|
||||
ctx: CredentialsContext;
|
||||
app: AppLookupParams;
|
||||
name?: string;
|
||||
keystore: AndroidKeystoreFragment;
|
||||
@@ -82,7 +82,7 @@ export class SetupBuildCredentials {
|
||||
app,
|
||||
name,
|
||||
}: {
|
||||
ctx: Context;
|
||||
ctx: CredentialsContext;
|
||||
app: AppLookupParams;
|
||||
name?: string;
|
||||
}): Promise<AndroidAppBuildCredentialsFragment | null> {
|
||||
@@ -108,7 +108,7 @@ export class SetupBuildCredentials {
|
||||
app,
|
||||
name,
|
||||
}: {
|
||||
ctx: Context;
|
||||
ctx: CredentialsContext;
|
||||
app: AppLookupParams;
|
||||
name: string;
|
||||
}): Promise<AndroidAppBuildCredentialsFragment | null> {
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import {
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { readAndroidCredentialsAsync } from '../../credentialsJson/read';
|
||||
import {
|
||||
SelectAndroidBuildCredentials,
|
||||
@@ -17,7 +17,7 @@ import { BackupKeystore } from './DownloadKeystore';
|
||||
export class SetupBuildCredentialsFromCredentialsJson {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<AndroidAppBuildCredentialsFragment | null> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<AndroidAppBuildCredentialsFragment | null> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(
|
||||
'Setting up build credentials from credentials.json is only available in interactive mode'
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { AndroidAppBuildCredentialsFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { updateAndroidCredentialsAsync } from '../../credentialsJson/update';
|
||||
|
||||
export class UpdateCredentialsJson {
|
||||
async runAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
buildCredentials: AndroidAppBuildCredentialsFragment
|
||||
): Promise<void> {
|
||||
Log.log('Updating Android credentials in credentials.json');
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { GoogleServiceAccountKeyFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { fromNow } from '../../../utils/date';
|
||||
import { CredentialsContext } from '../../context';
|
||||
|
||||
export class UseExistingGoogleServiceAccountKey {
|
||||
constructor(private account: Account) {}
|
||||
|
||||
public async runAsync(ctx: CredentialsContext): Promise<GoogleServiceAccountKeyFragment | null> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(
|
||||
`Existing Google Service Account Key cannot be chosen in non-interactive mode.`
|
||||
);
|
||||
}
|
||||
const gsaKeyFragments = await ctx.android.getGoogleServiceAccountKeysForAccountAsync(
|
||||
this.account
|
||||
);
|
||||
if (gsaKeyFragments.length === 0) {
|
||||
Log.error("There aren't any Google Service Account Keys associated with your account.");
|
||||
return null;
|
||||
}
|
||||
return await this.selectGoogleServiceAccountKeyAsync(gsaKeyFragments);
|
||||
}
|
||||
|
||||
private async selectGoogleServiceAccountKeyAsync(
|
||||
keys: GoogleServiceAccountKeyFragment[]
|
||||
): Promise<GoogleServiceAccountKeyFragment | null> {
|
||||
const sortedKeys = this.sortGoogleServiceAccountKeysByUpdatedAtDesc(keys);
|
||||
const { chosenKey } = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'chosenKey',
|
||||
message: 'Select a Google Service Account Key:',
|
||||
choices: sortedKeys.map(key => ({
|
||||
title: this.formatGoogleServiceAccountKey(key),
|
||||
value: key,
|
||||
})),
|
||||
});
|
||||
return chosenKey;
|
||||
}
|
||||
|
||||
private sortGoogleServiceAccountKeysByUpdatedAtDesc(
|
||||
keys: GoogleServiceAccountKeyFragment[]
|
||||
): GoogleServiceAccountKeyFragment[] {
|
||||
return keys.sort(
|
||||
(keyA, keyB) =>
|
||||
new Date(keyB.updatedAt).getMilliseconds() - new Date(keyA.updatedAt).getMilliseconds()
|
||||
);
|
||||
}
|
||||
|
||||
private formatGoogleServiceAccountKey({
|
||||
projectIdentifier,
|
||||
privateKeyIdentifier,
|
||||
clientEmail,
|
||||
clientIdentifier,
|
||||
updatedAt,
|
||||
}: GoogleServiceAccountKeyFragment): string {
|
||||
let line: string = '';
|
||||
line += `Client Email: ${clientEmail}, Project Id: ${projectIdentifier}`;
|
||||
line += chalk.gray(
|
||||
`\n Client Id: ${clientIdentifier}, Private Key Id: ${privateKeyIdentifier}`
|
||||
);
|
||||
line += chalk.gray(`\n Updated: ${fromNow(new Date(updatedAt))} ago,`);
|
||||
return line;
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,9 @@ describe(AssignFcm, () => {
|
||||
|
||||
// expect app credentials to be fetched/created, then updated
|
||||
expect(
|
||||
ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync as any
|
||||
ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.updateAndroidAppCredentialsAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.updateAndroidAppCredentialsAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('works in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { testGoogleServiceAccountKeyFragment } from '../../../__tests__/fixtures-android';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { AssignGoogleServiceAccountKey } from '../AssignGoogleServiceAccountKey';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
|
||||
describe(AssignGoogleServiceAccountKey, () => {
|
||||
it('assigns a Google Service Account key in Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const assignGoogleServiceAccountKeyAction = new AssignGoogleServiceAccountKey(appLookupParams);
|
||||
await assignGoogleServiceAccountKeyAction.runAsync(ctx, testGoogleServiceAccountKeyFragment);
|
||||
|
||||
// expect app credentials to be fetched/created, then updated
|
||||
expect(
|
||||
ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.updateAndroidAppCredentialsAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('works in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const assignGoogleServiceAccountKeyAction = new AssignGoogleServiceAccountKey(appLookupParams);
|
||||
|
||||
// dont fail if users are running in non-interactive mode
|
||||
await expect(
|
||||
assignGoogleServiceAccountKeyAction.runAsync(ctx, testGoogleServiceAccountKeyFragment)
|
||||
).resolves.not.toThrowError();
|
||||
});
|
||||
});
|
||||
+8
-7
@@ -1,3 +1,4 @@
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { confirmAsync, promptAsync } from '../../../../prompts';
|
||||
import {
|
||||
getNewAndroidApiMock,
|
||||
@@ -16,7 +17,7 @@ import {
|
||||
|
||||
jest.mock('../../../../ora');
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe('BuildCredentialsUtils', () => {
|
||||
describe(canCopyLegacyCredentialsAsync, () => {
|
||||
@@ -83,7 +84,7 @@ describe('BuildCredentialsUtils', () => {
|
||||
});
|
||||
describe(promptUserAndCopyLegacyCredentialsAsync, () => {
|
||||
it('copies all legacy credentials to EAS if the user is eligible', async () => {
|
||||
(promptAsync as jest.Mock).mockImplementation(() => ({ providedName: 'test-provided-name' }));
|
||||
asMock(promptAsync).mockImplementation(() => ({ providedName: 'test-provided-name' }));
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
android: {
|
||||
@@ -103,12 +104,12 @@ describe('BuildCredentialsUtils', () => {
|
||||
await promptUserAndCopyLegacyCredentialsAsync(ctx, appLookupParams);
|
||||
|
||||
expect(
|
||||
ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync as any
|
||||
ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.createFcmAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.updateAndroidAppCredentialsAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.createKeystoreAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.createAndroidAppBuildCredentialsAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.createFcmAsync).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.updateAndroidAppCredentialsAsync).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.createKeystoreAsync).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.createAndroidAppBuildCredentialsAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('errors in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { promptAsync } from '../../../../prompts';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { CreateFcm } from '../CreateFcm';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
(promptAsync as jest.Mock).mockImplementation(() => ({ fcmApiKey: 'blah' }));
|
||||
asMock(promptAsync).mockImplementation(() => ({ fcmApiKey: 'blah' }));
|
||||
|
||||
describe(CreateFcm, () => {
|
||||
it('creates an fcm api key in Interactive Mode', async () => {
|
||||
@@ -14,7 +15,7 @@ describe(CreateFcm, () => {
|
||||
await createFcmAction.runAsync(ctx);
|
||||
|
||||
// expect fcm api key to be created on expo servers
|
||||
expect(ctx.android.createFcmAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.createFcmAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('errors in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { vol } from 'memfs';
|
||||
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { promptAsync } from '../../../../prompts';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { CreateGoogleServiceAccountKey } from '../CreateGoogleServiceAccountKey';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('fs');
|
||||
asMock(promptAsync).mockImplementation(() => ({
|
||||
keyJsonPath: '/google-service-account-key.json',
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
describe(CreateGoogleServiceAccountKey, () => {
|
||||
it('creates a Google Service Account Key in Interactive Mode', async () => {
|
||||
vol.fromJSON({
|
||||
'/google-service-account-key.json': JSON.stringify({ private_key: 'super secret' }),
|
||||
});
|
||||
|
||||
const ctx = createCtxMock({ nonInteractive: false });
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const createGsaKeyAction = new CreateGoogleServiceAccountKey(appLookupParams.account);
|
||||
await createGsaKeyAction.runAsync(ctx);
|
||||
|
||||
// expect fcm api key to be created on expo servers
|
||||
expect(ctx.android.createGoogleServiceAccountKeyAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('errors in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const createGsaKeyAction = new CreateGoogleServiceAccountKey(appLookupParams.account);
|
||||
|
||||
// fail if users are running in non-interactive mode
|
||||
await expect(createGsaKeyAction.runAsync(ctx)).rejects.toThrowError();
|
||||
});
|
||||
});
|
||||
@@ -16,8 +16,8 @@ describe(DownloadKeystore, () => {
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const downloadKeystoreAction = new DownloadKeystore({ app: appLookupParams });
|
||||
await downloadKeystoreAction.runAsync(ctx, testAndroidBuildCredentialsFragment);
|
||||
expect(fsWriteFileSpy as any).toHaveBeenCalledTimes(1);
|
||||
expect(fsWriteFileSpy as any).toHaveBeenCalledWith(
|
||||
expect(fsWriteFileSpy).toHaveBeenCalledTimes(1);
|
||||
expect(fsWriteFileSpy).toHaveBeenCalledWith(
|
||||
`@${appLookupParams.account.name}__${appLookupParams.projectName}.jks`,
|
||||
testAndroidBuildCredentialsFragment.androidKeystore?.keystore,
|
||||
'base64'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
import {
|
||||
getNewAndroidApiMock,
|
||||
@@ -8,7 +9,7 @@ import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { RemoveFcm } from '../RemoveFcm';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe(RemoveFcm, () => {
|
||||
it('removes an FCM Api Key', async () => {
|
||||
@@ -24,7 +25,7 @@ describe(RemoveFcm, () => {
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const removeFcmApiKeyAction = new RemoveFcm(appLookupParams);
|
||||
await removeFcmApiKeyAction.runAsync(ctx);
|
||||
expect(ctx.android.deleteFcmAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.deleteFcmAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('errors in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
import { testAndroidBuildCredentialsFragment } from '../../../__tests__/fixtures-android';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
@@ -6,7 +7,7 @@ import { RemoveKeystore } from '../RemoveKeystore';
|
||||
|
||||
jest.mock('fs-extra');
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe(RemoveKeystore, () => {
|
||||
it('removes a keystore', async () => {
|
||||
@@ -16,7 +17,7 @@ describe(RemoveKeystore, () => {
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const removeKeystoreAction = new RemoveKeystore(appLookupParams);
|
||||
await removeKeystoreAction.runAsync(ctx, testAndroidBuildCredentialsFragment);
|
||||
expect(ctx.android.deleteKeystoreAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.deleteKeystoreAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('errors in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
|
||||
+10
-9
@@ -1,5 +1,6 @@
|
||||
import { vol } from 'memfs';
|
||||
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { AndroidKeystoreType } from '../../../../graphql/generated';
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
import {
|
||||
@@ -18,7 +19,7 @@ import { SetupBuildCredentialsFromCredentialsJson } from '../SetupBuildCredentia
|
||||
jest.mock('fs');
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
jest.mock('../../../manager/SelectAndroidBuildCredentials');
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -27,7 +28,7 @@ beforeEach(() => {
|
||||
|
||||
describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
it('sets up a new build configuration from credentials.json upon user request', async () => {
|
||||
(SelectAndroidBuildCredentials as any).mockImplementation(() => {
|
||||
asMock(SelectAndroidBuildCredentials).mockImplementation(() => {
|
||||
return {
|
||||
runAsync: () => {
|
||||
return {
|
||||
@@ -64,8 +65,8 @@ describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
await setupBuildCredentialsAction.runAsync(ctx);
|
||||
|
||||
// expect keystore to be created with credentials.json content
|
||||
expect(ctx.android.createKeystoreAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.createKeystoreAsync as any).toBeCalledWith(appLookupParams.account, {
|
||||
expect(ctx.android.createKeystoreAsync).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.createKeystoreAsync).toBeCalledWith(appLookupParams.account, {
|
||||
keystorePassword: testJksAndroidKeystoreFragment.keystorePassword,
|
||||
keyAlias: testJksAndroidKeystoreFragment.keyAlias,
|
||||
keyPassword: testJksAndroidKeystoreFragment.keyPassword,
|
||||
@@ -74,10 +75,10 @@ describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
});
|
||||
|
||||
// expect new build credentials to be created
|
||||
expect(ctx.android.createAndroidAppBuildCredentialsAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.createAndroidAppBuildCredentialsAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('uses an existing build configuration upon user request', async () => {
|
||||
(SelectAndroidBuildCredentials as any).mockImplementation(() => {
|
||||
asMock(SelectAndroidBuildCredentials).mockImplementation(() => {
|
||||
return {
|
||||
runAsync: () => {
|
||||
return {
|
||||
@@ -114,8 +115,8 @@ describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
await setupBuildCredentialsAction.runAsync(ctx);
|
||||
|
||||
// expect keystore to be created with credentials.json content
|
||||
expect(ctx.android.createKeystoreAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.createKeystoreAsync as any).toBeCalledWith(appLookupParams.account, {
|
||||
expect(ctx.android.createKeystoreAsync).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.createKeystoreAsync).toBeCalledWith(appLookupParams.account, {
|
||||
keystorePassword: testJksAndroidKeystoreFragment.keystorePassword,
|
||||
keyAlias: testJksAndroidKeystoreFragment.keyAlias,
|
||||
keyPassword: testJksAndroidKeystoreFragment.keyPassword,
|
||||
@@ -124,7 +125,7 @@ describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
});
|
||||
|
||||
// expect existing build credentials to be updated
|
||||
expect(ctx.android.updateAndroidAppBuildCredentialsAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.android.updateAndroidAppBuildCredentialsAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('errors in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { promptAsync } from '../../../../prompts';
|
||||
import {
|
||||
getNewAndroidApiMock,
|
||||
testGoogleServiceAccountKeyFragment,
|
||||
} from '../../../__tests__/fixtures-android';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { UseExistingGoogleServiceAccountKey } from '../UseExistingGoogleServiceAccountKey';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
asMock(promptAsync).mockImplementation(() => ({ chosenKey: testGoogleServiceAccountKeyFragment }));
|
||||
|
||||
describe(UseExistingGoogleServiceAccountKey, () => {
|
||||
it('uses an existing Google Service Account Key in Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
android: {
|
||||
...getNewAndroidApiMock(),
|
||||
getGoogleServiceAccountKeysForAccountAsync: jest.fn(() => [
|
||||
testGoogleServiceAccountKeyFragment,
|
||||
]),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const useExistingGoogleServiceAccountKeyAction = new UseExistingGoogleServiceAccountKey(
|
||||
appLookupParams.account
|
||||
);
|
||||
const selectedKey = await useExistingGoogleServiceAccountKeyAction.runAsync(ctx);
|
||||
expect(ctx.android.getGoogleServiceAccountKeysForAccountAsync).toHaveBeenCalledTimes(1);
|
||||
expect(selectedKey).toMatchObject(testGoogleServiceAccountKeyFragment);
|
||||
});
|
||||
it("returns null if the account doesn't have any Google Service Account Keys", async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
android: {
|
||||
...getNewAndroidApiMock(),
|
||||
getGoogleServiceAccountKeysForAccountAsync: jest.fn(() => []),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const useExistingGoogleServiceAccountKeyAction = new UseExistingGoogleServiceAccountKey(
|
||||
appLookupParams.account
|
||||
);
|
||||
const selectedKey = await useExistingGoogleServiceAccountKeyAction.runAsync(ctx);
|
||||
expect(ctx.android.getGoogleServiceAccountKeysForAccountAsync).toHaveBeenCalledTimes(1);
|
||||
expect(selectedKey).toBe(null);
|
||||
});
|
||||
it('errors in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const useExistingGoogleServiceAccountKeyAction = new UseExistingGoogleServiceAccountKey(
|
||||
appLookupParams.account
|
||||
);
|
||||
|
||||
// fail if users are running in non-interactive mode
|
||||
await expect(useExistingGoogleServiceAccountKeyAction.runAsync(ctx)).rejects.toThrowError();
|
||||
});
|
||||
});
|
||||
@@ -5,15 +5,18 @@ import {
|
||||
AndroidKeystoreFragment,
|
||||
AppFragment,
|
||||
CommonAndroidAppCredentialsFragment,
|
||||
GoogleServiceAccountKeyFragment,
|
||||
} from '../../../graphql/generated';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { AppQuery } from '../../ios/api/graphql/queries/AppQuery';
|
||||
import { KeystoreWithType } from '../credentials';
|
||||
import { GoogleServiceAccountKey, KeystoreWithType } from '../credentials';
|
||||
import { AndroidAppBuildCredentialsMutation } from './graphql/mutations/AndroidAppBuildCredentialsMutation';
|
||||
import { AndroidAppCredentialsMutation } from './graphql/mutations/AndroidAppCredentialsMutation';
|
||||
import { AndroidFcmMutation } from './graphql/mutations/AndroidFcmMutation';
|
||||
import { AndroidKeystoreMutation } from './graphql/mutations/AndroidKeystoreMutation';
|
||||
import { GoogleServiceAccountKeyMutation } from './graphql/mutations/GoogleServiceAccountKeyMutation';
|
||||
import { AndroidAppCredentialsQuery } from './graphql/queries/AndroidAppCredentialsQuery';
|
||||
import { GoogleServiceAccountKeyQuery } from './graphql/queries/GoogleServiceAccountKeyQuery';
|
||||
|
||||
export interface AppLookupParams {
|
||||
account: Account;
|
||||
@@ -87,11 +90,27 @@ export async function updateAndroidAppCredentialsAsync(
|
||||
appCredentials: CommonAndroidAppCredentialsFragment,
|
||||
{
|
||||
androidFcmId,
|
||||
googleServiceAccountKeyForSubmissionsId,
|
||||
}: {
|
||||
androidFcmId: string;
|
||||
androidFcmId?: string;
|
||||
googleServiceAccountKeyForSubmissionsId?: string;
|
||||
}
|
||||
): Promise<CommonAndroidAppCredentialsFragment> {
|
||||
return await AndroidAppCredentialsMutation.setFcmKeyAsync(appCredentials.id, androidFcmId);
|
||||
let updatedAppCredentials = appCredentials;
|
||||
if (androidFcmId) {
|
||||
updatedAppCredentials = await AndroidAppCredentialsMutation.setFcmKeyAsync(
|
||||
appCredentials.id,
|
||||
androidFcmId
|
||||
);
|
||||
}
|
||||
if (googleServiceAccountKeyForSubmissionsId) {
|
||||
updatedAppCredentials =
|
||||
await AndroidAppCredentialsMutation.setGoogleServiceAccountKeyForSubmissionsAsync(
|
||||
appCredentials.id,
|
||||
googleServiceAccountKeyForSubmissionsId
|
||||
);
|
||||
}
|
||||
return updatedAppCredentials;
|
||||
}
|
||||
|
||||
export async function updateAndroidAppBuildCredentialsAsync(
|
||||
@@ -223,6 +242,22 @@ export async function deleteFcmAsync(fcm: AndroidFcmFragment): Promise<void> {
|
||||
return await AndroidFcmMutation.deleteAndroidFcmAsync(fcm.id);
|
||||
}
|
||||
|
||||
export async function createGoogleServiceAccountKeyAsync(
|
||||
account: Account,
|
||||
jsonKey: GoogleServiceAccountKey
|
||||
): Promise<GoogleServiceAccountKeyFragment> {
|
||||
return await GoogleServiceAccountKeyMutation.createGoogleServiceAccountKeyAsync(
|
||||
{ jsonKey },
|
||||
account.id
|
||||
);
|
||||
}
|
||||
|
||||
export async function getGoogleServiceAccountKeysForAccountAsync(
|
||||
account: Account
|
||||
): Promise<GoogleServiceAccountKeyFragment[]> {
|
||||
return await GoogleServiceAccountKeyQuery.getAllForAccountAsync(account.name);
|
||||
}
|
||||
|
||||
async function getAppAsync(appLookupParams: AppLookupParams): Promise<AppFragment> {
|
||||
const projectFullName = formatProjectFullName(appLookupParams);
|
||||
return await AppQuery.byFullNameAsync(projectFullName);
|
||||
|
||||
+1
-3
@@ -15,7 +15,7 @@ export type AndroidAppBuildCredentialsMetadataInput = Omit<
|
||||
AndroidAppBuildCredentialsInput,
|
||||
'keystoreId'
|
||||
>;
|
||||
const AndroidAppBuildCredentialsMutation = {
|
||||
export const AndroidAppBuildCredentialsMutation = {
|
||||
async createAndroidAppBuildCredentialsAsync(
|
||||
androidAppBuildCredentialsInput: AndroidAppBuildCredentialsInput,
|
||||
androidAppCredentialsId: string
|
||||
@@ -85,5 +85,3 @@ const AndroidAppBuildCredentialsMutation = {
|
||||
return data.androidAppBuildCredentials.setKeystore;
|
||||
},
|
||||
};
|
||||
|
||||
export { AndroidAppBuildCredentialsMutation };
|
||||
+39
-3
@@ -7,10 +7,11 @@ import {
|
||||
CommonAndroidAppCredentialsFragment,
|
||||
CreateAndroidAppCredentialsMutation,
|
||||
SetFcmMutation,
|
||||
SetGoogleServiceAccountKeyForSubmissionsMutation,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { CommonAndroidAppCredentialsFragmentNode } from '../../../../../graphql/types/credentials/AndroidAppCredentials';
|
||||
|
||||
const AndroidAppCredentialsMutation = {
|
||||
export const AndroidAppCredentialsMutation = {
|
||||
async createAndroidAppCredentialsAsync(
|
||||
androidAppCredentialsInput: {
|
||||
fcmId?: string;
|
||||
@@ -82,6 +83,41 @@ const AndroidAppCredentialsMutation = {
|
||||
assert(data.androidAppCredentials.setFcm, 'GraphQL: `setFcm` not defined in server response');
|
||||
return data.androidAppCredentials.setFcm;
|
||||
},
|
||||
async setGoogleServiceAccountKeyForSubmissionsAsync(
|
||||
androidAppCredentialsId: string,
|
||||
googleServiceAccountKeyId: string
|
||||
): Promise<CommonAndroidAppCredentialsFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<SetGoogleServiceAccountKeyForSubmissionsMutation>(
|
||||
gql`
|
||||
mutation SetGoogleServiceAccountKeyForSubmissionsMutation(
|
||||
$androidAppCredentialsId: ID!
|
||||
$googleServiceAccountKeyId: ID!
|
||||
) {
|
||||
androidAppCredentials {
|
||||
setGoogleServiceAccountKeyForSubmissions(
|
||||
id: $androidAppCredentialsId
|
||||
googleServiceAccountKeyId: $googleServiceAccountKeyId
|
||||
) {
|
||||
id
|
||||
...CommonAndroidAppCredentialsFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(CommonAndroidAppCredentialsFragmentNode)}
|
||||
`,
|
||||
{
|
||||
androidAppCredentialsId,
|
||||
googleServiceAccountKeyId,
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
assert(
|
||||
data.androidAppCredentials.setGoogleServiceAccountKeyForSubmissions,
|
||||
'GraphQL: `setGoogleServiceAccountKeyForSubmissions` not defined in server response'
|
||||
);
|
||||
return data.androidAppCredentials.setGoogleServiceAccountKeyForSubmissions;
|
||||
},
|
||||
};
|
||||
|
||||
export { AndroidAppCredentialsMutation };
|
||||
+1
-3
@@ -11,7 +11,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { AndroidFcmFragmentNode } from '../../../../../graphql/types/credentials/AndroidFcm';
|
||||
|
||||
const AndroidFcmMutation = {
|
||||
export const AndroidFcmMutation = {
|
||||
async createAndroidFcmAsync(
|
||||
androidFcmInput: AndroidFcmInput,
|
||||
accountId: string
|
||||
@@ -67,5 +67,3 @@ const AndroidFcmMutation = {
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export { AndroidFcmMutation };
|
||||
+1
-3
@@ -11,7 +11,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { AndroidKeystoreFragmentNode } from '../../../../../graphql/types/credentials/AndroidKeystore';
|
||||
|
||||
const AndroidKeystoreMutation = {
|
||||
export const AndroidKeystoreMutation = {
|
||||
async createAndroidKeystoreAsync(
|
||||
androidKeystoreInput: AndroidKeystoreInput,
|
||||
accountId: string
|
||||
@@ -73,5 +73,3 @@ const AndroidKeystoreMutation = {
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export { AndroidKeystoreMutation };
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import assert from 'assert';
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../../../../graphql/client';
|
||||
import {
|
||||
CreateGoogleServiceAccountKeyMutation,
|
||||
GoogleServiceAccountKeyFragment,
|
||||
GoogleServiceAccountKeyInput,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { GoogleServiceAccountKeyFragmentNode } from '../../../../../graphql/types/credentials/GoogleServiceAccountKey';
|
||||
|
||||
export const GoogleServiceAccountKeyMutation = {
|
||||
async createGoogleServiceAccountKeyAsync(
|
||||
googleServiceAccountKeyInput: GoogleServiceAccountKeyInput,
|
||||
accountId: string
|
||||
): Promise<GoogleServiceAccountKeyFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateGoogleServiceAccountKeyMutation>(
|
||||
gql`
|
||||
mutation CreateGoogleServiceAccountKeyMutation(
|
||||
$googleServiceAccountKeyInput: GoogleServiceAccountKeyInput!
|
||||
$accountId: ID!
|
||||
) {
|
||||
googleServiceAccountKey {
|
||||
createGoogleServiceAccountKey(
|
||||
googleServiceAccountKeyInput: $googleServiceAccountKeyInput
|
||||
accountId: $accountId
|
||||
) {
|
||||
id
|
||||
...GoogleServiceAccountKeyFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(GoogleServiceAccountKeyFragmentNode)}
|
||||
`,
|
||||
{
|
||||
googleServiceAccountKeyInput,
|
||||
accountId,
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
assert(
|
||||
data.googleServiceAccountKey.createGoogleServiceAccountKey,
|
||||
'GraphQL: `createAndroidFcm` not defined in server response'
|
||||
);
|
||||
return data.googleServiceAccountKey.createGoogleServiceAccountKey;
|
||||
},
|
||||
};
|
||||
+1
-3
@@ -9,7 +9,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { CommonAndroidAppCredentialsFragmentNode } from '../../../../../graphql/types/credentials/AndroidAppCredentials';
|
||||
|
||||
const AndroidAppCredentialsQuery = {
|
||||
export const AndroidAppCredentialsQuery = {
|
||||
async withCommonFieldsByApplicationIdentifierAsync(
|
||||
projectFullName: string,
|
||||
{
|
||||
@@ -66,5 +66,3 @@ const AndroidAppCredentialsQuery = {
|
||||
return data.app.byFullName.androidAppCredentials[0] ?? null;
|
||||
},
|
||||
};
|
||||
|
||||
export { AndroidAppCredentialsQuery };
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../../../../graphql/client';
|
||||
import {
|
||||
GoogleServiceAccountKeyByAccountQuery,
|
||||
GoogleServiceAccountKeyFragment,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { GoogleServiceAccountKeyFragmentNode } from '../../../../../graphql/types/credentials/GoogleServiceAccountKey';
|
||||
|
||||
export const GoogleServiceAccountKeyQuery = {
|
||||
async getAllForAccountAsync(accountName: string): Promise<GoogleServiceAccountKeyFragment[]> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<GoogleServiceAccountKeyByAccountQuery>(
|
||||
gql`
|
||||
query GoogleServiceAccountKeyByAccountQuery($accountName: String!) {
|
||||
account {
|
||||
byName(accountName: $accountName) {
|
||||
id
|
||||
googleServiceAccountKeys {
|
||||
id
|
||||
...GoogleServiceAccountKeyFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(GoogleServiceAccountKeyFragmentNode)}
|
||||
`,
|
||||
{
|
||||
accountName,
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.account.byName.googleServiceAccountKeys;
|
||||
},
|
||||
};
|
||||
@@ -16,6 +16,11 @@ export type KeystoreWithType = Keystore & {
|
||||
type: AndroidKeystoreType;
|
||||
};
|
||||
|
||||
export type GoogleServiceAccountKey = {
|
||||
[key: string]: any;
|
||||
private_key: string;
|
||||
};
|
||||
|
||||
export type AndroidCredentials = {
|
||||
experienceName: string;
|
||||
keystore: Keystore | null;
|
||||
|
||||
+7
-1
@@ -1,3 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`print credentials prints the AndroidAppCredentials fragment 1`] = `"Android Credentials Project: testApp Application Identifier: test.com.app Push Notifications (FCM): Key: abcd...efgh Updated 0 second ago Configuration: legacy (Default) Keystore: Type: JKS Key Alias: QHdrb3p5cmEvY3JlZGVudGlhbHMtdGVzdA== MD5 Fingerprint: TE:ST:-M:D5 SHA1 Fingerprint: TE:ST:-S:HA:1 SHA256 Fingerprint: TE:ST:-S:HA:25:6 Updated 0 second ago"`;
|
||||
exports[`print credentials prints the AndroidAppCredentials fragment 1`] = `
|
||||
"Android Credentials Project: testApp Application Identifier: test.com.app Push Notifications (FCM): Key: abcd...efgh Updated 0 second agoGoogle Service Account Key For Submissions Project ID sdf.sdf.sdf
|
||||
Client Email [email protected]
|
||||
Client ID test-client-identifier
|
||||
Private Key ID test-private-key-identifier
|
||||
Updated 0 second ago Configuration: legacy (Default) Keystore: Type: JKS Key Alias: QHdrb3p5cmEvY3JlZGVudGlhbHMtdGVzdA== MD5 Fingerprint: TE:ST:-M:D5 SHA1 Fingerprint: TE:ST:-S:HA:1 SHA256 Fingerprint: TE:ST:-S:HA:25:6 Updated 0 second ago"
|
||||
`;
|
||||
@@ -1,8 +1,10 @@
|
||||
import mockdate from 'mockdate';
|
||||
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import Log from '../../../../log';
|
||||
import {
|
||||
testAndroidAppCredentialsFragment,
|
||||
testGoogleServiceAccountKeyFragment,
|
||||
testLegacyAndroidFcmFragment,
|
||||
} from '../../../__tests__/fixtures-android';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
@@ -10,9 +12,10 @@ import { getAppLookupParamsFromContextAsync } from '../../actions/BuildCredentia
|
||||
import { displayAndroidAppCredentials } from '../printCredentials';
|
||||
|
||||
jest.mock('../../../../log');
|
||||
jest.mock('chalk', () => ({ bold: jest.fn(log => log) }));
|
||||
jest.mock('chalk', () => ({ bold: jest.fn(log => log), cyan: { bold: jest.fn(log => log) } }));
|
||||
|
||||
mockdate.set(testLegacyAndroidFcmFragment.updatedAt);
|
||||
mockdate.set(testGoogleServiceAccountKeyFragment.updatedAt);
|
||||
|
||||
describe('print credentials', () => {
|
||||
it('prints the AndroidAppCredentials fragment', async () => {
|
||||
@@ -22,9 +25,7 @@ describe('print credentials', () => {
|
||||
appLookupParams,
|
||||
appCredentials: testAndroidAppCredentialsFragment,
|
||||
});
|
||||
const loggedSoFar = (Log.log as jest.Mock).mock.calls.reduce(
|
||||
(acc, mockValue) => acc + mockValue
|
||||
);
|
||||
const loggedSoFar = asMock(Log.log).mock.calls.reduce((acc, mockValue) => acc + mockValue);
|
||||
expect(loggedSoFar).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import JsonFile from '@expo/json-file';
|
||||
import Joi from 'joi';
|
||||
|
||||
import Log, { learnMore } from '../../../log';
|
||||
import { GoogleServiceAccountKey } from '../credentials';
|
||||
|
||||
export const MinimalGoogleServiceAccountKeySchema = Joi.object({
|
||||
private_key: Joi.string().required(),
|
||||
});
|
||||
|
||||
export function readAndValidateServiceAccountKey(keyJsonPath: string): GoogleServiceAccountKey {
|
||||
try {
|
||||
const jsonKeyObject = JsonFile.read(keyJsonPath);
|
||||
const { error, value } = MinimalGoogleServiceAccountKeySchema.validate(jsonKeyObject, {
|
||||
abortEarly: false,
|
||||
allowUnknown: true,
|
||||
});
|
||||
if (error) {
|
||||
const maybeGoogleServicesJson = keyJsonPath.includes('google-services');
|
||||
if (maybeGoogleServicesJson) {
|
||||
Log.error(
|
||||
`Oops! Looks like you uploaded a google-services.json instead of your service account key. ${learnMore(
|
||||
'https://expo.fyi/creating-google-service-account'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
throw new Error(`Google Service Account JSON Key is not valid [${error.toString()}].`);
|
||||
}
|
||||
return value as unknown as GoogleServiceAccountKey;
|
||||
} catch (err: any) {
|
||||
if (err.code === 'EJSONPARSE') {
|
||||
err.message = `Found invalid JSON in Google Service Account JSON Key. ${err.message}`;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { fromNow } from '../../../utils/date';
|
||||
import formatFields from '../../../utils/formatFields';
|
||||
import { sortBuildCredentials } from '../actions/BuildCredentialsUtils';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
|
||||
@@ -44,10 +45,43 @@ function displayAndroidFcmCredentials(appCredentials: CommonAndroidAppCredential
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
function displayGoogleServiceAccountKeyForSubmissions(
|
||||
appCredentials: CommonAndroidAppCredentialsFragment
|
||||
): void {
|
||||
const maybeGsaKey = appCredentials.googleServiceAccountKeyForSubmissions;
|
||||
Log.log(
|
||||
formatFields([{ label: 'Google Service Account Key For Submissions', value: '' }], {
|
||||
labelFormat: chalk.cyan.bold,
|
||||
})
|
||||
);
|
||||
if (!maybeGsaKey) {
|
||||
Log.log(
|
||||
formatFields([{ label: '', value: 'None assigned yet' }], {
|
||||
labelFormat: chalk.cyan.bold,
|
||||
})
|
||||
);
|
||||
Log.newLine();
|
||||
return;
|
||||
}
|
||||
const { projectIdentifier, privateKeyIdentifier, clientEmail, clientIdentifier, updatedAt } =
|
||||
maybeGsaKey;
|
||||
|
||||
const fields = [
|
||||
{ label: 'Project ID', value: projectIdentifier },
|
||||
{ label: 'Client Email', value: clientEmail },
|
||||
{ label: 'Client ID', value: clientIdentifier },
|
||||
{ label: 'Private Key ID', value: privateKeyIdentifier },
|
||||
{ label: 'Updated', value: `${fromNow(new Date(updatedAt))} ago` },
|
||||
];
|
||||
Log.log(formatFields(fields, { labelFormat: chalk.cyan.bold }));
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
function displayEASAndroidAppCredentials(
|
||||
appCredentials: CommonAndroidAppCredentialsFragment
|
||||
): void {
|
||||
displayAndroidFcmCredentials(appCredentials);
|
||||
displayGoogleServiceAccountKeyForSubmissions(appCredentials);
|
||||
const sortedBuildCredentialsList = sortBuildCredentials(
|
||||
appCredentials.androidAppBuildCredentialsList
|
||||
);
|
||||
|
||||
@@ -6,74 +6,41 @@ import { getExpoConfig } from '../project/expoConfig';
|
||||
import { getProjectAccountName } from '../project/projectUtils';
|
||||
import { confirmAsync } from '../prompts';
|
||||
import { Actor, getActorDisplayName } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import pick from '../utils/expodash/pick';
|
||||
import * as AndroidGraphqlClient from './android/api/GraphqlClient';
|
||||
import * as IosGraphqlClient from './ios/api/GraphqlClient';
|
||||
import AppStoreApi from './ios/appstore/AppStoreApi';
|
||||
|
||||
export interface Action<T = void> {
|
||||
runAsync(ctx: Context): Promise<T>;
|
||||
}
|
||||
|
||||
interface AppleCtxOptions {
|
||||
appleId?: string;
|
||||
appleIdPassword?: string;
|
||||
teamId?: string;
|
||||
}
|
||||
|
||||
interface Options extends AppleCtxOptions {
|
||||
nonInteractive?: boolean;
|
||||
exp?: ExpoConfig;
|
||||
}
|
||||
|
||||
export interface Context {
|
||||
readonly projectDir: string;
|
||||
readonly user: Actor;
|
||||
readonly nonInteractive: boolean;
|
||||
readonly android: typeof AndroidGraphqlClient;
|
||||
readonly ios: typeof IosGraphqlClient;
|
||||
readonly appStore: AppStoreApi;
|
||||
readonly hasProjectContext: boolean;
|
||||
readonly exp: ExpoConfig;
|
||||
|
||||
ensureProjectContext(): void;
|
||||
bestEffortAppStoreAuthenticateAsync(): Promise<void>;
|
||||
}
|
||||
|
||||
export async function createCredentialsContextAsync(
|
||||
projectDir: string,
|
||||
options: Options
|
||||
): Promise<Context> {
|
||||
const user = await ensureLoggedInAsync();
|
||||
|
||||
let expoConfig: ExpoConfig | undefined = options.exp;
|
||||
if (!expoConfig) {
|
||||
try {
|
||||
expoConfig = getExpoConfig(projectDir);
|
||||
} catch (error) {
|
||||
// ignore error, context might be created outside of expo project
|
||||
}
|
||||
}
|
||||
|
||||
return new CredentialsContext(projectDir, user, expoConfig, options);
|
||||
}
|
||||
|
||||
class CredentialsContext implements Context {
|
||||
export class CredentialsContext {
|
||||
public readonly android = AndroidGraphqlClient;
|
||||
public readonly appStore = new AppStoreApi();
|
||||
public readonly ios = IosGraphqlClient;
|
||||
public readonly appStore: AppStoreApi;
|
||||
public readonly nonInteractive: boolean;
|
||||
public readonly projectDir: string;
|
||||
public readonly user: Actor;
|
||||
|
||||
private shouldAskAuthenticateAppStore: boolean = true;
|
||||
private _exp?: ExpoConfig;
|
||||
|
||||
constructor(
|
||||
public readonly projectDir: string,
|
||||
public readonly user: Actor,
|
||||
private _exp: ExpoConfig | undefined,
|
||||
options: Options
|
||||
private options: {
|
||||
exp?: ExpoConfig;
|
||||
nonInteractive?: boolean;
|
||||
projectDir: string;
|
||||
user: Actor;
|
||||
}
|
||||
) {
|
||||
this.appStore = new AppStoreApi(pick(options, ['appleId', 'appleIdPassword', 'teamId']));
|
||||
this.projectDir = options.projectDir;
|
||||
this.user = options.user;
|
||||
this.nonInteractive = options.nonInteractive ?? false;
|
||||
|
||||
this._exp = options.exp;
|
||||
if (!this._exp) {
|
||||
try {
|
||||
this._exp = getExpoConfig(options.projectDir);
|
||||
} catch (error) {
|
||||
// ignore error, context might be created outside of expo project
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get hasProjectContext(): boolean {
|
||||
@@ -90,14 +57,15 @@ class CredentialsContext implements Context {
|
||||
return;
|
||||
}
|
||||
// trigger getConfig error
|
||||
getConfig(this.projectDir, { skipSDKVersionRequirement: true });
|
||||
getConfig(this.options.projectDir, { skipSDKVersionRequirement: true });
|
||||
}
|
||||
|
||||
public logOwnerAndProject(): void {
|
||||
const { user } = this.options;
|
||||
if (this.hasProjectContext) {
|
||||
const owner = getProjectAccountName(this.exp, this.user);
|
||||
const owner = getProjectAccountName(this.exp, user);
|
||||
// Figure out if User A is configuring credentials as admin for User B's project
|
||||
const isProxyUser = this.user.__typename === 'Robot' || owner !== this.user.username;
|
||||
const isProxyUser = user.__typename === 'Robot' || owner !== user.username;
|
||||
|
||||
Log.log(
|
||||
`Accessing credentials ${isProxyUser ? 'on behalf of' : 'for'} ${owner} in project ${
|
||||
@@ -105,7 +73,7 @@ class CredentialsContext implements Context {
|
||||
}`
|
||||
);
|
||||
} else {
|
||||
Log.log(`Accessing credentials for ${this.exp.owner ?? getActorDisplayName(this.user)}`);
|
||||
Log.log(`Accessing credentials for ${this.exp.owner ?? getActorDisplayName(user)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { findApplicationTarget, findTargetByName } from '../../project/ios/targe
|
||||
import zipObject from '../../utils/expodash/zipObject';
|
||||
import vcs from '../../vcs';
|
||||
import GitClient from '../../vcs/git';
|
||||
import { Context } from '../context';
|
||||
import { CredentialsContext } from '../context';
|
||||
import { App, Target, TargetCredentials } from '../ios/types';
|
||||
import { readRawAsync } from './read';
|
||||
import {
|
||||
@@ -23,7 +23,7 @@ import { getCredentialsJsonPath } from './utils';
|
||||
* is not validated
|
||||
*/
|
||||
export async function updateAndroidCredentialsAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
buildCredentials: AndroidAppBuildCredentialsFragment
|
||||
): Promise<void> {
|
||||
const rawCredentialsJson: any =
|
||||
@@ -70,7 +70,7 @@ export async function updateAndroidCredentialsAsync(
|
||||
* credentials.json will be updated partially
|
||||
*/
|
||||
export async function updateIosCredentialsAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
app: App,
|
||||
targets: Target[],
|
||||
distributionType: IosDistributionType
|
||||
@@ -205,7 +205,7 @@ function createTargetCredentialsPathsMap(
|
||||
}
|
||||
|
||||
async function getTargetBuildCredentialsAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
app: App,
|
||||
target: Target,
|
||||
iosDistributionType: IosDistributionType
|
||||
@@ -244,7 +244,7 @@ async function getTargetBuildCredentialsAsync(
|
||||
}
|
||||
|
||||
async function backupTargetCredentialsAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
{
|
||||
targetCredentials,
|
||||
targetCredentialsPaths,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { CommonIosAppCredentialsFragment } from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import { findApplicationTarget } from '../../project/ios/target';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { Context } from '../context';
|
||||
import { CredentialsContext } from '../context';
|
||||
import * as credentialsJsonReader from '../credentialsJson/read';
|
||||
import { ensureAllTargetsAreConfigured } from '../credentialsJson/utils';
|
||||
import { getAppFromContext } from './actions/BuildCredentialsUtils';
|
||||
@@ -26,7 +26,7 @@ interface Options {
|
||||
export default class IosCredentialsProvider {
|
||||
public readonly platform = Platform.IOS;
|
||||
|
||||
constructor(private ctx: Context, private options: Options) {}
|
||||
constructor(private ctx: CredentialsContext, private options: Options) {}
|
||||
|
||||
public async getCredentialsAsync(
|
||||
src: CredentialsSource.LOCAL | CredentialsSource.REMOTE
|
||||
@@ -68,7 +68,7 @@ export default class IosCredentialsProvider {
|
||||
}
|
||||
|
||||
private async getPushKeyAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
targets: Target[]
|
||||
): Promise<CommonIosAppCredentialsFragment | null> {
|
||||
if (ctx.nonInteractive) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { AppleTeamFragment } from '../../../graphql/generated';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
|
||||
export async function resolveAppleTeamIfAuthenticatedAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
app: AppLookupParams
|
||||
): Promise<AppleTeamFragment | null> {
|
||||
if (!ctx.appStore.authCtx) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApplePushKeyFragment, CommonIosAppCredentialsFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
|
||||
|
||||
@@ -8,7 +8,7 @@ export class AssignPushKey {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
public async runAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
pushKey: ApplePushKeyFragment
|
||||
): Promise<CommonIosAppCredentialsFragment> {
|
||||
const appleTeam =
|
||||
|
||||
@@ -9,13 +9,13 @@ import {
|
||||
} from '../../../graphql/generated';
|
||||
import { getProjectAccountName } from '../../../project/projectUtils';
|
||||
import { findAccountByName } from '../../../user/Account';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { App, Target } from '../types';
|
||||
import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
|
||||
|
||||
export async function getAllBuildCredentialsAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
app: AppLookupParams
|
||||
): Promise<IosAppBuildCredentialsFragment[]> {
|
||||
const appCredentials = await ctx.ios.getIosAppCredentialsWithBuildCredentialsAsync(app, {});
|
||||
@@ -26,7 +26,7 @@ export async function getAllBuildCredentialsAsync(
|
||||
}
|
||||
|
||||
export async function getBuildCredentialsAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
app: AppLookupParams,
|
||||
iosDistributionType: GraphQLIosDistributionType
|
||||
): Promise<IosAppBuildCredentialsFragment | null> {
|
||||
@@ -41,7 +41,7 @@ export async function getBuildCredentialsAsync(
|
||||
}
|
||||
|
||||
export async function getProvisioningProfileAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
app: AppLookupParams,
|
||||
iosDistributionType: GraphQLIosDistributionType
|
||||
): Promise<AppleProvisioningProfileFragment | null> {
|
||||
@@ -50,7 +50,7 @@ export async function getProvisioningProfileAsync(
|
||||
}
|
||||
|
||||
export async function getDistributionCertificateAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
app: AppLookupParams,
|
||||
iosDistributionType: GraphQLIosDistributionType
|
||||
): Promise<AppleDistributionCertificateFragment | null> {
|
||||
@@ -59,7 +59,7 @@ export async function getDistributionCertificateAsync(
|
||||
}
|
||||
|
||||
export async function assignBuildCredentialsAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
app: AppLookupParams,
|
||||
iosDistributionType: GraphQLIosDistributionType,
|
||||
distCert: AppleDistributionCertificateFragment,
|
||||
@@ -82,7 +82,7 @@ export async function assignBuildCredentialsAsync(
|
||||
});
|
||||
}
|
||||
|
||||
export function getAppFromContext(ctx: Context): App {
|
||||
export function getAppFromContext(ctx: CredentialsContext): App {
|
||||
ctx.ensureProjectContext();
|
||||
const projectName = ctx.exp.slug;
|
||||
const accountName = getProjectAccountName(ctx.exp, ctx.user);
|
||||
@@ -96,7 +96,10 @@ export function getAppFromContext(ctx: Context): App {
|
||||
};
|
||||
}
|
||||
|
||||
export function getAppLookupParamsFromContext(ctx: Context, target: Target): AppLookupParams {
|
||||
export function getAppLookupParamsFromContext(
|
||||
ctx: CredentialsContext,
|
||||
target: Target
|
||||
): AppLookupParams {
|
||||
const app = getAppFromContext(ctx);
|
||||
return { ...app, bundleIdentifier: target.bundleIdentifier };
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { ora } from '../../../ora';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { AppleProvisioningProfileMutationResult } from '../api/graphql/mutations/AppleProvisioningProfileMutation';
|
||||
@@ -21,7 +21,9 @@ export class ConfigureProvisioningProfile {
|
||||
private originalProvisioningProfile: AppleProvisioningProfileFragment
|
||||
) {}
|
||||
|
||||
public async runAsync(ctx: Context): Promise<AppleProvisioningProfileMutationResult | null> {
|
||||
public async runAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<AppleProvisioningProfileMutationResult | null> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new MissingCredentialsNonInteractiveError(
|
||||
'Configuring Provisioning Profiles is only supported in interactive mode.'
|
||||
@@ -62,7 +64,7 @@ export class ConfigureProvisioningProfile {
|
||||
}
|
||||
|
||||
private async configureAndUpdateAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
authCtx: AuthCtx,
|
||||
app: AppLookupParams,
|
||||
profileFromApple: ProvisioningProfileStoreInfo
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import Log from '../../../log';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppleDistributionCertificateMutationResult } from '../api/graphql/mutations/AppleDistributionCertificateMutation';
|
||||
import { provideOrGenerateDistributionCertificateAsync } from './DistributionCertificateUtils';
|
||||
|
||||
export class CreateDistributionCertificate {
|
||||
constructor(private account: Account) {}
|
||||
|
||||
public async runAsync(ctx: Context): Promise<AppleDistributionCertificateMutationResult> {
|
||||
public async runAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<AppleDistributionCertificateMutationResult> {
|
||||
const distCert = await provideOrGenerateDistributionCertificateAsync(ctx, this.account.name);
|
||||
const result = await ctx.ios.createDistributionCertificateAsync(this.account, distCert);
|
||||
Log.succeed('Created distribution certificate');
|
||||
|
||||
@@ -3,7 +3,7 @@ import nullthrows from 'nullthrows';
|
||||
|
||||
import { AppleDistributionCertificateFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import { askForUserProvidedAsync } from '../../utils/promptForCredentials';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
@@ -20,7 +20,7 @@ export class CreateProvisioningProfile {
|
||||
private distributionCertificate: AppleDistributionCertificateFragment
|
||||
) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<AppleProvisioningProfileMutationResult> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<AppleProvisioningProfileMutationResult> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new MissingCredentialsNonInteractiveError(
|
||||
'Creating Provisioning Profiles is only supported in interactive mode.'
|
||||
@@ -46,7 +46,7 @@ export class CreateProvisioningProfile {
|
||||
}
|
||||
|
||||
private async provideOrGenerateAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
appleAuthCtx: AuthCtx
|
||||
): Promise<ProvisioningProfile> {
|
||||
const userProvided = await askForUserProvidedAsync(provisioningProfileSchema);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ApplePushKeyFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { provideOrGeneratePushKeyAsync } from './PushKeyUtils';
|
||||
|
||||
export class CreatePushKey {
|
||||
constructor(private account: Account) {}
|
||||
|
||||
public async runAsync(ctx: Context): Promise<ApplePushKeyFragment> {
|
||||
public async runAsync(ctx: CredentialsContext): Promise<ApplePushKeyFragment> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`A new push key cannot be created in non-interactive mode.`);
|
||||
}
|
||||
|
||||
@@ -7,15 +7,15 @@ import Log, { learnMore } from '../../../log';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { fromNow } from '../../../utils/date';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { askForUserProvidedAsync } from '../../utils/promptForCredentials';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { AppleTooManyCertsError } from '../appstore/AppStoreApi';
|
||||
import {
|
||||
DistributionCertificate,
|
||||
DistributionCertificateStoreInfo,
|
||||
} from '../appstore/Credentials.types';
|
||||
import { filterRevokedDistributionCertsFromEasServers } from '../appstore/CredentialsUtils';
|
||||
import { AppleTooManyCertsError } from '../appstore/distributionCertificate';
|
||||
import { distributionCertificateSchema } from '../credentials';
|
||||
import { validateDistributionCertificateAsync } from '../validators/validateDistributionCertificate';
|
||||
import { formatAppleTeam } from './AppleTeamUtils';
|
||||
@@ -84,7 +84,7 @@ async function _selectDistributionCertificateAsync(
|
||||
* select a distribution certificate from an account (validity status shown on a best effort basis)
|
||||
* */
|
||||
export async function selectDistributionCertificateWithDependenciesAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
account: Account
|
||||
): Promise<AppleDistributionCertificateFragment | null> {
|
||||
const distCertsForAccount = await ctx.ios.getDistributionCertificatesForAccountAsync(account);
|
||||
@@ -110,7 +110,7 @@ export async function selectDistributionCertificateWithDependenciesAsync(
|
||||
* select a distribution certificate from a valid set (curated on a best effort basis)
|
||||
* */
|
||||
export async function selectValidDistributionCertificateAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
appLookupParams: AppLookupParams
|
||||
): Promise<AppleDistributionCertificateFragment | null> {
|
||||
const distCertsForAccount = await ctx.ios.getDistributionCertificatesForAccountAsync(
|
||||
@@ -133,7 +133,7 @@ export async function selectValidDistributionCertificateAsync(
|
||||
|
||||
// filter by valid certs on the developer portal
|
||||
const certInfoFromApple = await ctx.appStore.listDistributionCertificatesAsync();
|
||||
const validDistCerts = await filterRevokedDistributionCertsFromEasServers(
|
||||
const validDistCerts = filterRevokedDistributionCertsFromEasServers(
|
||||
distCertsForAppleTeam,
|
||||
certInfoFromApple
|
||||
);
|
||||
@@ -152,7 +152,7 @@ Please remember that Apple Distribution Certificates are not application specifi
|
||||
`;
|
||||
|
||||
export async function provideOrGenerateDistributionCertificateAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
accountName: string
|
||||
): Promise<DistributionCertificate> {
|
||||
if (!ctx.nonInteractive) {
|
||||
@@ -177,7 +177,9 @@ export async function provideOrGenerateDistributionCertificateAsync(
|
||||
return await generateDistributionCertificateAsync(ctx, accountName);
|
||||
}
|
||||
|
||||
async function promptForDistCertAsync(ctx: Context): Promise<DistributionCertificate | null> {
|
||||
async function promptForDistCertAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<DistributionCertificate | null> {
|
||||
let initialValues: { teamId?: string } = {};
|
||||
if (ctx.appStore.authCtx) {
|
||||
initialValues = {
|
||||
@@ -198,7 +200,7 @@ async function promptForDistCertAsync(ctx: Context): Promise<DistributionCertifi
|
||||
}
|
||||
|
||||
async function generateDistributionCertificateAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
accountName: string
|
||||
): Promise<DistributionCertificate> {
|
||||
await ctx.appStore.ensureAuthenticatedAsync();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import {
|
||||
DistributionCertificate,
|
||||
ProvisioningProfile,
|
||||
@@ -19,7 +19,7 @@ export function formatProvisioningProfileFromApple(
|
||||
}
|
||||
|
||||
export async function generateProvisioningProfileAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
bundleIdentifier: string,
|
||||
distCert: DistributionCertificate
|
||||
): Promise<ProvisioningProfile> {
|
||||
|
||||
@@ -5,7 +5,7 @@ import Log, { learnMore } from '../../../log';
|
||||
import { confirmAsync, promptAsync } from '../../../prompts';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { fromNow } from '../../../utils/date';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { askForUserProvidedAsync } from '../../utils/promptForCredentials';
|
||||
import { PushKey, PushKeyStoreInfo } from '../appstore/Credentials.types';
|
||||
import { filterRevokedAndUntrackedPushKeysFromEasServersAsync } from '../appstore/CredentialsUtils';
|
||||
@@ -15,7 +15,7 @@ import { isPushKeyValidAndTrackedAsync } from '../validators/validatePushKey';
|
||||
import { formatAppleTeam } from './AppleTeamUtils';
|
||||
|
||||
export async function provideOrGeneratePushKeyAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
accountName: string
|
||||
): Promise<PushKey> {
|
||||
if (!ctx.nonInteractive) {
|
||||
@@ -43,7 +43,7 @@ export async function provideOrGeneratePushKeyAsync(
|
||||
return await generatePushKeyAsync(ctx, accountName);
|
||||
}
|
||||
|
||||
async function promptForPushKeyAsync(ctx: Context): Promise<PushKey | null> {
|
||||
async function promptForPushKeyAsync(ctx: CredentialsContext): Promise<PushKey | null> {
|
||||
let initialValues: { teamId?: string } = {};
|
||||
if (ctx.appStore.authCtx) {
|
||||
initialValues = {
|
||||
@@ -63,7 +63,10 @@ async function promptForPushKeyAsync(ctx: Context): Promise<PushKey | null> {
|
||||
return userProvided;
|
||||
}
|
||||
|
||||
async function generatePushKeyAsync(ctx: Context, accountName: string): Promise<PushKey> {
|
||||
async function generatePushKeyAsync(
|
||||
ctx: CredentialsContext,
|
||||
accountName: string
|
||||
): Promise<PushKey> {
|
||||
await ctx.appStore.ensureAuthenticatedAsync();
|
||||
try {
|
||||
return await ctx.appStore.createPushKeyAsync();
|
||||
@@ -116,7 +119,7 @@ function formatPushKeyFromApple(pushKey: PushKeyStoreInfo): string {
|
||||
* select a push key from an account (validity status shown on a best effort basis)
|
||||
* */
|
||||
export async function selectPushKeyAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
account: Account
|
||||
): Promise<ApplePushKeyFragment | null> {
|
||||
const pushKeysForAccount = await ctx.ios.getPushKeysForAccountAsync(account);
|
||||
@@ -133,7 +136,7 @@ export async function selectPushKeyAsync(
|
||||
}
|
||||
|
||||
export async function getValidAndTrackedPushKeysOnEasServersAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
pushKeysForAccount: ApplePushKeyFragment[]
|
||||
): Promise<ApplePushKeyFragment[]> {
|
||||
const pushInfoFromApple = await ctx.appStore.listPushKeysAsync();
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { selectDistributionCertificateWithDependenciesAsync } from './DistributionCertificateUtils';
|
||||
import { RemoveProvisioningProfiles } from './RemoveProvisioningProfile';
|
||||
@@ -13,7 +13,7 @@ import { RemoveProvisioningProfiles } from './RemoveProvisioningProfile';
|
||||
export class SelectAndRemoveDistributionCertificate {
|
||||
constructor(private account: Account) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<void> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
const selected = await selectDistributionCertificateWithDependenciesAsync(ctx, this.account);
|
||||
if (selected) {
|
||||
await new RemoveDistributionCertificate(this.account, selected).runAsync(ctx);
|
||||
@@ -29,7 +29,7 @@ export class RemoveDistributionCertificate {
|
||||
private distributionCertificate: AppleDistributionCertificateFragment
|
||||
) {}
|
||||
|
||||
public async runAsync(ctx: Context): Promise<void> {
|
||||
public async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
const apps = this.distributionCertificate.iosAppBuildCredentialsList.map(
|
||||
buildCredentials => buildCredentials.iosAppCredentials.app
|
||||
);
|
||||
@@ -71,7 +71,7 @@ export class RemoveDistributionCertificate {
|
||||
await this.removeInvalidProvisioningProfilesAsync(ctx);
|
||||
}
|
||||
|
||||
private async removeInvalidProvisioningProfilesAsync(ctx: Context): Promise<void> {
|
||||
private async removeInvalidProvisioningProfilesAsync(ctx: CredentialsContext): Promise<void> {
|
||||
const buildCredentialsList = this.distributionCertificate.iosAppBuildCredentialsList;
|
||||
const appsWithProfilesToRemove: AppLookupParams[] = [];
|
||||
const profilesToRemove: AppleProvisioningProfileIdentifiersFragment[] = [];
|
||||
|
||||
@@ -2,7 +2,7 @@ import assert from 'assert';
|
||||
|
||||
import { AppleProvisioningProfileIdentifiersFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
|
||||
export class RemoveProvisioningProfiles {
|
||||
@@ -16,7 +16,7 @@ export class RemoveProvisioningProfiles {
|
||||
);
|
||||
}
|
||||
|
||||
async runAsync(ctx: Context): Promise<void> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
if (this.provisioningProfiles.length === 0) {
|
||||
Log.log(`Skipping deletion of Provisioning Profiles`);
|
||||
return;
|
||||
|
||||
@@ -2,13 +2,13 @@ import { ApplePushKeyFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { selectPushKeyAsync } from './PushKeyUtils';
|
||||
|
||||
export class SelectAndRemovePushKey {
|
||||
constructor(private account: Account) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<void> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
const selected = await selectPushKeyAsync(ctx, this.account);
|
||||
if (selected) {
|
||||
await new RemovePushKey(this.account, selected).runAsync(ctx);
|
||||
@@ -21,7 +21,7 @@ export class SelectAndRemovePushKey {
|
||||
export class RemovePushKey {
|
||||
constructor(private account: Account, private pushKey: ApplePushKeyFragment) {}
|
||||
|
||||
public async runAsync(ctx: Context): Promise<void> {
|
||||
public async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`Cannot remove push keys in non-interactive mode`);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync, pressAnyKeyToContinueAsync } from '../../../prompts';
|
||||
import differenceBy from '../../../utils/expodash/differenceBy';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { validateProvisioningProfileAsync } from '../validators/validateProvisioningProfile';
|
||||
@@ -26,7 +26,7 @@ import { SetupDistributionCertificate } from './SetupDistributionCertificate';
|
||||
export class SetupAdhocProvisioningProfile {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<IosAppBuildCredentialsFragment> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosAppBuildCredentialsFragment> {
|
||||
const distCert = await new SetupDistributionCertificate(
|
||||
this.app,
|
||||
IosDistributionType.AdHoc
|
||||
@@ -60,7 +60,7 @@ export class SetupAdhocProvisioningProfile {
|
||||
}
|
||||
|
||||
async runWithDistributionCertificateAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
distCert: AppleDistributionCertificateFragment
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
const currentBuildCredentials = await getBuildCredentialsAsync(
|
||||
@@ -158,7 +158,7 @@ export class SetupAdhocProvisioningProfile {
|
||||
);
|
||||
}
|
||||
|
||||
private async areBuildCredentialsSetupAsync(ctx: Context): Promise<boolean> {
|
||||
private async areBuildCredentialsSetupAsync(ctx: CredentialsContext): Promise<boolean> {
|
||||
const buildCredentials = await getBuildCredentialsAsync(
|
||||
ctx,
|
||||
this.app,
|
||||
@@ -168,7 +168,7 @@ export class SetupAdhocProvisioningProfile {
|
||||
}
|
||||
|
||||
private async shouldUseExistingProfileAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
buildCredentials: IosAppBuildCredentialsFragment
|
||||
): Promise<boolean> {
|
||||
const provisioningProfile = nullthrows(buildCredentials.provisioningProfile);
|
||||
@@ -202,7 +202,7 @@ export class SetupAdhocProvisioningProfile {
|
||||
}
|
||||
|
||||
private async registerDevicesAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
appleTeam: AppleTeamFragment
|
||||
): Promise<AppleDeviceFragment[]> {
|
||||
const action = new DeviceCreateAction(this.app.account, appleTeam);
|
||||
|
||||
@@ -3,7 +3,7 @@ import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import Log from '../../../log';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { IosCapabilitiesOptions } from '../appstore/ensureAppExists';
|
||||
import { App, IosAppBuildCredentialsMap, IosCredentials, Target } from '../types';
|
||||
import { displayProjectCredentials } from '../utils/printCredentials';
|
||||
@@ -20,7 +20,7 @@ interface Options {
|
||||
export class SetupBuildCredentials {
|
||||
constructor(private options: Options) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<IosCredentials> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosCredentials> {
|
||||
const hasManyTargets = this.options.targets.length > 1;
|
||||
const iosAppBuildCredentialsMap: IosAppBuildCredentialsMap = {};
|
||||
if (hasManyTargets) {
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@ import chalk from 'chalk';
|
||||
import { IosDistributionType } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { findApplicationTarget } from '../../../project/ios/target';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { readIosCredentialsAsync } from '../../credentialsJson/read';
|
||||
import { IosCredentials } from '../../credentialsJson/types';
|
||||
import { ensureAllTargetsAreConfigured } from '../../credentialsJson/utils';
|
||||
@@ -17,7 +17,7 @@ export class SetupBuildCredentialsFromCredentialsJson {
|
||||
private distributionType: IosDistributionType
|
||||
) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<void> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
const credentialsJson = await this.readCredentialsJsonAsync(ctx);
|
||||
ensureAllTargetsAreConfigured(this.targets, credentialsJson);
|
||||
|
||||
@@ -45,7 +45,7 @@ export class SetupBuildCredentialsFromCredentialsJson {
|
||||
}
|
||||
}
|
||||
|
||||
private async readCredentialsJsonAsync(ctx: Context): Promise<IosCredentials> {
|
||||
private async readCredentialsJsonAsync(ctx: CredentialsContext): Promise<IosCredentials> {
|
||||
const applicationTarget = findApplicationTarget(this.targets);
|
||||
try {
|
||||
return await readIosCredentialsAsync(ctx.projectDir, applicationTarget);
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync, promptAsync } from '../../../prompts';
|
||||
import sortBy from '../../../utils/expodash/sortBy';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { AppleDistributionCertificateMutationResult } from '../api/graphql/mutations/AppleDistributionCertificateMutation';
|
||||
@@ -23,7 +23,7 @@ export class SetupDistributionCertificate {
|
||||
|
||||
constructor(private app: AppLookupParams, private distributionType: IosDistributionType) {}
|
||||
|
||||
public async runAsync(ctx: Context): Promise<AppleDistributionCertificateFragment> {
|
||||
public async runAsync(ctx: CredentialsContext): Promise<AppleDistributionCertificateFragment> {
|
||||
const appleTeam = await resolveAppleTeamIfAuthenticatedAsync(ctx, this.app);
|
||||
|
||||
try {
|
||||
@@ -47,7 +47,7 @@ export class SetupDistributionCertificate {
|
||||
}
|
||||
|
||||
private async runNonInteractiveAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
currentCertificate: AppleDistributionCertificateFragment | null
|
||||
): Promise<AppleDistributionCertificateFragment> {
|
||||
// TODO: implement validation
|
||||
@@ -60,7 +60,7 @@ export class SetupDistributionCertificate {
|
||||
}
|
||||
|
||||
private async runInteractiveAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
currentCertificate: AppleDistributionCertificateFragment | null
|
||||
): Promise<AppleDistributionCertificateFragment> {
|
||||
if (await this.isCurrentCertificateValidAsync(ctx, currentCertificate)) {
|
||||
@@ -75,7 +75,7 @@ export class SetupDistributionCertificate {
|
||||
}
|
||||
|
||||
private async isCurrentCertificateValidAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
currentCertificate: AppleDistributionCertificateFragment | null
|
||||
): Promise<boolean> {
|
||||
if (!currentCertificate) {
|
||||
@@ -108,7 +108,7 @@ export class SetupDistributionCertificate {
|
||||
}
|
||||
|
||||
private async createOrReuseDistCertAsync(
|
||||
ctx: Context
|
||||
ctx: CredentialsContext
|
||||
): Promise<AppleDistributionCertificateFragment> {
|
||||
const validDistCerts = await this.getValidDistCertsAsync(ctx);
|
||||
const autoselectedDistCert = validDistCerts[0];
|
||||
@@ -147,12 +147,12 @@ export class SetupDistributionCertificate {
|
||||
}
|
||||
|
||||
private async createNewDistCertAsync(
|
||||
ctx: Context
|
||||
ctx: CredentialsContext
|
||||
): Promise<AppleDistributionCertificateMutationResult> {
|
||||
return new CreateDistributionCertificate(this.app.account).runAsync(ctx);
|
||||
}
|
||||
|
||||
async reuseDistCertAsync(ctx: Context): Promise<AppleDistributionCertificate> {
|
||||
async reuseDistCertAsync(ctx: CredentialsContext): Promise<AppleDistributionCertificate> {
|
||||
const validDistCerts = await this.getValidDistCertsAsync(ctx);
|
||||
const { distCert } = await promptAsync({
|
||||
type: 'select',
|
||||
@@ -167,7 +167,7 @@ export class SetupDistributionCertificate {
|
||||
}
|
||||
|
||||
private async getValidDistCertsAsync(
|
||||
ctx: Context
|
||||
ctx: CredentialsContext
|
||||
): Promise<AppleDistributionCertificateFragment[]> {
|
||||
if (this.validDistCerts) {
|
||||
return this.validDistCerts;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IosAppBuildCredentialsFragment, IosDistributionType } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { getAllBuildCredentialsAsync } from './BuildCredentialsUtils';
|
||||
import { SetupAdhocProvisioningProfile } from './SetupAdhocProvisioningProfile';
|
||||
@@ -17,7 +17,7 @@ import { SetupProvisioningProfile } from './SetupProvisioningProfile';
|
||||
export class SetupInternalProvisioningProfile {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<IosAppBuildCredentialsFragment> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosAppBuildCredentialsFragment> {
|
||||
const buildCredentials = await getAllBuildCredentialsAsync(ctx, this.app);
|
||||
|
||||
const adhocBuildCredentialsExist =
|
||||
@@ -80,13 +80,13 @@ export class SetupInternalProvisioningProfile {
|
||||
}
|
||||
|
||||
private async setupAdhocProvisioningProfileAsync(
|
||||
ctx: Context
|
||||
ctx: CredentialsContext
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
return await new SetupAdhocProvisioningProfile(this.app).runAsync(ctx);
|
||||
}
|
||||
|
||||
private async setupUniversalProvisioningProfileAsync(
|
||||
ctx: Context
|
||||
ctx: CredentialsContext
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
return await new SetupProvisioningProfile(this.app, IosDistributionType.Enterprise).runAsync(
|
||||
ctx
|
||||
@@ -94,7 +94,7 @@ export class SetupInternalProvisioningProfile {
|
||||
}
|
||||
|
||||
private async askForDistributionTypeAndSetupAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
message: string
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
const { distributionType } = await promptAsync({
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
IosDistributionType,
|
||||
} from '../../../graphql/generated';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { ProvisioningProfileStoreInfo } from '../appstore/Credentials.types';
|
||||
@@ -28,13 +28,13 @@ import { SetupDistributionCertificate } from './SetupDistributionCertificate';
|
||||
export class SetupProvisioningProfile {
|
||||
constructor(private app: AppLookupParams, private distributionType: IosDistributionType) {}
|
||||
|
||||
async areBuildCredentialsSetupAsync(ctx: Context): Promise<boolean> {
|
||||
async areBuildCredentialsSetupAsync(ctx: CredentialsContext): Promise<boolean> {
|
||||
const buildCredentials = await getBuildCredentialsAsync(ctx, this.app, this.distributionType);
|
||||
return await validateProvisioningProfileAsync(ctx, this.app, buildCredentials);
|
||||
}
|
||||
|
||||
async assignNewAndDeleteOldProfileAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
distCert: AppleDistributionCertificateFragment,
|
||||
currentProfile: AppleProvisioningProfileFragment
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
@@ -45,7 +45,7 @@ export class SetupProvisioningProfile {
|
||||
}
|
||||
|
||||
async createAndAssignProfileAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
distCert: AppleDistributionCertificateFragment
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
const provisioningProfile = await new CreateProvisioningProfile(this.app, distCert).runAsync(
|
||||
@@ -61,7 +61,7 @@ export class SetupProvisioningProfile {
|
||||
}
|
||||
|
||||
async configureAndAssignProfileAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
distCert: AppleDistributionCertificateFragment,
|
||||
originalProvisioningProfile: AppleProvisioningProfileFragment
|
||||
): Promise<IosAppBuildCredentialsFragment | null> {
|
||||
@@ -83,7 +83,7 @@ export class SetupProvisioningProfile {
|
||||
);
|
||||
}
|
||||
|
||||
async runAsync(ctx: Context): Promise<IosAppBuildCredentialsFragment> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosAppBuildCredentialsFragment> {
|
||||
const distCert = await new SetupDistributionCertificate(
|
||||
this.app,
|
||||
this.distributionType
|
||||
|
||||
@@ -4,7 +4,7 @@ import nullthrows from 'nullthrows';
|
||||
import { ApplePushKeyFragment, CommonIosAppCredentialsFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync, promptAsync } from '../../../prompts';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { AssignPushKey } from './AssignPushKey';
|
||||
import { CreatePushKey } from './CreatePushKey';
|
||||
@@ -17,12 +17,12 @@ import {
|
||||
export class SetupPushKey {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async isPushKeySetupAsync(ctx: Context): Promise<boolean> {
|
||||
async isPushKeySetupAsync(ctx: CredentialsContext): Promise<boolean> {
|
||||
const pushKey = await ctx.ios.getPushKeyForAppAsync(this.app);
|
||||
return !!pushKey;
|
||||
}
|
||||
|
||||
public async runAsync(ctx: Context): Promise<CommonIosAppCredentialsFragment | null> {
|
||||
public async runAsync(ctx: CredentialsContext): Promise<CommonIosAppCredentialsFragment | null> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`Push keys cannot be setup in non-interactive mode.`);
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export class SetupPushKey {
|
||||
return await new AssignPushKey(this.app).runAsync(ctx, pushKey);
|
||||
}
|
||||
|
||||
private async createOrReusePushKeyAsync(ctx: Context): Promise<ApplePushKeyFragment> {
|
||||
private async createOrReusePushKeyAsync(ctx: CredentialsContext): Promise<ApplePushKeyFragment> {
|
||||
const pushKeysForAccount = await ctx.ios.getPushKeysForAccountAsync(this.app.account);
|
||||
assert(
|
||||
pushKeysForAccount.length > 0,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
IosAppBuildCredentialsFragment,
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { Action, Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams as GraphQLAppLookupParams } from '../api/GraphqlClient';
|
||||
import { IosCapabilitiesOptions } from '../appstore/ensureAppExists';
|
||||
import { SetupAdhocProvisioningProfile } from './SetupAdhocProvisioningProfile';
|
||||
@@ -18,10 +18,10 @@ interface Options {
|
||||
enterpriseProvisioning?: IosEnterpriseProvisioning;
|
||||
iosCapabilitiesOptions?: IosCapabilitiesOptions;
|
||||
}
|
||||
export class SetupTargetBuildCredentials implements Action<IosAppBuildCredentialsFragment> {
|
||||
export class SetupTargetBuildCredentials {
|
||||
constructor(private options: Options) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<IosAppBuildCredentialsFragment> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosAppBuildCredentialsFragment> {
|
||||
const { app, iosCapabilitiesOptions } = this.options;
|
||||
|
||||
await ctx.bestEffortAppStoreAuthenticateAsync();
|
||||
@@ -44,7 +44,9 @@ export class SetupTargetBuildCredentials implements Action<IosAppBuildCredential
|
||||
}
|
||||
}
|
||||
|
||||
async setupBuildCredentialsAsync(ctx: Context): Promise<IosAppBuildCredentialsFragment> {
|
||||
async setupBuildCredentialsAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
const { app, distribution, enterpriseProvisioning } = this.options;
|
||||
if (distribution === 'internal') {
|
||||
if (enterpriseProvisioning === 'adhoc') {
|
||||
|
||||
+5
-5
@@ -10,7 +10,7 @@ import {
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { IosTargetCredentials } from '../../credentialsJson/types';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { displayProjectCredentials } from '../utils/printCredentials';
|
||||
@@ -29,7 +29,7 @@ export class SetupTargetBuildCredentialsFromCredentialsJson {
|
||||
private targetCredentials: IosTargetCredentials
|
||||
) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<IosAppBuildCredentialsFragment> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosAppBuildCredentialsFragment> {
|
||||
// currently configured credentials
|
||||
const buildCredentials = await getBuildCredentialsAsync(ctx, this.app, this.distributionType);
|
||||
const currentDistributionCertificate = await getDistributionCertificateAsync(
|
||||
@@ -102,7 +102,7 @@ export class SetupTargetBuildCredentialsFromCredentialsJson {
|
||||
}
|
||||
|
||||
async getDistributionCertificateToAssignAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
appleTeam: AppleTeamFragment,
|
||||
currentDistributionCertificate: AppleDistributionCertificateFragment | null
|
||||
): Promise<AppleDistributionCertificateFragment> {
|
||||
@@ -132,7 +132,7 @@ export class SetupTargetBuildCredentialsFromCredentialsJson {
|
||||
}
|
||||
|
||||
async getProvisioningProfileToAssignAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
appleTeam: AppleTeamFragment,
|
||||
currentProvisioningProfile: AppleProvisioningProfileFragment | null
|
||||
): Promise<AppleProvisioningProfileFragment> {
|
||||
@@ -152,7 +152,7 @@ export class SetupTargetBuildCredentialsFromCredentialsJson {
|
||||
}
|
||||
|
||||
async createNewProvisioningProfileAsync(
|
||||
ctx: Context,
|
||||
ctx: CredentialsContext,
|
||||
appleTeam: AppleTeamFragment
|
||||
): Promise<AppleProvisioningProfileFragment> {
|
||||
const { provisioningProfile } = this.targetCredentials;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IosDistributionType } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { Context } from '../../context';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { updateIosCredentialsAsync } from '../../credentialsJson/update';
|
||||
import { App, Target } from '../types';
|
||||
|
||||
@@ -11,7 +11,7 @@ export class UpdateCredentialsJson {
|
||||
private distributionType: IosDistributionType
|
||||
) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<void> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
Log.log('Updating iOS credentials in credentials.json');
|
||||
await updateIosCredentialsAsync(ctx, this.app, this.targets, this.distributionType);
|
||||
Log.succeed(
|
||||
|
||||
+4
-2
@@ -3,7 +3,7 @@ import {
|
||||
AppleProvisioningProfileFragment,
|
||||
} from '../../../../graphql/generated';
|
||||
import { testProvisioningProfileFragment } from '../../../__tests__/fixtures-ios';
|
||||
import { Context } from '../../../context';
|
||||
import { CredentialsContext } from '../../../context';
|
||||
import { AppLookupParams } from '../../api/GraphqlClient';
|
||||
import { AppleProvisioningProfileMutationResult } from '../../api/graphql/mutations/AppleProvisioningProfileMutation';
|
||||
|
||||
@@ -14,7 +14,9 @@ export class ConfigureProvisioningProfile {
|
||||
private originalProvisioningProfile: AppleProvisioningProfileFragment
|
||||
) {}
|
||||
|
||||
public async runAsync(ctx: Context): Promise<AppleProvisioningProfileMutationResult | null> {
|
||||
public async runAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<AppleProvisioningProfileMutationResult | null> {
|
||||
return testProvisioningProfileFragment;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AppleDistributionCertificateFragment } from '../../../../graphql/generated';
|
||||
import { testProvisioningProfileFragment } from '../../../__tests__/fixtures-ios';
|
||||
import { Context } from '../../../context';
|
||||
import { CredentialsContext } from '../../../context';
|
||||
import { AppLookupParams } from '../../api/GraphqlClient';
|
||||
import { AppleProvisioningProfileMutationResult } from '../../api/graphql/mutations/AppleProvisioningProfileMutation';
|
||||
|
||||
@@ -10,7 +10,7 @@ export class CreateProvisioningProfile {
|
||||
private distributionCertificate: AppleDistributionCertificateFragment
|
||||
) {}
|
||||
|
||||
async runAsync(ctx: Context): Promise<AppleProvisioningProfileMutationResult> {
|
||||
async runAsync(ctx: CredentialsContext): Promise<AppleProvisioningProfileMutationResult> {
|
||||
return testProvisioningProfileFragment;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -3,12 +3,12 @@ import {
|
||||
IosDistributionType,
|
||||
} from '../../../../graphql/generated';
|
||||
import { testDistCertFragmentOneDependency } from '../../../__tests__/fixtures-ios';
|
||||
import { Context } from '../../../context';
|
||||
import { CredentialsContext } from '../../../context';
|
||||
import { AppLookupParams } from '../../api/GraphqlClient';
|
||||
export class SetupDistributionCertificate {
|
||||
constructor(private app: AppLookupParams, private distributionType: IosDistributionType) {}
|
||||
|
||||
public async runAsync(_ctx: Context): Promise<AppleDistributionCertificateFragment> {
|
||||
public async runAsync(_ctx: CredentialsContext): Promise<AppleDistributionCertificateFragment> {
|
||||
return testDistCertFragmentOneDependency;
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,8 @@ describe(AssignPushKey, () => {
|
||||
await assignPushKeyAction.runAsync(ctx, testPushKey);
|
||||
|
||||
// expect app credentials to be fetched/created, then updated
|
||||
expect(ctx.ios.createOrGetIosAppCredentialsWithCommonFieldsAsync as any).toHaveBeenCalledTimes(
|
||||
1
|
||||
);
|
||||
expect(ctx.ios.updateIosAppCredentialsAsync as any).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.ios.createOrGetIosAppCredentialsWithCommonFieldsAsync).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.ios.updateIosAppCredentialsAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('works in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
|
||||
+6
-5
@@ -1,3 +1,4 @@
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { findApplicationTarget } from '../../../../project/ios/target';
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
import { getAppstoreMock, testAuthCtx } from '../../../__tests__/fixtures-appstore';
|
||||
@@ -14,7 +15,7 @@ import { ConfigureProvisioningProfile } from '../ConfigureProvisioningProfile';
|
||||
|
||||
jest.mock('../../../../ora');
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe('ConfigureProvisioningProfile', () => {
|
||||
it('configures a Provisioning Profile in Interactive Mode', async () => {
|
||||
@@ -42,9 +43,9 @@ describe('ConfigureProvisioningProfile', () => {
|
||||
await provProfConfigurator.runAsync(ctx);
|
||||
|
||||
// expect provisioning profile not to be updated on expo servers
|
||||
expect((ctx.ios.updateProvisioningProfileAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.updateProvisioningProfileAsync).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile not to be updated on apple portal
|
||||
expect((ctx.appStore.useExistingProvisioningProfileAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.appStore.useExistingProvisioningProfileAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('errors in Non Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
@@ -61,8 +62,8 @@ describe('ConfigureProvisioningProfile', () => {
|
||||
);
|
||||
|
||||
// expect provisioning profile not to be updated on expo servers
|
||||
expect((ctx.ios.updateProvisioningProfileAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.updateProvisioningProfileAsync).mock.calls.length).toBe(0);
|
||||
// expect provisioning profile not to be updated on apple portal
|
||||
expect((ctx.appStore.useExistingProvisioningProfileAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.appStore.useExistingProvisioningProfileAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
+7
-5
@@ -1,3 +1,4 @@
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { findApplicationTarget } from '../../../../project/ios/target';
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
import { getAppstoreMock, testAuthCtx } from '../../../__tests__/fixtures-appstore';
|
||||
@@ -10,8 +11,9 @@ import {
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { CreateProvisioningProfile } from '../CreateProvisioningProfile';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe('CreateProvisioningProfile', () => {
|
||||
it('creates a Provisioning Profile in Interactive Mode', async () => {
|
||||
@@ -32,9 +34,9 @@ describe('CreateProvisioningProfile', () => {
|
||||
await createProvProfAction.runAsync(ctx);
|
||||
|
||||
// expect provisioning profile to be created on expo servers
|
||||
expect((ctx.ios.createProvisioningProfileAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createProvisioningProfileAsync).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile to be created on apple portal
|
||||
expect((ctx.appStore.createProvisioningProfileAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.appStore.createProvisioningProfileAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('errors in Non Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
@@ -50,8 +52,8 @@ describe('CreateProvisioningProfile', () => {
|
||||
);
|
||||
|
||||
// expect provisioning profile not to be created on expo servers
|
||||
expect((ctx.ios.createProvisioningProfileAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.createProvisioningProfileAsync).mock.calls.length).toBe(0);
|
||||
// expect provisioning profile not to be created on apple portal
|
||||
expect((ctx.appStore.createProvisioningProfileAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.appStore.createProvisioningProfileAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { findApplicationTarget } from '../../../../project/ios/target';
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
import { getAppstoreMock, testAuthCtx } from '../../../__tests__/fixtures-appstore';
|
||||
@@ -7,7 +8,7 @@ import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { CreatePushKey } from '../CreatePushKey';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe(CreatePushKey, () => {
|
||||
it('creates a Push Key in Interactive Mode', async () => {
|
||||
@@ -24,9 +25,9 @@ describe(CreatePushKey, () => {
|
||||
await createPushKeyAction.runAsync(ctx);
|
||||
|
||||
// expect push key to be created on expo servers
|
||||
expect((ctx.ios.createPushKeyAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createPushKeyAsync).mock.calls.length).toBe(1);
|
||||
// expect push key to be created on apple portal
|
||||
expect((ctx.appStore.createPushKeyAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.appStore.createPushKeyAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('errors in Non Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
|
||||
+11
-10
@@ -1,3 +1,4 @@
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { findApplicationTarget } from '../../../../project/ios/target';
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
@@ -10,7 +11,7 @@ import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { RemoveDistributionCertificate } from '../RemoveDistributionCertificate';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe('RemoveDistributionCertificate', () => {
|
||||
it('deletes the distribution certificate on Expo and Apple servers when there are no App Dependencies in Interactive Mode', async () => {
|
||||
@@ -23,11 +24,11 @@ describe('RemoveDistributionCertificate', () => {
|
||||
await removeDistCertAction.runAsync(ctx);
|
||||
|
||||
// expect dist cert to be deleted on expo servers
|
||||
expect((ctx.ios.deleteDistributionCertificateAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.deleteDistributionCertificateAsync).mock.calls.length).toBe(1);
|
||||
// expect dist cert to be deleted on apple portal
|
||||
expect((ctx.appStore.revokeDistributionCertificateAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.appStore.revokeDistributionCertificateAsync).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile deletion to be skipped because there arent any associated with the dist cert
|
||||
expect((ctx.ios.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('deletes the distribution certificate on Expo servers when there are no App Dependencies in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
@@ -39,11 +40,11 @@ describe('RemoveDistributionCertificate', () => {
|
||||
await removeDistCertAction.runAsync(ctx);
|
||||
|
||||
// expect dist cert to be deleted on expo servers
|
||||
expect((ctx.ios.deleteDistributionCertificateAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.deleteDistributionCertificateAsync).mock.calls.length).toBe(1);
|
||||
// not supported in non-interactive mode
|
||||
expect((ctx.appStore.revokeDistributionCertificateAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.appStore.revokeDistributionCertificateAsync).mock.calls.length).toBe(0);
|
||||
// expect provisioning profile deletion to be skipped because there arent any associated with the dist cert
|
||||
expect((ctx.ios.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('deletes the distribution certificate and its provisioning profile on Expo and Apple servers when there are App Dependencies in Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: false });
|
||||
@@ -55,11 +56,11 @@ describe('RemoveDistributionCertificate', () => {
|
||||
await removeDistCertAction.runAsync(ctx);
|
||||
|
||||
// expect dist cert to be deleted on expo servers
|
||||
expect((ctx.ios.deleteDistributionCertificateAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.deleteDistributionCertificateAsync).mock.calls.length).toBe(1);
|
||||
// expect dist cert to be deleted on apple portal
|
||||
expect((ctx.appStore.revokeDistributionCertificateAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.appStore.revokeDistributionCertificateAsync).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile deletion to be invoked on expo servers
|
||||
expect((ctx.ios.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('errors when the distribution certificate has App Dependencies in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
|
||||
Loaded 100 of 176 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user