mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-14 17:50:32 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e1c7dd91e | ||
|
|
9812406c18 | ||
|
|
cf11ae93af | ||
|
|
14756ad39b | ||
|
|
1486b481f6 | ||
|
|
84d82e9a59 | ||
|
|
c7ce4a8670 | ||
|
|
fe0a3b4c41 | ||
|
|
0bce278bd4 |
No files matched your search
+15
-2
@@ -8,13 +8,26 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 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.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.0/packages/eas-cli/src/commands/account/login.js)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -87,7 +87,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/logout.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/account/logout.js)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -101,7 +101,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/view.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/account/view.js)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -112,7 +112,7 @@ USAGE
|
||||
$ eas analytics [STATUS]
|
||||
```
|
||||
|
||||
_See code: [src/commands/analytics.js](https://github.com/expo/eas-cli/blob/v0.28.2/packages/eas-cli/src/commands/analytics.js)_
|
||||
_See code: [src/commands/analytics.js](https://github.com/expo/eas-cli/blob/v0.29.0/packages/eas-cli/src/commands/analytics.js)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -145,7 +145,7 @@ OPTIONS
|
||||
--[no-]wait Wait for build(s) to complete
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/index.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/build/index.js)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -156,7 +156,7 @@ USAGE
|
||||
$ eas build:cancel [BUILD_ID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/cancel.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/build/cancel.js)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -170,7 +170,7 @@ OPTIONS
|
||||
-p, --platform=(android|ios|all) Platform to configure
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/configure.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/build/configure.js)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -203,7 +203,7 @@ OPTIONS
|
||||
--status=(new|in-queue|in-progress|errored|finished|canceled)
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/list.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/build/list.js)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -217,7 +217,7 @@ OPTIONS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/view.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/build/view.js)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -232,7 +232,7 @@ OPTIONS
|
||||
--profile=profile
|
||||
```
|
||||
|
||||
_See code: [src/commands/config.js](https://github.com/expo/eas-cli/blob/v0.28.2/packages/eas-cli/src/commands/config.js)_
|
||||
_See code: [src/commands/config.js](https://github.com/expo/eas-cli/blob/v0.29.0/packages/eas-cli/src/commands/config.js)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -243,7 +243,7 @@ USAGE
|
||||
$ eas credentials
|
||||
```
|
||||
|
||||
_See code: [src/commands/credentials.js](https://github.com/expo/eas-cli/blob/v0.28.2/packages/eas-cli/src/commands/credentials.js)_
|
||||
_See code: [src/commands/credentials.js](https://github.com/expo/eas-cli/blob/v0.29.0/packages/eas-cli/src/commands/credentials.js)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -254,7 +254,7 @@ USAGE
|
||||
$ eas device:create
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/create.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/device/create.js)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -268,7 +268,7 @@ OPTIONS
|
||||
--apple-team-id=apple-team-id
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/list.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/device/list.js)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -279,7 +279,7 @@ USAGE
|
||||
$ eas device:view [UDID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/view.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/device/view.js)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -290,7 +290,7 @@ USAGE
|
||||
$ eas diagnostics
|
||||
```
|
||||
|
||||
_See code: [src/commands/diagnostics.js](https://github.com/expo/eas-cli/blob/v0.28.2/packages/eas-cli/src/commands/diagnostics.js)_
|
||||
_See code: [src/commands/diagnostics.js](https://github.com/expo/eas-cli/blob/v0.29.0/packages/eas-cli/src/commands/diagnostics.js)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -318,7 +318,7 @@ USAGE
|
||||
$ eas project:info
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/info.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/project/info.js)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -332,7 +332,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/init.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/project/init.js)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
@@ -349,7 +349,7 @@ OPTIONS
|
||||
--value=value Value of the secret
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/create.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/secret/create.js)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -366,7 +366,7 @@ DESCRIPTION
|
||||
Unsure where to find the secret's ID? Run eas secrets:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/delete.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/secret/delete.js)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -377,7 +377,7 @@ USAGE
|
||||
$ eas secret:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/list.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/secret/list.js)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -410,7 +410,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [src/commands/submit.js](https://github.com/expo/eas-cli/blob/v0.28.2/packages/eas-cli/src/commands/submit.js)_
|
||||
_See code: [src/commands/submit.js](https://github.com/expo/eas-cli/blob/v0.29.0/packages/eas-cli/src/commands/submit.js)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -429,7 +429,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/create.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/webhook/create.js)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -443,7 +443,7 @@ ARGUMENTS
|
||||
ID ID of the webhook to delete
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/delete.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/webhook/delete.js)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -457,7 +457,7 @@ OPTIONS
|
||||
--event=(BUILD) Event type that triggers the webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/list.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/webhook/list.js)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -477,7 +477,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/update.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/webhook/update.js)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -491,5 +491,5 @@ ARGUMENTS
|
||||
ID ID of the webhook to view
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/view.js](https://github.com/expo/eas-cli/blob/v0.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.0/packages/eas-cli/src/commands/webhook/view.js)_
|
||||
<!-- commandsstop -->
|
||||
Generated
+2007
-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.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
@@ -11,8 +11,8 @@
|
||||
"@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",
|
||||
|
||||
@@ -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 };
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
bumpVersionAsync,
|
||||
bumpVersionInAppJsonAsync,
|
||||
evaluateTemplateString,
|
||||
getInfoPlistPath,
|
||||
readBuildNumberAsync,
|
||||
readShortVersionAsync,
|
||||
} from '../version';
|
||||
@@ -30,9 +31,22 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@ export function getNewAndroidApiMock(): { [key in keyof typeof AndroidGraphqlCli
|
||||
getAndroidAppBuildCredentialsByNameAsync: jest.fn(),
|
||||
createOrUpdateAndroidAppBuildCredentialsByNameAsync: jest.fn(),
|
||||
createKeystoreAsync: jest.fn(),
|
||||
createGoogleServiceAccountKeyAsync: jest.fn(),
|
||||
createFcmAsync: jest.fn(),
|
||||
deleteKeystoreAsync: jest.fn(),
|
||||
deleteFcmAsync: jest.fn(),
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { GoogleServiceAccountKeyFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { Account } from '../../../user/Account';
|
||||
import { Context } from '../../context';
|
||||
import { GoogleServiceAccountKey } from '../credentials';
|
||||
import { readAndValidateServiceAccountKey } from '../utils/googleServiceAccountKey';
|
||||
|
||||
export class CreateGoogleServiceAccountKey {
|
||||
constructor(private account: Account) {}
|
||||
|
||||
public async runAsync(ctx: Context): 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
+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({
|
||||
|
||||
@@ -5,14 +5,16 @@ 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';
|
||||
|
||||
export interface AppLookupParams {
|
||||
@@ -223,6 +225,16 @@ 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
|
||||
);
|
||||
}
|
||||
|
||||
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 };
|
||||
+1
-3
@@ -10,7 +10,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { CommonAndroidAppCredentialsFragmentNode } from '../../../../../graphql/types/credentials/AndroidAppCredentials';
|
||||
|
||||
const AndroidAppCredentialsMutation = {
|
||||
export const AndroidAppCredentialsMutation = {
|
||||
async createAndroidAppCredentialsAsync(
|
||||
androidAppCredentialsInput: {
|
||||
fcmId?: string;
|
||||
@@ -83,5 +83,3 @@ const AndroidAppCredentialsMutation = {
|
||||
return data.androidAppCredentials.setFcm;
|
||||
},
|
||||
};
|
||||
|
||||
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 };
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import mockdate from 'mockdate';
|
||||
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import Log from '../../../../log';
|
||||
import {
|
||||
testAndroidAppCredentialsFragment,
|
||||
@@ -22,9 +23,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;
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { findApplicationTarget } from '../../../../project/ios/target';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { testTargets } from '../../../__tests__/fixtures-ios';
|
||||
@@ -19,6 +20,6 @@ describe('RemoveProvisioningProfile', () => {
|
||||
await removeProfilesAction.runAsync(ctx);
|
||||
|
||||
// expect provisioning profile to be deleted from servers
|
||||
expect((ctx.ios.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
@@ -6,7 +7,7 @@ import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { RemovePushKey } from '../RemovePushKey';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe(RemovePushKey, () => {
|
||||
it('deletes the push key on Expo and Apple servers in Interactive Mode', async () => {
|
||||
@@ -16,9 +17,9 @@ describe(RemovePushKey, () => {
|
||||
await removePushKeyAction.runAsync(ctx);
|
||||
|
||||
// expect push key to be deleted on expo servers
|
||||
expect((ctx.ios.deletePushKeyAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.deletePushKeyAsync).mock.calls.length).toBe(1);
|
||||
// expect push key to be revoked on apple portal
|
||||
expect((ctx.appStore.revokePushKeyAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.appStore.revokePushKeyAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('errors in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
|
||||
+18
-16
@@ -1,5 +1,6 @@
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { IosDistributionType } from '../../../../graphql/generated';
|
||||
import { findApplicationTarget } from '../../../../project/ios/target';
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
@@ -16,8 +17,9 @@ import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import { validateProvisioningProfileAsync } from '../../validators/validateProvisioningProfile';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { SetupProvisioningProfile } from '../SetupProvisioningProfile';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
jest.mock('../SetupDistributionCertificate');
|
||||
jest.mock('../ConfigureProvisioningProfile');
|
||||
jest.mock('../CreateProvisioningProfile');
|
||||
@@ -25,7 +27,7 @@ jest.mock('../../validators/validateProvisioningProfile');
|
||||
|
||||
describe('SetupProvisioningProfile', () => {
|
||||
it('repairs existing Provisioning Profile with bad build credentials in Interactive Mode', async () => {
|
||||
(validateProvisioningProfileAsync as jest.Mock).mockImplementation(() => false);
|
||||
asMock(validateProvisioningProfileAsync).mockImplementation(() => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
@@ -59,12 +61,12 @@ describe('SetupProvisioningProfile', () => {
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect((ctx.ios.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect((ctx.ios.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('sets up a new Provisioning Profile with bad build credentials in Interactive Mode', async () => {
|
||||
(validateProvisioningProfileAsync as jest.Mock).mockImplementation(() => false);
|
||||
asMock(validateProvisioningProfileAsync).mockImplementation(() => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
@@ -89,12 +91,12 @@ describe('SetupProvisioningProfile', () => {
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect((ctx.ios.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile to be deleted 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('skips setting up a Provisioning Profile with prior build credentials configured properly in Interactive Mode', async () => {
|
||||
(validateProvisioningProfileAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(validateProvisioningProfileAsync).mockImplementation(() => true);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
@@ -119,12 +121,12 @@ describe('SetupProvisioningProfile', () => {
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials not to be created or updated on expo servers
|
||||
expect((ctx.ios.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(0);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect((ctx.ios.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('sets up a Provisioning Profile with no prior build credentials configured in Interactive Mode', async () => {
|
||||
(validateProvisioningProfileAsync as jest.Mock).mockImplementation(() => false);
|
||||
asMock(validateProvisioningProfileAsync).mockImplementation(() => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
@@ -145,12 +147,12 @@ describe('SetupProvisioningProfile', () => {
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect((ctx.ios.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect((ctx.ios.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('errors in Non Interactive Mode', async () => {
|
||||
(validateProvisioningProfileAsync as jest.Mock).mockImplementation(() => false);
|
||||
asMock(validateProvisioningProfileAsync).mockImplementation(() => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
});
|
||||
@@ -164,8 +166,8 @@ describe('SetupProvisioningProfile', () => {
|
||||
);
|
||||
|
||||
// expect build credentials not to be created or updated on expo servers
|
||||
expect((ctx.ios.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(0);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect((ctx.ios.deleteProvisioningProfilesAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.deleteProvisioningProfilesAsync).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 { SetupPushKey } from '../SetupPushKey';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe(SetupPushKey, () => {
|
||||
it('skips setting up a Push Key if it is already configured', async () => {
|
||||
@@ -23,9 +24,9 @@ describe(SetupPushKey, () => {
|
||||
await setupPushKeyAction.runAsync(ctx);
|
||||
|
||||
// expect not to create a new push key on expo servers
|
||||
expect((ctx.ios.createPushKeyAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.createPushKeyAsync).mock.calls.length).toBe(0);
|
||||
// expect configuration not to be updated with a new push key
|
||||
expect((ctx.ios.updateIosAppCredentialsAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.updateIosAppCredentialsAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('sets up a Push Key, creating a new one if there are no existing push keys', async () => {
|
||||
const ctx = createCtxMock({
|
||||
@@ -41,9 +42,9 @@ describe(SetupPushKey, () => {
|
||||
await setupPushKeyAction.runAsync(ctx);
|
||||
|
||||
// expect to create a new push key 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 configuration to be updated with a new push key
|
||||
expect((ctx.ios.updateIosAppCredentialsAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.updateIosAppCredentialsAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('sets up a Push Key, use autoselected push key when there are existing keys', async () => {
|
||||
const ctx = createCtxMock({
|
||||
@@ -65,9 +66,9 @@ describe(SetupPushKey, () => {
|
||||
await setupPushKeyAction.runAsync(ctx);
|
||||
|
||||
// expect not to create a new push key on expo servers, we are using the existing one
|
||||
expect((ctx.ios.createPushKeyAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.createPushKeyAsync).mock.calls.length).toBe(0);
|
||||
// expect configuration not to be updated with a new push key
|
||||
expect((ctx.ios.updateIosAppCredentialsAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.updateIosAppCredentialsAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('errors in Non Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
|
||||
+14
-14
@@ -22,7 +22,7 @@ jest.mock('../../../credentialsJson/read');
|
||||
|
||||
beforeEach(() => {
|
||||
asMock(confirmAsync).mockReset();
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
});
|
||||
|
||||
describe('SetupTargetBuildCredentialsFromCredentialsJson', () => {
|
||||
@@ -61,11 +61,11 @@ describe('SetupTargetBuildCredentialsFromCredentialsJson', () => {
|
||||
await setupBuildCredentialsFromCredentialsJsonAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials not to be created or updated on expo servers
|
||||
expect((ctx.ios.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(0);
|
||||
// expect distribution certificate not to be uploaded on expo servers
|
||||
expect((ctx.ios.createDistributionCertificateAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.createDistributionCertificateAsync).mock.calls.length).toBe(0);
|
||||
// expect provisioning profile not to be uploaded on expo servers
|
||||
expect((ctx.ios.createProvisioningProfileAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(ctx.ios.createProvisioningProfileAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('sets up build credentials with no prior configuration in Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
@@ -90,11 +90,11 @@ describe('SetupTargetBuildCredentialsFromCredentialsJson', () => {
|
||||
await setupBuildCredentialsFromCredentialsJsonAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect((ctx.ios.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(1);
|
||||
// expect distribution certificate to be uploaded on expo servers
|
||||
expect((ctx.ios.createDistributionCertificateAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createDistributionCertificateAsync).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile to be uploaded on expo servers
|
||||
expect((ctx.ios.createProvisioningProfileAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createProvisioningProfileAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('sets up build credentials with different prior configuration in Interactive Mode', async () => {
|
||||
// create deep clone the quick and dirty way
|
||||
@@ -123,11 +123,11 @@ describe('SetupTargetBuildCredentialsFromCredentialsJson', () => {
|
||||
await setupBuildCredentialsFromCredentialsJsonAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect((ctx.ios.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(1);
|
||||
// expect distribution certificate to be uploaded on expo servers
|
||||
expect((ctx.ios.createDistributionCertificateAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createDistributionCertificateAsync).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile to be uploaded on expo servers
|
||||
expect((ctx.ios.createProvisioningProfileAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createProvisioningProfileAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('works in Non Interactive Mode - sets up build credentials with different prior configuration', async () => {
|
||||
// create deep clone the quick and dirty way
|
||||
@@ -157,12 +157,12 @@ describe('SetupTargetBuildCredentialsFromCredentialsJson', () => {
|
||||
await setupBuildCredentialsFromCredentialsJsonAction.runAsync(ctx);
|
||||
|
||||
// expect no prompts to be called
|
||||
expect((confirmAsync as any).mock.calls.length).toBe(0);
|
||||
expect(asMock(confirmAsync).mock.calls.length).toBe(0);
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect((ctx.ios.createOrUpdateIosAppBuildCredentialsAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(1);
|
||||
// expect distribution certificate to be uploaded on expo servers
|
||||
expect((ctx.ios.createDistributionCertificateAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createDistributionCertificateAsync).mock.calls.length).toBe(1);
|
||||
// expect provisioning profile to be uploaded on expo servers
|
||||
expect((ctx.ios.createProvisioningProfileAsync as any).mock.calls.length).toBe(1);
|
||||
expect(asMock(ctx.ios.createProvisioningProfileAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
});
|
||||
+1
-3
@@ -10,7 +10,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { AppleAppIdentifierFragmentNode } from '../../../../../graphql/types/credentials/AppleAppIdentifier';
|
||||
|
||||
const AppleAppIdentifierMutation = {
|
||||
export const AppleAppIdentifierMutation = {
|
||||
async createAppleAppIdentifierAsync(
|
||||
appleAppIdentifierInput: AppleAppIdentifierInput,
|
||||
accountId: string
|
||||
@@ -49,5 +49,3 @@ const AppleAppIdentifierMutation = {
|
||||
return data.appleAppIdentifier.createAppleAppIdentifier;
|
||||
},
|
||||
};
|
||||
|
||||
export { AppleAppIdentifierMutation };
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { AppleDeviceFragmentNode } from '../../../../../graphql/types/credentials/AppleDevice';
|
||||
|
||||
const AppleDeviceMutation = {
|
||||
export const AppleDeviceMutation = {
|
||||
async createAppleDeviceAsync(
|
||||
appleDeviceInput: AppleDeviceInput,
|
||||
accountId: string
|
||||
@@ -41,5 +41,3 @@ const AppleDeviceMutation = {
|
||||
return data.appleDevice.createAppleDevice;
|
||||
},
|
||||
};
|
||||
|
||||
export { AppleDeviceMutation };
|
||||
+1
-3
@@ -8,7 +8,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { AppleDeviceRegistrationRequestFragmentNode } from '../../../../../graphql/types/credentials/AppleDeviceRegistrationRequest';
|
||||
|
||||
const AppleDeviceRegistrationRequestMutation = {
|
||||
export const AppleDeviceRegistrationRequestMutation = {
|
||||
async createAppleDeviceRegistrationRequestAsync(
|
||||
appleTeamId: string,
|
||||
accountId: string
|
||||
@@ -43,5 +43,3 @@ const AppleDeviceRegistrationRequestMutation = {
|
||||
return data.appleDeviceRegistrationRequest.createAppleDeviceRegistrationRequest;
|
||||
},
|
||||
};
|
||||
|
||||
export { AppleDeviceRegistrationRequestMutation };
|
||||
+1
-3
@@ -17,7 +17,7 @@ export type AppleDistributionCertificateMutationResult = AppleDistributionCertif
|
||||
appleTeam?: AppleTeamFragment | null;
|
||||
};
|
||||
|
||||
const AppleDistributionCertificateMutation = {
|
||||
export const AppleDistributionCertificateMutation = {
|
||||
async createAppleDistributionCertificateAsync(
|
||||
appleDistributionCertificateInput: AppleDistributionCertificateInput,
|
||||
accountId: string
|
||||
@@ -88,5 +88,3 @@ const AppleDistributionCertificateMutation = {
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export { AppleDistributionCertificateMutation };
|
||||
+1
-3
@@ -16,7 +16,7 @@ export type AppleProvisioningProfileMutationResult = AppleProvisioningProfileFra
|
||||
appleTeam?: AppleTeamFragment | null;
|
||||
};
|
||||
|
||||
const AppleProvisioningProfileMutation = {
|
||||
export const AppleProvisioningProfileMutation = {
|
||||
async createAppleProvisioningProfileAsync(
|
||||
appleProvisioningProfileInput: AppleProvisioningProfileInput,
|
||||
accountId: string,
|
||||
@@ -124,5 +124,3 @@ const AppleProvisioningProfileMutation = {
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export { AppleProvisioningProfileMutation };
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { ApplePushKeyFragmentNode } from '../../../../../graphql/types/credentials/ApplePushKey';
|
||||
|
||||
const ApplePushKeyMutation = {
|
||||
export const ApplePushKeyMutation = {
|
||||
async createApplePushKeyAsync(
|
||||
applePushKeyInput: ApplePushKeyInput,
|
||||
accountId: string
|
||||
@@ -70,5 +70,3 @@ const ApplePushKeyMutation = {
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export { ApplePushKeyMutation };
|
||||
@@ -14,7 +14,7 @@ export type AppleTeamMutationResult = AppleTeamFragment & {
|
||||
account: Account;
|
||||
};
|
||||
|
||||
const AppleTeamMutation = {
|
||||
export const AppleTeamMutation = {
|
||||
async createAppleTeamAsync(
|
||||
appleTeamInput: AppleTeamInput,
|
||||
accountId: string
|
||||
@@ -47,5 +47,3 @@ const AppleTeamMutation = {
|
||||
return data.appleTeam.createAppleTeam;
|
||||
},
|
||||
};
|
||||
|
||||
export { AppleTeamMutation };
|
||||
+1
-3
@@ -11,7 +11,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { IosAppBuildCredentialsFragmentNode } from '../../../../../graphql/types/credentials/IosAppBuildCredentials';
|
||||
|
||||
const IosAppBuildCredentialsMutation = {
|
||||
export const IosAppBuildCredentialsMutation = {
|
||||
async createIosAppBuildCredentialsAsync(
|
||||
iosAppBuildCredentialsInput: IosAppBuildCredentialsInput,
|
||||
iosAppCredentialsId: string
|
||||
@@ -120,5 +120,3 @@ const IosAppBuildCredentialsMutation = {
|
||||
return data.iosAppBuildCredentials.setProvisioningProfile;
|
||||
},
|
||||
};
|
||||
|
||||
export { IosAppBuildCredentialsMutation };
|
||||
+1
-3
@@ -11,7 +11,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { CommonIosAppCredentialsFragmentNode } from '../../../../../graphql/types/credentials/IosAppCredentials';
|
||||
|
||||
const IosAppCredentialsMutation = {
|
||||
export const IosAppCredentialsMutation = {
|
||||
async createIosAppCredentialsAsync(
|
||||
iosAppCredentialsInput: IosAppCredentialsInput,
|
||||
appId: string,
|
||||
@@ -81,5 +81,3 @@ const IosAppCredentialsMutation = {
|
||||
return data.iosAppCredentials.setPushKey;
|
||||
},
|
||||
};
|
||||
|
||||
export { IosAppCredentialsMutation };
|
||||
+1
-3
@@ -1,6 +1,6 @@
|
||||
import { AppleDevice } from '../../../../../../graphql/generated';
|
||||
|
||||
const AppleDeviceMutation = {
|
||||
export const AppleDeviceMutation = {
|
||||
createAppleDeviceAsync: jest.fn().mockImplementation(() => {
|
||||
const appleDevice: Pick<AppleDevice, 'id' | 'identifier'> = {
|
||||
id: 'apple-device-id',
|
||||
@@ -9,5 +9,3 @@ const AppleDeviceMutation = {
|
||||
return appleDevice;
|
||||
}),
|
||||
};
|
||||
|
||||
export { AppleDeviceMutation };
|
||||
@@ -6,7 +6,7 @@ import { graphqlClient, withErrorHandlingAsync } from '../../../../../graphql/cl
|
||||
import { AppByFullNameQuery, AppFragment } from '../../../../../graphql/generated';
|
||||
import { AppFragmentNode } from '../../../../../graphql/types/App';
|
||||
|
||||
const AppQuery = {
|
||||
export const AppQuery = {
|
||||
async byFullNameAsync(fullName: string): Promise<AppFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
@@ -31,5 +31,3 @@ const AppQuery = {
|
||||
return data.app.byFullName;
|
||||
},
|
||||
};
|
||||
|
||||
export { AppQuery };
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { AppleAppIdentifierFragmentNode } from '../../../../../graphql/types/credentials/AppleAppIdentifier';
|
||||
|
||||
const AppleAppIdentifierQuery = {
|
||||
export const AppleAppIdentifierQuery = {
|
||||
async byBundleIdentifierAsync(
|
||||
accountName: string,
|
||||
bundleIdentifier: string
|
||||
@@ -46,5 +46,3 @@ const AppleAppIdentifierQuery = {
|
||||
return data.account.byName.appleAppIdentifiers[0];
|
||||
},
|
||||
};
|
||||
|
||||
export { AppleAppIdentifierQuery };
|
||||
@@ -31,7 +31,7 @@ export type AppleDevicesByIdentifierQueryResult = AppleDeviceQueryResult & {
|
||||
appleTeam: AppleTeamFragment;
|
||||
};
|
||||
|
||||
const AppleDeviceQuery = {
|
||||
export const AppleDeviceQuery = {
|
||||
async getAllByAppleTeamIdentifierAsync(
|
||||
accountId: string,
|
||||
appleTeamIdentifier: string,
|
||||
@@ -155,5 +155,3 @@ const AppleDeviceQuery = {
|
||||
return data.account.byName.appleDevices[0] ?? null;
|
||||
},
|
||||
};
|
||||
|
||||
export { AppleDeviceQuery };
|
||||
+1
-3
@@ -12,7 +12,7 @@ import {
|
||||
import { AppleDistributionCertificateFragmentNode } from '../../../../../graphql/types/credentials/AppleDistributionCertificate';
|
||||
import { AppleTeamFragmentNode } from '../../../../../graphql/types/credentials/AppleTeam';
|
||||
|
||||
const AppleDistributionCertificateQuery = {
|
||||
export const AppleDistributionCertificateQuery = {
|
||||
async getForAppAsync(
|
||||
projectFullName: string,
|
||||
{
|
||||
@@ -100,5 +100,3 @@ const AppleDistributionCertificateQuery = {
|
||||
return data.account.byName.appleDistributionCertificates;
|
||||
},
|
||||
};
|
||||
|
||||
export { AppleDistributionCertificateQuery };
|
||||
+1
-3
@@ -19,7 +19,7 @@ import { AppleTeamFragmentNode } from '../../../../../graphql/types/credentials/
|
||||
export type AppleProvisioningProfileQueryResult = AppleProvisioningProfileFragment & {
|
||||
appleTeam?: AppleTeamFragment | null;
|
||||
} & { appleDevices: AppleDeviceFragment[] } & { appleAppIdentifier: AppleAppIdentifierFragment };
|
||||
const AppleProvisioningProfileQuery = {
|
||||
export const AppleProvisioningProfileQuery = {
|
||||
async getForAppAsync(
|
||||
projectFullName: string,
|
||||
{
|
||||
@@ -89,5 +89,3 @@ const AppleProvisioningProfileQuery = {
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export { AppleProvisioningProfileQuery };
|
||||
@@ -5,7 +5,7 @@ import { graphqlClient, withErrorHandlingAsync } from '../../../../../graphql/cl
|
||||
import { ApplePushKeyByAccountQuery, ApplePushKeyFragment } from '../../../../../graphql/generated';
|
||||
import { ApplePushKeyFragmentNode } from '../../../../../graphql/types/credentials/ApplePushKey';
|
||||
|
||||
const ApplePushKeyQuery = {
|
||||
export const ApplePushKeyQuery = {
|
||||
async getAllForAccountAsync(accountName: string): Promise<ApplePushKeyFragment[]> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
@@ -33,5 +33,3 @@ const ApplePushKeyQuery = {
|
||||
return data.account.byName.applePushKeys;
|
||||
},
|
||||
};
|
||||
|
||||
export { ApplePushKeyQuery };
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { AppleTeamFragmentNode } from '../../../../../graphql/types/credentials/AppleTeam';
|
||||
|
||||
const AppleTeamQuery = {
|
||||
export const AppleTeamQuery = {
|
||||
async getAllForAccountAsync(accountName: string): Promise<AppleTeamFragment[]> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
@@ -64,5 +64,3 @@ const AppleTeamQuery = {
|
||||
return data.appleTeam.byAppleTeamIdentifier ?? null;
|
||||
},
|
||||
};
|
||||
|
||||
export { AppleTeamQuery };
|
||||
+1
-3
@@ -10,7 +10,7 @@ import {
|
||||
} from '../../../../../graphql/generated';
|
||||
import { IosAppBuildCredentialsFragmentNode } from '../../../../../graphql/types/credentials/IosAppBuildCredentials';
|
||||
|
||||
const IosAppBuildCredentialsQuery = {
|
||||
export const IosAppBuildCredentialsQuery = {
|
||||
async byAppIdentifierIdAndDistributionTypeAsync(
|
||||
projectFullName: string,
|
||||
{
|
||||
@@ -59,5 +59,3 @@ const IosAppBuildCredentialsQuery = {
|
||||
return data.app.byFullName.iosAppCredentials[0]?.iosAppBuildCredentialsList[0] ?? null;
|
||||
},
|
||||
};
|
||||
|
||||
export { IosAppBuildCredentialsQuery };
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
CommonIosAppCredentialsWithoutBuildCredentialsFragmentNode,
|
||||
} from '../../../../../graphql/types/credentials/IosAppCredentials';
|
||||
|
||||
const IosAppCredentialsQuery = {
|
||||
export const IosAppCredentialsQuery = {
|
||||
async withBuildCredentialsByAppIdentifierIdAsync(
|
||||
projectFullName: string,
|
||||
{
|
||||
@@ -110,5 +110,3 @@ const IosAppCredentialsQuery = {
|
||||
return data.app.byFullName.iosAppCredentials[0] ?? null;
|
||||
},
|
||||
};
|
||||
|
||||
export { IosAppCredentialsQuery };
|
||||
+1
-3
@@ -1,4 +1,4 @@
|
||||
const AppleDistributionCertificateQuery = {
|
||||
export const AppleDistributionCertificateQuery = {
|
||||
getAllForAccountAsync: jest.fn().mockImplementation(() => {
|
||||
return [
|
||||
{
|
||||
@@ -40,5 +40,3 @@ const AppleDistributionCertificateQuery = {
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
export { AppleDistributionCertificateQuery };
|
||||
+1
-3
@@ -1,6 +1,6 @@
|
||||
import { AppleTeam } from '../../../../../../graphql/generated';
|
||||
|
||||
const AppleTeamQuery = {
|
||||
export const AppleTeamQuery = {
|
||||
byAppleTeamIdentifierAsync: jest.fn().mockImplementation(() => {
|
||||
const appleTeam: Pick<AppleTeam, 'id' | 'appleTeamIdentifier' | 'appleTeamName'> = {
|
||||
id: 'apple-team-id',
|
||||
@@ -10,5 +10,3 @@ const AppleTeamQuery = {
|
||||
return appleTeam;
|
||||
}),
|
||||
};
|
||||
|
||||
export { AppleTeamQuery };
|
||||
+1
-3
@@ -1,4 +1,4 @@
|
||||
const IosAppCredentialsQuery = {
|
||||
export const IosAppCredentialsQuery = {
|
||||
withCommonFieldsByAppIdentifierIdAsync: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
id: '48d0cd34-96d8-447c-9de8-40dd2c3ca0f8',
|
||||
@@ -112,5 +112,3 @@ const IosAppCredentialsQuery = {
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
export { IosAppCredentialsQuery };
|
||||
@@ -1,6 +1,7 @@
|
||||
import mockdate from 'mockdate';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import Log from '../../../../log';
|
||||
import { IosAppCredentialsQuery } from '../../api/graphql/queries/IosAppCredentialsQuery';
|
||||
import { App, Target } from '../../types';
|
||||
@@ -30,9 +31,7 @@ describe('print credentials', () => {
|
||||
};
|
||||
const targets: Target[] = [{ targetName: 'test52', bundleIdentifier: 'com.quinlanj.test52' }];
|
||||
displayIosCredentials(app, appCredentials, targets);
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@ interface AppleTeam {
|
||||
teamName: string;
|
||||
}
|
||||
|
||||
function readAppleTeam(dataBase64: string): AppleTeam {
|
||||
export function readAppleTeam(dataBase64: string): AppleTeam {
|
||||
const profilePlist = parse(dataBase64);
|
||||
const teamId = (profilePlist['TeamIdentifier'] as PlistArray)?.[0] as string;
|
||||
const teamName = profilePlist['TeamName'] as string;
|
||||
@@ -15,18 +15,18 @@ function readAppleTeam(dataBase64: string): AppleTeam {
|
||||
return { teamId, teamName };
|
||||
}
|
||||
|
||||
function readProfileName(dataBase64: string): string {
|
||||
export function readProfileName(dataBase64: string): string {
|
||||
const profilePlist = parse(dataBase64);
|
||||
return profilePlist['Name'] as string;
|
||||
}
|
||||
|
||||
function isAdHocProfile(dataBase64: string): boolean {
|
||||
export function isAdHocProfile(dataBase64: string): boolean {
|
||||
const profilePlist = parse(dataBase64);
|
||||
const provisionedDevices = profilePlist['ProvisionedDevices'] as string[] | undefined;
|
||||
return Array.isArray(provisionedDevices);
|
||||
}
|
||||
|
||||
function parse(dataBase64: string): PlistObject {
|
||||
export function parse(dataBase64: string): PlistObject {
|
||||
try {
|
||||
const buffer = Buffer.from(dataBase64, 'base64');
|
||||
const profile = buffer.toString('utf8');
|
||||
@@ -35,5 +35,3 @@ function parse(dataBase64: string): PlistObject {
|
||||
throw new Error('Provisioning profile is malformed');
|
||||
}
|
||||
}
|
||||
|
||||
export { readAppleTeam, readProfileName, isAdHocProfile, parse };
|
||||
@@ -145,17 +145,17 @@ export class ManageAndroid {
|
||||
constructor(private callingAction: Action) {}
|
||||
|
||||
async runAsync(ctx: Context, currentActions: ActionInfo[] = highLevelActions): Promise<void> {
|
||||
const accountName = ctx.hasProjectContext
|
||||
? getProjectAccountName(ctx.exp, ctx.user)
|
||||
: ensureActorHasUsername(ctx.user);
|
||||
const account = findAccountByName(ctx.user.accounts, accountName);
|
||||
if (!account) {
|
||||
throw new Error(`You do not have access to account: ${accountName}`);
|
||||
}
|
||||
const { gradleContext } = await this.createProjectContextAsync(ctx);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const accountName = ctx.hasProjectContext
|
||||
? getProjectAccountName(ctx.exp, ctx.user)
|
||||
: ensureActorHasUsername(ctx.user);
|
||||
|
||||
const account = findAccountByName(ctx.user.accounts, accountName);
|
||||
if (!account) {
|
||||
throw new Error(`You do not have access to account: ${accountName}`);
|
||||
}
|
||||
const { gradleContext } = await this.createProjectContextAsync(ctx);
|
||||
if (ctx.hasProjectContext) {
|
||||
const maybeProjectId = await promptToCreateProjectIfNotExistsAsync(ctx.exp);
|
||||
if (!maybeProjectId) {
|
||||
|
||||
+6
-5
@@ -1,3 +1,4 @@
|
||||
import { asMock } from '../../../__tests__/utils';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import {
|
||||
getNewAndroidApiMock,
|
||||
@@ -14,12 +15,12 @@ const TEST_STRING = 'TEST_STRING';
|
||||
jest.mock('../../../prompts');
|
||||
|
||||
beforeEach(() => {
|
||||
(promptAsync as jest.Mock).mockReset();
|
||||
asMock(promptAsync).mockReset();
|
||||
});
|
||||
|
||||
describe(SelectAndroidBuildCredentials, () => {
|
||||
it('returns a request to make default build credentials when there are no credentials', async () => {
|
||||
(promptAsync as jest.Mock).mockImplementation(() => ({
|
||||
asMock(promptAsync).mockImplementation(() => ({
|
||||
providedName: TEST_STRING,
|
||||
}));
|
||||
const ctx = createCtxMock({
|
||||
@@ -37,7 +38,7 @@ describe(SelectAndroidBuildCredentials, () => {
|
||||
});
|
||||
});
|
||||
it('returns a request to make build credentials when the user chooses to make a new one', async () => {
|
||||
(promptAsync as jest.Mock).mockImplementation(() => ({
|
||||
asMock(promptAsync).mockImplementation(() => ({
|
||||
buildCredentialsResultOrRequestToCreateNew:
|
||||
SelectAndroidBuildCredentialsResultType.CREATE_REQUEST,
|
||||
providedName: TEST_STRING,
|
||||
@@ -59,7 +60,7 @@ describe(SelectAndroidBuildCredentials, () => {
|
||||
});
|
||||
});
|
||||
it('returns a request to make default build credentials when the user chooses to make a new one, and if they have no existing credentials', async () => {
|
||||
(promptAsync as jest.Mock).mockImplementation(() => ({
|
||||
asMock(promptAsync).mockImplementation(() => ({
|
||||
buildCredentialsResultOrRequestToCreateNew:
|
||||
SelectAndroidBuildCredentialsResultType.CREATE_REQUEST,
|
||||
providedName: TEST_STRING,
|
||||
@@ -79,7 +80,7 @@ describe(SelectAndroidBuildCredentials, () => {
|
||||
});
|
||||
});
|
||||
it('returns buildCredentials of the users choice', async () => {
|
||||
(promptAsync as jest.Mock).mockImplementation(() => ({
|
||||
asMock(promptAsync).mockImplementation(() => ({
|
||||
buildCredentialsResultOrRequestToCreateNew: testAndroidBuildCredentialsFragment,
|
||||
}));
|
||||
const ctx = createCtxMock({
|
||||
|
||||
@@ -50,7 +50,7 @@ export async function askForUserProvidedAsync<T>(
|
||||
export async function getCredentialsFromUserAsync<T>(
|
||||
credentialsSchema: CredentialSchema<T>,
|
||||
initialValues: Partial<T>
|
||||
): Promise<T | null> {
|
||||
): Promise<T> {
|
||||
const results: any = {};
|
||||
for (const question of credentialsSchema.questions) {
|
||||
results[question.field] = await askQuestionAndProcessAnswerAsync(
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
const runInputMethodAsync = jest.fn();
|
||||
|
||||
export { runInputMethodAsync };
|
||||
export const runInputMethodAsync = jest.fn();
|
||||
@@ -1,3 +1 @@
|
||||
const runRegistrationUrlMethodAsync = jest.fn();
|
||||
|
||||
export { runRegistrationUrlMethodAsync };
|
||||
export const runRegistrationUrlMethodAsync = jest.fn();
|
||||
+47
@@ -213,6 +213,8 @@ export type Account = {
|
||||
appleProvisioningProfiles: Array<AppleProvisioningProfile>;
|
||||
appleDevices: Array<AppleDevice>;
|
||||
appleAppSpecificPasswords: Array<AppleAppSpecificPassword>;
|
||||
/** Android credentials for account */
|
||||
googleServiceAccountKeys: Array<GoogleServiceAccountKey>;
|
||||
/** Environment secrets for an account */
|
||||
environmentSecrets: Array<EnvironmentSecret>;
|
||||
/** @deprecated Legacy access tokens are deprecated */
|
||||
@@ -1816,6 +1818,8 @@ export type RootMutation = {
|
||||
androidFcm: AndroidFcmMutation;
|
||||
/** Mutations that modify a Keystore */
|
||||
androidKeystore: AndroidKeystoreMutation;
|
||||
/** Mutations that modify a Google Service Account Key */
|
||||
googleServiceAccountKey: GoogleServiceAccountKeyMutation;
|
||||
/** Mutations that modify an Identifier for an iOS App */
|
||||
appleAppIdentifier: AppleAppIdentifierMutation;
|
||||
/** Mutations that modify an App Specific Password for an Apple User Account */
|
||||
@@ -2074,6 +2078,8 @@ export type AndroidAppCredentialsMutation = {
|
||||
createAndroidAppCredentials?: Maybe<AndroidAppCredentials>;
|
||||
/** Set the FCM push key to be used in an Android app */
|
||||
setFcm?: Maybe<AndroidAppCredentials>;
|
||||
/** Set the Google Service Account Key to be used for submitting an Android app */
|
||||
setGoogleServiceAccountKeyForSubmissions?: Maybe<AndroidAppCredentials>;
|
||||
};
|
||||
|
||||
|
||||
@@ -2089,8 +2095,15 @@ export type AndroidAppCredentialsMutationSetFcmArgs = {
|
||||
fcmId: Scalars['ID'];
|
||||
};
|
||||
|
||||
|
||||
export type AndroidAppCredentialsMutationSetGoogleServiceAccountKeyForSubmissionsArgs = {
|
||||
id: Scalars['ID'];
|
||||
googleServiceAccountKeyId: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type AndroidAppCredentialsInput = {
|
||||
fcmId?: Maybe<Scalars['ID']>;
|
||||
googleServiceAccountKeyForSubmissionsId?: Maybe<Scalars['ID']>;
|
||||
};
|
||||
|
||||
export type AndroidFcmMutation = {
|
||||
@@ -2154,6 +2167,22 @@ export type DeleteAndroidKeystoreResult = {
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type GoogleServiceAccountKeyMutation = {
|
||||
__typename?: 'GoogleServiceAccountKeyMutation';
|
||||
/** Create an Google Service Account Key */
|
||||
createGoogleServiceAccountKey: GoogleServiceAccountKey;
|
||||
};
|
||||
|
||||
|
||||
export type GoogleServiceAccountKeyMutationCreateGoogleServiceAccountKeyArgs = {
|
||||
googleServiceAccountKeyInput: GoogleServiceAccountKeyInput;
|
||||
accountId: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type GoogleServiceAccountKeyInput = {
|
||||
jsonKey: Scalars['JSONObject'];
|
||||
};
|
||||
|
||||
export type AppleAppIdentifierMutation = {
|
||||
__typename?: 'AppleAppIdentifierMutation';
|
||||
/** Create an Identifier for an iOS App */
|
||||
@@ -3957,6 +3986,24 @@ export type DeleteAndroidKeystoreMutation = (
|
||||
) }
|
||||
);
|
||||
|
||||
export type CreateGoogleServiceAccountKeyMutationVariables = Exact<{
|
||||
googleServiceAccountKeyInput: GoogleServiceAccountKeyInput;
|
||||
accountId: Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateGoogleServiceAccountKeyMutation = (
|
||||
{ __typename?: 'RootMutation' }
|
||||
& { googleServiceAccountKey: (
|
||||
{ __typename?: 'GoogleServiceAccountKeyMutation' }
|
||||
& { createGoogleServiceAccountKey: (
|
||||
{ __typename?: 'GoogleServiceAccountKey' }
|
||||
& Pick<GoogleServiceAccountKey, 'id'>
|
||||
& GoogleServiceAccountKeyFragment
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type CommonAndroidAppCredentialsWithBuildCredentialsByApplicationIdentifierQueryVariables = Exact<{
|
||||
projectFullName: Scalars['String'];
|
||||
applicationIdentifier?: Maybe<Scalars['String']>;
|
||||
|
||||
@@ -4,7 +4,7 @@ import gql from 'graphql-tag';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../client';
|
||||
import { AppPrivacy, CreateAppMutation, CreateAppMutationVariables } from '../generated';
|
||||
|
||||
const AppMutation = {
|
||||
export const AppMutation = {
|
||||
async createAppAsync(appInput: {
|
||||
accountId: string;
|
||||
projectName: string;
|
||||
@@ -33,5 +33,3 @@ const AppMutation = {
|
||||
return appId;
|
||||
},
|
||||
};
|
||||
|
||||
export { AppMutation };
|
||||
@@ -21,7 +21,7 @@ export interface BuildResult {
|
||||
deprecationInfo?: EasBuildDeprecationInfo | null;
|
||||
}
|
||||
|
||||
const BuildMutation = {
|
||||
export const BuildMutation = {
|
||||
async createAndroidBuildAsync(input: {
|
||||
appId: string;
|
||||
job: AndroidJobInput;
|
||||
@@ -93,5 +93,3 @@ const BuildMutation = {
|
||||
return nullthrows(data.build?.createIosBuild);
|
||||
},
|
||||
};
|
||||
|
||||
export { BuildMutation };
|
||||
@@ -3,7 +3,7 @@ import gql from 'graphql-tag';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../client';
|
||||
import { PublishUpdateGroupInput, UpdatePublishMutation } from '../generated';
|
||||
|
||||
const PublishMutation = {
|
||||
export const PublishMutation = {
|
||||
async getUploadURLsAsync(contentTypes: string[]): Promise<{ specifications: string[] }> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
@@ -54,5 +54,3 @@ const PublishMutation = {
|
||||
return data.updateBranch.publishUpdateGroups;
|
||||
},
|
||||
};
|
||||
|
||||
export { PublishMutation };
|
||||
@@ -5,7 +5,7 @@ import { Project, ProjectByUsernameAndSlugQuery } from '../generated';
|
||||
|
||||
type ProjectQueryResult = Pick<Project, 'id'>;
|
||||
|
||||
const ProjectQuery = {
|
||||
export const ProjectQuery = {
|
||||
async byUsernameAndSlugAsync(username: string, slug: string): Promise<ProjectQueryResult> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
@@ -27,5 +27,3 @@ const ProjectQuery = {
|
||||
return data.project.byUsernameAndSlug;
|
||||
},
|
||||
};
|
||||
|
||||
export { ProjectQuery };
|
||||
@@ -3,7 +3,7 @@ import gql from 'graphql-tag';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../client';
|
||||
import { AssetMetadataResult, GetAssetMetadataQuery } from '../generated';
|
||||
|
||||
const PublishQuery = {
|
||||
export const PublishQuery = {
|
||||
async getAssetMetadataAsync(storageKeys: string[]): Promise<AssetMetadataResult[]> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
@@ -28,5 +28,3 @@ const PublishQuery = {
|
||||
return data.asset.metadata;
|
||||
},
|
||||
};
|
||||
|
||||
export { PublishQuery };
|
||||
@@ -3,7 +3,7 @@ import gql from 'graphql-tag';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../client';
|
||||
import { CurrentUserQuery } from '../generated';
|
||||
|
||||
const UserQuery = {
|
||||
export const UserQuery = {
|
||||
async currentUserAsync(): Promise<CurrentUserQuery['meActor']> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
@@ -34,5 +34,3 @@ const UserQuery = {
|
||||
return data.meActor;
|
||||
},
|
||||
};
|
||||
|
||||
export { UserQuery };
|
||||
@@ -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 };
|
||||
@@ -192,7 +192,7 @@ describe(promptToCreateProjectIfNotExistsAsync, () => {
|
||||
asMock(ensureProjectExistsAsync).mockImplementation(() => 'new-project-id');
|
||||
|
||||
// user agrees to make new project
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
const exp: any = {};
|
||||
const projectId = await promptToCreateProjectIfNotExistsAsync(exp);
|
||||
expect(projectId).toBe('new-project-id');
|
||||
@@ -210,7 +210,7 @@ describe(promptToCreateProjectIfNotExistsAsync, () => {
|
||||
asMock(ensureProjectExistsAsync).mockImplementation(() => 'new-project-id');
|
||||
|
||||
// user doesnt agree to make new project
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => false);
|
||||
asMock(confirmAsync).mockImplementation(() => false);
|
||||
const exp: any = {};
|
||||
const projectId = await promptToCreateProjectIfNotExistsAsync(exp);
|
||||
expect(projectId).toBe(null);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { exit } from '@oclif/errors';
|
||||
import chalk from 'chalk';
|
||||
import type MetroConfig from 'metro-config';
|
||||
import resolveFrom from 'resolve-from';
|
||||
@@ -27,19 +28,28 @@ export async function validateMetroConfigForManagedWorkflowAsync(
|
||||
Log.warn(
|
||||
'This can result in unexpected and hard to debug issues, like missing assets in the production bundle.'
|
||||
);
|
||||
Log.warn(`We recommend you to abort, fix the ${chalk.bold('metro.config.js')}, and try again.`);
|
||||
if (!ctx.nonInteractive) {
|
||||
Log.warn(
|
||||
`We recommend you to abort, fix the ${chalk.bold('metro.config.js')}, and try again.`
|
||||
);
|
||||
}
|
||||
Log.warn(
|
||||
learnMore('https://docs.expo.dev/guides/customizing-metro/', {
|
||||
learnMoreMessage: 'Learn more on customizing Metro',
|
||||
})
|
||||
);
|
||||
|
||||
if (ctx.nonInteractive) {
|
||||
Log.warn("You've run EAS CLI in non-interactive mode, proceeding...");
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldAbort = await confirmAsync({
|
||||
message: 'Would you like to abort?',
|
||||
});
|
||||
if (shouldAbort) {
|
||||
Log.log('Aborting...');
|
||||
process.exit(1);
|
||||
Log.error('Aborting...');
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import ApiV2Error from '../../ApiV2Error';
|
||||
import { asMock } from '../../__tests__/utils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { Actor, loginAsync } from '../User';
|
||||
import { ensureActorHasUsername, showLoginPromptAsync } from '../actions';
|
||||
@@ -12,12 +13,12 @@ jest.mock('../User', () => ({
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
(promptAsync as jest.Mock).mockReset();
|
||||
(promptAsync as jest.Mock).mockImplementation(() => {
|
||||
asMock(promptAsync).mockReset();
|
||||
asMock(promptAsync).mockImplementation(() => {
|
||||
throw new Error('Should not be called');
|
||||
});
|
||||
|
||||
(loginAsync as jest.Mock).mockReset();
|
||||
asMock(loginAsync).mockReset();
|
||||
});
|
||||
|
||||
const userStub: Actor = {
|
||||
@@ -48,13 +49,13 @@ describe('ensureActorHasUsername', () => {
|
||||
|
||||
describe(showLoginPromptAsync, () => {
|
||||
it('prompts for OTP when 2FA is enabled', async () => {
|
||||
(promptAsync as jest.Mock)
|
||||
asMock(promptAsync)
|
||||
.mockImplementationOnce(() => ({ username: 'hello', password: 'world' }))
|
||||
.mockImplementationOnce(() => ({ otp: '123456' }))
|
||||
.mockImplementation(() => {
|
||||
throw new Error("shouldn't happen");
|
||||
});
|
||||
(loginAsync as jest.Mock)
|
||||
asMock(loginAsync)
|
||||
.mockImplementationOnce(async () => {
|
||||
throw new ApiV2Error({ code: 'testcode', request: {} } as any, {
|
||||
message: 'An OTP is required',
|
||||
@@ -76,20 +77,16 @@ describe(showLoginPromptAsync, () => {
|
||||
|
||||
await showLoginPromptAsync();
|
||||
|
||||
expect(retryUsernamePasswordAuthWithOTPAsync as jest.Mock).toHaveBeenCalledWith(
|
||||
'hello',
|
||||
'world',
|
||||
{
|
||||
secondFactorDevices: [
|
||||
{
|
||||
id: 'p0',
|
||||
is_primary: true,
|
||||
method: UserSecondFactorDeviceMethod.SMS,
|
||||
sms_phone_number: 'testphone',
|
||||
},
|
||||
],
|
||||
smsAutomaticallySent: true,
|
||||
}
|
||||
);
|
||||
expect(retryUsernamePasswordAuthWithOTPAsync).toHaveBeenCalledWith('hello', 'world', {
|
||||
secondFactorDevices: [
|
||||
{
|
||||
id: 'p0',
|
||||
is_primary: true,
|
||||
method: UserSecondFactorDeviceMethod.SMS,
|
||||
sms_phone_number: 'testphone',
|
||||
},
|
||||
],
|
||||
smsAutomaticallySent: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asMock } from '../../__tests__/utils';
|
||||
import { apiClient } from '../../api';
|
||||
import Log from '../../log';
|
||||
import { promptAsync, selectAsync } from '../../prompts';
|
||||
@@ -12,23 +13,23 @@ jest.mock('../User', () => ({
|
||||
jest.mock('../../log');
|
||||
|
||||
beforeEach(() => {
|
||||
(promptAsync as jest.Mock).mockReset();
|
||||
(promptAsync as jest.Mock).mockImplementation(() => {
|
||||
asMock(promptAsync).mockReset();
|
||||
asMock(promptAsync).mockImplementation(() => {
|
||||
throw new Error('Should not be called');
|
||||
});
|
||||
|
||||
(selectAsync as jest.Mock).mockReset();
|
||||
(selectAsync as jest.Mock).mockImplementation(() => {
|
||||
asMock(selectAsync).mockReset();
|
||||
asMock(selectAsync).mockImplementation(() => {
|
||||
throw new Error('Should not be called');
|
||||
});
|
||||
|
||||
(loginAsync as jest.Mock).mockReset();
|
||||
(Log.log as jest.Mock).mockReset();
|
||||
asMock(loginAsync).mockReset();
|
||||
asMock(Log.log).mockReset();
|
||||
});
|
||||
|
||||
describe(retryUsernamePasswordAuthWithOTPAsync, () => {
|
||||
it('shows SMS OTP prompt when SMS is primary and code was automatically sent', async () => {
|
||||
(promptAsync as jest.Mock)
|
||||
asMock(promptAsync)
|
||||
.mockImplementationOnce(() => ({ otp: 'hello' }))
|
||||
.mockImplementation(() => {
|
||||
throw new Error("shouldn't happen");
|
||||
@@ -50,11 +51,11 @@ describe(retryUsernamePasswordAuthWithOTPAsync, () => {
|
||||
'One-time password was sent to the phone number ending in testphone.'
|
||||
);
|
||||
|
||||
expect(loginAsync as jest.Mock).toHaveBeenCalledTimes(1);
|
||||
expect(asMock(loginAsync)).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('shows authenticator OTP prompt when authenticator is primary', async () => {
|
||||
(promptAsync as jest.Mock)
|
||||
asMock(promptAsync)
|
||||
.mockImplementationOnce(() => ({ otp: 'hello' }))
|
||||
.mockImplementation(() => {
|
||||
throw new Error("shouldn't happen");
|
||||
@@ -73,18 +74,18 @@ describe(retryUsernamePasswordAuthWithOTPAsync, () => {
|
||||
});
|
||||
|
||||
expect(Log.log).toHaveBeenCalledWith('One-time password from authenticator required.');
|
||||
expect(loginAsync as jest.Mock).toHaveBeenCalledTimes(1);
|
||||
expect(asMock(loginAsync)).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('shows menu when user bails on primary', async () => {
|
||||
(promptAsync as jest.Mock)
|
||||
asMock(promptAsync)
|
||||
.mockImplementationOnce(() => ({ otp: null }))
|
||||
.mockImplementationOnce(() => ({ otp: 'hello' })) // second time it is prompted after selecting backup code
|
||||
.mockImplementation(() => {
|
||||
throw new Error("shouldn't happen");
|
||||
});
|
||||
|
||||
(selectAsync as any)
|
||||
asMock(selectAsync)
|
||||
.mockImplementationOnce(() => -1)
|
||||
.mockImplementation(() => {
|
||||
throw new Error("shouldn't happen");
|
||||
@@ -108,12 +109,12 @@ describe(retryUsernamePasswordAuthWithOTPAsync, () => {
|
||||
smsAutomaticallySent: false,
|
||||
});
|
||||
|
||||
expect((selectAsync as any).mock.calls.length).toEqual(1);
|
||||
expect(loginAsync as jest.Mock).toHaveBeenCalledTimes(1);
|
||||
expect(asMock(selectAsync).mock.calls.length).toEqual(1);
|
||||
expect(asMock(loginAsync)).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('shows a warning when when user bails on primary and does not have any secondary set up', async () => {
|
||||
(promptAsync as jest.Mock)
|
||||
asMock(promptAsync)
|
||||
.mockImplementationOnce(() => ({ otp: null }))
|
||||
.mockImplementation(() => {
|
||||
throw new Error("shouldn't happen");
|
||||
@@ -137,14 +138,14 @@ describe(retryUsernamePasswordAuthWithOTPAsync, () => {
|
||||
});
|
||||
|
||||
it('prompts for authenticator OTP when user selects authenticator secondary', async () => {
|
||||
(promptAsync as jest.Mock)
|
||||
asMock(promptAsync)
|
||||
.mockImplementationOnce(() => ({ otp: null }))
|
||||
.mockImplementationOnce(() => ({ otp: 'hello' })) // second time it is prompted after selecting backup code
|
||||
.mockImplementation(() => {
|
||||
throw new Error("shouldn't happen");
|
||||
});
|
||||
|
||||
(selectAsync as any)
|
||||
asMock(selectAsync)
|
||||
.mockImplementationOnce(() => -1)
|
||||
.mockImplementation(() => {
|
||||
throw new Error("shouldn't happen");
|
||||
@@ -168,24 +169,24 @@ describe(retryUsernamePasswordAuthWithOTPAsync, () => {
|
||||
smsAutomaticallySent: false,
|
||||
});
|
||||
|
||||
expect((promptAsync as jest.Mock).mock.calls.length).toBe(2); // first OTP, second OTP
|
||||
expect(asMock(promptAsync).mock.calls.length).toBe(2); // first OTP, second OTP
|
||||
});
|
||||
|
||||
it('requests SMS OTP and prompts for SMS OTP when user selects SMS secondary', async () => {
|
||||
(promptAsync as jest.Mock)
|
||||
asMock(promptAsync)
|
||||
.mockImplementationOnce(() => ({ otp: null }))
|
||||
.mockImplementationOnce(() => ({ otp: 'hello' })) // second time it is prompted after selecting backup code
|
||||
.mockImplementation(() => {
|
||||
throw new Error("shouldn't happen");
|
||||
});
|
||||
|
||||
(selectAsync as any)
|
||||
asMock(selectAsync)
|
||||
.mockImplementationOnce(() => 0)
|
||||
.mockImplementation(() => {
|
||||
throw new Error("shouldn't happen");
|
||||
});
|
||||
|
||||
(apiClient.post as jest.Mock).mockReturnValueOnce({
|
||||
asMock(apiClient.post).mockReturnValueOnce({
|
||||
json: () => Promise.resolve({ data: { sessionSecret: 'SESSION_SECRET' } }),
|
||||
});
|
||||
|
||||
@@ -207,8 +208,8 @@ describe(retryUsernamePasswordAuthWithOTPAsync, () => {
|
||||
smsAutomaticallySent: false,
|
||||
});
|
||||
|
||||
expect((promptAsync as jest.Mock).mock.calls.length).toBe(2); // first OTP, second OTP
|
||||
expect((apiClient.post as jest.Mock).mock.calls[0]).toEqual([
|
||||
expect(asMock(promptAsync).mock.calls.length).toBe(2); // first OTP, second OTP
|
||||
expect(asMock(apiClient.post).mock.calls[0]).toEqual([
|
||||
'auth/send-sms-otp',
|
||||
{
|
||||
json: {
|
||||
@@ -221,13 +222,13 @@ describe(retryUsernamePasswordAuthWithOTPAsync, () => {
|
||||
});
|
||||
|
||||
it('exits when user bails on primary and backup', async () => {
|
||||
(promptAsync as jest.Mock)
|
||||
asMock(promptAsync)
|
||||
.mockImplementationOnce(() => ({ otp: null }))
|
||||
.mockImplementation(() => {
|
||||
throw new Error("shouldn't happen");
|
||||
});
|
||||
|
||||
(selectAsync as any)
|
||||
asMock(selectAsync)
|
||||
.mockImplementationOnce(() => -2)
|
||||
.mockImplementation(() => {
|
||||
throw new Error("shouldn't happen");
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"description": "A library for interacting with the eas.json",
|
||||
"version": "0.28.2",
|
||||
"version": "0.29.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/eas-build-job": "0.2.46",
|
||||
"@expo/eas-build-job": "0.2.48",
|
||||
"@expo/json-file": "8.2.33",
|
||||
"env-string": "1.0.1",
|
||||
"fs-extra": "10.0.0",
|
||||
|
||||
@@ -889,10 +889,10 @@
|
||||
slugify "^1.3.4"
|
||||
sucrase "^3.20.0"
|
||||
|
||||
"@expo/[email protected]6":
|
||||
version "0.2.46"
|
||||
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-0.2.46.tgz#2742fd21a0fb0fdd2857705f931a4c10c0ad45f7"
|
||||
integrity sha512-D3/RcwlLdywmT7UCtOOylLGy47W+o4UwGxrLH1j8IVYTJxyT+s/n1ss5ipbLkssEPPUfibTBysgZfwVGdCSWkQ==
|
||||
"@expo/[email protected]8":
|
||||
version "0.2.48"
|
||||
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-0.2.48.tgz#ebc2abea02cb3f23be9714318bea04515980b03d"
|
||||
integrity sha512-QWg44uXndNw3ZKrP+1FD2Li1VSQoSWn94iQ6j6lwMfVfFpp6DiiZ73HXf4Bxix52cl5DTmpaE7hxYyhxSvV0yA==
|
||||
dependencies:
|
||||
joi "^17.4.2"
|
||||
|
||||
|
||||
Reference in new issue
Block a user