eas-cli 16.14.1 → 16.16.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 +137 -84
- package/build/commandUtils/workflow/fetchLogs.d.ts +2 -0
- package/build/commandUtils/workflow/fetchLogs.js +16 -0
- package/build/commandUtils/workflow/stateMachine.d.ts +44 -0
- package/build/commandUtils/workflow/stateMachine.js +212 -0
- package/build/commandUtils/workflow/types.d.ts +39 -0
- package/build/commandUtils/workflow/types.js +13 -0
- package/build/commandUtils/workflow/utils.d.ts +12 -0
- package/build/commandUtils/workflow/utils.js +116 -0
- package/build/commands/deploy/index.js +47 -49
- package/build/commands/workflow/cancel.js +3 -6
- package/build/commands/workflow/logs.d.ts +18 -0
- package/build/commands/workflow/logs.js +94 -0
- package/build/commands/workflow/run.d.ts +105 -0
- package/build/commands/workflow/run.js +280 -0
- package/build/commands/workflow/runs.js +4 -3
- package/build/commands/workflow/view.d.ts +17 -0
- package/build/commands/workflow/view.js +95 -0
- package/build/credentials/ios/appstore/bundleIdCapabilities.d.ts +4 -17
- package/build/credentials/ios/appstore/bundleIdCapabilities.js +45 -625
- package/build/credentials/ios/appstore/capabilityIdentifiers.js +33 -34
- package/build/credentials/ios/appstore/capabilityList.d.ts +33 -0
- package/build/credentials/ios/appstore/capabilityList.js +646 -0
- package/build/graphql/generated.d.ts +236 -19
- package/build/graphql/queries/WorkflowJobQuery.d.ts +7 -0
- package/build/graphql/queries/WorkflowJobQuery.js +29 -0
- package/build/graphql/queries/WorkflowRunQuery.js +13 -13
- package/build/graphql/types/WorkflowJob.d.ts +1 -0
- package/build/graphql/types/WorkflowJob.js +32 -0
- package/build/graphql/types/WorkflowRun.js +18 -0
- package/build/worker/assets.d.ts +19 -16
- package/build/worker/assets.js +51 -22
- package/build/worker/upload.d.ts +21 -9
- package/build/worker/upload.js +98 -80
- package/build/worker/utils/multipart.d.ts +11 -0
- package/build/worker/utils/multipart.js +98 -0
- package/oclif.manifest.json +85 -1
- package/package.json +2 -2
- package/build/commandUtils/workflows.d.ts +0 -20
- package/build/commandUtils/workflows.js +0 -21
package/build/worker/assets.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.packFilesIterableAsync = exports.
|
|
3
|
+
exports.packFilesIterableAsync = exports.listWorkerFilesAsync = exports.createManifestAsync = exports.assetsToAssetsMap = exports.collectAssetsAsync = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const env_1 = require("@expo/env");
|
|
6
|
+
const mime_1 = tslib_1.__importDefault(require("mime"));
|
|
6
7
|
const minizlib_1 = require("minizlib");
|
|
7
8
|
const node_crypto_1 = require("node:crypto");
|
|
8
9
|
const node_fs_1 = tslib_1.__importStar(require("node:fs"));
|
|
@@ -34,8 +35,8 @@ async function createTempWritePathAsync() {
|
|
|
34
35
|
return node_path_1.default.resolve(tmpdir, `tmp-${basename}-${process.pid}-${random}`);
|
|
35
36
|
}
|
|
36
37
|
/** Computes a SHA512 hash for a file */
|
|
37
|
-
async function computeSha512HashAsync(filePath
|
|
38
|
-
const hash = (0, node_crypto_1.createHash)('sha512', { encoding: 'hex'
|
|
38
|
+
async function computeSha512HashAsync(filePath) {
|
|
39
|
+
const hash = (0, node_crypto_1.createHash)('sha512', { encoding: 'hex' });
|
|
39
40
|
await (0, promises_1.pipeline)(node_fs_1.default.createReadStream(filePath), hash);
|
|
40
41
|
return `${hash.read()}`;
|
|
41
42
|
}
|
|
@@ -50,9 +51,12 @@ function listFilesRecursively(basePath) {
|
|
|
50
51
|
continue;
|
|
51
52
|
}
|
|
52
53
|
else if (dirent.isFile()) {
|
|
54
|
+
const absolutePath = node_path_1.default.resolve(target, dirent.name);
|
|
55
|
+
const stats = await node_fs_1.default.promises.stat(absolutePath);
|
|
53
56
|
yield {
|
|
54
57
|
normalizedPath,
|
|
55
|
-
path:
|
|
58
|
+
path: absolutePath,
|
|
59
|
+
size: stats.size,
|
|
56
60
|
};
|
|
57
61
|
}
|
|
58
62
|
else if (dirent.isDirectory()) {
|
|
@@ -62,17 +66,54 @@ function listFilesRecursively(basePath) {
|
|
|
62
66
|
}
|
|
63
67
|
return recurseAsync();
|
|
64
68
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
async function determineMimeTypeAsync(filePath) {
|
|
70
|
+
let contentType = mime_1.default.getType(node_path_1.default.basename(filePath));
|
|
71
|
+
if (!contentType) {
|
|
72
|
+
const fileContent = await node_fs_1.default.promises.readFile(filePath, 'utf-8');
|
|
73
|
+
try {
|
|
74
|
+
// check if file is valid JSON without an extension, e.g. for the apple app site association file
|
|
75
|
+
const parsedData = JSON.parse(fileContent);
|
|
76
|
+
if (parsedData) {
|
|
77
|
+
contentType = 'application/json';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
}
|
|
82
|
+
return contentType;
|
|
83
|
+
}
|
|
84
|
+
/** Collects assets from a given target path */
|
|
85
|
+
async function collectAssetsAsync(assetPath, options) {
|
|
86
|
+
const assets = [];
|
|
68
87
|
if (assetPath) {
|
|
69
88
|
for await (const file of listFilesRecursively(assetPath)) {
|
|
70
|
-
|
|
89
|
+
if (file.size > options.maxFileSize) {
|
|
90
|
+
throw new Error(`Upload of "${file.normalizedPath}" aborted: File size is greater than the upload limit (>500MB)`);
|
|
91
|
+
}
|
|
92
|
+
const sha512$ = computeSha512HashAsync(file.path);
|
|
93
|
+
const contentType$ = determineMimeTypeAsync(file.path);
|
|
94
|
+
assets.push({
|
|
95
|
+
normalizedPath: file.normalizedPath,
|
|
96
|
+
path: file.path,
|
|
97
|
+
size: file.size,
|
|
98
|
+
sha512: await sha512$,
|
|
99
|
+
type: await contentType$,
|
|
100
|
+
});
|
|
71
101
|
}
|
|
72
102
|
}
|
|
73
|
-
return
|
|
103
|
+
return assets;
|
|
74
104
|
}
|
|
75
|
-
exports.
|
|
105
|
+
exports.collectAssetsAsync = collectAssetsAsync;
|
|
106
|
+
/** Converts array of asset entries into AssetMap (as sent to deployment-api) */
|
|
107
|
+
function assetsToAssetsMap(assets) {
|
|
108
|
+
return assets.reduce((map, entry) => {
|
|
109
|
+
map[entry.normalizedPath] = {
|
|
110
|
+
sha512: entry.sha512,
|
|
111
|
+
size: entry.size,
|
|
112
|
+
};
|
|
113
|
+
return map;
|
|
114
|
+
}, Object.create(null));
|
|
115
|
+
}
|
|
116
|
+
exports.assetsToAssetsMap = assetsToAssetsMap;
|
|
76
117
|
/** Creates a manifest configuration sent up for deployment */
|
|
77
118
|
async function createManifestAsync(params, graphqlClient) {
|
|
78
119
|
// Resolve .env file variables
|
|
@@ -110,18 +151,6 @@ async function* listWorkerFilesAsync(workerPath) {
|
|
|
110
151
|
}
|
|
111
152
|
}
|
|
112
153
|
exports.listWorkerFilesAsync = listWorkerFilesAsync;
|
|
113
|
-
/** Reads files of an asset maps and enumerates normalized paths and data */
|
|
114
|
-
async function* listAssetMapFilesAsync(assetPath, assetMap) {
|
|
115
|
-
for (const normalizedPath in assetMap) {
|
|
116
|
-
const filePath = node_path_1.default.resolve(assetPath, normalizedPath.split('/').join(node_path_1.default.sep));
|
|
117
|
-
yield {
|
|
118
|
-
normalizedPath,
|
|
119
|
-
path: filePath,
|
|
120
|
-
sha512: assetMap[normalizedPath],
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
exports.listAssetMapFilesAsync = listAssetMapFilesAsync;
|
|
125
154
|
/** Packs file entries into a tar.gz file (path to tgz returned) */
|
|
126
155
|
async function packFilesIterableAsync(iterable, options) {
|
|
127
156
|
const writePath = `${await createTempWritePathAsync()}.tar.gz`;
|
package/build/worker/upload.d.ts
CHANGED
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { HeadersInit, RequestInit, Response } from 'node-fetch';
|
|
4
|
-
|
|
4
|
+
import { AssetFileEntry } from './assets';
|
|
5
|
+
export type UploadPayload = {
|
|
5
6
|
filePath: string;
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
} | {
|
|
8
|
+
asset: AssetFileEntry;
|
|
9
|
+
} | {
|
|
10
|
+
multipart: AssetFileEntry[];
|
|
11
|
+
};
|
|
12
|
+
export interface UploadRequestInit {
|
|
13
|
+
baseURL: string | URL;
|
|
8
14
|
method?: string;
|
|
9
15
|
headers?: HeadersInit;
|
|
10
|
-
body?: undefined;
|
|
11
16
|
signal?: AbortSignal;
|
|
12
17
|
}
|
|
13
18
|
export interface UploadResult {
|
|
14
|
-
|
|
19
|
+
payload: UploadPayload;
|
|
15
20
|
response: Response;
|
|
16
21
|
}
|
|
17
|
-
|
|
22
|
+
type OnProgressUpdateCallback = (progress: number) => void;
|
|
23
|
+
export declare function uploadAsync(init: UploadRequestInit, payload: UploadPayload, onProgressUpdate?: OnProgressUpdateCallback): Promise<UploadResult>;
|
|
18
24
|
export declare function callUploadApiAsync(url: string | URL, init?: RequestInit): Promise<unknown>;
|
|
19
25
|
export interface UploadPending {
|
|
20
|
-
|
|
26
|
+
payload: UploadPayload;
|
|
27
|
+
progress: number;
|
|
21
28
|
}
|
|
22
|
-
export
|
|
23
|
-
|
|
29
|
+
export declare function batchUploadAsync(init: UploadRequestInit, payloads: UploadPayload[], onProgressUpdate?: OnProgressUpdateCallback): AsyncGenerator<UploadPending>;
|
|
30
|
+
interface UploadProgressBar {
|
|
31
|
+
update(progress: number): void;
|
|
32
|
+
stop(): void;
|
|
33
|
+
}
|
|
34
|
+
export declare function createProgressBar(label?: string): UploadProgressBar;
|
|
35
|
+
export {};
|
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,52 @@ 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_stream_1.Readable.from((0, multipart_1.createReadStreamAsync)(asset), { objectMode: false });
|
|
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
|
+
body = node_stream_1.Readable.from((0, multipart_1.createMultipartBodyFromFilesAsync)(multipart, onProgressUpdate), {
|
|
69
|
+
objectMode: false,
|
|
70
|
+
});
|
|
88
71
|
}
|
|
89
72
|
let response;
|
|
90
73
|
try {
|
|
91
|
-
response = await (0, node_fetch_1.default)(
|
|
92
|
-
...requestInit,
|
|
74
|
+
response = await (0, node_fetch_1.default)(url, {
|
|
93
75
|
method,
|
|
94
|
-
body
|
|
76
|
+
body,
|
|
95
77
|
headers,
|
|
96
78
|
agent: getAgent(),
|
|
97
|
-
|
|
98
|
-
signal,
|
|
79
|
+
signal: init.signal,
|
|
99
80
|
});
|
|
100
81
|
}
|
|
101
82
|
catch (error) {
|
|
@@ -113,11 +94,11 @@ async function uploadAsync(params) {
|
|
|
113
94
|
if (rayId) {
|
|
114
95
|
message += `\nReport this error quoting Request ID ${rayId}`;
|
|
115
96
|
}
|
|
116
|
-
return
|
|
97
|
+
return `${errorPrefix}: ${message}`;
|
|
117
98
|
}
|
|
118
99
|
else {
|
|
119
100
|
const json = await response.json().catch(() => null);
|
|
120
|
-
return json?.error ??
|
|
101
|
+
return json?.error ?? `${errorPrefix}: ${response.statusText}`;
|
|
121
102
|
}
|
|
122
103
|
};
|
|
123
104
|
if (response.status === 408 ||
|
|
@@ -127,21 +108,23 @@ async function uploadAsync(params) {
|
|
|
127
108
|
return retry(new Error(await getErrorMessageAsync()));
|
|
128
109
|
}
|
|
129
110
|
else if (response.status === 413) {
|
|
130
|
-
const message =
|
|
111
|
+
const message = `${errorPrefix}: File size exceeded the upload limit`;
|
|
131
112
|
throw new Error(message);
|
|
132
113
|
}
|
|
133
114
|
else if (!response.ok) {
|
|
134
115
|
throw new Error(await getErrorMessageAsync());
|
|
135
116
|
}
|
|
117
|
+
else if (onProgressUpdate) {
|
|
118
|
+
onProgressUpdate(1);
|
|
119
|
+
}
|
|
136
120
|
return {
|
|
137
|
-
|
|
121
|
+
payload,
|
|
138
122
|
response,
|
|
139
123
|
};
|
|
140
124
|
}, {
|
|
141
125
|
retries: MAX_RETRIES,
|
|
142
|
-
minTimeout:
|
|
143
|
-
randomize:
|
|
144
|
-
factor: 2,
|
|
126
|
+
minTimeout: 50,
|
|
127
|
+
randomize: false,
|
|
145
128
|
});
|
|
146
129
|
}
|
|
147
130
|
exports.uploadAsync = uploadAsync;
|
|
@@ -169,19 +152,41 @@ async function callUploadApiAsync(url, init) {
|
|
|
169
152
|
});
|
|
170
153
|
}
|
|
171
154
|
exports.callUploadApiAsync = callUploadApiAsync;
|
|
172
|
-
async function* batchUploadAsync(
|
|
155
|
+
async function* batchUploadAsync(init, payloads, onProgressUpdate) {
|
|
156
|
+
const progressTracker = new Array(payloads.length).fill(0);
|
|
173
157
|
const controller = new AbortController();
|
|
174
158
|
const queue = new Set();
|
|
159
|
+
const initWithSignal = { ...init, signal: controller.signal };
|
|
160
|
+
const getProgressValue = () => {
|
|
161
|
+
const progress = progressTracker.reduce((acc, value) => acc + value, 0);
|
|
162
|
+
return progress / payloads.length;
|
|
163
|
+
};
|
|
164
|
+
const sendProgressUpdate = onProgressUpdate &&
|
|
165
|
+
(() => {
|
|
166
|
+
onProgressUpdate(getProgressValue());
|
|
167
|
+
});
|
|
175
168
|
try {
|
|
176
169
|
let index = 0;
|
|
177
|
-
while (index <
|
|
178
|
-
while (queue.size < MAX_CONCURRENCY && index <
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
170
|
+
while (index < payloads.length || queue.size > 0) {
|
|
171
|
+
while (queue.size < MAX_CONCURRENCY && index < payloads.length) {
|
|
172
|
+
const currentIndex = index++;
|
|
173
|
+
const payload = payloads[currentIndex];
|
|
174
|
+
const onChildProgressUpdate = sendProgressUpdate &&
|
|
175
|
+
((progress) => {
|
|
176
|
+
progressTracker[currentIndex] = progress;
|
|
177
|
+
sendProgressUpdate();
|
|
178
|
+
});
|
|
179
|
+
const uploadPromise = uploadAsync(initWithSignal, payload, onChildProgressUpdate).finally(() => {
|
|
180
|
+
queue.delete(uploadPromise);
|
|
181
|
+
progressTracker[currentIndex] = 1;
|
|
182
|
+
});
|
|
183
|
+
queue.add(uploadPromise);
|
|
184
|
+
yield { payload, progress: getProgressValue() };
|
|
183
185
|
}
|
|
184
|
-
yield
|
|
186
|
+
yield {
|
|
187
|
+
...(await Promise.race(queue)),
|
|
188
|
+
progress: getProgressValue(),
|
|
189
|
+
};
|
|
185
190
|
}
|
|
186
191
|
if (queue.size > 0) {
|
|
187
192
|
controller.abort();
|
|
@@ -194,3 +199,16 @@ async function* batchUploadAsync(uploads) {
|
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
201
|
exports.batchUploadAsync = batchUploadAsync;
|
|
202
|
+
function createProgressBar(label = 'Uploading assets') {
|
|
203
|
+
const queueProgressBar = new cli_progress_1.default.SingleBar({ format: `|{bar}| {percentage}% ${label}` }, cli_progress_1.default.Presets.rect);
|
|
204
|
+
queueProgressBar.start(1, 0);
|
|
205
|
+
return {
|
|
206
|
+
update(progress) {
|
|
207
|
+
queueProgressBar.update(progress);
|
|
208
|
+
},
|
|
209
|
+
stop() {
|
|
210
|
+
queueProgressBar.stop();
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
exports.createProgressBar = createProgressBar;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function createReadStreamAsync(fileEntry: MultipartFileEntry): AsyncGenerator<Uint8Array>;
|
|
2
|
+
export interface MultipartFileEntry {
|
|
3
|
+
sha512: string;
|
|
4
|
+
path: string;
|
|
5
|
+
type: string | null;
|
|
6
|
+
size: number;
|
|
7
|
+
}
|
|
8
|
+
export declare const multipartContentType = "multipart/form-data; boundary=----formdata-eas-cli";
|
|
9
|
+
type OnProgressUpdateCallback = (progress: number) => void;
|
|
10
|
+
export declare function createMultipartBodyFromFilesAsync(entries: MultipartFileEntry[], onProgressUpdate?: OnProgressUpdateCallback): AsyncGenerator<Uint8Array>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMultipartBodyFromFilesAsync = exports.multipartContentType = exports.createReadStreamAsync = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
7
|
+
const CRLF = '\r\n';
|
|
8
|
+
const BOUNDARY_HYPHEN_CHARS = '--';
|
|
9
|
+
const BOUNDARY_ID = '----formdata-eas-cli';
|
|
10
|
+
const FORM_FOOTER = `${BOUNDARY_HYPHEN_CHARS}${BOUNDARY_ID}${BOUNDARY_HYPHEN_CHARS}${CRLF}${CRLF}`;
|
|
11
|
+
const encodeName = (input) => {
|
|
12
|
+
return input.replace(/["\n\\]/g, (c) => {
|
|
13
|
+
switch (c) {
|
|
14
|
+
case '\\':
|
|
15
|
+
return '\\\\';
|
|
16
|
+
case '"':
|
|
17
|
+
return '%22';
|
|
18
|
+
case '\n':
|
|
19
|
+
return '%0A';
|
|
20
|
+
default:
|
|
21
|
+
return `%${c.charCodeAt(0).toString(16).toUpperCase()}`;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
async function* createReadStreamAsync(fileEntry) {
|
|
26
|
+
let handle;
|
|
27
|
+
try {
|
|
28
|
+
handle = await node_fs_1.default.promises.open(fileEntry.path);
|
|
29
|
+
const hash = (0, node_crypto_1.createHash)('sha512');
|
|
30
|
+
// NOTE(@kitten): fs.createReadStream() was previously used here as an async iterator
|
|
31
|
+
// However, if an early 'end' event is emitted, the async iterator may abort too early and cut off file contents
|
|
32
|
+
let bytesTotal = 0;
|
|
33
|
+
while (bytesTotal < fileEntry.size) {
|
|
34
|
+
const read = await handle.read();
|
|
35
|
+
const output = read.buffer.subarray(0, read.bytesRead);
|
|
36
|
+
bytesTotal += output.byteLength;
|
|
37
|
+
if (bytesTotal > fileEntry.size) {
|
|
38
|
+
throw new RangeError(`Asset "${fileEntry.path}" was modified during the upload (length mismatch)`);
|
|
39
|
+
}
|
|
40
|
+
if (output.byteLength) {
|
|
41
|
+
hash.update(output);
|
|
42
|
+
}
|
|
43
|
+
if (bytesTotal === fileEntry.size) {
|
|
44
|
+
const sha512 = hash.digest('hex');
|
|
45
|
+
if (sha512 !== fileEntry.sha512) {
|
|
46
|
+
throw new Error(`Asset "${fileEntry.path}" was modified during the upload (checksum mismatch)`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (output.byteLength) {
|
|
50
|
+
yield output;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (bytesTotal < fileEntry.size) {
|
|
57
|
+
throw new RangeError(`Asset "${fileEntry.path}" was modified during the upload (length mismatch)`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
await handle?.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.createReadStreamAsync = createReadStreamAsync;
|
|
65
|
+
const makeFormHeader = (params) => {
|
|
66
|
+
const name = encodeName(params.name);
|
|
67
|
+
let header = BOUNDARY_HYPHEN_CHARS + BOUNDARY_ID + CRLF;
|
|
68
|
+
header += `Content-Disposition: form-data; name="${name}"; filename="${name}"`;
|
|
69
|
+
if (params.contentType) {
|
|
70
|
+
header += `${CRLF}Content-Type: ${params.contentType}`;
|
|
71
|
+
}
|
|
72
|
+
if (params.contentLength) {
|
|
73
|
+
header += `${CRLF}Content-Length: ${params.contentLength}`;
|
|
74
|
+
}
|
|
75
|
+
header += CRLF;
|
|
76
|
+
header += CRLF;
|
|
77
|
+
return header;
|
|
78
|
+
};
|
|
79
|
+
exports.multipartContentType = `multipart/form-data; boundary=${BOUNDARY_ID}`;
|
|
80
|
+
async function* createMultipartBodyFromFilesAsync(entries, onProgressUpdate) {
|
|
81
|
+
const encoder = new TextEncoder();
|
|
82
|
+
for (let idx = 0; idx < entries.length; idx++) {
|
|
83
|
+
const entry = entries[idx];
|
|
84
|
+
const header = makeFormHeader({
|
|
85
|
+
name: entry.sha512,
|
|
86
|
+
contentType: entry.type,
|
|
87
|
+
contentLength: entry.size,
|
|
88
|
+
});
|
|
89
|
+
yield encoder.encode(header);
|
|
90
|
+
yield* createReadStreamAsync(entry);
|
|
91
|
+
yield encoder.encode(CRLF);
|
|
92
|
+
if (onProgressUpdate) {
|
|
93
|
+
onProgressUpdate((idx + 1) / entries.length);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
yield encoder.encode(FORM_FOOTER);
|
|
97
|
+
}
|
|
98
|
+
exports.createMultipartBodyFromFilesAsync = createMultipartBodyFromFilesAsync;
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "16.
|
|
2
|
+
"version": "16.16.0",
|
|
3
3
|
"commands": {
|
|
4
4
|
"analytics": {
|
|
5
5
|
"id": "analytics",
|
|
@@ -4196,6 +4196,45 @@
|
|
|
4196
4196
|
"loggedIn": {}
|
|
4197
4197
|
}
|
|
4198
4198
|
},
|
|
4199
|
+
"workflow:logs": {
|
|
4200
|
+
"id": "workflow:logs",
|
|
4201
|
+
"description": "view logs for a workflow run, selecting a job and step to view. You can pass in either a workflow run ID or a job ID. If no ID is passed in, you will be prompted to select from recent workflow runs for the current project.",
|
|
4202
|
+
"strict": true,
|
|
4203
|
+
"pluginName": "eas-cli",
|
|
4204
|
+
"pluginAlias": "eas-cli",
|
|
4205
|
+
"pluginType": "core",
|
|
4206
|
+
"aliases": [],
|
|
4207
|
+
"flags": {
|
|
4208
|
+
"json": {
|
|
4209
|
+
"name": "json",
|
|
4210
|
+
"type": "boolean",
|
|
4211
|
+
"description": "Enable JSON output, non-JSON messages will be printed to stderr.",
|
|
4212
|
+
"allowNo": false
|
|
4213
|
+
},
|
|
4214
|
+
"non-interactive": {
|
|
4215
|
+
"name": "non-interactive",
|
|
4216
|
+
"type": "boolean",
|
|
4217
|
+
"description": "Run the command in non-interactive mode.",
|
|
4218
|
+
"allowNo": false
|
|
4219
|
+
},
|
|
4220
|
+
"all-steps": {
|
|
4221
|
+
"name": "all-steps",
|
|
4222
|
+
"type": "boolean",
|
|
4223
|
+
"description": "Print all logs, rather than prompting for a specific step. This will be automatically set when in non-interactive mode.",
|
|
4224
|
+
"allowNo": false
|
|
4225
|
+
}
|
|
4226
|
+
},
|
|
4227
|
+
"args": {
|
|
4228
|
+
"id": {
|
|
4229
|
+
"name": "id",
|
|
4230
|
+
"description": "ID of the workflow run or workflow job to view logs for"
|
|
4231
|
+
}
|
|
4232
|
+
},
|
|
4233
|
+
"contextDefinition": {
|
|
4234
|
+
"projectId": {},
|
|
4235
|
+
"loggedIn": {}
|
|
4236
|
+
}
|
|
4237
|
+
},
|
|
4199
4238
|
"workflow:run": {
|
|
4200
4239
|
"id": "workflow:run",
|
|
4201
4240
|
"description": "run an EAS workflow",
|
|
@@ -4218,6 +4257,18 @@
|
|
|
4218
4257
|
"description": "Exit codes: 0 = success, 11 = failure, 12 = canceled, 13 = wait aborted.",
|
|
4219
4258
|
"allowNo": true
|
|
4220
4259
|
},
|
|
4260
|
+
"input": {
|
|
4261
|
+
"name": "input",
|
|
4262
|
+
"type": "option",
|
|
4263
|
+
"char": "F",
|
|
4264
|
+
"summary": "Set workflow inputs",
|
|
4265
|
+
"description": "Add a parameter in key=value format. Use multiple instances of this flag to set multiple inputs.",
|
|
4266
|
+
"multiple": true,
|
|
4267
|
+
"aliases": [
|
|
4268
|
+
"f",
|
|
4269
|
+
"field"
|
|
4270
|
+
]
|
|
4271
|
+
},
|
|
4221
4272
|
"json": {
|
|
4222
4273
|
"name": "json",
|
|
4223
4274
|
"type": "boolean",
|
|
@@ -4320,6 +4371,39 @@
|
|
|
4320
4371
|
"loggedIn": {}
|
|
4321
4372
|
}
|
|
4322
4373
|
},
|
|
4374
|
+
"workflow:view": {
|
|
4375
|
+
"id": "workflow:view",
|
|
4376
|
+
"description": "view details for a workflow run, including jobs. If no run ID is provided, you will be prompted to select from recent workflow runs for the current project.",
|
|
4377
|
+
"strict": true,
|
|
4378
|
+
"pluginName": "eas-cli",
|
|
4379
|
+
"pluginAlias": "eas-cli",
|
|
4380
|
+
"pluginType": "core",
|
|
4381
|
+
"aliases": [],
|
|
4382
|
+
"flags": {
|
|
4383
|
+
"json": {
|
|
4384
|
+
"name": "json",
|
|
4385
|
+
"type": "boolean",
|
|
4386
|
+
"description": "Enable JSON output, non-JSON messages will be printed to stderr.",
|
|
4387
|
+
"allowNo": false
|
|
4388
|
+
},
|
|
4389
|
+
"non-interactive": {
|
|
4390
|
+
"name": "non-interactive",
|
|
4391
|
+
"type": "boolean",
|
|
4392
|
+
"description": "Run the command in non-interactive mode.",
|
|
4393
|
+
"allowNo": false
|
|
4394
|
+
}
|
|
4395
|
+
},
|
|
4396
|
+
"args": {
|
|
4397
|
+
"id": {
|
|
4398
|
+
"name": "id",
|
|
4399
|
+
"description": "ID of the workflow run to view"
|
|
4400
|
+
}
|
|
4401
|
+
},
|
|
4402
|
+
"contextDefinition": {
|
|
4403
|
+
"projectId": {},
|
|
4404
|
+
"loggedIn": {}
|
|
4405
|
+
}
|
|
4406
|
+
},
|
|
4323
4407
|
"build:version:get": {
|
|
4324
4408
|
"id": "build:version:get",
|
|
4325
4409
|
"description": "get the latest version from EAS servers",
|
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.16.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": "8e96143b59aa8cd4b1b1bb269670ed3b56768258"
|
|
245
245
|
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { WorkflowRunFragment } from '../graphql/generated';
|
|
2
|
-
export type WorkflowRunResult = {
|
|
3
|
-
id: string;
|
|
4
|
-
status: string;
|
|
5
|
-
gitCommitMessage: string | null;
|
|
6
|
-
gitCommitHash: string | null;
|
|
7
|
-
startedAt: string;
|
|
8
|
-
finishedAt: string;
|
|
9
|
-
workflowId: string;
|
|
10
|
-
workflowName: string | null;
|
|
11
|
-
workflowFileName: string;
|
|
12
|
-
};
|
|
13
|
-
export declare function processWorkflowRuns(runs: WorkflowRunFragment[]): WorkflowRunResult[];
|
|
14
|
-
export type WorkflowResult = {
|
|
15
|
-
id: string;
|
|
16
|
-
name?: string | null | undefined;
|
|
17
|
-
fileName: string;
|
|
18
|
-
createdAt: string;
|
|
19
|
-
updatedAt: string;
|
|
20
|
-
};
|