eas-cli 16.31.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/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
|
+
}
|