securewithtrace 0.40.1 → 0.41.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/dist/index.js +297 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -29594,6 +29594,11 @@ var ResumeCodeUploadResponseSchema = external_exports.object({
|
|
|
29594
29594
|
var ListCodeUploadsResponseSchema = external_exports.object({
|
|
29595
29595
|
uploads: external_exports.array(CodeUploadSchema)
|
|
29596
29596
|
});
|
|
29597
|
+
var DownloadCodeUploadResponseSchema = external_exports.object({
|
|
29598
|
+
upload: CodeUploadSchema,
|
|
29599
|
+
downloadUrl: external_exports.url(),
|
|
29600
|
+
expiresAt: external_exports.coerce.date()
|
|
29601
|
+
});
|
|
29597
29602
|
|
|
29598
29603
|
// ../../packages/api-contracts/src/contracts/v1/code-upload.ts
|
|
29599
29604
|
var c12 = initContract();
|
|
@@ -29647,6 +29652,15 @@ var codeUploadContract = c12.router({
|
|
|
29647
29652
|
...commonErrors
|
|
29648
29653
|
}
|
|
29649
29654
|
},
|
|
29655
|
+
downloadCodeUpload: {
|
|
29656
|
+
method: "GET",
|
|
29657
|
+
path: "/organizations/:organizationId/code-uploads/:codeUploadId/download",
|
|
29658
|
+
pathParams: CodeUploadIdParamsSchema,
|
|
29659
|
+
responses: {
|
|
29660
|
+
200: DownloadCodeUploadResponseSchema,
|
|
29661
|
+
...commonErrors
|
|
29662
|
+
}
|
|
29663
|
+
},
|
|
29650
29664
|
deleteCodeUpload: {
|
|
29651
29665
|
method: "DELETE",
|
|
29652
29666
|
path: "/organizations/:organizationId/code-uploads/:codeUploadId",
|
|
@@ -37241,7 +37255,7 @@ async function requestDeviceAuthorization(clientId) {
|
|
|
37241
37255
|
}
|
|
37242
37256
|
return await response.json();
|
|
37243
37257
|
}
|
|
37244
|
-
var sleep = (ms) => new Promise((
|
|
37258
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
37245
37259
|
async function pollForToken(clientId, deviceCode, options = {}) {
|
|
37246
37260
|
const expiresIn = options.expiresIn ?? 300;
|
|
37247
37261
|
let interval = options.interval ?? 5;
|
|
@@ -37926,18 +37940,18 @@ var proto = Object.defineProperties(() => {
|
|
|
37926
37940
|
}
|
|
37927
37941
|
}
|
|
37928
37942
|
});
|
|
37929
|
-
var createStyler = (
|
|
37943
|
+
var createStyler = (open3, close, parent) => {
|
|
37930
37944
|
let openAll;
|
|
37931
37945
|
let closeAll;
|
|
37932
37946
|
if (parent === void 0) {
|
|
37933
|
-
openAll =
|
|
37947
|
+
openAll = open3;
|
|
37934
37948
|
closeAll = close;
|
|
37935
37949
|
} else {
|
|
37936
|
-
openAll = parent.openAll +
|
|
37950
|
+
openAll = parent.openAll + open3;
|
|
37937
37951
|
closeAll = close + parent.closeAll;
|
|
37938
37952
|
}
|
|
37939
37953
|
return {
|
|
37940
|
-
open:
|
|
37954
|
+
open: open3,
|
|
37941
37955
|
close,
|
|
37942
37956
|
openAll,
|
|
37943
37957
|
closeAll,
|
|
@@ -47291,6 +47305,11 @@ Examples:
|
|
|
47291
47305
|
});
|
|
47292
47306
|
}
|
|
47293
47307
|
|
|
47308
|
+
// src/upload/download.ts
|
|
47309
|
+
import { createHash as createHash2 } from "crypto";
|
|
47310
|
+
import { open as open2, rename, rm, stat } from "fs/promises";
|
|
47311
|
+
import { basename as basename4, join as join7, resolve } from "path";
|
|
47312
|
+
|
|
47294
47313
|
// src/upload/prepare.ts
|
|
47295
47314
|
import { createHash } from "crypto";
|
|
47296
47315
|
import { createReadStream, statSync as statSync4 } from "fs";
|
|
@@ -47434,6 +47453,128 @@ async function prepareUploadFile(filePath, options = {}) {
|
|
|
47434
47453
|
};
|
|
47435
47454
|
}
|
|
47436
47455
|
|
|
47456
|
+
// src/upload/download.ts
|
|
47457
|
+
var CodeUploadDownloadError = class extends Error {
|
|
47458
|
+
constructor(message) {
|
|
47459
|
+
super(message);
|
|
47460
|
+
this.name = "CodeUploadDownloadError";
|
|
47461
|
+
}
|
|
47462
|
+
};
|
|
47463
|
+
function safeDownloadFilename(upload) {
|
|
47464
|
+
const extension = requiredExtensionForKind(upload.artifactKind);
|
|
47465
|
+
const fallback = `app${extension}`;
|
|
47466
|
+
const stripped = Array.from(basename4(upload.originalFilename.replace(/\\/g, "/")), (char) => {
|
|
47467
|
+
const code = char.codePointAt(0) ?? 0;
|
|
47468
|
+
return code <= 31 || code === 127 ? "_" : char;
|
|
47469
|
+
}).join("").trim();
|
|
47470
|
+
if (stripped.length === 0 || stripped === "." || stripped === "..") return fallback;
|
|
47471
|
+
return stripped.toLowerCase().endsWith(extension) ? stripped : `${stripped}${extension}`;
|
|
47472
|
+
}
|
|
47473
|
+
async function resolveDownloadDestination(upload, outOption) {
|
|
47474
|
+
const filename = safeDownloadFilename(upload);
|
|
47475
|
+
if (outOption === void 0) return resolve(process.cwd(), filename);
|
|
47476
|
+
const target = resolve(outOption);
|
|
47477
|
+
try {
|
|
47478
|
+
const info = await stat(target);
|
|
47479
|
+
if (info.isDirectory()) return join7(target, filename);
|
|
47480
|
+
} catch {
|
|
47481
|
+
}
|
|
47482
|
+
return target;
|
|
47483
|
+
}
|
|
47484
|
+
async function pathExists(path) {
|
|
47485
|
+
try {
|
|
47486
|
+
await stat(path);
|
|
47487
|
+
return true;
|
|
47488
|
+
} catch {
|
|
47489
|
+
return false;
|
|
47490
|
+
}
|
|
47491
|
+
}
|
|
47492
|
+
async function downloadCodeUploadToFile(upload, downloadUrl, destination, options = {}) {
|
|
47493
|
+
const fetchImpl = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
47494
|
+
const partialPath = `${destination}.part`;
|
|
47495
|
+
let response;
|
|
47496
|
+
try {
|
|
47497
|
+
response = await fetchImpl(downloadUrl, { method: "GET", redirect: "manual" });
|
|
47498
|
+
} catch (error48) {
|
|
47499
|
+
const detail = error48 instanceof Error ? error48.message : "network error";
|
|
47500
|
+
throw new CodeUploadDownloadError(`Download from storage failed: ${detail}.`);
|
|
47501
|
+
}
|
|
47502
|
+
if (!response.ok || response.body === null) {
|
|
47503
|
+
if (response.body !== null) await response.body.cancel();
|
|
47504
|
+
throw new CodeUploadDownloadError(
|
|
47505
|
+
`Storage answered HTTP ${response.status} instead of the build bytes. Retry; the download link may have expired.`
|
|
47506
|
+
);
|
|
47507
|
+
}
|
|
47508
|
+
const contentLength = response.headers.get("content-length");
|
|
47509
|
+
if (contentLength !== null) {
|
|
47510
|
+
const declared = Number(contentLength);
|
|
47511
|
+
if (!Number.isSafeInteger(declared) || declared !== upload.sizeBytes) {
|
|
47512
|
+
await response.body.cancel();
|
|
47513
|
+
throw new CodeUploadDownloadError(
|
|
47514
|
+
`Storage declared ${contentLength} bytes; the upload record says ${upload.sizeBytes}.`
|
|
47515
|
+
);
|
|
47516
|
+
}
|
|
47517
|
+
}
|
|
47518
|
+
await rm(partialPath, { force: true });
|
|
47519
|
+
const file2 = await open2(partialPath, "wx");
|
|
47520
|
+
const hash2 = createHash2("sha256");
|
|
47521
|
+
const reader = response.body.getReader();
|
|
47522
|
+
let downloadedBytes = 0;
|
|
47523
|
+
options.onProgress?.(progress(0, upload.sizeBytes));
|
|
47524
|
+
try {
|
|
47525
|
+
for (; ; ) {
|
|
47526
|
+
const chunk = await reader.read();
|
|
47527
|
+
if (chunk.done) break;
|
|
47528
|
+
downloadedBytes += chunk.value.byteLength;
|
|
47529
|
+
if (downloadedBytes > upload.sizeBytes) {
|
|
47530
|
+
await reader.cancel();
|
|
47531
|
+
throw new CodeUploadDownloadError(
|
|
47532
|
+
`Storage sent more than the ${upload.sizeBytes} bytes on record.`
|
|
47533
|
+
);
|
|
47534
|
+
}
|
|
47535
|
+
hash2.update(chunk.value);
|
|
47536
|
+
await writeAll(file2, chunk.value);
|
|
47537
|
+
options.onProgress?.(progress(downloadedBytes, upload.sizeBytes));
|
|
47538
|
+
}
|
|
47539
|
+
await file2.close();
|
|
47540
|
+
if (downloadedBytes !== upload.sizeBytes) {
|
|
47541
|
+
throw new CodeUploadDownloadError(
|
|
47542
|
+
`Download ended after ${downloadedBytes} of ${upload.sizeBytes} bytes.`
|
|
47543
|
+
);
|
|
47544
|
+
}
|
|
47545
|
+
const digest = hash2.digest("hex");
|
|
47546
|
+
if (digest !== upload.archiveSha256) {
|
|
47547
|
+
throw new CodeUploadDownloadError(
|
|
47548
|
+
"Downloaded bytes do not match the SHA-256 recorded at upload time."
|
|
47549
|
+
);
|
|
47550
|
+
}
|
|
47551
|
+
await rename(partialPath, destination);
|
|
47552
|
+
} catch (error48) {
|
|
47553
|
+
await file2.close().catch(() => void 0);
|
|
47554
|
+
await rm(partialPath, { force: true });
|
|
47555
|
+
if (error48 instanceof CodeUploadDownloadError) throw error48;
|
|
47556
|
+
const detail = error48 instanceof Error ? error48.message : "unknown error";
|
|
47557
|
+
throw new CodeUploadDownloadError(`Download from storage failed: ${detail}.`);
|
|
47558
|
+
}
|
|
47559
|
+
}
|
|
47560
|
+
function progress(downloadedBytes, totalBytes) {
|
|
47561
|
+
return {
|
|
47562
|
+
downloadedBytes,
|
|
47563
|
+
totalBytes,
|
|
47564
|
+
fraction: totalBytes === 0 ? 1 : downloadedBytes / totalBytes
|
|
47565
|
+
};
|
|
47566
|
+
}
|
|
47567
|
+
async function writeAll(file2, bytes) {
|
|
47568
|
+
let offset = 0;
|
|
47569
|
+
while (offset < bytes.byteLength) {
|
|
47570
|
+
const { bytesWritten } = await file2.write(bytes, offset, bytes.byteLength - offset);
|
|
47571
|
+
if (bytesWritten === 0) {
|
|
47572
|
+
throw new CodeUploadDownloadError("Writing the build to disk stalled.");
|
|
47573
|
+
}
|
|
47574
|
+
offset += bytesWritten;
|
|
47575
|
+
}
|
|
47576
|
+
}
|
|
47577
|
+
|
|
47437
47578
|
// src/upload/multipart.ts
|
|
47438
47579
|
var MAX_CONCURRENT_UPLOADS = 3;
|
|
47439
47580
|
var MAX_PART_ATTEMPTS = 3;
|
|
@@ -47446,7 +47587,7 @@ async function uploadCodeArchiveParts(filePath, totalBytes, partSizeBytes, parts
|
|
|
47446
47587
|
if (!Number.isSafeInteger(uploadedBytes) || uploadedBytes < 0 || uploadedBytes > totalBytes) {
|
|
47447
47588
|
throw new Error("Invalid multipart progress: uploaded byte count is outside the archive.");
|
|
47448
47589
|
}
|
|
47449
|
-
options.onProgress?.(
|
|
47590
|
+
options.onProgress?.(progress2(uploadedBytes, totalBytes));
|
|
47450
47591
|
if (parts.length === 0) return;
|
|
47451
47592
|
const urls = new Map(parts.map((part) => [part.partNumber, part.uploadUrl]));
|
|
47452
47593
|
const refreshUrls = createUrlRefresher(urls, options.refreshParts);
|
|
@@ -47463,7 +47604,7 @@ async function uploadCodeArchiveParts(filePath, totalBytes, partSizeBytes, parts
|
|
|
47463
47604
|
const body = await readFileSlice(filePath, start, end);
|
|
47464
47605
|
await uploadPartWithRetry(part.partNumber, urls, body, fetchPart, wait, refreshUrls);
|
|
47465
47606
|
uploadedBytes += body.byteLength;
|
|
47466
|
-
options.onProgress?.(
|
|
47607
|
+
options.onProgress?.(progress2(uploadedBytes, totalBytes));
|
|
47467
47608
|
}
|
|
47468
47609
|
})
|
|
47469
47610
|
);
|
|
@@ -47551,7 +47692,7 @@ function assertPresignedUploadUrl(value) {
|
|
|
47551
47692
|
throw new Error("Invalid multipart upload: part URLs must use HTTPS.");
|
|
47552
47693
|
}
|
|
47553
47694
|
}
|
|
47554
|
-
function
|
|
47695
|
+
function progress2(uploadedBytes, totalBytes) {
|
|
47555
47696
|
return {
|
|
47556
47697
|
uploadedBytes,
|
|
47557
47698
|
totalBytes,
|
|
@@ -47559,13 +47700,13 @@ function progress(uploadedBytes, totalBytes) {
|
|
|
47559
47700
|
};
|
|
47560
47701
|
}
|
|
47561
47702
|
function defaultWait(milliseconds) {
|
|
47562
|
-
return new Promise((
|
|
47703
|
+
return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
|
|
47563
47704
|
}
|
|
47564
47705
|
|
|
47565
47706
|
// src/upload/commands.ts
|
|
47566
47707
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["READY", "REJECTED", "FAILED", "DELETING"]);
|
|
47567
47708
|
var DEFAULT_RUNTIME = {
|
|
47568
|
-
wait: (milliseconds) => new Promise((
|
|
47709
|
+
wait: (milliseconds) => new Promise((resolve2) => setTimeout(resolve2, milliseconds)),
|
|
47569
47710
|
pollIntervalMs: 3e3,
|
|
47570
47711
|
fetch: (input, init) => globalThis.fetch(input, init)
|
|
47571
47712
|
};
|
|
@@ -47678,6 +47819,53 @@ function failReadOp5(action, status, body) {
|
|
|
47678
47819
|
);
|
|
47679
47820
|
process.exit(1);
|
|
47680
47821
|
}
|
|
47822
|
+
function failDownloadOp(codeUploadId, status, body) {
|
|
47823
|
+
const { message, errorCode } = readErrorFields(body);
|
|
47824
|
+
if (status === UNAUTHORIZED_STATUS2) {
|
|
47825
|
+
exitAuthFailed();
|
|
47826
|
+
}
|
|
47827
|
+
if (status === 403) {
|
|
47828
|
+
console.error(
|
|
47829
|
+
colorError(
|
|
47830
|
+
"Only an organization admin (or Trace staff) on a pentest-enabled organization can download builds."
|
|
47831
|
+
)
|
|
47832
|
+
);
|
|
47833
|
+
process.exit(1);
|
|
47834
|
+
}
|
|
47835
|
+
if (status === 404) {
|
|
47836
|
+
console.error(colorError(message ?? "No code upload with that id."));
|
|
47837
|
+
process.exit(1);
|
|
47838
|
+
}
|
|
47839
|
+
if (status === 409) {
|
|
47840
|
+
if (errorCode === "CODE_UPLOAD_NOT_BUILD") {
|
|
47841
|
+
console.error(
|
|
47842
|
+
colorError(
|
|
47843
|
+
message ?? "Only APK and IPA builds can be downloaded. Source archives stay on the server."
|
|
47844
|
+
)
|
|
47845
|
+
);
|
|
47846
|
+
} else if (errorCode === "CODE_UPLOAD_NOT_READY") {
|
|
47847
|
+
console.error(
|
|
47848
|
+
colorError(
|
|
47849
|
+
message ?? "The build must be ready and pass malware scanning before it can be downloaded."
|
|
47850
|
+
)
|
|
47851
|
+
);
|
|
47852
|
+
console.error(`Wait for it with: tracecli upload wait ${codeUploadId}`);
|
|
47853
|
+
} else {
|
|
47854
|
+
console.error(colorError(message ?? "Conflict (status 409)."));
|
|
47855
|
+
}
|
|
47856
|
+
process.exit(1);
|
|
47857
|
+
}
|
|
47858
|
+
if (status === 503) {
|
|
47859
|
+
console.error(
|
|
47860
|
+
colorError(message ?? "Source archive uploads are not configured on this server.")
|
|
47861
|
+
);
|
|
47862
|
+
process.exit(1);
|
|
47863
|
+
}
|
|
47864
|
+
console.error(
|
|
47865
|
+
colorError(`Failed to download build (status ${status})${message ? `: ${message}` : "."}`)
|
|
47866
|
+
);
|
|
47867
|
+
process.exit(1);
|
|
47868
|
+
}
|
|
47681
47869
|
function resumeHint(codeUploadId, filePath) {
|
|
47682
47870
|
return `Resume with: tracecli upload resume ${codeUploadId} ${filePath}`;
|
|
47683
47871
|
}
|
|
@@ -47767,11 +47955,18 @@ function outputUpload(upload, output) {
|
|
|
47767
47955
|
}
|
|
47768
47956
|
console.log(renderUploadDetail(upload));
|
|
47769
47957
|
}
|
|
47770
|
-
function writeProgress(
|
|
47958
|
+
function writeProgress(progress3) {
|
|
47771
47959
|
if (!process.stderr.isTTY) return;
|
|
47772
|
-
const pct = Math.floor(
|
|
47960
|
+
const pct = Math.floor(progress3.fraction * 100);
|
|
47773
47961
|
process.stderr.write(
|
|
47774
|
-
`\rUploading ${pct}% (${formatArchiveBytes(
|
|
47962
|
+
`\rUploading ${pct}% (${formatArchiveBytes(progress3.uploadedBytes)} / ${formatArchiveBytes(progress3.totalBytes)})`
|
|
47963
|
+
);
|
|
47964
|
+
}
|
|
47965
|
+
function writeDownloadProgress(progress3) {
|
|
47966
|
+
if (!process.stderr.isTTY) return;
|
|
47967
|
+
const pct = Math.floor(progress3.fraction * 100);
|
|
47968
|
+
process.stderr.write(
|
|
47969
|
+
`\rDownloading ${pct}% (${formatArchiveBytes(progress3.downloadedBytes)} / ${formatArchiveBytes(progress3.totalBytes)})`
|
|
47775
47970
|
);
|
|
47776
47971
|
}
|
|
47777
47972
|
function finishProgress() {
|
|
@@ -48085,6 +48280,76 @@ async function runGet4(codeUploadIdArg, options) {
|
|
|
48085
48280
|
const upload = await requireUpload(organizationId, codeUploadId);
|
|
48086
48281
|
outputUpload(upload, output);
|
|
48087
48282
|
}
|
|
48283
|
+
async function runDownload(codeUploadIdArg, options, runtime) {
|
|
48284
|
+
const output = parseOutput(options.output);
|
|
48285
|
+
const codeUploadId = requireUuid(codeUploadIdArg, "code upload id");
|
|
48286
|
+
const organizationId = await resolveOrganizationId();
|
|
48287
|
+
const result = await authenticatedRequest(
|
|
48288
|
+
(client) => client.codeUpload.downloadCodeUpload({
|
|
48289
|
+
params: { organizationId, codeUploadId }
|
|
48290
|
+
})
|
|
48291
|
+
);
|
|
48292
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
48293
|
+
failDownloadOp(codeUploadId, result.status, result.body);
|
|
48294
|
+
}
|
|
48295
|
+
const { upload, downloadUrl } = result.body;
|
|
48296
|
+
const destination = await resolveDownloadDestination(upload, options.out);
|
|
48297
|
+
if (!options.force && await pathExists(destination)) {
|
|
48298
|
+
console.error(
|
|
48299
|
+
colorError(`${destination} already exists. Pass --force to overwrite it or --out <path>.`)
|
|
48300
|
+
);
|
|
48301
|
+
process.exit(1);
|
|
48302
|
+
}
|
|
48303
|
+
if (output === "table") {
|
|
48304
|
+
console.error(
|
|
48305
|
+
`Downloading ${sanitizeDisplay(upload.displayName)} (${formatArchiveBytes(upload.sizeBytes)}) to ${destination}`
|
|
48306
|
+
);
|
|
48307
|
+
}
|
|
48308
|
+
try {
|
|
48309
|
+
await downloadCodeUploadToFile(upload, downloadUrl, destination, {
|
|
48310
|
+
fetch: runtime.fetch,
|
|
48311
|
+
onProgress: writeDownloadProgress
|
|
48312
|
+
});
|
|
48313
|
+
finishProgress();
|
|
48314
|
+
} catch (error48) {
|
|
48315
|
+
finishProgress();
|
|
48316
|
+
const detail = error48 instanceof Error ? error48.message : "Download from storage failed.";
|
|
48317
|
+
console.error(colorError(detail));
|
|
48318
|
+
process.exit(1);
|
|
48319
|
+
}
|
|
48320
|
+
if (output === "json") {
|
|
48321
|
+
console.log(
|
|
48322
|
+
JSON.stringify(
|
|
48323
|
+
{
|
|
48324
|
+
upload,
|
|
48325
|
+
path: destination,
|
|
48326
|
+
sizeBytes: upload.sizeBytes,
|
|
48327
|
+
archiveSha256: upload.archiveSha256
|
|
48328
|
+
},
|
|
48329
|
+
null,
|
|
48330
|
+
2
|
|
48331
|
+
)
|
|
48332
|
+
);
|
|
48333
|
+
return;
|
|
48334
|
+
}
|
|
48335
|
+
if (output === "tsv") {
|
|
48336
|
+
console.log(["codeUploadId", "path", "sizeBytes", "archiveSha256"].join(" "));
|
|
48337
|
+
console.log(
|
|
48338
|
+
[
|
|
48339
|
+
tsvSafe(upload.codeUploadId),
|
|
48340
|
+
tsvSafe(destination),
|
|
48341
|
+
String(upload.sizeBytes),
|
|
48342
|
+
tsvSafe(upload.archiveSha256)
|
|
48343
|
+
].join(" ")
|
|
48344
|
+
);
|
|
48345
|
+
return;
|
|
48346
|
+
}
|
|
48347
|
+
console.log(
|
|
48348
|
+
colorSuccess(
|
|
48349
|
+
`Saved ${sanitizeDisplay(upload.displayName)} to ${destination} (${formatArchiveBytes(upload.sizeBytes)}, SHA-256 verified).`
|
|
48350
|
+
)
|
|
48351
|
+
);
|
|
48352
|
+
}
|
|
48088
48353
|
async function runWait(codeUploadIdArg, options, runtime) {
|
|
48089
48354
|
const output = parseOutput(options.output);
|
|
48090
48355
|
const codeUploadId = requireUuid(codeUploadIdArg, "code upload id");
|
|
@@ -48209,6 +48474,23 @@ Examples:
|
|
|
48209
48474
|
await runResume(codeUploadId, file2, options, runtime);
|
|
48210
48475
|
});
|
|
48211
48476
|
}
|
|
48477
|
+
function createDownloadCommand(runtime) {
|
|
48478
|
+
return new Command("download").description("Download an APK or IPA build that finished malware scanning").argument("<codeUploadId>", "Upload UUID of the build").option("--out <path>", "File to write, or an existing directory (default: working directory)").option("-f, --force", "Overwrite an existing file").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
48479
|
+
"after",
|
|
48480
|
+
`
|
|
48481
|
+
Only READY builds with a clean malware scan can be downloaded. Source ZIPs
|
|
48482
|
+
are not downloadable. The file is written under the name it was uploaded
|
|
48483
|
+
with and its size and SHA-256 are checked against the upload record before
|
|
48484
|
+
it is moved into place.
|
|
48485
|
+
|
|
48486
|
+
Examples:
|
|
48487
|
+
tracecli upload download <codeUploadId>
|
|
48488
|
+
tracecli upload download <codeUploadId> --out ./builds/
|
|
48489
|
+
tracecli upload download <codeUploadId> --out ./release.apk --force`
|
|
48490
|
+
).action(async (codeUploadId, options) => {
|
|
48491
|
+
await runDownload(codeUploadId, options, runtime);
|
|
48492
|
+
});
|
|
48493
|
+
}
|
|
48212
48494
|
function createWaitCommand(runtime) {
|
|
48213
48495
|
return new Command("wait").description("Wait until an upload finishes malware scanning and validation").argument("<codeUploadId>", "Upload UUID").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").action(async (codeUploadId, options) => {
|
|
48214
48496
|
await runWait(codeUploadId, options, runtime);
|
|
@@ -48234,11 +48516,12 @@ Examples:
|
|
|
48234
48516
|
}
|
|
48235
48517
|
function createUploadCommand(runtime = {}) {
|
|
48236
48518
|
const resolved = resolveRuntime(runtime);
|
|
48237
|
-
const upload = new Command("upload").alias("code-upload").description("Upload source archives
|
|
48519
|
+
const upload = new Command("upload").alias("code-upload").description("Upload source archives and mobile builds for pentest; download builds back");
|
|
48238
48520
|
upload.addCommand(createCreateCommand5(resolved));
|
|
48239
48521
|
upload.addCommand(createBuildCommand(resolved));
|
|
48240
48522
|
upload.addCommand(createListCommand7());
|
|
48241
48523
|
upload.addCommand(createGetCommand4());
|
|
48524
|
+
upload.addCommand(createDownloadCommand(resolved));
|
|
48242
48525
|
upload.addCommand(createResumeCommand(resolved));
|
|
48243
48526
|
upload.addCommand(createWaitCommand(resolved));
|
|
48244
48527
|
upload.addCommand(createDeleteCommand5());
|