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.
Files changed (40) hide show
  1. package/README.md +137 -84
  2. package/build/commandUtils/workflow/fetchLogs.d.ts +2 -0
  3. package/build/commandUtils/workflow/fetchLogs.js +16 -0
  4. package/build/commandUtils/workflow/stateMachine.d.ts +44 -0
  5. package/build/commandUtils/workflow/stateMachine.js +212 -0
  6. package/build/commandUtils/workflow/types.d.ts +39 -0
  7. package/build/commandUtils/workflow/types.js +13 -0
  8. package/build/commandUtils/workflow/utils.d.ts +12 -0
  9. package/build/commandUtils/workflow/utils.js +116 -0
  10. package/build/commands/deploy/index.js +47 -49
  11. package/build/commands/workflow/cancel.js +3 -6
  12. package/build/commands/workflow/logs.d.ts +18 -0
  13. package/build/commands/workflow/logs.js +94 -0
  14. package/build/commands/workflow/run.d.ts +105 -0
  15. package/build/commands/workflow/run.js +280 -0
  16. package/build/commands/workflow/runs.js +4 -3
  17. package/build/commands/workflow/view.d.ts +17 -0
  18. package/build/commands/workflow/view.js +95 -0
  19. package/build/credentials/ios/appstore/bundleIdCapabilities.d.ts +4 -17
  20. package/build/credentials/ios/appstore/bundleIdCapabilities.js +45 -625
  21. package/build/credentials/ios/appstore/capabilityIdentifiers.js +33 -34
  22. package/build/credentials/ios/appstore/capabilityList.d.ts +33 -0
  23. package/build/credentials/ios/appstore/capabilityList.js +646 -0
  24. package/build/graphql/generated.d.ts +236 -19
  25. package/build/graphql/queries/WorkflowJobQuery.d.ts +7 -0
  26. package/build/graphql/queries/WorkflowJobQuery.js +29 -0
  27. package/build/graphql/queries/WorkflowRunQuery.js +13 -13
  28. package/build/graphql/types/WorkflowJob.d.ts +1 -0
  29. package/build/graphql/types/WorkflowJob.js +32 -0
  30. package/build/graphql/types/WorkflowRun.js +18 -0
  31. package/build/worker/assets.d.ts +19 -16
  32. package/build/worker/assets.js +51 -22
  33. package/build/worker/upload.d.ts +21 -9
  34. package/build/worker/upload.js +98 -80
  35. package/build/worker/utils/multipart.d.ts +11 -0
  36. package/build/worker/utils/multipart.js +98 -0
  37. package/oclif.manifest.json +85 -1
  38. package/package.json +2 -2
  39. package/build/commandUtils/workflows.d.ts +0 -20
  40. package/build/commandUtils/workflows.js +0 -21
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.packFilesIterableAsync = exports.listAssetMapFilesAsync = exports.listWorkerFilesAsync = exports.createAssetMapAsync = exports.createManifestAsync = void 0;
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, options) {
38
- const hash = (0, node_crypto_1.createHash)('sha512', { encoding: 'hex', ...options });
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: node_path_1.default.resolve(target, dirent.name),
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
- /** Creates an asset map of a given target path */
66
- async function createAssetMapAsync(assetPath, options) {
67
- const map = Object.create(null);
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
- map[file.normalizedPath] = await computeSha512HashAsync(file.path, options?.hashOptions);
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 map;
103
+ return assets;
74
104
  }
75
- exports.createAssetMapAsync = createAssetMapAsync;
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`;
@@ -1,23 +1,35 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
3
  import { HeadersInit, RequestInit, Response } from 'node-fetch';
4
- export interface UploadParams extends Omit<RequestInit, 'signal' | 'body'> {
4
+ import { AssetFileEntry } from './assets';
5
+ export type UploadPayload = {
5
6
  filePath: string;
6
- compress?: boolean;
7
- url: string;
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
- params: UploadParams;
19
+ payload: UploadPayload;
15
20
  response: Response;
16
21
  }
17
- export declare function uploadAsync(params: UploadParams): Promise<UploadResult>;
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
- params: UploadParams;
26
+ payload: UploadPayload;
27
+ progress: number;
21
28
  }
22
- export type BatchUploadSignal = UploadResult | UploadPending;
23
- export declare function batchUploadAsync(uploads: readonly UploadParams[]): AsyncGenerator<BatchUploadSignal>;
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 {};
@@ -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.__importStar(require("node:fs"));
11
- const promises_1 = require("node:fs/promises");
12
- const node_path_1 = tslib_1.__importDefault(require("node:path"));
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(params) {
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
- const headers = new node_fetch_1.Headers(headersInit);
78
- if (contentType) {
79
- headers.set('content-type', contentType);
80
- }
81
- let bodyStream = (0, node_fs_1.createReadStream)(filePath);
82
- if (compress && isCompressible(contentType, stat.size)) {
83
- const gzip = new minizlib_1.Gzip({ portable: true });
84
- bodyStream.on('error', error => gzip.emit('error', error));
85
- // @ts-expect-error: Gzip implements a Readable-like interface
86
- bodyStream = bodyStream.pipe(gzip);
87
- headers.set('content-encoding', 'gzip');
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)(params.url, {
92
- ...requestInit,
74
+ response = await (0, node_fetch_1.default)(url, {
93
75
  method,
94
- body: bodyStream,
76
+ body,
95
77
  headers,
96
78
  agent: getAgent(),
97
- // @ts-expect-error: Internal types don't match
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 `Upload of "${filePath}" failed: ${message}`;
97
+ return `${errorPrefix}: ${message}`;
117
98
  }
118
99
  else {
119
100
  const json = await response.json().catch(() => null);
120
- return json?.error ?? `Upload of "${filePath}" failed: ${response.statusText}`;
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 = `Upload of "${filePath}" failed: File size exceeded the upload limit`;
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
- params,
121
+ payload,
138
122
  response,
139
123
  };
140
124
  }, {
141
125
  retries: MAX_RETRIES,
142
- minTimeout: MIN_RETRY_TIMEOUT,
143
- randomize: true,
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(uploads) {
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 < uploads.length || queue.size > 0) {
178
- while (queue.size < MAX_CONCURRENCY && index < uploads.length) {
179
- const uploadParams = uploads[index++];
180
- let uploadPromise;
181
- queue.add((uploadPromise = uploadAsync({ ...uploadParams, signal: controller.signal }).finally(() => queue.delete(uploadPromise))));
182
- yield { params: uploadParams };
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 await Promise.race(queue);
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;
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "16.14.1",
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.14.1",
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": "2502b843f8f75322d0d9b87476efc9bc975631ba"
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
- };