eas-cli 16.14.0 → 16.15.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 +80 -80
- package/build/commands/deploy/index.js +47 -49
- package/build/credentials/ios/actions/AppleTeamUtils.js +1 -1
- package/build/credentials/ios/actions/SetUpTargetBuildCredentialsFromCredentialsJson.js +1 -1
- package/build/credentials/ios/api/GraphqlClient.d.ts +1 -1
- package/build/credentials/ios/api/GraphqlClient.js +14 -8
- package/build/credentials/ios/api/graphql/mutations/AppleTeamMutation.d.ts +3 -5
- package/build/credentials/ios/api/graphql/mutations/AppleTeamMutation.js +22 -6
- package/build/devices/manager.js +2 -15
- package/build/graphql/generated.d.ts +16 -28
- package/build/worker/assets.d.ts +19 -13
- package/build/worker/assets.js +49 -20
- package/build/worker/upload.d.ts +21 -9
- package/build/worker/upload.js +102 -80
- package/build/worker/utils/multipart.d.ts +10 -0
- package/build/worker/utils/multipart.js +63 -0
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
package/build/worker/upload.js
CHANGED
|
@@ -1,53 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.batchUploadAsync = exports.callUploadApiAsync = exports.uploadAsync = void 0;
|
|
3
|
+
exports.createProgressBar = exports.batchUploadAsync = exports.callUploadApiAsync = exports.uploadAsync = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
+
const cli_progress_1 = tslib_1.__importDefault(require("cli-progress"));
|
|
5
6
|
const https = tslib_1.__importStar(require("https"));
|
|
6
7
|
const https_proxy_agent_1 = tslib_1.__importDefault(require("https-proxy-agent"));
|
|
7
|
-
const mime_1 = tslib_1.__importDefault(require("mime"));
|
|
8
|
-
const minizlib_1 = require("minizlib");
|
|
9
8
|
const node_fetch_1 = tslib_1.__importStar(require("node-fetch"));
|
|
10
|
-
const node_fs_1 = tslib_1.
|
|
11
|
-
const
|
|
12
|
-
const
|
|
9
|
+
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
10
|
+
const node_os_1 = tslib_1.__importDefault(require("node:os"));
|
|
11
|
+
const node_stream_1 = require("node:stream");
|
|
13
12
|
const promise_retry_1 = tslib_1.__importDefault(require("promise-retry"));
|
|
13
|
+
const multipart_1 = require("./utils/multipart");
|
|
14
|
+
const MAX_CONCURRENCY = Math.min(10, Math.max(node_os_1.default.availableParallelism() * 2, 20));
|
|
14
15
|
const MAX_RETRIES = 4;
|
|
15
|
-
const MAX_CONCURRENCY = 10;
|
|
16
|
-
const MIN_RETRY_TIMEOUT = 100;
|
|
17
|
-
const MAX_UPLOAD_SIZE = 5e8; // 5MB
|
|
18
|
-
const MIN_COMPRESSION_SIZE = 5e4; // 50kB
|
|
19
|
-
const isCompressible = (contentType, size) => {
|
|
20
|
-
if (size < MIN_COMPRESSION_SIZE) {
|
|
21
|
-
// Don't compress small files
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
else if (contentType && /^(?:audio|video|image)\//i.test(contentType)) {
|
|
25
|
-
// Never compress images, audio, or videos as they're presumably precompressed
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
else if (contentType && /^application\//i.test(contentType)) {
|
|
29
|
-
// Only compress `application/` files if they're marked as XML/JSON/JS
|
|
30
|
-
return /(?:xml|json5?|javascript)$/i.test(contentType);
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
const getContentTypeAsync = async (filePath) => {
|
|
37
|
-
let contentType = mime_1.default.getType(node_path_1.default.basename(filePath));
|
|
38
|
-
if (!contentType) {
|
|
39
|
-
const fileContent = await (0, promises_1.readFile)(filePath, 'utf-8');
|
|
40
|
-
try {
|
|
41
|
-
// check if file is valid JSON without an extension, e.g. for the apple app site association file
|
|
42
|
-
const parsedData = JSON.parse(fileContent);
|
|
43
|
-
if (parsedData) {
|
|
44
|
-
contentType = 'application/json';
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
catch { }
|
|
48
|
-
}
|
|
49
|
-
return contentType;
|
|
50
|
-
};
|
|
51
16
|
let sharedAgent;
|
|
52
17
|
const getAgent = () => {
|
|
53
18
|
if (sharedAgent) {
|
|
@@ -66,36 +31,56 @@ const getAgent = () => {
|
|
|
66
31
|
}));
|
|
67
32
|
}
|
|
68
33
|
};
|
|
69
|
-
async function uploadAsync(
|
|
70
|
-
const { filePath, signal, compress, method = 'POST', url, headers: headersInit, ...requestInit } = params;
|
|
71
|
-
const stat = await node_fs_1.default.promises.stat(filePath);
|
|
72
|
-
if (stat.size > MAX_UPLOAD_SIZE) {
|
|
73
|
-
throw new Error(`Upload of "${filePath}" aborted: File size is greater than the upload limit (>500MB)`);
|
|
74
|
-
}
|
|
75
|
-
const contentType = await getContentTypeAsync(filePath);
|
|
34
|
+
async function uploadAsync(init, payload, onProgressUpdate) {
|
|
76
35
|
return await (0, promise_retry_1.default)(async (retry) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
36
|
+
if (onProgressUpdate) {
|
|
37
|
+
onProgressUpdate(0);
|
|
38
|
+
}
|
|
39
|
+
const headers = new node_fetch_1.Headers(init.headers);
|
|
40
|
+
const url = new URL(`${init.baseURL}`);
|
|
41
|
+
let errorPrefix;
|
|
42
|
+
let body;
|
|
43
|
+
let method = init.method || 'POST';
|
|
44
|
+
if ('asset' in payload) {
|
|
45
|
+
const { asset } = payload;
|
|
46
|
+
errorPrefix = `Upload of "${asset.normalizedPath}" failed`;
|
|
47
|
+
if (asset.type) {
|
|
48
|
+
headers.set('content-type', asset.type);
|
|
49
|
+
}
|
|
50
|
+
if (asset.size) {
|
|
51
|
+
headers.set('content-length', `${asset.size}`);
|
|
52
|
+
}
|
|
53
|
+
method = 'POST';
|
|
54
|
+
url.pathname = `/asset/${asset.sha512}`;
|
|
55
|
+
body = node_fs_1.default.createReadStream(asset.path);
|
|
56
|
+
}
|
|
57
|
+
else if ('filePath' in payload) {
|
|
58
|
+
const { filePath } = payload;
|
|
59
|
+
errorPrefix = 'Worker deployment failed';
|
|
60
|
+
body = node_fs_1.default.createReadStream(filePath);
|
|
61
|
+
}
|
|
62
|
+
else if ('multipart' in payload) {
|
|
63
|
+
const { multipart } = payload;
|
|
64
|
+
errorPrefix = `Upload of ${multipart.length} assets failed`;
|
|
65
|
+
headers.set('content-type', multipart_1.multipartContentType);
|
|
66
|
+
method = 'PATCH';
|
|
67
|
+
url.pathname = '/asset/batch';
|
|
68
|
+
const iterator = (0, multipart_1.createMultipartBodyFromFilesAsync)(multipart.map(asset => ({
|
|
69
|
+
name: asset.sha512,
|
|
70
|
+
filePath: asset.path,
|
|
71
|
+
contentType: asset.type,
|
|
72
|
+
contentLength: asset.size,
|
|
73
|
+
})), onProgressUpdate);
|
|
74
|
+
body = node_stream_1.Readable.from(iterator);
|
|
88
75
|
}
|
|
89
76
|
let response;
|
|
90
77
|
try {
|
|
91
|
-
response = await (0, node_fetch_1.default)(
|
|
92
|
-
...requestInit,
|
|
78
|
+
response = await (0, node_fetch_1.default)(url, {
|
|
93
79
|
method,
|
|
94
|
-
body
|
|
80
|
+
body,
|
|
95
81
|
headers,
|
|
96
82
|
agent: getAgent(),
|
|
97
|
-
|
|
98
|
-
signal,
|
|
83
|
+
signal: init.signal,
|
|
99
84
|
});
|
|
100
85
|
}
|
|
101
86
|
catch (error) {
|
|
@@ -113,11 +98,11 @@ async function uploadAsync(params) {
|
|
|
113
98
|
if (rayId) {
|
|
114
99
|
message += `\nReport this error quoting Request ID ${rayId}`;
|
|
115
100
|
}
|
|
116
|
-
return
|
|
101
|
+
return `${errorPrefix}: ${message}`;
|
|
117
102
|
}
|
|
118
103
|
else {
|
|
119
104
|
const json = await response.json().catch(() => null);
|
|
120
|
-
return json?.error ??
|
|
105
|
+
return json?.error ?? `${errorPrefix}: ${response.statusText}`;
|
|
121
106
|
}
|
|
122
107
|
};
|
|
123
108
|
if (response.status === 408 ||
|
|
@@ -127,21 +112,23 @@ async function uploadAsync(params) {
|
|
|
127
112
|
return retry(new Error(await getErrorMessageAsync()));
|
|
128
113
|
}
|
|
129
114
|
else if (response.status === 413) {
|
|
130
|
-
const message =
|
|
115
|
+
const message = `${errorPrefix}: File size exceeded the upload limit`;
|
|
131
116
|
throw new Error(message);
|
|
132
117
|
}
|
|
133
118
|
else if (!response.ok) {
|
|
134
119
|
throw new Error(await getErrorMessageAsync());
|
|
135
120
|
}
|
|
121
|
+
else if (onProgressUpdate) {
|
|
122
|
+
onProgressUpdate(1);
|
|
123
|
+
}
|
|
136
124
|
return {
|
|
137
|
-
|
|
125
|
+
payload,
|
|
138
126
|
response,
|
|
139
127
|
};
|
|
140
128
|
}, {
|
|
141
129
|
retries: MAX_RETRIES,
|
|
142
|
-
minTimeout:
|
|
143
|
-
randomize:
|
|
144
|
-
factor: 2,
|
|
130
|
+
minTimeout: 50,
|
|
131
|
+
randomize: false,
|
|
145
132
|
});
|
|
146
133
|
}
|
|
147
134
|
exports.uploadAsync = uploadAsync;
|
|
@@ -169,19 +156,41 @@ async function callUploadApiAsync(url, init) {
|
|
|
169
156
|
});
|
|
170
157
|
}
|
|
171
158
|
exports.callUploadApiAsync = callUploadApiAsync;
|
|
172
|
-
async function* batchUploadAsync(
|
|
159
|
+
async function* batchUploadAsync(init, payloads, onProgressUpdate) {
|
|
160
|
+
const progressTracker = new Array(payloads.length).fill(0);
|
|
173
161
|
const controller = new AbortController();
|
|
174
162
|
const queue = new Set();
|
|
163
|
+
const initWithSignal = { ...init, signal: controller.signal };
|
|
164
|
+
const getProgressValue = () => {
|
|
165
|
+
const progress = progressTracker.reduce((acc, value) => acc + value, 0);
|
|
166
|
+
return progress / payloads.length;
|
|
167
|
+
};
|
|
168
|
+
const sendProgressUpdate = onProgressUpdate &&
|
|
169
|
+
(() => {
|
|
170
|
+
onProgressUpdate(getProgressValue());
|
|
171
|
+
});
|
|
175
172
|
try {
|
|
176
173
|
let index = 0;
|
|
177
|
-
while (index <
|
|
178
|
-
while (queue.size < MAX_CONCURRENCY && index <
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
174
|
+
while (index < payloads.length || queue.size > 0) {
|
|
175
|
+
while (queue.size < MAX_CONCURRENCY && index < payloads.length) {
|
|
176
|
+
const currentIndex = index++;
|
|
177
|
+
const payload = payloads[currentIndex];
|
|
178
|
+
const onChildProgressUpdate = sendProgressUpdate &&
|
|
179
|
+
((progress) => {
|
|
180
|
+
progressTracker[currentIndex] = progress;
|
|
181
|
+
sendProgressUpdate();
|
|
182
|
+
});
|
|
183
|
+
const uploadPromise = uploadAsync(initWithSignal, payload, onChildProgressUpdate).finally(() => {
|
|
184
|
+
queue.delete(uploadPromise);
|
|
185
|
+
progressTracker[currentIndex] = 1;
|
|
186
|
+
});
|
|
187
|
+
queue.add(uploadPromise);
|
|
188
|
+
yield { payload, progress: getProgressValue() };
|
|
183
189
|
}
|
|
184
|
-
yield
|
|
190
|
+
yield {
|
|
191
|
+
...(await Promise.race(queue)),
|
|
192
|
+
progress: getProgressValue(),
|
|
193
|
+
};
|
|
185
194
|
}
|
|
186
195
|
if (queue.size > 0) {
|
|
187
196
|
controller.abort();
|
|
@@ -194,3 +203,16 @@ async function* batchUploadAsync(uploads) {
|
|
|
194
203
|
}
|
|
195
204
|
}
|
|
196
205
|
exports.batchUploadAsync = batchUploadAsync;
|
|
206
|
+
function createProgressBar(label = 'Uploading assets') {
|
|
207
|
+
const queueProgressBar = new cli_progress_1.default.SingleBar({ format: `|{bar}| {percentage}% ${label}` }, cli_progress_1.default.Presets.rect);
|
|
208
|
+
queueProgressBar.start(1, 0);
|
|
209
|
+
return {
|
|
210
|
+
update(progress) {
|
|
211
|
+
queueProgressBar.update(progress);
|
|
212
|
+
},
|
|
213
|
+
stop() {
|
|
214
|
+
queueProgressBar.stop();
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
exports.createProgressBar = createProgressBar;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface MultipartFileEntry {
|
|
2
|
+
name: string;
|
|
3
|
+
filePath: string;
|
|
4
|
+
contentType: string | null;
|
|
5
|
+
contentLength: number | null;
|
|
6
|
+
}
|
|
7
|
+
export declare const multipartContentType = "multipart/form-data; boundary=----formdata-eas-cli";
|
|
8
|
+
type OnProgressUpdateCallback = (progress: number) => void;
|
|
9
|
+
export declare function createMultipartBodyFromFilesAsync(entries: MultipartFileEntry[], onProgressUpdate?: OnProgressUpdateCallback): AsyncGenerator<Uint8Array>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMultipartBodyFromFilesAsync = exports.multipartContentType = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
6
|
+
const CRLF = '\r\n';
|
|
7
|
+
const BOUNDARY_HYPHEN_CHARS = '--';
|
|
8
|
+
const BOUNDARY_ID = '----formdata-eas-cli';
|
|
9
|
+
const FORM_FOOTER = `${BOUNDARY_HYPHEN_CHARS}${BOUNDARY_ID}${BOUNDARY_HYPHEN_CHARS}${CRLF}${CRLF}`;
|
|
10
|
+
const encodeName = (input) => {
|
|
11
|
+
return input.replace(/["\n\\]/g, (c) => {
|
|
12
|
+
switch (c) {
|
|
13
|
+
case '\\':
|
|
14
|
+
return '\\\\';
|
|
15
|
+
case '"':
|
|
16
|
+
return '%22';
|
|
17
|
+
case '\n':
|
|
18
|
+
return '%0A';
|
|
19
|
+
default:
|
|
20
|
+
return `%${c.charCodeAt(0).toString(16).toUpperCase()}`;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
async function* createReadStreamAsync(filePath) {
|
|
25
|
+
for await (const raw of node_fs_1.default.createReadStream(filePath)) {
|
|
26
|
+
const chunk = raw;
|
|
27
|
+
yield new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const makeFormHeader = (params) => {
|
|
31
|
+
const name = encodeName(params.name);
|
|
32
|
+
let header = BOUNDARY_HYPHEN_CHARS + BOUNDARY_ID + CRLF;
|
|
33
|
+
header += `Content-Disposition: form-data; name="${name}"; filename="${name}"`;
|
|
34
|
+
if (params.contentType) {
|
|
35
|
+
header += `${CRLF}Content-Type: ${params.contentType}`;
|
|
36
|
+
}
|
|
37
|
+
if (params.contentLength) {
|
|
38
|
+
header += `${CRLF}Content-Length: ${params.contentLength}`;
|
|
39
|
+
}
|
|
40
|
+
header += CRLF;
|
|
41
|
+
header += CRLF;
|
|
42
|
+
return header;
|
|
43
|
+
};
|
|
44
|
+
exports.multipartContentType = `multipart/form-data; boundary=${BOUNDARY_ID}`;
|
|
45
|
+
async function* createMultipartBodyFromFilesAsync(entries, onProgressUpdate) {
|
|
46
|
+
const encoder = new TextEncoder();
|
|
47
|
+
for (let idx = 0; idx < entries.length; idx++) {
|
|
48
|
+
const entry = entries[idx];
|
|
49
|
+
const header = makeFormHeader({
|
|
50
|
+
name: entry.name,
|
|
51
|
+
contentType: entry.contentType,
|
|
52
|
+
contentLength: entry.contentLength,
|
|
53
|
+
});
|
|
54
|
+
yield encoder.encode(header);
|
|
55
|
+
yield* createReadStreamAsync(entry.filePath);
|
|
56
|
+
yield encoder.encode(CRLF);
|
|
57
|
+
if (onProgressUpdate) {
|
|
58
|
+
onProgressUpdate((idx + 1) / entries.length);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
yield encoder.encode(FORM_FOOTER);
|
|
62
|
+
}
|
|
63
|
+
exports.createMultipartBodyFromFilesAsync = createMultipartBodyFromFilesAsync;
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eas-cli",
|
|
3
3
|
"description": "EAS command line tool",
|
|
4
|
-
"version": "16.
|
|
4
|
+
"version": "16.15.0",
|
|
5
5
|
"author": "Expo <support@expo.dev>",
|
|
6
6
|
"bin": {
|
|
7
7
|
"eas": "./bin/run"
|
|
@@ -241,5 +241,5 @@
|
|
|
241
241
|
"node": "20.11.0",
|
|
242
242
|
"yarn": "1.22.21"
|
|
243
243
|
},
|
|
244
|
-
"gitHead": "
|
|
244
|
+
"gitHead": "72f3ba817fcf4f210ef9786c2346d76ce32a0378"
|
|
245
245
|
}
|