eas-cli 18.8.0 → 18.9.0
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 +162 -94
- package/build/build/utils/url.d.ts +6 -0
- package/build/build/utils/url.js +9 -0
- package/build/commands/build/download.d.ts +6 -2
- package/build/commands/build/download.js +129 -18
- package/build/commands/integrations/asc/connect.d.ts +19 -0
- package/build/commands/integrations/asc/connect.js +159 -0
- package/build/commands/integrations/asc/disconnect.d.ts +15 -0
- package/build/commands/integrations/asc/disconnect.js +115 -0
- package/build/commands/integrations/asc/status.d.ts +14 -0
- package/build/commands/integrations/asc/status.js +65 -0
- package/build/commands/simulator/start.d.ts +16 -0
- package/build/commands/simulator/start.js +205 -0
- package/build/commands/simulator/stop.d.ts +13 -0
- package/build/commands/simulator/stop.js +38 -0
- package/build/credentials/ios/actions/AscApiKeyUtils.d.ts +2 -1
- package/build/credentials/ios/actions/AscApiKeyUtils.js +16 -0
- package/build/credentials/ios/appstore/AppStoreApi.d.ts +3 -1
- package/build/credentials/ios/appstore/AppStoreApi.js +2 -2
- package/build/graphql/generated.d.ts +368 -0
- package/build/graphql/generated.js +23 -3
- package/build/graphql/mutations/AscAppLinkMutation.d.ts +12 -0
- package/build/graphql/mutations/AscAppLinkMutation.js +36 -0
- package/build/graphql/mutations/DeviceRunSessionMutation.d.ts +6 -0
- package/build/graphql/mutations/DeviceRunSessionMutation.js +49 -0
- package/build/graphql/queries/AscAppLinkQuery.d.ts +8 -0
- package/build/graphql/queries/AscAppLinkQuery.js +67 -0
- package/build/graphql/queries/DeviceRunSessionQuery.d.ts +5 -0
- package/build/graphql/queries/DeviceRunSessionQuery.js +28 -0
- package/build/integrations/asc/ascApiKey.d.ts +7 -0
- package/build/integrations/asc/ascApiKey.js +26 -0
- package/build/integrations/asc/utils.d.ts +21 -0
- package/build/integrations/asc/utils.js +56 -0
- package/build/utils/download.d.ts +1 -0
- package/build/utils/download.js +1 -0
- package/oclif.manifest.json +3152 -2671
- package/package.json +6 -3
|
@@ -5,5 +5,11 @@ export declare function getArtifactUrl(artifactId: string): string;
|
|
|
5
5
|
export declare function getInternalDistributionInstallUrl(build: BuildFragment): string;
|
|
6
6
|
export declare function getUpdateGroupUrl(accountName: string, projectName: string, updateGroupId: string): string;
|
|
7
7
|
export declare function getWorkflowRunUrl(accountName: string, projectName: string, workflowRunId: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Links to the raw job-run page; prefer a higher-level URL (e.g. the workflow run
|
|
10
|
+
* or the feature-specific dashboard) that gives users more context. Use this only for internal
|
|
11
|
+
* tooling where no richer URL exists.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getBareJobRunUrl(accountName: string, projectName: string, jobRunId: string): string;
|
|
8
14
|
export declare function getProjectGitHubSettingsUrl(accountName: string, projectName: string): string;
|
|
9
15
|
export declare function getHostingDeploymentsUrl(accountName: string, projectName: string): string;
|
package/build/build/utils/url.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.getArtifactUrl = getArtifactUrl;
|
|
|
6
6
|
exports.getInternalDistributionInstallUrl = getInternalDistributionInstallUrl;
|
|
7
7
|
exports.getUpdateGroupUrl = getUpdateGroupUrl;
|
|
8
8
|
exports.getWorkflowRunUrl = getWorkflowRunUrl;
|
|
9
|
+
exports.getBareJobRunUrl = getBareJobRunUrl;
|
|
9
10
|
exports.getProjectGitHubSettingsUrl = getProjectGitHubSettingsUrl;
|
|
10
11
|
exports.getHostingDeploymentsUrl = getHostingDeploymentsUrl;
|
|
11
12
|
const tslib_1 = require("tslib");
|
|
@@ -38,6 +39,14 @@ function getUpdateGroupUrl(accountName, projectName, updateGroupId) {
|
|
|
38
39
|
function getWorkflowRunUrl(accountName, projectName, workflowRunId) {
|
|
39
40
|
return new URL(`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(projectName)}/workflows/${workflowRunId}`, (0, api_1.getExpoWebsiteBaseUrl)()).toString();
|
|
40
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* @deprecated Links to the raw job-run page; prefer a higher-level URL (e.g. the workflow run
|
|
44
|
+
* or the feature-specific dashboard) that gives users more context. Use this only for internal
|
|
45
|
+
* tooling where no richer URL exists.
|
|
46
|
+
*/
|
|
47
|
+
function getBareJobRunUrl(accountName, projectName, jobRunId) {
|
|
48
|
+
return new URL(`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(projectName)}/job-runs/${encodeURIComponent(jobRunId)}`, (0, api_1.getExpoWebsiteBaseUrl)()).toString();
|
|
49
|
+
}
|
|
41
50
|
function getProjectGitHubSettingsUrl(accountName, projectName) {
|
|
42
51
|
return new URL(`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(projectName)}/github`, (0, api_1.getExpoWebsiteBaseUrl)()).toString();
|
|
43
52
|
}
|
|
@@ -6,15 +6,19 @@ export default class Download extends EasCommand {
|
|
|
6
6
|
static flags: {
|
|
7
7
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
8
|
'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
-
|
|
9
|
+
'build-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
fingerprint: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
11
|
platform: import("@oclif/core/lib/interfaces").OptionFlag<Platform | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
12
|
'dev-client': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
'all-artifacts': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
14
|
};
|
|
13
15
|
static contextDefinition: {
|
|
14
16
|
projectId: import("../../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
|
|
15
17
|
loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
|
|
16
18
|
};
|
|
17
19
|
runAsync(): Promise<void>;
|
|
18
|
-
private
|
|
20
|
+
private downloadExtraArtifactsAsync;
|
|
21
|
+
private getBuildByIdAsync;
|
|
22
|
+
private getBuildByFingerprintAsync;
|
|
19
23
|
getPathToBuildArtifactAsync(build: BuildFragment, platform: AppPlatform): Promise<string>;
|
|
20
24
|
}
|
|
@@ -4,7 +4,8 @@ const tslib_1 = require("tslib");
|
|
|
4
4
|
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
5
5
|
const core_1 = require("@oclif/core");
|
|
6
6
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
-
const fs_extra_1 = require("fs-extra");
|
|
7
|
+
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
8
9
|
const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
|
|
9
10
|
const flags_1 = require("../../commandUtils/flags");
|
|
10
11
|
const generated_1 = require("../../graphql/generated");
|
|
@@ -14,21 +15,33 @@ const log_1 = tslib_1.__importDefault(require("../../log"));
|
|
|
14
15
|
const prompts_1 = require("../../prompts");
|
|
15
16
|
const run_1 = require("../../run/run");
|
|
16
17
|
const download_1 = require("../../utils/download");
|
|
18
|
+
const files_1 = require("../../utils/files");
|
|
17
19
|
const json_1 = require("../../utils/json");
|
|
20
|
+
const paths_1 = require("../../utils/paths");
|
|
18
21
|
class Download extends EasCommand_1.default {
|
|
19
|
-
static description = 'download simulator/emulator
|
|
22
|
+
static description = 'download a simulator/emulator build by build ID or fingerprint hash';
|
|
20
23
|
static flags = {
|
|
24
|
+
'build-id': core_1.Flags.string({
|
|
25
|
+
description: 'ID of the build to download. Mutually exclusive with --fingerprint, --platform, and --dev-client; the platform is derived from the build itself.',
|
|
26
|
+
exclusive: ['fingerprint', 'platform', 'dev-client'],
|
|
27
|
+
}),
|
|
21
28
|
fingerprint: core_1.Flags.string({
|
|
22
29
|
description: 'Fingerprint hash of the build to download',
|
|
23
|
-
|
|
30
|
+
exclusive: ['build-id'],
|
|
24
31
|
}),
|
|
25
32
|
platform: core_1.Flags.option({
|
|
26
33
|
char: 'p',
|
|
27
34
|
options: [eas_build_job_1.Platform.IOS, eas_build_job_1.Platform.ANDROID],
|
|
35
|
+
exclusive: ['build-id'],
|
|
28
36
|
})(),
|
|
29
37
|
'dev-client': core_1.Flags.boolean({
|
|
30
38
|
description: 'Filter only dev-client builds.',
|
|
31
39
|
allowNo: true,
|
|
40
|
+
exclusive: ['build-id'],
|
|
41
|
+
}),
|
|
42
|
+
'all-artifacts': core_1.Flags.boolean({
|
|
43
|
+
description: 'Download all available build artifacts (build artifacts archive, Xcode logs, etc.) in addition to the application archive. Without this flag, only the application archive is downloaded and the command errors if it is missing.',
|
|
44
|
+
default: false,
|
|
32
45
|
}),
|
|
33
46
|
...flags_1.EasNonInteractiveAndJsonFlags,
|
|
34
47
|
};
|
|
@@ -37,32 +50,103 @@ class Download extends EasCommand_1.default {
|
|
|
37
50
|
...this.ContextOptions.ProjectId,
|
|
38
51
|
};
|
|
39
52
|
async runAsync() {
|
|
40
|
-
const { flags: { platform, fingerprint, 'dev-client': developmentClient, ...rawFlags }, } = await this.parse(Download);
|
|
53
|
+
const { flags: { 'build-id': buildId, platform, fingerprint, 'dev-client': developmentClient, 'all-artifacts': allArtifacts, ...rawFlags }, } = await this.parse(Download);
|
|
41
54
|
const { json: jsonFlag, nonInteractive } = (0, flags_1.resolveNonInteractiveAndJsonFlags)(rawFlags);
|
|
55
|
+
if (!buildId && !fingerprint) {
|
|
56
|
+
throw new Error('Either --build-id or --fingerprint is required.');
|
|
57
|
+
}
|
|
42
58
|
const { loggedIn: { graphqlClient }, projectId, } = await this.getContextAsync(Download, {
|
|
43
59
|
nonInteractive,
|
|
44
60
|
});
|
|
45
61
|
if (jsonFlag) {
|
|
46
62
|
(0, json_1.enableJsonOutput)();
|
|
47
63
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
platform
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
let build;
|
|
65
|
+
let selectedPlatform;
|
|
66
|
+
if (buildId) {
|
|
67
|
+
build = await this.getBuildByIdAsync({ graphqlClient, buildId });
|
|
68
|
+
selectedPlatform = build.platform;
|
|
69
|
+
log_1.default.succeed(`🎯 Found build ${chalk_1.default.bold(buildId)} on EAS servers.`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
selectedPlatform = await resolvePlatformAsync({ nonInteractive, platform });
|
|
73
|
+
build = await this.getBuildByFingerprintAsync({
|
|
74
|
+
graphqlClient,
|
|
75
|
+
projectId,
|
|
76
|
+
platform: selectedPlatform,
|
|
77
|
+
fingerprintHash: fingerprint,
|
|
78
|
+
developmentClient,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
let buildArtifactPath = null;
|
|
82
|
+
if (build.artifacts?.applicationArchiveUrl) {
|
|
83
|
+
buildArtifactPath = await this.getPathToBuildArtifactAsync(build, selectedPlatform);
|
|
84
|
+
}
|
|
85
|
+
else if (!allArtifacts) {
|
|
86
|
+
const availableArtifacts = listAvailableExtraArtifactNames(build);
|
|
87
|
+
if (availableArtifacts.length > 0) {
|
|
88
|
+
throw new Error(`Build does not have an application archive url. Other artifacts are available (${availableArtifacts.join(', ')}); re-run with --all-artifacts to download them.`);
|
|
89
|
+
}
|
|
90
|
+
throw new Error('Build does not have an application archive url');
|
|
91
|
+
}
|
|
92
|
+
let extraArtifactPaths = {};
|
|
93
|
+
if (allArtifacts) {
|
|
94
|
+
extraArtifactPaths = await this.downloadExtraArtifactsAsync(build);
|
|
95
|
+
}
|
|
57
96
|
if (jsonFlag) {
|
|
58
|
-
const jsonResults = {
|
|
97
|
+
const jsonResults = {
|
|
98
|
+
...(buildArtifactPath != null && { path: buildArtifactPath }),
|
|
99
|
+
...extraArtifactPaths,
|
|
100
|
+
};
|
|
59
101
|
(0, json_1.printJsonOnlyOutput)(jsonResults);
|
|
60
102
|
}
|
|
61
103
|
else {
|
|
62
|
-
|
|
104
|
+
if (buildArtifactPath != null) {
|
|
105
|
+
log_1.default.log(`Build downloaded to ${chalk_1.default.bold(buildArtifactPath)}`);
|
|
106
|
+
}
|
|
107
|
+
for (const [name, filePath] of Object.entries(extraArtifactPaths)) {
|
|
108
|
+
log_1.default.log(`${name} downloaded to ${chalk_1.default.bold(filePath)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async downloadExtraArtifactsAsync(build) {
|
|
113
|
+
const artifacts = build.artifacts;
|
|
114
|
+
if (!artifacts) {
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
const extraArtifacts = [];
|
|
118
|
+
if (artifacts.buildArtifactsUrl) {
|
|
119
|
+
extraArtifacts.push({ name: 'buildArtifacts', url: artifacts.buildArtifactsUrl });
|
|
63
120
|
}
|
|
121
|
+
if (artifacts.xcodeBuildLogsUrl) {
|
|
122
|
+
extraArtifacts.push({ name: 'xcodeBuildLogs', url: artifacts.xcodeBuildLogsUrl });
|
|
123
|
+
}
|
|
124
|
+
if (artifacts.buildUrl && artifacts.buildUrl !== artifacts.applicationArchiveUrl) {
|
|
125
|
+
extraArtifacts.push({ name: 'build', url: artifacts.buildUrl });
|
|
126
|
+
}
|
|
127
|
+
if (extraArtifacts.length === 0) {
|
|
128
|
+
return {};
|
|
129
|
+
}
|
|
130
|
+
const outputDir = path_1.default.join((0, paths_1.getEasBuildRunCacheDirectoryPath)(), `${build.id}-artifacts`);
|
|
131
|
+
await fs_extra_1.default.ensureDir(outputDir);
|
|
132
|
+
const downloaded = {};
|
|
133
|
+
for (const { name, url } of extraArtifacts) {
|
|
134
|
+
const fileName = getFileNameFromUrl(url, name);
|
|
135
|
+
const outputPath = path_1.default.join(outputDir, fileName);
|
|
136
|
+
await (0, download_1.downloadFileWithProgressTrackerAsync)(url, outputPath, (ratio, total) => `Downloading ${name} (${(0, files_1.formatBytes)(total * ratio)} / ${(0, files_1.formatBytes)(total)})`, `Successfully downloaded ${name}`);
|
|
137
|
+
downloaded[name] = outputPath;
|
|
138
|
+
}
|
|
139
|
+
return downloaded;
|
|
64
140
|
}
|
|
65
|
-
async
|
|
141
|
+
async getBuildByIdAsync({ graphqlClient, buildId, }) {
|
|
142
|
+
try {
|
|
143
|
+
return await BuildQuery_1.BuildQuery.byIdAsync(graphqlClient, buildId);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
throw new Error(`Could not find build with ID ${buildId}: ${error.message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async getBuildByFingerprintAsync({ graphqlClient, projectId, platform, fingerprintHash, developmentClient, }) {
|
|
66
150
|
const builds = await BuildQuery_1.BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
|
67
151
|
appId: projectId,
|
|
68
152
|
filter: {
|
|
@@ -77,14 +161,14 @@ class Download extends EasCommand_1.default {
|
|
|
77
161
|
limit: 1,
|
|
78
162
|
});
|
|
79
163
|
if (builds.length === 0) {
|
|
80
|
-
throw new
|
|
164
|
+
throw new Error(`No builds available for ${platform} with fingerprint hash ${fingerprintHash}`);
|
|
81
165
|
}
|
|
82
166
|
log_1.default.succeed(`🎯 Found successful build with matching fingerprint on EAS servers.`);
|
|
83
167
|
return builds[0];
|
|
84
168
|
}
|
|
85
169
|
async getPathToBuildArtifactAsync(build, platform) {
|
|
86
170
|
const cachedBuildArtifactPath = (0, run_1.getEasBuildRunCachedAppPath)(build.project.id, build.id, platform);
|
|
87
|
-
if (await
|
|
171
|
+
if (await fs_extra_1.default.pathExists(cachedBuildArtifactPath)) {
|
|
88
172
|
log_1.default.newLine();
|
|
89
173
|
log_1.default.log(`Using cached build...`);
|
|
90
174
|
return cachedBuildArtifactPath;
|
|
@@ -114,3 +198,30 @@ async function resolvePlatformAsync({ nonInteractive, platform, }) {
|
|
|
114
198
|
});
|
|
115
199
|
return selectedPlatform;
|
|
116
200
|
}
|
|
201
|
+
function listAvailableExtraArtifactNames(build) {
|
|
202
|
+
const names = [];
|
|
203
|
+
if (build.artifacts?.buildArtifactsUrl) {
|
|
204
|
+
names.push('buildArtifacts');
|
|
205
|
+
}
|
|
206
|
+
if (build.artifacts?.xcodeBuildLogsUrl) {
|
|
207
|
+
names.push('xcodeBuildLogs');
|
|
208
|
+
}
|
|
209
|
+
if (build.artifacts?.buildUrl &&
|
|
210
|
+
build.artifacts.buildUrl !== build.artifacts.applicationArchiveUrl) {
|
|
211
|
+
names.push('build');
|
|
212
|
+
}
|
|
213
|
+
return names;
|
|
214
|
+
}
|
|
215
|
+
function getFileNameFromUrl(url, fallbackName) {
|
|
216
|
+
try {
|
|
217
|
+
const pathname = new URL(url).pathname;
|
|
218
|
+
const basename = path_1.default.basename(pathname);
|
|
219
|
+
if (basename) {
|
|
220
|
+
return basename;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// fall through to default
|
|
225
|
+
}
|
|
226
|
+
return fallbackName;
|
|
227
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import EasCommand from '../../../commandUtils/EasCommand';
|
|
2
|
+
export default class IntegrationsAscConnect extends EasCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
6
|
+
'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
'api-key-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
'asc-app-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
'bundle-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
static contextDefinition: {
|
|
12
|
+
vcsClient: import("../../../commandUtils/context/VcsClientContextField").default;
|
|
13
|
+
analytics: import("../../../commandUtils/context/AnalyticsContextField").default;
|
|
14
|
+
loggedIn: import("../../../commandUtils/context/LoggedInContextField").default;
|
|
15
|
+
projectDir: import("../../../commandUtils/context/ProjectDirContextField").default;
|
|
16
|
+
projectId: import("../../../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
|
|
17
|
+
};
|
|
18
|
+
runAsync(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
|
+
const EasCommand_1 = tslib_1.__importDefault(require("../../../commandUtils/EasCommand"));
|
|
7
|
+
const errors_1 = require("../../../commandUtils/errors");
|
|
8
|
+
const flags_1 = require("../../../commandUtils/flags");
|
|
9
|
+
const context_1 = require("../../../credentials/context");
|
|
10
|
+
const utils_1 = require("../../../integrations/asc/utils");
|
|
11
|
+
const ascApiKey_1 = require("../../../integrations/asc/ascApiKey");
|
|
12
|
+
const AppStoreConnectApiKeyQuery_1 = require("../../../credentials/ios/api/graphql/queries/AppStoreConnectApiKeyQuery");
|
|
13
|
+
const AscAppLinkMutation_1 = require("../../../graphql/mutations/AscAppLinkMutation");
|
|
14
|
+
const AscAppLinkQuery_1 = require("../../../graphql/queries/AscAppLinkQuery");
|
|
15
|
+
const log_1 = tslib_1.__importDefault(require("../../../log"));
|
|
16
|
+
const ora_1 = require("../../../ora");
|
|
17
|
+
const prompts_1 = require("../../../prompts");
|
|
18
|
+
const json_1 = require("../../../utils/json");
|
|
19
|
+
class IntegrationsAscConnect extends EasCommand_1.default {
|
|
20
|
+
static description = 'connect a project to an App Store Connect app';
|
|
21
|
+
static flags = {
|
|
22
|
+
'api-key-id': core_1.Flags.string({
|
|
23
|
+
description: 'Apple App Store Connect API Key ID',
|
|
24
|
+
}),
|
|
25
|
+
'asc-app-id': core_1.Flags.string({
|
|
26
|
+
description: 'App Store Connect app identifier',
|
|
27
|
+
}),
|
|
28
|
+
'bundle-id': core_1.Flags.string({
|
|
29
|
+
description: 'Filter discovered apps by bundle identifier',
|
|
30
|
+
}),
|
|
31
|
+
...flags_1.EasNonInteractiveAndJsonFlags,
|
|
32
|
+
};
|
|
33
|
+
static contextDefinition = {
|
|
34
|
+
...this.ContextOptions.ProjectId,
|
|
35
|
+
...this.ContextOptions.ProjectDir,
|
|
36
|
+
...this.ContextOptions.LoggedIn,
|
|
37
|
+
...this.ContextOptions.Analytics,
|
|
38
|
+
...this.ContextOptions.Vcs,
|
|
39
|
+
};
|
|
40
|
+
async runAsync() {
|
|
41
|
+
const { flags } = await this.parse(IntegrationsAscConnect);
|
|
42
|
+
const { json, nonInteractive } = (0, flags_1.resolveNonInteractiveAndJsonFlags)(flags);
|
|
43
|
+
if (json) {
|
|
44
|
+
(0, json_1.enableJsonOutput)();
|
|
45
|
+
}
|
|
46
|
+
if (nonInteractive) {
|
|
47
|
+
if (!flags['api-key-id']) {
|
|
48
|
+
throw new errors_1.EasCommandError('--api-key-id is required in non-interactive mode.');
|
|
49
|
+
}
|
|
50
|
+
if (!flags['asc-app-id']) {
|
|
51
|
+
throw new errors_1.EasCommandError('--asc-app-id is required in non-interactive mode.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const { projectId, projectDir, loggedIn: { actor, graphqlClient }, analytics, vcsClient, } = await this.getContextAsync(IntegrationsAscConnect, {
|
|
55
|
+
nonInteractive,
|
|
56
|
+
});
|
|
57
|
+
// Step 1: Check current status
|
|
58
|
+
const statusSpinner = (0, ora_1.ora)('Checking current App Store Connect app link status').start();
|
|
59
|
+
const metadata = await AscAppLinkQuery_1.AscAppLinkQuery.getAppMetadataAsync(graphqlClient, projectId);
|
|
60
|
+
statusSpinner.succeed('Checked current status');
|
|
61
|
+
if (metadata.appStoreConnectApp) {
|
|
62
|
+
throw new errors_1.EasCommandError(`Project ${chalk_1.default.bold(metadata.fullName)} is already connected to App Store Connect app ${chalk_1.default.bold(metadata.appStoreConnectApp.ascAppIdentifier)}. Disconnect first with ${chalk_1.default.bold('eas integrations:asc:disconnect')}.`);
|
|
63
|
+
}
|
|
64
|
+
// Step 2: Get ASC API key
|
|
65
|
+
const keysSpinner = (0, ora_1.ora)('Fetching App Store Connect API keys').start();
|
|
66
|
+
const keys = await AppStoreConnectApiKeyQuery_1.AppStoreConnectApiKeyQuery.getAllForAccountAsync(graphqlClient, metadata.ownerAccount.name);
|
|
67
|
+
keysSpinner.succeed(`Found ${keys.length} API key(s)`);
|
|
68
|
+
let apiKeyId = flags['api-key-id'];
|
|
69
|
+
if (!apiKeyId) {
|
|
70
|
+
const credentialsContext = new context_1.CredentialsContext({
|
|
71
|
+
projectInfo: null,
|
|
72
|
+
nonInteractive,
|
|
73
|
+
projectDir,
|
|
74
|
+
user: actor,
|
|
75
|
+
graphqlClient,
|
|
76
|
+
analytics,
|
|
77
|
+
vcsClient,
|
|
78
|
+
});
|
|
79
|
+
apiKeyId = await (0, ascApiKey_1.selectOrCreateAscApiKeyIdAsync)({
|
|
80
|
+
credentialsContext,
|
|
81
|
+
existingKeys: keys,
|
|
82
|
+
ownerAccount: metadata.ownerAccount,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const keysByAppleId = keys.filter(key => key.keyIdentifier === apiKeyId);
|
|
87
|
+
if (keysByAppleId.length > 1) {
|
|
88
|
+
throw new errors_1.EasCommandError(`Multiple App Store Connect API keys match Apple key identifier "${apiKeyId}".`);
|
|
89
|
+
}
|
|
90
|
+
else if (keysByAppleId.length === 1) {
|
|
91
|
+
apiKeyId = keysByAppleId[0].id;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
throw new errors_1.EasCommandError(`No App Store Connect API key found with Apple key identifier "${apiKeyId}".`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!apiKeyId) {
|
|
98
|
+
throw new errors_1.EasCommandError('No App Store Connect API key selected.');
|
|
99
|
+
}
|
|
100
|
+
// Step 3: Discover remote apps
|
|
101
|
+
const discoverSpinner = (0, ora_1.ora)('Discovering App Store Connect apps').start();
|
|
102
|
+
let remoteApps;
|
|
103
|
+
try {
|
|
104
|
+
remoteApps = await AscAppLinkQuery_1.AscAppLinkQuery.discoverAccessibleAppsAsync(graphqlClient, apiKeyId, flags['bundle-id']);
|
|
105
|
+
discoverSpinner.succeed(`Found ${remoteApps.length} app(s) on App Store Connect`);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
discoverSpinner.fail('Failed to discover apps');
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
if (remoteApps.length === 0) {
|
|
112
|
+
throw new errors_1.EasCommandError('No accessible apps found on App Store Connect for the selected API key.' +
|
|
113
|
+
(flags['bundle-id']
|
|
114
|
+
? ` Try removing the --bundle-id filter or verify the bundle ID "${flags['bundle-id']}".`
|
|
115
|
+
: ''));
|
|
116
|
+
}
|
|
117
|
+
// Step 4: Select remote app
|
|
118
|
+
let selectedApp;
|
|
119
|
+
if (flags['asc-app-id']) {
|
|
120
|
+
const match = remoteApps.find(app => app.ascAppIdentifier === flags['asc-app-id']);
|
|
121
|
+
if (!match) {
|
|
122
|
+
throw new errors_1.EasCommandError(`App with identifier "${flags['asc-app-id']}" was not found among accessible apps. Run ${chalk_1.default.bold('eas integrations:asc:connect')} interactively to discover available apps.`);
|
|
123
|
+
}
|
|
124
|
+
selectedApp = match;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
selectedApp = await (0, prompts_1.selectAsync)('Select an App Store Connect app:', remoteApps.map(app => ({
|
|
128
|
+
title: `${app.name} (${app.bundleIdentifier}) [${app.ascAppIdentifier}]`,
|
|
129
|
+
value: app,
|
|
130
|
+
})));
|
|
131
|
+
}
|
|
132
|
+
// Step 5: Create link
|
|
133
|
+
const createSpinner = (0, ora_1.ora)('Connecting project to App Store Connect app').start();
|
|
134
|
+
try {
|
|
135
|
+
await AscAppLinkMutation_1.AscAppLinkMutation.createAppStoreConnectAppAsync(graphqlClient, {
|
|
136
|
+
appId: metadata.id,
|
|
137
|
+
ascAppIdentifier: selectedApp.ascAppIdentifier,
|
|
138
|
+
appStoreConnectApiKeyId: apiKeyId,
|
|
139
|
+
});
|
|
140
|
+
createSpinner.succeed('Connected project to App Store Connect app');
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
createSpinner.fail('Failed to connect project');
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
// Step 6: Refetch and display
|
|
147
|
+
const refetchSpinner = (0, ora_1.ora)('Verifying connection').start();
|
|
148
|
+
const updatedMetadata = await AscAppLinkQuery_1.AscAppLinkQuery.getAppMetadataAsync(graphqlClient, projectId);
|
|
149
|
+
refetchSpinner.succeed('Verified connection');
|
|
150
|
+
if (json) {
|
|
151
|
+
(0, json_1.printJsonOnlyOutput)((0, utils_1.buildJsonOutput)('connect', updatedMetadata));
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
log_1.default.addNewLineIfNone();
|
|
155
|
+
log_1.default.log((0, utils_1.formatAscAppLinkStatus)(updatedMetadata));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.default = IntegrationsAscConnect;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import EasCommand from '../../../commandUtils/EasCommand';
|
|
2
|
+
export default class IntegrationsAscDisconnect extends EasCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
6
|
+
'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
};
|
|
9
|
+
static contextDefinition: {
|
|
10
|
+
loggedIn: import("../../../commandUtils/context/LoggedInContextField").default;
|
|
11
|
+
projectId: import("../../../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
|
|
12
|
+
};
|
|
13
|
+
runAsync(): Promise<void>;
|
|
14
|
+
private fetchCurrentStatusAsync;
|
|
15
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
|
+
const EasCommand_1 = tslib_1.__importDefault(require("../../../commandUtils/EasCommand"));
|
|
7
|
+
const flags_1 = require("../../../commandUtils/flags");
|
|
8
|
+
const utils_1 = require("../../../integrations/asc/utils");
|
|
9
|
+
const AscAppLinkMutation_1 = require("../../../graphql/mutations/AscAppLinkMutation");
|
|
10
|
+
const AscAppLinkQuery_1 = require("../../../graphql/queries/AscAppLinkQuery");
|
|
11
|
+
const log_1 = tslib_1.__importDefault(require("../../../log"));
|
|
12
|
+
const ora_1 = require("../../../ora");
|
|
13
|
+
const prompts_1 = require("../../../prompts");
|
|
14
|
+
const json_1 = require("../../../utils/json");
|
|
15
|
+
class IntegrationsAscDisconnect extends EasCommand_1.default {
|
|
16
|
+
static description = 'disconnect the current project from its App Store Connect app';
|
|
17
|
+
static flags = {
|
|
18
|
+
yes: core_1.Flags.boolean({
|
|
19
|
+
description: 'Skip confirmation prompt',
|
|
20
|
+
default: false,
|
|
21
|
+
}),
|
|
22
|
+
...flags_1.EasNonInteractiveAndJsonFlags,
|
|
23
|
+
};
|
|
24
|
+
static contextDefinition = {
|
|
25
|
+
...this.ContextOptions.ProjectId,
|
|
26
|
+
...this.ContextOptions.LoggedIn,
|
|
27
|
+
};
|
|
28
|
+
async runAsync() {
|
|
29
|
+
const { flags } = await this.parse(IntegrationsAscDisconnect);
|
|
30
|
+
const { json, nonInteractive } = (0, flags_1.resolveNonInteractiveAndJsonFlags)(flags);
|
|
31
|
+
if (json) {
|
|
32
|
+
(0, json_1.enableJsonOutput)();
|
|
33
|
+
}
|
|
34
|
+
const { projectId, loggedIn: { graphqlClient }, } = await this.getContextAsync(IntegrationsAscDisconnect, {
|
|
35
|
+
nonInteractive: nonInteractive || flags.yes,
|
|
36
|
+
});
|
|
37
|
+
// Step 1: Check current status
|
|
38
|
+
const metadata = await this.fetchCurrentStatusAsync(graphqlClient, projectId);
|
|
39
|
+
if (!metadata) {
|
|
40
|
+
if (json) {
|
|
41
|
+
(0, json_1.printJsonOnlyOutput)((0, utils_1.buildInvalidJsonOutput)('disconnect', projectId));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
log_1.default.addNewLineIfNone();
|
|
45
|
+
log_1.default.warn('The App Store Connect API key linked to this project has been revoked or is no longer valid.\nThe connection cannot be resolved from the CLI. Please update the API key on the Expo dashboard.');
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!metadata.appStoreConnectApp) {
|
|
50
|
+
if (json) {
|
|
51
|
+
(0, json_1.printJsonOnlyOutput)((0, utils_1.buildJsonOutput)('disconnect', metadata));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
log_1.default.addNewLineIfNone();
|
|
55
|
+
log_1.default.log(`Project ${chalk_1.default.bold(metadata.fullName)} is not connected to any App Store Connect app.`);
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Step 2: Confirm
|
|
60
|
+
if (!flags.yes && !nonInteractive) {
|
|
61
|
+
log_1.default.addNewLineIfNone();
|
|
62
|
+
log_1.default.log((0, utils_1.formatAscAppLinkStatus)(metadata));
|
|
63
|
+
log_1.default.newLine();
|
|
64
|
+
log_1.default.warn('You are about to disconnect this project from its App Store Connect app.\nThis action is reversible by reconnecting.');
|
|
65
|
+
log_1.default.newLine();
|
|
66
|
+
const confirmed = await (0, prompts_1.toggleConfirmAsync)({
|
|
67
|
+
message: 'Are you sure you wish to proceed?',
|
|
68
|
+
});
|
|
69
|
+
if (!confirmed) {
|
|
70
|
+
log_1.default.error('Canceled disconnection');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Step 3: Delete
|
|
75
|
+
const deleteSpinner = (0, ora_1.ora)('Disconnecting App Store Connect app').start();
|
|
76
|
+
try {
|
|
77
|
+
await AscAppLinkMutation_1.AscAppLinkMutation.deleteAppStoreConnectAppAsync(graphqlClient, metadata.appStoreConnectApp.id);
|
|
78
|
+
deleteSpinner.succeed('Disconnected App Store Connect app');
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
deleteSpinner.fail('Failed to disconnect App Store Connect app');
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
// Step 4: Refetch and display
|
|
85
|
+
const refetchSpinner = (0, ora_1.ora)('Verifying disconnection').start();
|
|
86
|
+
const updatedMetadata = await AscAppLinkQuery_1.AscAppLinkQuery.getAppMetadataAsync(graphqlClient, projectId, {
|
|
87
|
+
useCache: false,
|
|
88
|
+
});
|
|
89
|
+
refetchSpinner.succeed('Verified disconnection');
|
|
90
|
+
if (json) {
|
|
91
|
+
(0, json_1.printJsonOnlyOutput)((0, utils_1.buildJsonOutput)('disconnect', updatedMetadata));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
log_1.default.addNewLineIfNone();
|
|
95
|
+
log_1.default.log((0, utils_1.formatAscAppLinkStatus)(updatedMetadata));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async fetchCurrentStatusAsync(graphqlClient, projectId) {
|
|
99
|
+
const spinner = (0, ora_1.ora)('Checking current App Store Connect app link status').start();
|
|
100
|
+
try {
|
|
101
|
+
const metadata = await AscAppLinkQuery_1.AscAppLinkQuery.getAppMetadataAsync(graphqlClient, projectId);
|
|
102
|
+
spinner.succeed('Checked current status');
|
|
103
|
+
return metadata;
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
if ((0, utils_1.isAscAuthenticationError)(err)) {
|
|
107
|
+
spinner.fail('App Store Connect connection is invalid');
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
spinner.fail('Failed to check current status');
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.default = IntegrationsAscDisconnect;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import EasCommand from '../../../commandUtils/EasCommand';
|
|
2
|
+
export default class IntegrationsAscStatus extends EasCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
6
|
+
'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
};
|
|
8
|
+
static contextDefinition: {
|
|
9
|
+
loggedIn: import("../../../commandUtils/context/LoggedInContextField").default;
|
|
10
|
+
projectId: import("../../../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
|
|
11
|
+
};
|
|
12
|
+
runAsync(): Promise<void>;
|
|
13
|
+
private fetchStatusAsync;
|
|
14
|
+
}
|