eas-cli 0.31.0 → 0.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -28
- package/build/build/android/UpdatesModule.js +4 -15
- package/build/build/android/configure.js +1 -1
- package/build/build/configure.js +12 -6
- package/build/build/ios/UpdatesModule.js +4 -16
- package/build/build/ios/configure.js +1 -1
- package/build/build/utils/appJson.d.ts +1 -0
- package/build/build/utils/appJson.js +13 -4
- package/build/build/utils/devClient.d.ts +4 -4
- package/build/build/utils/devClient.js +8 -16
- package/build/commands/branch/publish.js +14 -43
- package/build/commands/build/index.d.ts +1 -1
- package/build/commands/build/index.js +29 -24
- package/build/commands/submit.js +16 -11
- package/build/credentials/android/actions/SetupGoogleServiceAccountKey.js +3 -3
- package/build/credentials/context.d.ts +1 -1
- package/build/credentials/context.js +5 -5
- package/build/credentials/errors.d.ts +3 -0
- package/build/credentials/errors.js +7 -1
- package/build/credentials/ios/IosCredentialsProvider.js +1 -1
- package/build/credentials/ios/actions/AscApiKeyUtils.d.ts +5 -0
- package/build/credentials/ios/actions/AscApiKeyUtils.js +63 -0
- package/build/credentials/ios/actions/DistributionCertificateUtils.js +5 -5
- package/build/credentials/ios/actions/SetupTargetBuildCredentials.js +1 -1
- package/build/credentials/ios/appstore/AppStoreApi.d.ts +7 -1
- package/build/credentials/ios/appstore/AppStoreApi.js +17 -0
- package/build/credentials/ios/appstore/Credentials.js +3 -3
- package/build/credentials/ios/appstore/Credentials.types.d.ts +13 -0
- package/build/credentials/ios/appstore/ascApiKey.d.ts +9 -0
- package/build/credentials/ios/appstore/ascApiKey.js +99 -0
- package/build/credentials/ios/credentials.d.ts +17 -0
- package/build/credentials/ios/credentials.js +16 -1
- package/build/credentials/manager/ManageAndroid.js +1 -1
- package/build/credentials/manager/ManageIos.js +1 -1
- package/build/graphql/generated.d.ts +49 -113
- package/build/graphql/generated.js +24 -28
- package/build/log.d.ts +11 -1
- package/build/log.js +21 -10
- package/build/project/android/applicationId.d.ts +1 -1
- package/build/project/android/applicationId.js +7 -6
- package/build/project/ios/bundleIdentifier.d.ts +1 -1
- package/build/project/ios/bundleIdentifier.js +7 -6
- package/build/submit/ArchiveSource.d.ts +7 -2
- package/build/submit/ArchiveSource.js +94 -11
- package/build/submit/ios/AscApiKeySource.d.ts +28 -0
- package/build/submit/ios/AscApiKeySource.js +51 -0
- package/build/submit/ios/IosSubmitCommand.d.ts +2 -0
- package/build/submit/ios/IosSubmitCommand.js +55 -3
- package/build/submit/ios/IosSubmitter.d.ts +3 -1
- package/build/submit/ios/IosSubmitter.js +48 -4
- package/build/submit/submit.js +1 -1
- package/build/submit/utils/builds.d.ts +3 -1
- package/build/submit/utils/builds.js +6 -6
- package/build/utils/profiles.d.ts +11 -0
- package/build/utils/profiles.js +39 -0
- package/oclif.manifest.json +1 -1
- package/package.json +6 -6
|
@@ -29,6 +29,7 @@ const context_2 = require("../../submit/context");
|
|
|
29
29
|
const submit_1 = require("../../submit/submit");
|
|
30
30
|
const urls_1 = require("../../submit/utils/urls");
|
|
31
31
|
const json_1 = require("../../utils/json");
|
|
32
|
+
const profiles_1 = require("../../utils/profiles");
|
|
32
33
|
const vcs_1 = (0, tslib_1.__importDefault)(require("../../vcs"));
|
|
33
34
|
class Build extends EasCommand_1.default {
|
|
34
35
|
constructor() {
|
|
@@ -49,41 +50,49 @@ class Build extends EasCommand_1.default {
|
|
|
49
50
|
await (0, repository_1.ensureRepoIsCleanAsync)(flags.nonInteractive);
|
|
50
51
|
await (0, configure_1.ensureProjectConfiguredAsync)(projectDir, requestedPlatform);
|
|
51
52
|
const platforms = (0, platform_1.toPlatforms)(requestedPlatform);
|
|
52
|
-
|
|
53
|
+
const easJsonReader = new eas_json_1.EasJsonReader(projectDir);
|
|
54
|
+
const buildProfiles = await (0, profiles_1.getProfilesAsync)({
|
|
53
55
|
platforms,
|
|
56
|
+
profileName: flags.profile,
|
|
57
|
+
async readProfileAsync(platform, profileName) {
|
|
58
|
+
return await easJsonReader.readBuildProfileAsync(platform, profileName);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
await (0, devClient_1.ensureExpoDevClientInstalledForDevClientBuildsAsync)({
|
|
54
62
|
projectDir,
|
|
55
|
-
profile: flags.profile,
|
|
56
63
|
nonInteractive: flags.nonInteractive,
|
|
64
|
+
buildProfiles,
|
|
57
65
|
});
|
|
58
66
|
const startedBuilds = [];
|
|
59
67
|
const buildCtxByPlatform = {};
|
|
60
|
-
for (const
|
|
68
|
+
for (const buildProfile of buildProfiles) {
|
|
61
69
|
const { build: maybeBuild, buildCtx } = await this.prepareAndStartBuildAsync({
|
|
62
70
|
projectDir,
|
|
63
|
-
platform,
|
|
64
71
|
flags,
|
|
65
72
|
moreBuilds: platforms.length > 1,
|
|
73
|
+
buildProfile,
|
|
66
74
|
});
|
|
67
75
|
if (maybeBuild) {
|
|
68
|
-
startedBuilds.push(maybeBuild);
|
|
76
|
+
startedBuilds.push({ build: maybeBuild, buildProfile });
|
|
69
77
|
}
|
|
70
|
-
buildCtxByPlatform[(0, AppPlatform_1.toAppPlatform)(platform)] = buildCtx;
|
|
78
|
+
buildCtxByPlatform[(0, AppPlatform_1.toAppPlatform)(buildProfile.platform)] = buildCtx;
|
|
71
79
|
}
|
|
72
80
|
if (flags.local) {
|
|
73
81
|
return;
|
|
74
82
|
}
|
|
75
83
|
log_1.default.newLine();
|
|
76
|
-
(0, printBuildInfo_1.printLogsUrls)(startedBuilds);
|
|
84
|
+
(0, printBuildInfo_1.printLogsUrls)(startedBuilds.map(startedBuild => startedBuild.build));
|
|
77
85
|
log_1.default.newLine();
|
|
78
86
|
const submissions = [];
|
|
79
87
|
if (flags.autoSubmit) {
|
|
80
|
-
for (const
|
|
88
|
+
for (const startedBuild of startedBuilds) {
|
|
81
89
|
const submission = await this.prepareAndStartSubmissionAsync({
|
|
82
|
-
build,
|
|
83
|
-
credentialsCtx: (0, nullthrows_1.default)((_a = buildCtxByPlatform[build.platform]) === null || _a === void 0 ? void 0 : _a.credentialsCtx),
|
|
90
|
+
build: startedBuild.build,
|
|
91
|
+
credentialsCtx: (0, nullthrows_1.default)((_a = buildCtxByPlatform[startedBuild.build.platform]) === null || _a === void 0 ? void 0 : _a.credentialsCtx),
|
|
84
92
|
flags,
|
|
85
93
|
moreBuilds: startedBuilds.length > 1,
|
|
86
94
|
projectDir,
|
|
95
|
+
buildProfile: startedBuild.buildProfile,
|
|
87
96
|
});
|
|
88
97
|
submissions.push(submission);
|
|
89
98
|
}
|
|
@@ -94,7 +103,7 @@ class Build extends EasCommand_1.default {
|
|
|
94
103
|
if (!flags.wait) {
|
|
95
104
|
return;
|
|
96
105
|
}
|
|
97
|
-
const builds = await (0, build_2.waitForBuildEndAsync)(startedBuilds.map(build => build.id));
|
|
106
|
+
const builds = await (0, build_2.waitForBuildEndAsync)(startedBuilds.map(({ build }) => build.id));
|
|
98
107
|
(0, printBuildInfo_1.printBuildResults)(builds, flags.json);
|
|
99
108
|
const haveAllBuildsFailedOrCanceled = builds.every(build => (build === null || build === void 0 ? void 0 : build.status) && [generated_1.BuildStatus.Errored, generated_1.BuildStatus.Canceled].includes(build === null || build === void 0 ? void 0 : build.status));
|
|
100
109
|
if (haveAllBuildsFailedOrCanceled || !flags.autoSubmit) {
|
|
@@ -135,7 +144,7 @@ class Build extends EasCommand_1.default {
|
|
|
135
144
|
return {
|
|
136
145
|
requestedPlatform,
|
|
137
146
|
skipProjectConfiguration: flags['skip-project-configuration'],
|
|
138
|
-
profile,
|
|
147
|
+
profile: profile !== null && profile !== void 0 ? profile : null,
|
|
139
148
|
nonInteractive,
|
|
140
149
|
local: flags['local'],
|
|
141
150
|
wait: flags['wait'],
|
|
@@ -145,22 +154,20 @@ class Build extends EasCommand_1.default {
|
|
|
145
154
|
submitProfile: (_a = flags['auto-submit-with-profile']) !== null && _a !== void 0 ? _a : profile,
|
|
146
155
|
};
|
|
147
156
|
}
|
|
148
|
-
async prepareAndStartBuildAsync({ projectDir,
|
|
149
|
-
const easJsonReader = new eas_json_1.EasJsonReader(projectDir);
|
|
150
|
-
const buildProfile = await easJsonReader.readBuildProfileAsync(platform, flags.profile);
|
|
157
|
+
async prepareAndStartBuildAsync({ projectDir, flags, moreBuilds, buildProfile, }) {
|
|
151
158
|
const buildCtx = await (0, context_1.createBuildContextAsync)({
|
|
152
|
-
buildProfileName:
|
|
159
|
+
buildProfileName: buildProfile.profileName,
|
|
153
160
|
clearCache: flags.clearCache,
|
|
154
|
-
buildProfile,
|
|
161
|
+
buildProfile: buildProfile.profile,
|
|
155
162
|
local: flags.local,
|
|
156
163
|
nonInteractive: flags.nonInteractive,
|
|
157
|
-
platform,
|
|
164
|
+
platform: buildProfile.platform,
|
|
158
165
|
projectDir,
|
|
159
166
|
skipProjectConfiguration: flags.skipProjectConfiguration,
|
|
160
167
|
});
|
|
161
168
|
if (moreBuilds) {
|
|
162
169
|
log_1.default.newLine();
|
|
163
|
-
const appPlatform = (0, AppPlatform_1.toAppPlatform)(platform);
|
|
170
|
+
const appPlatform = (0, AppPlatform_1.toAppPlatform)(buildProfile.platform);
|
|
164
171
|
log_1.default.log(`${platform_1.appPlatformEmojis[appPlatform]} ${chalk_1.default.bold(`${platform_1.appPlatformDisplayNames[appPlatform]} build`)}`);
|
|
165
172
|
}
|
|
166
173
|
if (buildCtx.workflow === eas_build_job_1.Workflow.MANAGED && !this.metroConfigValidated) {
|
|
@@ -186,10 +193,9 @@ class Build extends EasCommand_1.default {
|
|
|
186
193
|
}
|
|
187
194
|
return await sendBuildRequestAsync();
|
|
188
195
|
}
|
|
189
|
-
async prepareAndStartSubmissionAsync({ build, credentialsCtx, flags, moreBuilds, projectDir, }) {
|
|
196
|
+
async prepareAndStartSubmissionAsync({ build, credentialsCtx, flags, moreBuilds, projectDir, buildProfile, }) {
|
|
190
197
|
const easJsonReader = new eas_json_1.EasJsonReader(projectDir);
|
|
191
198
|
const platform = (0, AppPlatform_1.toPlatform)(build.platform);
|
|
192
|
-
const buildProfile = await easJsonReader.readBuildProfileAsync(platform, flags.profile);
|
|
193
199
|
const submitProfile = await easJsonReader.readSubmitProfileAsync(platform, flags.submitProfile);
|
|
194
200
|
const submissionCtx = await (0, context_2.createSubmissionContextAsync)({
|
|
195
201
|
platform,
|
|
@@ -198,7 +204,7 @@ class Build extends EasCommand_1.default {
|
|
|
198
204
|
profile: submitProfile,
|
|
199
205
|
archiveFlags: { id: build.id },
|
|
200
206
|
nonInteractive: flags.nonInteractive,
|
|
201
|
-
env: buildProfile.env,
|
|
207
|
+
env: buildProfile.profile.env,
|
|
202
208
|
credentialsCtx,
|
|
203
209
|
});
|
|
204
210
|
if (moreBuilds) {
|
|
@@ -234,8 +240,7 @@ Build.flags = {
|
|
|
234
240
|
description: 'Skip project configuration',
|
|
235
241
|
}),
|
|
236
242
|
profile: command_1.flags.string({
|
|
237
|
-
|
|
238
|
-
description: 'Name of the build profile from eas.json',
|
|
243
|
+
description: 'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
|
|
239
244
|
helpValue: 'PROFILE_NAME',
|
|
240
245
|
}),
|
|
241
246
|
'non-interactive': command_1.flags.boolean({
|
package/build/commands/submit.js
CHANGED
|
@@ -15,6 +15,7 @@ const projectUtils_1 = require("../project/projectUtils");
|
|
|
15
15
|
const context_1 = require("../submit/context");
|
|
16
16
|
const submit_1 = require("../submit/submit");
|
|
17
17
|
const urls_1 = require("../submit/utils/urls");
|
|
18
|
+
const profiles_1 = require("../utils/profiles");
|
|
18
19
|
class Submit extends EasCommand_1.default {
|
|
19
20
|
async runAsync() {
|
|
20
21
|
const { flags: rawFlags } = this.parse(Submit);
|
|
@@ -27,22 +28,28 @@ class Submit extends EasCommand_1.default {
|
|
|
27
28
|
process.exitCode = 1;
|
|
28
29
|
return;
|
|
29
30
|
}
|
|
30
|
-
const easJsonReader = new eas_json_1.EasJsonReader(projectDir);
|
|
31
31
|
const platforms = (0, platform_1.toPlatforms)(flags.requestedPlatform);
|
|
32
|
+
const easJsonReader = new eas_json_1.EasJsonReader(projectDir);
|
|
33
|
+
const submissionProfiles = await (0, profiles_1.getProfilesAsync)({
|
|
34
|
+
platforms,
|
|
35
|
+
profileName: flags.profile,
|
|
36
|
+
async readProfileAsync(platform, profileName) {
|
|
37
|
+
return await easJsonReader.readSubmitProfileAsync(platform, profileName);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
32
40
|
const submissions = [];
|
|
33
|
-
for (const
|
|
34
|
-
const profile = await easJsonReader.readSubmitProfileAsync(platform, flags.profile);
|
|
41
|
+
for (const submissionProfile of submissionProfiles) {
|
|
35
42
|
const ctx = await (0, context_1.createSubmissionContextAsync)({
|
|
36
|
-
platform,
|
|
43
|
+
platform: submissionProfile.platform,
|
|
37
44
|
projectDir,
|
|
38
45
|
projectId,
|
|
39
|
-
profile,
|
|
46
|
+
profile: submissionProfile.profile,
|
|
40
47
|
archiveFlags: flags.archiveFlags,
|
|
41
48
|
nonInteractive: flags.nonInteractive,
|
|
42
49
|
});
|
|
43
|
-
if (
|
|
50
|
+
if (submissionProfiles.length > 1) {
|
|
44
51
|
log_1.default.newLine();
|
|
45
|
-
const appPlatform = (0, AppPlatform_1.toAppPlatform)(platform);
|
|
52
|
+
const appPlatform = (0, AppPlatform_1.toAppPlatform)(submissionProfile.platform);
|
|
46
53
|
log_1.default.log(`${platform_1.appPlatformEmojis[appPlatform]} ${chalk_1.default.bold(`${platform_1.appPlatformDisplayNames[appPlatform]} submission`)}`);
|
|
47
54
|
}
|
|
48
55
|
const submission = await (0, submit_1.submitAsync)(ctx);
|
|
@@ -77,9 +84,7 @@ class Submit extends EasCommand_1.default {
|
|
|
77
84
|
}
|
|
78
85
|
exports.default = Submit;
|
|
79
86
|
Submit.description = `submit build archive to app store
|
|
80
|
-
See how to configure submits with eas.json: ${(0, log_1.
|
|
81
|
-
learnMoreMessage: '',
|
|
82
|
-
})}`;
|
|
87
|
+
See how to configure submits with eas.json: ${(0, log_1.link)('https://docs.expo.dev/submit/eas-json/')}`;
|
|
83
88
|
Submit.aliases = ['build:submit'];
|
|
84
89
|
Submit.flags = {
|
|
85
90
|
platform: command_1.flags.enum({
|
|
@@ -87,7 +92,7 @@ Submit.flags = {
|
|
|
87
92
|
options: ['android', 'ios', 'all'],
|
|
88
93
|
}),
|
|
89
94
|
profile: command_1.flags.string({
|
|
90
|
-
description: 'Name of the submit profile from eas.json. Defaults to "
|
|
95
|
+
description: 'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.',
|
|
91
96
|
}),
|
|
92
97
|
latest: command_1.flags.boolean({
|
|
93
98
|
description: 'Submit the latest build for specified platform',
|
|
@@ -14,14 +14,14 @@ class SetupGoogleServiceAccountKey {
|
|
|
14
14
|
this.app = app;
|
|
15
15
|
}
|
|
16
16
|
async runAsync(ctx) {
|
|
17
|
-
if (ctx.nonInteractive) {
|
|
18
|
-
throw new errors_1.MissingCredentialsNonInteractiveError('Google Service Account Keys cannot be set up in --non-interactive mode.');
|
|
19
|
-
}
|
|
20
17
|
const isKeySetup = await this.isGoogleServiceAccountKeySetupAsync(ctx);
|
|
21
18
|
if (isKeySetup) {
|
|
22
19
|
log_1.default.succeed('Google Service Account Key already set up.');
|
|
23
20
|
return (0, nullthrows_1.default)(await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(this.app), 'androidAppCredentials cannot be null if google service account key is already set up');
|
|
24
21
|
}
|
|
22
|
+
if (ctx.nonInteractive) {
|
|
23
|
+
throw new errors_1.MissingCredentialsNonInteractiveError('Google Service Account Keys cannot be set up in --non-interactive mode.');
|
|
24
|
+
}
|
|
25
25
|
const keysForAccount = await ctx.android.getGoogleServiceAccountKeysForAccountAsync(this.app.account);
|
|
26
26
|
let googleServiceAccountKey = null;
|
|
27
27
|
if (keysForAccount.length === 0) {
|
|
@@ -23,10 +23,10 @@ class CredentialsContext {
|
|
|
23
23
|
this.projectDir = options.projectDir;
|
|
24
24
|
this.user = options.user;
|
|
25
25
|
this.nonInteractive = (_a = options.nonInteractive) !== null && _a !== void 0 ? _a : false;
|
|
26
|
-
this.
|
|
27
|
-
if (!this.
|
|
26
|
+
this.resolvedExp = options.exp;
|
|
27
|
+
if (!this.resolvedExp) {
|
|
28
28
|
try {
|
|
29
|
-
this.
|
|
29
|
+
this.resolvedExp = (0, expoConfig_1.getExpoConfig)(options.projectDir);
|
|
30
30
|
}
|
|
31
31
|
catch (error) {
|
|
32
32
|
// ignore error, context might be created outside of expo project
|
|
@@ -34,11 +34,11 @@ class CredentialsContext {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
get hasProjectContext() {
|
|
37
|
-
return !!this.
|
|
37
|
+
return !!this.resolvedExp;
|
|
38
38
|
}
|
|
39
39
|
get exp() {
|
|
40
40
|
this.ensureProjectContext();
|
|
41
|
-
return this.
|
|
41
|
+
return this.resolvedExp;
|
|
42
42
|
}
|
|
43
43
|
ensureProjectContext() {
|
|
44
44
|
if (this.hasProjectContext) {
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MissingCredentialsNonInteractiveError = void 0;
|
|
3
|
+
exports.MissingCredentialsError = exports.MissingCredentialsNonInteractiveError = void 0;
|
|
4
4
|
class MissingCredentialsNonInteractiveError extends Error {
|
|
5
5
|
constructor(message) {
|
|
6
6
|
super(message !== null && message !== void 0 ? message : 'Credentials are not set up. Please run this command again in interactive mode.');
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
exports.MissingCredentialsNonInteractiveError = MissingCredentialsNonInteractiveError;
|
|
10
|
+
class MissingCredentialsError extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message !== null && message !== void 0 ? message : 'Credentials are not set up.');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.MissingCredentialsError = MissingCredentialsError;
|
|
@@ -65,7 +65,7 @@ class IosCredentialsProvider {
|
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
67
|
const confirmSetup = await (0, prompts_1.confirmAsync)({
|
|
68
|
-
message: `Would you like to
|
|
68
|
+
message: `Would you like to set up Push Notifications for your project?`,
|
|
69
69
|
});
|
|
70
70
|
if (!confirmSetup) {
|
|
71
71
|
return null;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { AscApiKey } from '../appstore/Credentials.types';
|
|
2
|
+
import { AscApiKeyPath, MinimalAscApiKey } from '../credentials';
|
|
3
|
+
export declare function promptForAscApiKeyAsync(): Promise<AscApiKeyPath>;
|
|
4
|
+
export declare function promptForIssuerIdAsync(): Promise<string>;
|
|
5
|
+
export declare function getMinimalAscApiKeyAsync(ascApiKey: AscApiKey): Promise<MinimalAscApiKey>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getMinimalAscApiKeyAsync = exports.promptForIssuerIdAsync = exports.promptForAscApiKeyAsync = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const chalk_1 = (0, tslib_1.__importDefault)(require("chalk"));
|
|
6
|
+
const fs_extra_1 = (0, tslib_1.__importDefault)(require("fs-extra"));
|
|
7
|
+
const path_1 = (0, tslib_1.__importDefault)(require("path"));
|
|
8
|
+
const uuid = (0, tslib_1.__importStar)(require("uuid"));
|
|
9
|
+
const log_1 = (0, tslib_1.__importStar)(require("../../../log"));
|
|
10
|
+
const prompts_1 = require("../../../prompts");
|
|
11
|
+
const promptForCredentials_1 = require("../../utils/promptForCredentials");
|
|
12
|
+
const credentials_1 = require("../credentials");
|
|
13
|
+
async function promptForAscApiKeyAsync() {
|
|
14
|
+
var _a, _b;
|
|
15
|
+
log_1.default.log(chalk_1.default.bold('An App Store Connect Api key is required to upload your app to the Apple App Store'));
|
|
16
|
+
log_1.default.log(`If you're not sure what this is or how to create one, ${(0, log_1.learnMore)('https://expo.fyi/creating-asc-api-key')}`);
|
|
17
|
+
const { keyP8Path } = await (0, prompts_1.promptAsync)({
|
|
18
|
+
type: 'text',
|
|
19
|
+
name: 'keyP8Path',
|
|
20
|
+
message: 'Path to App Store Connect Api Key:',
|
|
21
|
+
initial: 'AuthKey_ABCD.p8',
|
|
22
|
+
// eslint-disable-next-line async-protect/async-suffix
|
|
23
|
+
validate: async (filePath) => {
|
|
24
|
+
try {
|
|
25
|
+
const stats = await fs_extra_1.default.stat(filePath);
|
|
26
|
+
if (stats.isFile()) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return 'Input is not a file.';
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return 'File does not exist.';
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const regex = /^AuthKey_(?<keyId>\w+)\.p8$/; // Common ASC Api file name downloaded from Apple
|
|
37
|
+
const bestEffortKeyId = (_b = (_a = path_1.default.basename(keyP8Path).match(regex)) === null || _a === void 0 ? void 0 : _a.groups) === null || _b === void 0 ? void 0 : _b.keyId;
|
|
38
|
+
const ascApiKeyMetadata = await (0, promptForCredentials_1.getCredentialsFromUserAsync)(credentials_1.ascApiKeyMetadataSchema, {
|
|
39
|
+
keyId: bestEffortKeyId,
|
|
40
|
+
});
|
|
41
|
+
return { ...ascApiKeyMetadata, keyP8Path };
|
|
42
|
+
}
|
|
43
|
+
exports.promptForAscApiKeyAsync = promptForAscApiKeyAsync;
|
|
44
|
+
async function promptForIssuerIdAsync() {
|
|
45
|
+
log_1.default.log(chalk_1.default.bold('An App Store Connect Issuer ID is required'));
|
|
46
|
+
log_1.default.log(`If you're not sure what this is or how to find yours, ${(0, log_1.learnMore)('https://expo.fyi/asc-issuer-id')}`);
|
|
47
|
+
const { issuerId } = await (0, prompts_1.promptAsync)({
|
|
48
|
+
type: 'text',
|
|
49
|
+
name: 'issuerId',
|
|
50
|
+
message: 'App Store Connect Issuer ID:',
|
|
51
|
+
validate: (input) => uuid.validate(input),
|
|
52
|
+
});
|
|
53
|
+
return issuerId;
|
|
54
|
+
}
|
|
55
|
+
exports.promptForIssuerIdAsync = promptForIssuerIdAsync;
|
|
56
|
+
async function getMinimalAscApiKeyAsync(ascApiKey) {
|
|
57
|
+
var _a;
|
|
58
|
+
return {
|
|
59
|
+
...ascApiKey,
|
|
60
|
+
issuerId: (_a = ascApiKey.issuerId) !== null && _a !== void 0 ? _a : (await promptForIssuerIdAsync()),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
exports.getMinimalAscApiKeyAsync = getMinimalAscApiKeyAsync;
|
|
@@ -37,7 +37,7 @@ function formatDistributionCertificate(distributionCertificate, validSerialNumbe
|
|
|
37
37
|
return line;
|
|
38
38
|
}
|
|
39
39
|
exports.formatDistributionCertificate = formatDistributionCertificate;
|
|
40
|
-
async function
|
|
40
|
+
async function selectDistributionCertificateAsync(distCerts, validDistributionCertificates) {
|
|
41
41
|
const validDistCertSerialNumbers = validDistributionCertificates === null || validDistributionCertificates === void 0 ? void 0 : validDistributionCertificates.map(distCert => distCert.serialNumber);
|
|
42
42
|
const { chosenDistCert } = await (0, prompts_1.promptAsync)({
|
|
43
43
|
type: 'select',
|
|
@@ -60,12 +60,12 @@ async function selectDistributionCertificateWithDependenciesAsync(ctx, account)
|
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
if (!ctx.appStore.authCtx) {
|
|
63
|
-
return
|
|
63
|
+
return selectDistributionCertificateAsync(distCertsForAccount);
|
|
64
64
|
}
|
|
65
65
|
// get valid certs on the developer portal
|
|
66
66
|
const certInfoFromApple = await ctx.appStore.listDistributionCertificatesAsync();
|
|
67
67
|
const validDistCerts = await (0, CredentialsUtils_1.filterRevokedDistributionCertsFromEasServers)(distCertsForAccount, certInfoFromApple);
|
|
68
|
-
return
|
|
68
|
+
return selectDistributionCertificateAsync(distCertsForAccount, validDistCerts);
|
|
69
69
|
}
|
|
70
70
|
exports.selectDistributionCertificateWithDependenciesAsync = selectDistributionCertificateWithDependenciesAsync;
|
|
71
71
|
/**
|
|
@@ -79,7 +79,7 @@ async function selectValidDistributionCertificateAsync(ctx, appLookupParams) {
|
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
81
81
|
if (!ctx.appStore.authCtx) {
|
|
82
|
-
return
|
|
82
|
+
return selectDistributionCertificateAsync(distCertsForAccount);
|
|
83
83
|
}
|
|
84
84
|
// filter by apple team
|
|
85
85
|
(0, assert_1.default)(ctx.appStore.authCtx, 'authentication to the Apple App Store is required');
|
|
@@ -91,7 +91,7 @@ async function selectValidDistributionCertificateAsync(ctx, appLookupParams) {
|
|
|
91
91
|
const certInfoFromApple = await ctx.appStore.listDistributionCertificatesAsync();
|
|
92
92
|
const validDistCerts = (0, CredentialsUtils_1.filterRevokedDistributionCertsFromEasServers)(distCertsForAppleTeam, certInfoFromApple);
|
|
93
93
|
log_1.default.log(`${validDistCerts.length}/${distCertsForAccount.length} Distribution Certificates are currently valid for Apple Team ${(_a = ctx.appStore.authCtx) === null || _a === void 0 ? void 0 : _a.team.id}.`);
|
|
94
|
-
return
|
|
94
|
+
return selectDistributionCertificateAsync(validDistCerts);
|
|
95
95
|
}
|
|
96
96
|
exports.selectValidDistributionCertificateAsync = selectValidDistributionCertificateAsync;
|
|
97
97
|
const APPLE_DIST_CERTS_TOO_MANY_GENERATED_ERROR = `
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DistributionCertificate, DistributionCertificateStoreInfo, ProvisioningProfile, ProvisioningProfileStoreInfo, PushKey, PushKeyStoreInfo } from './Credentials.types';
|
|
1
|
+
import { AscApiKey, AscApiKeyInfo, DistributionCertificate, DistributionCertificateStoreInfo, ProvisioningProfile, ProvisioningProfileStoreInfo, PushKey, PushKeyStoreInfo } from './Credentials.types';
|
|
2
2
|
import { AuthCtx, Options as AuthenticateOptions } from './authenticate';
|
|
3
3
|
import { AppLookupParams, IosCapabilitiesOptions } from './ensureAppExists';
|
|
4
4
|
import { ProfileClass } from './provisioningProfile';
|
|
@@ -17,4 +17,10 @@ export default class AppStoreApi {
|
|
|
17
17
|
createProvisioningProfileAsync(bundleIdentifier: string, distCert: DistributionCertificate, profileName: string, profileClass?: ProfileClass): Promise<ProvisioningProfile>;
|
|
18
18
|
revokeProvisioningProfileAsync(bundleIdentifier: string, profileClass?: ProfileClass): Promise<void>;
|
|
19
19
|
createOrReuseAdhocProvisioningProfileAsync(udids: string[], bundleIdentifier: string, distCertSerialNumber: string): Promise<ProvisioningProfile>;
|
|
20
|
+
listAscApiKeysAsync(): Promise<AscApiKeyInfo[]>;
|
|
21
|
+
getAscApiKeyAsync(keyId: string): Promise<AscApiKeyInfo>;
|
|
22
|
+
createAscApiKeyAsync({ nickname }: {
|
|
23
|
+
nickname: string;
|
|
24
|
+
}): Promise<AscApiKey>;
|
|
25
|
+
revokeAscApiKeyAsync(keyId: string): Promise<AscApiKeyInfo>;
|
|
20
26
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ascApiKey_1 = require("./ascApiKey");
|
|
3
4
|
const authenticate_1 = require("./authenticate");
|
|
4
5
|
const distributionCertificate_1 = require("./distributionCertificate");
|
|
5
6
|
const ensureAppExists_1 = require("./ensureAppExists");
|
|
@@ -61,5 +62,21 @@ class AppStoreApi {
|
|
|
61
62
|
const ctx = await this.ensureAuthenticatedAsync();
|
|
62
63
|
return await (0, provisioningProfileAdhoc_1.createOrReuseAdhocProvisioningProfileAsync)(ctx, udids, bundleIdentifier, distCertSerialNumber);
|
|
63
64
|
}
|
|
65
|
+
async listAscApiKeysAsync() {
|
|
66
|
+
const ctx = await this.ensureAuthenticatedAsync();
|
|
67
|
+
return await (0, ascApiKey_1.listAscApiKeysAsync)(ctx);
|
|
68
|
+
}
|
|
69
|
+
async getAscApiKeyAsync(keyId) {
|
|
70
|
+
const ctx = await this.ensureAuthenticatedAsync();
|
|
71
|
+
return await (0, ascApiKey_1.getAscApiKeyAsync)(ctx, keyId);
|
|
72
|
+
}
|
|
73
|
+
async createAscApiKeyAsync({ nickname }) {
|
|
74
|
+
const ctx = await this.ensureAuthenticatedAsync();
|
|
75
|
+
return await (0, ascApiKey_1.createAscApiKeyAsync)(ctx, { nickname });
|
|
76
|
+
}
|
|
77
|
+
async revokeAscApiKeyAsync(keyId) {
|
|
78
|
+
const ctx = await this.ensureAuthenticatedAsync();
|
|
79
|
+
return await (0, ascApiKey_1.revokeAscApiKeyAsync)(ctx, keyId);
|
|
80
|
+
}
|
|
64
81
|
}
|
|
65
82
|
exports.default = AppStoreApi;
|
|
@@ -4,8 +4,8 @@ exports.isPushKey = exports.formatPushKey = exports.isDistributionCertificate =
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const dateformat_1 = (0, tslib_1.__importDefault)(require("dateformat"));
|
|
6
6
|
function formatDistributionCertificate({ name, id, status, expires, created, ownerName, }) {
|
|
7
|
-
const expiresDate =
|
|
8
|
-
const createdDate =
|
|
7
|
+
const expiresDate = formatTimestamp(expires);
|
|
8
|
+
const createdDate = formatTimestamp(created);
|
|
9
9
|
return `${name} (${status}) - ID: ${id} - expires: ${expiresDate} (created: ${createdDate}) - owner: ${ownerName}`;
|
|
10
10
|
}
|
|
11
11
|
exports.formatDistributionCertificate = formatDistributionCertificate;
|
|
@@ -31,6 +31,6 @@ function isPushKey(obj) {
|
|
|
31
31
|
typeof obj.teamId === 'string');
|
|
32
32
|
}
|
|
33
33
|
exports.isPushKey = isPushKey;
|
|
34
|
-
function
|
|
34
|
+
function formatTimestamp(timestamp) {
|
|
35
35
|
return (0, dateformat_1.default)(new Date(timestamp * 1000));
|
|
36
36
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/// <reference types="@expo/apple-utils/ts-declarations/expo__app-store" />
|
|
2
|
+
import { UserRole } from '@expo/apple-utils';
|
|
1
3
|
export interface Device {
|
|
2
4
|
id: string;
|
|
3
5
|
teamId: string;
|
|
@@ -59,3 +61,14 @@ export interface PushKey {
|
|
|
59
61
|
teamId: string;
|
|
60
62
|
teamName?: string;
|
|
61
63
|
}
|
|
64
|
+
export declare type AscApiKeyInfo = {
|
|
65
|
+
keyId: string;
|
|
66
|
+
issuerId?: string;
|
|
67
|
+
teamId: string;
|
|
68
|
+
name: string;
|
|
69
|
+
teamName?: string;
|
|
70
|
+
roles: UserRole[];
|
|
71
|
+
};
|
|
72
|
+
export declare type AscApiKey = AscApiKeyInfo & {
|
|
73
|
+
keyP8: string;
|
|
74
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="@expo/apple-utils/ts-declarations/expo__app-store" />
|
|
2
|
+
import { ApiKey, ApiKeyProps } from '@expo/apple-utils';
|
|
3
|
+
import { AscApiKey, AscApiKeyInfo } from './Credentials.types';
|
|
4
|
+
import { AuthCtx } from './authenticate';
|
|
5
|
+
export declare function listAscApiKeysAsync(authCtx: AuthCtx): Promise<AscApiKeyInfo[]>;
|
|
6
|
+
export declare function getAscApiKeyAsync(authCtx: AuthCtx, keyId: string): Promise<AscApiKeyInfo>;
|
|
7
|
+
export declare function createAscApiKeyAsync(authCtx: AuthCtx, { nickname, allAppsVisible, roles, keyType, }: Partial<Pick<ApiKeyProps, 'nickname' | 'roles' | 'allAppsVisible' | 'keyType'>>): Promise<AscApiKey>;
|
|
8
|
+
export declare function revokeAscApiKeyAsync(authCtx: AuthCtx, keyId: string): Promise<AscApiKeyInfo>;
|
|
9
|
+
export declare function getAscApiKeyInfo(apiKey: ApiKey, authCtx: AuthCtx): AscApiKeyInfo;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAscApiKeyInfo = exports.revokeAscApiKeyAsync = exports.createAscApiKeyAsync = exports.getAscApiKeyAsync = exports.listAscApiKeysAsync = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const apple_utils_1 = require("@expo/apple-utils");
|
|
6
|
+
const log_1 = (0, tslib_1.__importDefault)(require("../../../log"));
|
|
7
|
+
const ora_1 = require("../../../ora");
|
|
8
|
+
const authenticate_1 = require("./authenticate");
|
|
9
|
+
async function listAscApiKeysAsync(authCtx) {
|
|
10
|
+
const spinner = (0, ora_1.ora)(`Fetching App Store Connect API Keys.`).start();
|
|
11
|
+
try {
|
|
12
|
+
const context = (0, authenticate_1.getRequestContext)(authCtx);
|
|
13
|
+
const keys = await apple_utils_1.ApiKey.getAsync(context);
|
|
14
|
+
spinner.succeed(`Fetched App Store Connect API Keys.`);
|
|
15
|
+
return keys.map(key => getAscApiKeyInfo(key, authCtx));
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
spinner.fail(`Failed to fetch App Store Connect API Keys.`);
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.listAscApiKeysAsync = listAscApiKeysAsync;
|
|
23
|
+
async function getAscApiKeyAsync(authCtx, keyId) {
|
|
24
|
+
const spinner = (0, ora_1.ora)(`Fetching App Store Connect API Key.`).start();
|
|
25
|
+
try {
|
|
26
|
+
const context = (0, authenticate_1.getRequestContext)(authCtx);
|
|
27
|
+
const apiKey = await apple_utils_1.ApiKey.infoAsync(context, { id: keyId });
|
|
28
|
+
spinner.succeed(`Fetched App Store Connect API Key (ID: ${keyId}).`);
|
|
29
|
+
return getAscApiKeyInfo(apiKey, authCtx);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
log_1.default.error(error);
|
|
33
|
+
spinner.fail(`Failed to fetch App Store Connect API Key.`);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.getAscApiKeyAsync = getAscApiKeyAsync;
|
|
38
|
+
async function createAscApiKeyAsync(authCtx, { nickname, allAppsVisible, roles, keyType, }) {
|
|
39
|
+
const spinner = (0, ora_1.ora)(`Creating App Store Connect API Key.`).start();
|
|
40
|
+
try {
|
|
41
|
+
const context = (0, authenticate_1.getRequestContext)(authCtx);
|
|
42
|
+
const key = await apple_utils_1.ApiKey.createAsync(context, {
|
|
43
|
+
nickname: nickname !== null && nickname !== void 0 ? nickname : `[expo] ${new Date().getTime()}`,
|
|
44
|
+
allAppsVisible: allAppsVisible !== null && allAppsVisible !== void 0 ? allAppsVisible : true,
|
|
45
|
+
roles: roles !== null && roles !== void 0 ? roles : [apple_utils_1.UserRole.ADMIN],
|
|
46
|
+
keyType: keyType !== null && keyType !== void 0 ? keyType : apple_utils_1.ApiKeyType.PUBLIC_API,
|
|
47
|
+
});
|
|
48
|
+
const keyP8 = await key.downloadAsync();
|
|
49
|
+
if (!keyP8) {
|
|
50
|
+
const { nickname, roles } = key.attributes;
|
|
51
|
+
const humanReadableKey = `App Store Connect Key '${nickname}' (${key.id}) with roles {${roles.join(',')}}`;
|
|
52
|
+
if (!key.attributes.canDownload) {
|
|
53
|
+
// this case would be unexpected because we just created the key
|
|
54
|
+
throw new Error(`${humanReadableKey} is not available for download from Apple.`);
|
|
55
|
+
}
|
|
56
|
+
else if (!key.attributes.isActive) {
|
|
57
|
+
throw new Error(`${humanReadableKey} is inactive and could not be downloaded.`);
|
|
58
|
+
}
|
|
59
|
+
throw new Error(`Failed to download .p8 file of ${humanReadableKey}.`);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
...getAscApiKeyInfo(key, authCtx),
|
|
63
|
+
keyP8,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
spinner.fail('Failed to create App Store Connect API Key.');
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.createAscApiKeyAsync = createAscApiKeyAsync;
|
|
72
|
+
async function revokeAscApiKeyAsync(authCtx, keyId) {
|
|
73
|
+
const spinner = (0, ora_1.ora)(`Revoking App Store Connect API Key.`).start();
|
|
74
|
+
try {
|
|
75
|
+
const context = (0, authenticate_1.getRequestContext)(authCtx);
|
|
76
|
+
const apiKey = await apple_utils_1.ApiKey.infoAsync(context, { id: keyId });
|
|
77
|
+
const revokedKey = await apiKey.revokeAsync();
|
|
78
|
+
spinner.succeed(`Revoked App Store Connect API Key.`);
|
|
79
|
+
return getAscApiKeyInfo(revokedKey, authCtx);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
log_1.default.error(error);
|
|
83
|
+
spinner.fail(`Failed to revoke App Store Connect API Key.`);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.revokeAscApiKeyAsync = revokeAscApiKeyAsync;
|
|
88
|
+
function getAscApiKeyInfo(apiKey, authCtx) {
|
|
89
|
+
var _a;
|
|
90
|
+
return {
|
|
91
|
+
name: apiKey.attributes.nickname,
|
|
92
|
+
keyId: apiKey.id,
|
|
93
|
+
issuerId: (_a = apiKey.attributes.provider) === null || _a === void 0 ? void 0 : _a.id,
|
|
94
|
+
teamId: authCtx.team.id,
|
|
95
|
+
teamName: authCtx.team.name,
|
|
96
|
+
roles: apiKey.attributes.roles,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
exports.getAscApiKeyInfo = getAscApiKeyInfo;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/// <reference types="@expo/apple-utils/ts-declarations/expo__app-store" />
|
|
2
|
+
import { UserRole } from '@expo/apple-utils';
|
|
1
3
|
import { AppleDevice } from '../../graphql/generated';
|
|
2
4
|
import { CredentialSchema } from '../utils/promptForCredentials';
|
|
3
5
|
import { DistributionCertificate, ProvisioningProfile, PushKey } from './appstore/Credentials.types';
|
|
@@ -36,5 +38,20 @@ export interface IosDistCredentials extends DistributionCertificate {
|
|
|
36
38
|
type: 'dist-cert';
|
|
37
39
|
}
|
|
38
40
|
export declare const distributionCertificateSchema: CredentialSchema<DistributionCertificate>;
|
|
41
|
+
export declare type MinimalAscApiKey = {
|
|
42
|
+
keyP8: string;
|
|
43
|
+
keyId: string;
|
|
44
|
+
issuerId: string;
|
|
45
|
+
teamId?: string;
|
|
46
|
+
teamName?: string;
|
|
47
|
+
roles?: UserRole[];
|
|
48
|
+
name?: string;
|
|
49
|
+
};
|
|
50
|
+
export interface AscApiKeyPath {
|
|
51
|
+
keyP8Path: string;
|
|
52
|
+
keyId: string;
|
|
53
|
+
issuerId: string;
|
|
54
|
+
}
|
|
55
|
+
export declare const ascApiKeyMetadataSchema: CredentialSchema<Omit<MinimalAscApiKey, 'keyP8'>>;
|
|
39
56
|
export declare const pushKeySchema: CredentialSchema<PushKey>;
|
|
40
57
|
export declare const provisioningProfileSchema: CredentialSchema<ProvisioningProfile>;
|