eas-cli 16.30.0 → 16.32.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 +110 -87
- package/build/commandUtils/context/contextUtils/createGraphqlClient.d.ts +1 -5
- package/build/commandUtils/usageUtils.d.ts +58 -0
- package/build/commandUtils/usageUtils.js +204 -0
- package/build/commands/account/usage.d.ts +16 -0
- package/build/commands/account/usage.js +326 -0
- package/build/commands/submit/upload-to-asc.d.ts +12 -0
- package/build/commands/submit/upload-to-asc.js +217 -0
- package/build/commands/update/index.js +0 -54
- package/build/graphql/generated.d.ts +1063 -39
- package/build/graphql/generated.js +78 -11
- package/build/graphql/queries/AccountQuery.d.ts +15 -0
- package/build/graphql/queries/AccountQuery.js +197 -0
- package/build/graphql/types/Account.d.ts +11 -0
- package/build/graphql/types/Account.js +142 -1
- package/build/submit/ios/AscApiClient.d.ts +247 -0
- package/build/submit/ios/AscApiClient.js +287 -0
- package/build/utils/usage/checkForOverages.js +2 -2
- package/oclif.manifest.json +80 -1
- package/package.json +2 -2
- package/build/graphql/queries/AccountUsageQuery.d.ts +0 -5
- package/build/graphql/queries/AccountUsageQuery.js +0 -40
|
@@ -0,0 +1,217 @@
|
|
|
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 fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
6
|
+
const jsonwebtoken_1 = tslib_1.__importDefault(require("jsonwebtoken"));
|
|
7
|
+
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
|
|
8
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
9
|
+
const promises_1 = require("timers/promises");
|
|
10
|
+
const zod_1 = require("zod");
|
|
11
|
+
const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
|
|
12
|
+
const log_1 = tslib_1.__importDefault(require("../../log"));
|
|
13
|
+
const AscApiClient_1 = require("../../submit/ios/AscApiClient");
|
|
14
|
+
class SubmitUploadToAsc extends EasCommand_1.default {
|
|
15
|
+
static hidden = true;
|
|
16
|
+
static flags = {
|
|
17
|
+
path: core_1.Flags.string({
|
|
18
|
+
description: 'Path to the IPA file',
|
|
19
|
+
required: true,
|
|
20
|
+
}),
|
|
21
|
+
key: core_1.Flags.string({
|
|
22
|
+
description: 'Path to the ASC API Key JSON file',
|
|
23
|
+
required: true,
|
|
24
|
+
}),
|
|
25
|
+
'app-id': core_1.Flags.string({
|
|
26
|
+
description: 'App Store Connect App ID (e.g. 1491144534)',
|
|
27
|
+
required: true,
|
|
28
|
+
}),
|
|
29
|
+
'bundle-version': core_1.Flags.string({
|
|
30
|
+
description: 'CFBundleVersion (Build Version, e.g. 13)',
|
|
31
|
+
required: true,
|
|
32
|
+
}),
|
|
33
|
+
'bundle-short-version': core_1.Flags.string({
|
|
34
|
+
description: 'CFBundleShortVersionString (Marketing Version, e.g. 1.0.0)',
|
|
35
|
+
required: true,
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
async runAsync() {
|
|
39
|
+
const { flags } = await this.parse(SubmitUploadToAsc);
|
|
40
|
+
const ipaPath = path_1.default.resolve(flags.path);
|
|
41
|
+
if (!(await fs_extra_1.default.pathExists(ipaPath))) {
|
|
42
|
+
throw new Error(`IPA file not found: ${ipaPath}`);
|
|
43
|
+
}
|
|
44
|
+
const fileSize = (await fs_extra_1.default.stat(ipaPath)).size;
|
|
45
|
+
const fileName = path_1.default.basename(ipaPath);
|
|
46
|
+
const keyPath = path_1.default.resolve(flags.key);
|
|
47
|
+
if (!(await fs_extra_1.default.pathExists(keyPath))) {
|
|
48
|
+
throw new Error(`Key file not found: ${keyPath}`);
|
|
49
|
+
}
|
|
50
|
+
const keyJson = await fs_extra_1.default.readJson(keyPath);
|
|
51
|
+
const apiKey = zod_1.z
|
|
52
|
+
.object({
|
|
53
|
+
issuer_id: zod_1.z.string(),
|
|
54
|
+
key_id: zod_1.z.string(),
|
|
55
|
+
key: zod_1.z.string(),
|
|
56
|
+
})
|
|
57
|
+
.parse(keyJson);
|
|
58
|
+
log_1.default.log('Generating JWT...');
|
|
59
|
+
const token = jsonwebtoken_1.default.sign({}, apiKey.key, {
|
|
60
|
+
algorithm: 'ES256',
|
|
61
|
+
issuer: apiKey.issuer_id,
|
|
62
|
+
expiresIn: '20m',
|
|
63
|
+
audience: 'appstoreconnect-v1',
|
|
64
|
+
header: {
|
|
65
|
+
kid: apiKey.key_id,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
const client = new AscApiClient_1.AscApiClient({ token });
|
|
69
|
+
log_1.default.log('Reading App information...');
|
|
70
|
+
const appResponse = await client.getAsync('/v1/apps/:id', { 'fields[apps]': ['bundleId', 'name'] }, { id: flags['app-id'] });
|
|
71
|
+
log_1.default.log(`Uploading Build to "${appResponse.data.attributes.name}" (${appResponse.data.attributes.bundleId})...`);
|
|
72
|
+
log_1.default.log('Creating Build Upload...');
|
|
73
|
+
const buildUploadResponse = await client.postAsync('/v1/buildUploads', {
|
|
74
|
+
data: {
|
|
75
|
+
type: 'buildUploads',
|
|
76
|
+
attributes: {
|
|
77
|
+
platform: 'IOS',
|
|
78
|
+
cfBundleShortVersionString: flags['bundle-short-version'],
|
|
79
|
+
cfBundleVersion: flags['bundle-version'],
|
|
80
|
+
},
|
|
81
|
+
relationships: {
|
|
82
|
+
app: {
|
|
83
|
+
data: {
|
|
84
|
+
type: 'apps',
|
|
85
|
+
id: flags['app-id'],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
const buildUploadId = buildUploadResponse.data.id;
|
|
92
|
+
log_1.default.log(`Build Upload initialized (ID: ${buildUploadId}). Preparing IPA upload...`);
|
|
93
|
+
const buildFileResponse = await client.postAsync('/v1/buildUploadFiles', {
|
|
94
|
+
data: {
|
|
95
|
+
type: 'buildUploadFiles',
|
|
96
|
+
attributes: {
|
|
97
|
+
assetType: 'ASSET',
|
|
98
|
+
fileName,
|
|
99
|
+
fileSize,
|
|
100
|
+
uti: 'com.apple.ipa',
|
|
101
|
+
},
|
|
102
|
+
relationships: {
|
|
103
|
+
buildUpload: {
|
|
104
|
+
data: {
|
|
105
|
+
type: 'buildUploads',
|
|
106
|
+
id: buildUploadId,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
log_1.default.log(`IPA Upload initialized (ID: ${buildFileResponse.data.id}). Uploading IPA...`);
|
|
113
|
+
await uploadChunksAsync({
|
|
114
|
+
uploadOperations: buildFileResponse.data.attributes.uploadOperations,
|
|
115
|
+
ipaPath,
|
|
116
|
+
});
|
|
117
|
+
log_1.default.log('Committing upload...');
|
|
118
|
+
await client.patchAsync(`/v1/buildUploadFiles/:id`, {
|
|
119
|
+
data: {
|
|
120
|
+
type: 'buildUploadFiles',
|
|
121
|
+
id: buildFileResponse.data.id,
|
|
122
|
+
attributes: {
|
|
123
|
+
uploaded: true,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
}, {
|
|
127
|
+
id: buildFileResponse.data.id,
|
|
128
|
+
});
|
|
129
|
+
log_1.default.log('Checking upload file status...');
|
|
130
|
+
const waitingForFileStartedAt = performance.now();
|
|
131
|
+
while (performance.now() - waitingForFileStartedAt < 60 * 1000 /* 60 seconds */) {
|
|
132
|
+
const buildFileStatusResponse = await client.getAsync(`/v1/buildUploadFiles/:id`, { 'fields[buildUploadFiles]': ['assetDeliveryState'] }, { id: buildFileResponse.data.id });
|
|
133
|
+
if (buildFileStatusResponse.data.attributes.assetDeliveryState.state === 'AWAITING_UPLOAD') {
|
|
134
|
+
log_1.default.log(`Waiting for file upload to finish processing... (state = ${buildFileStatusResponse.data.attributes.assetDeliveryState.state})`);
|
|
135
|
+
await (0, promises_1.setTimeout)(2000);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const { errors = [], warnings = [] } = buildFileStatusResponse.data.attributes.assetDeliveryState;
|
|
139
|
+
if (warnings.length > 0) {
|
|
140
|
+
log_1.default.warn(`Warnings:\n- ${warnings.map(w => `${w.description} (${w.code})`).join('\n- ')}\n`);
|
|
141
|
+
}
|
|
142
|
+
if (errors.length > 0) {
|
|
143
|
+
log_1.default.error(`Errors:\n- ${errors.map(e => `${e.description} (${e.code})`).join('\n- ')}\n`);
|
|
144
|
+
}
|
|
145
|
+
if (buildFileStatusResponse.data.attributes.assetDeliveryState.state === 'FAILED') {
|
|
146
|
+
throw new Error(`File upload (ID: ${buildFileResponse.data.id}) failed.`);
|
|
147
|
+
}
|
|
148
|
+
else if (buildFileStatusResponse.data.attributes.assetDeliveryState.state === 'COMPLETE') {
|
|
149
|
+
log_1.default.log(`File upload (ID: ${buildFileResponse.data.id}) complete!`);
|
|
150
|
+
}
|
|
151
|
+
else if (buildFileStatusResponse.data.attributes.assetDeliveryState.state === 'UPLOAD_COMPLETE') {
|
|
152
|
+
log_1.default.log(`File upload (ID: ${buildFileResponse.data.id}) finished!`);
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
log_1.default.log('Checking build upload status...');
|
|
157
|
+
const waitingForBuildStartedAt = performance.now();
|
|
158
|
+
while (performance.now() - waitingForBuildStartedAt < 60 * 1000 /* 60 seconds */) {
|
|
159
|
+
const buildUploadStatusResponse = await client.getAsync(`/v1/buildUploads/:id`, { 'fields[buildUploads]': ['state', 'build'], include: ['build'] }, { id: buildUploadId });
|
|
160
|
+
if (buildUploadStatusResponse.data.attributes.state.state === 'AWAITING_UPLOAD' ||
|
|
161
|
+
buildUploadStatusResponse.data.attributes.state.state === 'PROCESSING') {
|
|
162
|
+
log_1.default.log(`Waiting for build upload to finish... (status = ${buildUploadStatusResponse.data.attributes.state.state})`);
|
|
163
|
+
await (0, promises_1.setTimeout)(2000);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
log_1.default.log('\n');
|
|
167
|
+
const { errors = [], warnings = [], infos = [], } = buildUploadStatusResponse.data.attributes.state;
|
|
168
|
+
if (infos.length > 0) {
|
|
169
|
+
log_1.default.log(`Infos:\n- ${infos.map(i => `${i.description} (${i.code})`).join('\n- ')}\n`);
|
|
170
|
+
}
|
|
171
|
+
if (warnings.length > 0) {
|
|
172
|
+
log_1.default.warn(`Warnings:\n- ${warnings.map(w => `${w.description} (${w.code})`).join('\n- ')}\n`);
|
|
173
|
+
}
|
|
174
|
+
if (errors.length > 0) {
|
|
175
|
+
log_1.default.error(`Errors:\n- ${errors.map(e => `${e.description} (${e.code})`).join('\n- ')}\n`);
|
|
176
|
+
}
|
|
177
|
+
if (buildUploadStatusResponse.data.attributes.state.state === 'FAILED') {
|
|
178
|
+
throw new Error(`Build upload (ID: ${buildUploadId}) failed.`);
|
|
179
|
+
}
|
|
180
|
+
else if (buildUploadStatusResponse.data.attributes.state.state === 'COMPLETE') {
|
|
181
|
+
log_1.default.log(`Build upload (ID: ${buildUploadId}) complete!`);
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
exports.default = SubmitUploadToAsc;
|
|
188
|
+
async function uploadChunksAsync({ uploadOperations, ipaPath, }) {
|
|
189
|
+
const fd = await fs_extra_1.default.open(ipaPath, 'r');
|
|
190
|
+
try {
|
|
191
|
+
const promises = uploadOperations.map(async (op, index) => {
|
|
192
|
+
log_1.default.log(` Uploading chunk ${index + 1}/${uploadOperations.length} (offset: ${op.offset}, length: ${op.length})...`);
|
|
193
|
+
const buffer = new Uint8Array(op.length);
|
|
194
|
+
await fs_extra_1.default.read(fd, buffer, 0, op.length, op.offset);
|
|
195
|
+
const headers = {
|
|
196
|
+
'Content-Type': 'application/octet-stream',
|
|
197
|
+
};
|
|
198
|
+
for (const { name, value } of op.requestHeaders) {
|
|
199
|
+
headers[name] = value;
|
|
200
|
+
}
|
|
201
|
+
const response = await (0, node_fetch_1.default)(op.url, {
|
|
202
|
+
method: op.method,
|
|
203
|
+
headers,
|
|
204
|
+
body: buffer,
|
|
205
|
+
});
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
const text = await response.text();
|
|
208
|
+
throw new Error(`Failed to upload chunk ${index + 1}: ${response.status} ${response.statusText}\n${text}`);
|
|
209
|
+
}
|
|
210
|
+
log_1.default.log(` Chunk ${index + 1}/${uploadOperations.length} uploaded.`);
|
|
211
|
+
});
|
|
212
|
+
await Promise.all(promises);
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
await fs_extra_1.default.close(fd);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -7,7 +7,6 @@ const eas_json_1 = require("@expo/eas-json");
|
|
|
7
7
|
const core_1 = require("@oclif/core");
|
|
8
8
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
9
9
|
const nullthrows_1 = tslib_1.__importDefault(require("nullthrows"));
|
|
10
|
-
// import { getExpoWebsiteBaseUrl } from '../../api';
|
|
11
10
|
const queries_1 = require("../../branch/queries");
|
|
12
11
|
const repository_1 = require("../../build/utils/repository");
|
|
13
12
|
const url_1 = require("../../build/utils/url");
|
|
@@ -17,7 +16,6 @@ const pagination_1 = require("../../commandUtils/pagination");
|
|
|
17
16
|
const fetch_1 = tslib_1.__importDefault(require("../../fetch"));
|
|
18
17
|
const generated_1 = require("../../graphql/generated");
|
|
19
18
|
const PublishMutation_1 = require("../../graphql/mutations/PublishMutation");
|
|
20
|
-
// import { AppQuery } from '../../graphql/queries/AppQuery';
|
|
21
19
|
const log_1 = tslib_1.__importStar(require("../../log"));
|
|
22
20
|
const ora_1 = require("../../ora");
|
|
23
21
|
const platform_1 = require("../../platform");
|
|
@@ -433,11 +431,6 @@ class UpdatePublish extends EasCommand_1.default {
|
|
|
433
431
|
log_1.default.log('👉 Since multiple runtime versions are defined, multiple update groups have been published.');
|
|
434
432
|
}
|
|
435
433
|
log_1.default.addNewLineIfNone();
|
|
436
|
-
// const runtimeToCompatibleBuilds = await Promise.all(
|
|
437
|
-
// runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping.map(obj =>
|
|
438
|
-
// findCompatibleBuildsAsync(graphqlClient, projectId, obj)
|
|
439
|
-
// )
|
|
440
|
-
// );
|
|
441
434
|
for (const runtime of (0, uniqBy_1.default)(runtimeToPlatformsAndFingerprintInfoMapping, version => version.runtimeVersion)) {
|
|
442
435
|
const newUpdatesForRuntimeVersion = newUpdates.filter(update => update.runtimeVersion === runtime.runtimeVersion);
|
|
443
436
|
if (newUpdatesForRuntimeVersion.length === 0) {
|
|
@@ -494,55 +487,8 @@ class UpdatePublish extends EasCommand_1.default {
|
|
|
494
487
|
})}.`);
|
|
495
488
|
log_1.default.addNewLineIfNone();
|
|
496
489
|
}
|
|
497
|
-
// NOTE(brentvatne): temporarily disable logging this until we can revisit the formatting
|
|
498
|
-
// and the logic for it - it's a bit too aggressive right now, and warns even if you're
|
|
499
|
-
// not using EAS Build
|
|
500
|
-
//
|
|
501
|
-
// const fingerprintsWithoutCompatibleBuilds = runtimeToCompatibleBuilds.find(
|
|
502
|
-
// ({ runtimeVersion }) => runtimeVersion === runtime.runtimeVersion
|
|
503
|
-
// )?.fingerprintInfoGroupWithCompatibleBuilds;
|
|
504
|
-
// if (fingerprintsWithoutCompatibleBuilds) {
|
|
505
|
-
// const missingBuilds = Object.entries(fingerprintsWithoutCompatibleBuilds).filter(
|
|
506
|
-
// ([_platform, fingerprintInfo]) => !fingerprintInfo.build
|
|
507
|
-
// );
|
|
508
|
-
// if (missingBuilds.length > 0) {
|
|
509
|
-
// const project = await AppQuery.byIdAsync(graphqlClient, projectId);
|
|
510
|
-
// Log.warn('No compatible builds found for the following fingerprints:');
|
|
511
|
-
// for (const [platform, fingerprintInfo] of missingBuilds) {
|
|
512
|
-
// const fingerprintUrl = new URL(
|
|
513
|
-
// `/accounts/${project.ownerAccount.name}/projects/${project.slug}/fingerprints/${fingerprintInfo.fingerprintHash}`,
|
|
514
|
-
// getExpoWebsiteBaseUrl()
|
|
515
|
-
// );
|
|
516
|
-
// Log.warn(
|
|
517
|
-
// formatFields(
|
|
518
|
-
// [
|
|
519
|
-
// {
|
|
520
|
-
// label: `${this.prettyPlatform(platform)} fingerprint`,
|
|
521
|
-
// value: fingerprintInfo.fingerprintHash,
|
|
522
|
-
// },
|
|
523
|
-
// { label: 'URL', value: fingerprintUrl.toString() },
|
|
524
|
-
// ],
|
|
525
|
-
// {
|
|
526
|
-
// labelFormat: label => ` ${chalk.dim(label)}:`,
|
|
527
|
-
// }
|
|
528
|
-
// )
|
|
529
|
-
// );
|
|
530
|
-
// Log.addNewLineIfNone();
|
|
531
|
-
// }
|
|
532
|
-
// }
|
|
533
|
-
// }
|
|
534
490
|
}
|
|
535
491
|
}
|
|
536
|
-
// private prettyPlatform(updatePlatform: string): string {
|
|
537
|
-
// switch (updatePlatform) {
|
|
538
|
-
// case 'android':
|
|
539
|
-
// return 'Android';
|
|
540
|
-
// case 'ios':
|
|
541
|
-
// return 'iOS';
|
|
542
|
-
// default:
|
|
543
|
-
// return updatePlatform;
|
|
544
|
-
// }
|
|
545
|
-
// }
|
|
546
492
|
sanitizeFlags(flags) {
|
|
547
493
|
const nonInteractive = flags['non-interactive'] ?? false;
|
|
548
494
|
const { auto, branch: branchName, channel: channelName, message: updateMessage } = flags;
|