securewithtrace 0.40.0 → 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 +301 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22144,7 +22144,7 @@ var AgentSessionFailureReasonValues = [
|
|
|
22144
22144
|
"INTERNAL"
|
|
22145
22145
|
];
|
|
22146
22146
|
var AgentSessionFailureReason = external_exports.enum(AgentSessionFailureReasonValues);
|
|
22147
|
-
var AgentSessionProviderValues = ["BEDROCK", "ANTHROPIC"];
|
|
22147
|
+
var AgentSessionProviderValues = ["BEDROCK", "ANTHROPIC", "OPENAI"];
|
|
22148
22148
|
var AgentSessionProvider = external_exports.enum(AgentSessionProviderValues);
|
|
22149
22149
|
var AgentSessionEventKindValues = [
|
|
22150
22150
|
"AGENT_TEXT",
|
|
@@ -22283,6 +22283,9 @@ var AgentSessionSummarySchema = external_exports.object({
|
|
|
22283
22283
|
status: AgentSessionStatus,
|
|
22284
22284
|
waitingReason: AgentSessionWaitingReason.nullable(),
|
|
22285
22285
|
failureReason: AgentSessionFailureReason.nullable(),
|
|
22286
|
+
/** Sanitized, shape-only failure description (e.g. a provider refusal message) — never customer
|
|
22287
|
+
* data. In the summary so the staff console can show WHY a listed session failed. */
|
|
22288
|
+
failureDetails: external_exports.string().nullable(),
|
|
22286
22289
|
objective: external_exports.string(),
|
|
22287
22290
|
provider: AgentSessionProvider,
|
|
22288
22291
|
model: external_exports.string(),
|
|
@@ -22305,8 +22308,6 @@ var AgentSessionRepositoryPinSchema = external_exports.object({
|
|
|
22305
22308
|
commitSha: external_exports.string()
|
|
22306
22309
|
});
|
|
22307
22310
|
var AgentSessionDetailSchema = AgentSessionSummarySchema.extend({
|
|
22308
|
-
/** Sanitized, shape-only failure description — never customer data. */
|
|
22309
|
-
failureDetails: external_exports.string().nullable(),
|
|
22310
22311
|
sdkSessionId: external_exports.string().nullable(),
|
|
22311
22312
|
promptVersion: external_exports.string(),
|
|
22312
22313
|
toolsetVersion: external_exports.string(),
|
|
@@ -29593,6 +29594,11 @@ var ResumeCodeUploadResponseSchema = external_exports.object({
|
|
|
29593
29594
|
var ListCodeUploadsResponseSchema = external_exports.object({
|
|
29594
29595
|
uploads: external_exports.array(CodeUploadSchema)
|
|
29595
29596
|
});
|
|
29597
|
+
var DownloadCodeUploadResponseSchema = external_exports.object({
|
|
29598
|
+
upload: CodeUploadSchema,
|
|
29599
|
+
downloadUrl: external_exports.url(),
|
|
29600
|
+
expiresAt: external_exports.coerce.date()
|
|
29601
|
+
});
|
|
29596
29602
|
|
|
29597
29603
|
// ../../packages/api-contracts/src/contracts/v1/code-upload.ts
|
|
29598
29604
|
var c12 = initContract();
|
|
@@ -29646,6 +29652,15 @@ var codeUploadContract = c12.router({
|
|
|
29646
29652
|
...commonErrors
|
|
29647
29653
|
}
|
|
29648
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
|
+
},
|
|
29649
29664
|
deleteCodeUpload: {
|
|
29650
29665
|
method: "DELETE",
|
|
29651
29666
|
path: "/organizations/:organizationId/code-uploads/:codeUploadId",
|
|
@@ -37240,7 +37255,7 @@ async function requestDeviceAuthorization(clientId) {
|
|
|
37240
37255
|
}
|
|
37241
37256
|
return await response.json();
|
|
37242
37257
|
}
|
|
37243
|
-
var sleep = (ms) => new Promise((
|
|
37258
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
37244
37259
|
async function pollForToken(clientId, deviceCode, options = {}) {
|
|
37245
37260
|
const expiresIn = options.expiresIn ?? 300;
|
|
37246
37261
|
let interval = options.interval ?? 5;
|
|
@@ -37925,18 +37940,18 @@ var proto = Object.defineProperties(() => {
|
|
|
37925
37940
|
}
|
|
37926
37941
|
}
|
|
37927
37942
|
});
|
|
37928
|
-
var createStyler = (
|
|
37943
|
+
var createStyler = (open3, close, parent) => {
|
|
37929
37944
|
let openAll;
|
|
37930
37945
|
let closeAll;
|
|
37931
37946
|
if (parent === void 0) {
|
|
37932
|
-
openAll =
|
|
37947
|
+
openAll = open3;
|
|
37933
37948
|
closeAll = close;
|
|
37934
37949
|
} else {
|
|
37935
|
-
openAll = parent.openAll +
|
|
37950
|
+
openAll = parent.openAll + open3;
|
|
37936
37951
|
closeAll = close + parent.closeAll;
|
|
37937
37952
|
}
|
|
37938
37953
|
return {
|
|
37939
|
-
open:
|
|
37954
|
+
open: open3,
|
|
37940
37955
|
close,
|
|
37941
37956
|
openAll,
|
|
37942
37957
|
closeAll,
|
|
@@ -47290,6 +47305,11 @@ Examples:
|
|
|
47290
47305
|
});
|
|
47291
47306
|
}
|
|
47292
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
|
+
|
|
47293
47313
|
// src/upload/prepare.ts
|
|
47294
47314
|
import { createHash } from "crypto";
|
|
47295
47315
|
import { createReadStream, statSync as statSync4 } from "fs";
|
|
@@ -47433,6 +47453,128 @@ async function prepareUploadFile(filePath, options = {}) {
|
|
|
47433
47453
|
};
|
|
47434
47454
|
}
|
|
47435
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
|
+
|
|
47436
47578
|
// src/upload/multipart.ts
|
|
47437
47579
|
var MAX_CONCURRENT_UPLOADS = 3;
|
|
47438
47580
|
var MAX_PART_ATTEMPTS = 3;
|
|
@@ -47445,7 +47587,7 @@ async function uploadCodeArchiveParts(filePath, totalBytes, partSizeBytes, parts
|
|
|
47445
47587
|
if (!Number.isSafeInteger(uploadedBytes) || uploadedBytes < 0 || uploadedBytes > totalBytes) {
|
|
47446
47588
|
throw new Error("Invalid multipart progress: uploaded byte count is outside the archive.");
|
|
47447
47589
|
}
|
|
47448
|
-
options.onProgress?.(
|
|
47590
|
+
options.onProgress?.(progress2(uploadedBytes, totalBytes));
|
|
47449
47591
|
if (parts.length === 0) return;
|
|
47450
47592
|
const urls = new Map(parts.map((part) => [part.partNumber, part.uploadUrl]));
|
|
47451
47593
|
const refreshUrls = createUrlRefresher(urls, options.refreshParts);
|
|
@@ -47462,7 +47604,7 @@ async function uploadCodeArchiveParts(filePath, totalBytes, partSizeBytes, parts
|
|
|
47462
47604
|
const body = await readFileSlice(filePath, start, end);
|
|
47463
47605
|
await uploadPartWithRetry(part.partNumber, urls, body, fetchPart, wait, refreshUrls);
|
|
47464
47606
|
uploadedBytes += body.byteLength;
|
|
47465
|
-
options.onProgress?.(
|
|
47607
|
+
options.onProgress?.(progress2(uploadedBytes, totalBytes));
|
|
47466
47608
|
}
|
|
47467
47609
|
})
|
|
47468
47610
|
);
|
|
@@ -47550,7 +47692,7 @@ function assertPresignedUploadUrl(value) {
|
|
|
47550
47692
|
throw new Error("Invalid multipart upload: part URLs must use HTTPS.");
|
|
47551
47693
|
}
|
|
47552
47694
|
}
|
|
47553
|
-
function
|
|
47695
|
+
function progress2(uploadedBytes, totalBytes) {
|
|
47554
47696
|
return {
|
|
47555
47697
|
uploadedBytes,
|
|
47556
47698
|
totalBytes,
|
|
@@ -47558,13 +47700,13 @@ function progress(uploadedBytes, totalBytes) {
|
|
|
47558
47700
|
};
|
|
47559
47701
|
}
|
|
47560
47702
|
function defaultWait(milliseconds) {
|
|
47561
|
-
return new Promise((
|
|
47703
|
+
return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
|
|
47562
47704
|
}
|
|
47563
47705
|
|
|
47564
47706
|
// src/upload/commands.ts
|
|
47565
47707
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["READY", "REJECTED", "FAILED", "DELETING"]);
|
|
47566
47708
|
var DEFAULT_RUNTIME = {
|
|
47567
|
-
wait: (milliseconds) => new Promise((
|
|
47709
|
+
wait: (milliseconds) => new Promise((resolve2) => setTimeout(resolve2, milliseconds)),
|
|
47568
47710
|
pollIntervalMs: 3e3,
|
|
47569
47711
|
fetch: (input, init) => globalThis.fetch(input, init)
|
|
47570
47712
|
};
|
|
@@ -47677,6 +47819,53 @@ function failReadOp5(action, status, body) {
|
|
|
47677
47819
|
);
|
|
47678
47820
|
process.exit(1);
|
|
47679
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
|
+
}
|
|
47680
47869
|
function resumeHint(codeUploadId, filePath) {
|
|
47681
47870
|
return `Resume with: tracecli upload resume ${codeUploadId} ${filePath}`;
|
|
47682
47871
|
}
|
|
@@ -47766,11 +47955,18 @@ function outputUpload(upload, output) {
|
|
|
47766
47955
|
}
|
|
47767
47956
|
console.log(renderUploadDetail(upload));
|
|
47768
47957
|
}
|
|
47769
|
-
function writeProgress(
|
|
47958
|
+
function writeProgress(progress3) {
|
|
47959
|
+
if (!process.stderr.isTTY) return;
|
|
47960
|
+
const pct = Math.floor(progress3.fraction * 100);
|
|
47961
|
+
process.stderr.write(
|
|
47962
|
+
`\rUploading ${pct}% (${formatArchiveBytes(progress3.uploadedBytes)} / ${formatArchiveBytes(progress3.totalBytes)})`
|
|
47963
|
+
);
|
|
47964
|
+
}
|
|
47965
|
+
function writeDownloadProgress(progress3) {
|
|
47770
47966
|
if (!process.stderr.isTTY) return;
|
|
47771
|
-
const pct = Math.floor(
|
|
47967
|
+
const pct = Math.floor(progress3.fraction * 100);
|
|
47772
47968
|
process.stderr.write(
|
|
47773
|
-
`\
|
|
47969
|
+
`\rDownloading ${pct}% (${formatArchiveBytes(progress3.downloadedBytes)} / ${formatArchiveBytes(progress3.totalBytes)})`
|
|
47774
47970
|
);
|
|
47775
47971
|
}
|
|
47776
47972
|
function finishProgress() {
|
|
@@ -48084,6 +48280,76 @@ async function runGet4(codeUploadIdArg, options) {
|
|
|
48084
48280
|
const upload = await requireUpload(organizationId, codeUploadId);
|
|
48085
48281
|
outputUpload(upload, output);
|
|
48086
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
|
+
}
|
|
48087
48353
|
async function runWait(codeUploadIdArg, options, runtime) {
|
|
48088
48354
|
const output = parseOutput(options.output);
|
|
48089
48355
|
const codeUploadId = requireUuid(codeUploadIdArg, "code upload id");
|
|
@@ -48208,6 +48474,23 @@ Examples:
|
|
|
48208
48474
|
await runResume(codeUploadId, file2, options, runtime);
|
|
48209
48475
|
});
|
|
48210
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
|
+
}
|
|
48211
48494
|
function createWaitCommand(runtime) {
|
|
48212
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) => {
|
|
48213
48496
|
await runWait(codeUploadId, options, runtime);
|
|
@@ -48233,11 +48516,12 @@ Examples:
|
|
|
48233
48516
|
}
|
|
48234
48517
|
function createUploadCommand(runtime = {}) {
|
|
48235
48518
|
const resolved = resolveRuntime(runtime);
|
|
48236
|
-
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");
|
|
48237
48520
|
upload.addCommand(createCreateCommand5(resolved));
|
|
48238
48521
|
upload.addCommand(createBuildCommand(resolved));
|
|
48239
48522
|
upload.addCommand(createListCommand7());
|
|
48240
48523
|
upload.addCommand(createGetCommand4());
|
|
48524
|
+
upload.addCommand(createDownloadCommand(resolved));
|
|
48241
48525
|
upload.addCommand(createResumeCommand(resolved));
|
|
48242
48526
|
upload.addCommand(createWaitCommand(resolved));
|
|
48243
48527
|
upload.addCommand(createDeleteCommand5());
|