@zero-transfer/sdk 0.4.0 → 0.4.6
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 +146 -21
- package/dist/index.cjs +724 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +738 -25
- package/dist/index.d.ts +738 -25
- package/dist/index.mjs +714 -38
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -1
package/dist/index.cjs
CHANGED
|
@@ -128,6 +128,7 @@ __export(index_exports, {
|
|
|
128
128
|
inboxFailedPath: () => inboxFailedPath,
|
|
129
129
|
inboxProcessedPath: () => inboxProcessedPath,
|
|
130
130
|
isClassicProviderId: () => isClassicProviderId,
|
|
131
|
+
isMainModule: () => isMainModule,
|
|
131
132
|
isSensitiveKey: () => isSensitiveKey,
|
|
132
133
|
joinRemotePath: () => joinRemotePath,
|
|
133
134
|
matchKnownHosts: () => matchKnownHosts,
|
|
@@ -160,6 +161,7 @@ __export(index_exports, {
|
|
|
160
161
|
resolveSecret: () => resolveSecret,
|
|
161
162
|
runConnectionDiagnostics: () => runConnectionDiagnostics,
|
|
162
163
|
runRoute: () => runRoute,
|
|
164
|
+
runSshCommand: () => runSshCommand,
|
|
163
165
|
serializeRemoteManifest: () => serializeRemoteManifest,
|
|
164
166
|
signWebhookPayload: () => signWebhookPayload,
|
|
165
167
|
sortRemoteEntries: () => sortRemoteEntries,
|
|
@@ -6273,7 +6275,7 @@ var SshTransportPacketUnprotector = class {
|
|
|
6273
6275
|
}
|
|
6274
6276
|
/**
|
|
6275
6277
|
* Feeds raw encrypted bytes from the socket and returns any fully decoded payloads.
|
|
6276
|
-
* Maintains internal framing state across calls
|
|
6278
|
+
* Maintains internal framing state across calls - pass each `data` event chunk directly.
|
|
6277
6279
|
*/
|
|
6278
6280
|
pushBytes(chunk) {
|
|
6279
6281
|
this.framePendingRaw = import_node_buffer15.Buffer.concat([this.framePendingRaw, chunk]);
|
|
@@ -6781,7 +6783,7 @@ var SshTransportConnection = class {
|
|
|
6781
6783
|
assertConnected() {
|
|
6782
6784
|
if (!this.connected) {
|
|
6783
6785
|
throw new ProtocolError({
|
|
6784
|
-
message: "SshTransportConnection is not yet connected
|
|
6786
|
+
message: "SshTransportConnection is not yet connected - call connect() first",
|
|
6785
6787
|
protocol: "sftp",
|
|
6786
6788
|
retryable: false
|
|
6787
6789
|
});
|
|
@@ -7121,14 +7123,14 @@ function sftpStatusToError(status, path2) {
|
|
|
7121
7123
|
case SFTP_STATUS.NO_SUCH_FILE:
|
|
7122
7124
|
return new PathNotFoundError({
|
|
7123
7125
|
details: { path: path2, sftpMessage: status.errorMessage },
|
|
7124
|
-
message: `SFTP: no such file or directory${path2 !== void 0 ? `
|
|
7126
|
+
message: `SFTP: no such file or directory${path2 !== void 0 ? ` - ${path2}` : ""}`,
|
|
7125
7127
|
protocol: "sftp",
|
|
7126
7128
|
retryable: false
|
|
7127
7129
|
});
|
|
7128
7130
|
case SFTP_STATUS.PERMISSION_DENIED:
|
|
7129
7131
|
return new PermissionDeniedError({
|
|
7130
7132
|
details: { path: path2, sftpMessage: status.errorMessage },
|
|
7131
|
-
message: `SFTP: permission denied${path2 !== void 0 ? `
|
|
7133
|
+
message: `SFTP: permission denied${path2 !== void 0 ? ` - ${path2}` : ""}`,
|
|
7132
7134
|
protocol: "sftp",
|
|
7133
7135
|
retryable: false
|
|
7134
7136
|
});
|
|
@@ -7136,21 +7138,21 @@ function sftpStatusToError(status, path2) {
|
|
|
7136
7138
|
case SFTP_STATUS.CONNECTION_LOST:
|
|
7137
7139
|
return new ConnectionError({
|
|
7138
7140
|
details: { sftpMessage: status.errorMessage, statusCode: status.statusCode },
|
|
7139
|
-
message: `SFTP: connection error
|
|
7141
|
+
message: `SFTP: connection error - ${status.errorMessage}`,
|
|
7140
7142
|
protocol: "sftp",
|
|
7141
7143
|
retryable: true
|
|
7142
7144
|
});
|
|
7143
7145
|
case SFTP_STATUS.OP_UNSUPPORTED:
|
|
7144
7146
|
return new UnsupportedFeatureError({
|
|
7145
7147
|
details: { sftpMessage: status.errorMessage },
|
|
7146
|
-
message: `SFTP: operation unsupported
|
|
7148
|
+
message: `SFTP: operation unsupported - ${status.errorMessage}`,
|
|
7147
7149
|
protocol: "sftp",
|
|
7148
7150
|
retryable: false
|
|
7149
7151
|
});
|
|
7150
7152
|
case SFTP_STATUS.BAD_MESSAGE:
|
|
7151
7153
|
return new ProtocolError({
|
|
7152
7154
|
details: { sftpMessage: status.errorMessage },
|
|
7153
|
-
message: `SFTP: bad message
|
|
7155
|
+
message: `SFTP: bad message - ${status.errorMessage}`,
|
|
7154
7156
|
protocol: "sftp",
|
|
7155
7157
|
retryable: false
|
|
7156
7158
|
});
|
|
@@ -7158,7 +7160,7 @@ function sftpStatusToError(status, path2) {
|
|
|
7158
7160
|
return new ZeroTransferError({
|
|
7159
7161
|
code: "SFTP_FAILURE",
|
|
7160
7162
|
details: { sftpMessage: status.errorMessage, statusCode: status.statusCode },
|
|
7161
|
-
message: `SFTP: operation failed (status ${status.statusCode})
|
|
7163
|
+
message: `SFTP: operation failed (status ${status.statusCode}) - ${status.errorMessage}`,
|
|
7162
7164
|
protocol: "sftp",
|
|
7163
7165
|
retryable: false
|
|
7164
7166
|
});
|
|
@@ -9261,6 +9263,9 @@ function concatChunks(chunks, totalSize) {
|
|
|
9261
9263
|
// src/providers/cloud/OneDriveProvider.ts
|
|
9262
9264
|
var ONEDRIVE_DRIVE_BASE = "https://graph.microsoft.com/v1.0/me/drive";
|
|
9263
9265
|
var ONEDRIVE_CHECKSUM_CAPABILITIES = ["sha1", "sha256", "quickxorhash"];
|
|
9266
|
+
var ONEDRIVE_CHUNK_ALIGNMENT = 320 * 1024;
|
|
9267
|
+
var DEFAULT_ONEDRIVE_PART_SIZE = 10 * 1024 * 1024;
|
|
9268
|
+
var DEFAULT_ONEDRIVE_THRESHOLD = 4 * 1024 * 1024;
|
|
9264
9269
|
function createOneDriveProviderFactory(options = {}) {
|
|
9265
9270
|
const id = options.id ?? "one-drive";
|
|
9266
9271
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
@@ -9271,6 +9276,37 @@ function createOneDriveProviderFactory(options = {}) {
|
|
|
9271
9276
|
retryable: false
|
|
9272
9277
|
});
|
|
9273
9278
|
}
|
|
9279
|
+
const multipartEnabled = options.multipart?.enabled ?? true;
|
|
9280
|
+
const partSizeBytes = options.multipart?.partSizeBytes ?? DEFAULT_ONEDRIVE_PART_SIZE;
|
|
9281
|
+
const thresholdBytes = options.multipart?.thresholdBytes ?? DEFAULT_ONEDRIVE_THRESHOLD;
|
|
9282
|
+
if (multipartEnabled) {
|
|
9283
|
+
if (!Number.isInteger(partSizeBytes) || partSizeBytes <= 0) {
|
|
9284
|
+
throw new ConfigurationError({
|
|
9285
|
+
details: { partSizeBytes },
|
|
9286
|
+
message: "OneDriveMultipartOptions.partSizeBytes must be a positive integer",
|
|
9287
|
+
retryable: false
|
|
9288
|
+
});
|
|
9289
|
+
}
|
|
9290
|
+
if (partSizeBytes % ONEDRIVE_CHUNK_ALIGNMENT !== 0) {
|
|
9291
|
+
throw new ConfigurationError({
|
|
9292
|
+
details: { partSizeBytes },
|
|
9293
|
+
message: `OneDrive multipart partSizeBytes must be a multiple of ${String(ONEDRIVE_CHUNK_ALIGNMENT)} bytes (320 KiB)`,
|
|
9294
|
+
retryable: false
|
|
9295
|
+
});
|
|
9296
|
+
}
|
|
9297
|
+
if (!Number.isInteger(thresholdBytes) || thresholdBytes < 0) {
|
|
9298
|
+
throw new ConfigurationError({
|
|
9299
|
+
details: { thresholdBytes },
|
|
9300
|
+
message: "OneDriveMultipartOptions.thresholdBytes must be a non-negative integer",
|
|
9301
|
+
retryable: false
|
|
9302
|
+
});
|
|
9303
|
+
}
|
|
9304
|
+
}
|
|
9305
|
+
const multipart = {
|
|
9306
|
+
enabled: multipartEnabled,
|
|
9307
|
+
partSizeBytes,
|
|
9308
|
+
thresholdBytes
|
|
9309
|
+
};
|
|
9274
9310
|
const capabilities = {
|
|
9275
9311
|
atomicRename: false,
|
|
9276
9312
|
authentication: ["token", "oauth"],
|
|
@@ -9280,13 +9316,17 @@ function createOneDriveProviderFactory(options = {}) {
|
|
|
9280
9316
|
list: true,
|
|
9281
9317
|
maxConcurrency: 4,
|
|
9282
9318
|
metadata: ["modifiedAt", "createdAt", "uniqueId"],
|
|
9283
|
-
notes: [
|
|
9284
|
-
|
|
9319
|
+
notes: multipartEnabled ? [
|
|
9320
|
+
`OneDrive upload session enabled by default (partSize=${String(multipart.partSizeBytes)}B, threshold=${String(multipart.thresholdBytes)}B).`,
|
|
9321
|
+
"Payloads at or below the threshold automatically fall back to single-shot PUT /content.",
|
|
9322
|
+
"Pass `multipart: { enabled: false }` to force the legacy single-shot behaviour."
|
|
9323
|
+
] : [
|
|
9324
|
+
"OneDrive provider performs single-shot uploads via PUT /content; resumable upload sessions are disabled."
|
|
9285
9325
|
],
|
|
9286
9326
|
provider: id,
|
|
9287
9327
|
readStream: true,
|
|
9288
9328
|
resumeDownload: true,
|
|
9289
|
-
resumeUpload:
|
|
9329
|
+
resumeUpload: multipartEnabled,
|
|
9290
9330
|
serverSideCopy: false,
|
|
9291
9331
|
serverSideMove: false,
|
|
9292
9332
|
stat: true,
|
|
@@ -9300,7 +9340,8 @@ function createOneDriveProviderFactory(options = {}) {
|
|
|
9300
9340
|
defaultHeaders: { ...options.defaultHeaders ?? {} },
|
|
9301
9341
|
driveBaseUrl,
|
|
9302
9342
|
fetch: fetchImpl,
|
|
9303
|
-
id
|
|
9343
|
+
id,
|
|
9344
|
+
multipart
|
|
9304
9345
|
}),
|
|
9305
9346
|
id
|
|
9306
9347
|
};
|
|
@@ -9334,6 +9375,7 @@ var OneDriveProvider = class {
|
|
|
9334
9375
|
driveBaseUrl: this.internals.driveBaseUrl,
|
|
9335
9376
|
fetch: this.internals.fetch,
|
|
9336
9377
|
id: this.internals.id,
|
|
9378
|
+
multipart: this.internals.multipart,
|
|
9337
9379
|
token
|
|
9338
9380
|
};
|
|
9339
9381
|
if (profile.timeoutMs !== void 0) sessionOptions.timeoutMs = profile.timeoutMs;
|
|
@@ -9437,12 +9479,19 @@ var OneDriveTransferOperations = class {
|
|
|
9437
9479
|
if (request.offset !== void 0 && request.offset > 0) {
|
|
9438
9480
|
throw new UnsupportedFeatureError({
|
|
9439
9481
|
details: { offset: request.offset },
|
|
9440
|
-
message: "OneDrive provider does not yet support
|
|
9482
|
+
message: "OneDrive provider does not yet support cross-attempt resume of upload sessions",
|
|
9441
9483
|
retryable: false
|
|
9442
9484
|
});
|
|
9443
9485
|
}
|
|
9444
9486
|
const normalized = normalizeRemotePath(request.endpoint.path);
|
|
9487
|
+
const multipart = this.options.multipart;
|
|
9445
9488
|
const buffered = await collectChunks3(request.content);
|
|
9489
|
+
if (!multipart.enabled || buffered.byteLength <= multipart.thresholdBytes) {
|
|
9490
|
+
return this.singleShotPut(request, normalized, buffered);
|
|
9491
|
+
}
|
|
9492
|
+
return this.writeUploadSession(request, normalized, buffered);
|
|
9493
|
+
}
|
|
9494
|
+
async singleShotPut(request, normalized, buffered) {
|
|
9446
9495
|
const url = `${this.options.driveBaseUrl}${itemSegment(normalized)}/content`;
|
|
9447
9496
|
const response = await graphFetch(this.options, "PUT", url, {
|
|
9448
9497
|
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
@@ -9462,6 +9511,77 @@ var OneDriveTransferOperations = class {
|
|
|
9462
9511
|
if (checksum !== void 0) result.checksum = checksum;
|
|
9463
9512
|
return result;
|
|
9464
9513
|
}
|
|
9514
|
+
async writeUploadSession(request, normalized, buffered) {
|
|
9515
|
+
const partSize = this.options.multipart.partSizeBytes;
|
|
9516
|
+
const total = buffered.byteLength;
|
|
9517
|
+
const sessionUrl = `${this.options.driveBaseUrl}${itemSegment(normalized)}/createUploadSession`;
|
|
9518
|
+
const initiate = await graphFetch(this.options, "POST", sessionUrl, {
|
|
9519
|
+
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
9520
|
+
body: new TextEncoder().encode(
|
|
9521
|
+
JSON.stringify({ item: { "@microsoft.graph.conflictBehavior": "replace" } })
|
|
9522
|
+
),
|
|
9523
|
+
extraHeaders: { "content-type": "application/json" }
|
|
9524
|
+
});
|
|
9525
|
+
if (!initiate.ok) {
|
|
9526
|
+
throw mapOneDriveResponseError(initiate, normalized, await safeReadText3(initiate));
|
|
9527
|
+
}
|
|
9528
|
+
const initiateBody = await initiate.json();
|
|
9529
|
+
const uploadUrl = initiateBody.uploadUrl;
|
|
9530
|
+
if (typeof uploadUrl !== "string" || uploadUrl === "") {
|
|
9531
|
+
throw new ConnectionError({
|
|
9532
|
+
details: { path: normalized },
|
|
9533
|
+
message: "OneDrive createUploadSession returned no uploadUrl",
|
|
9534
|
+
retryable: true
|
|
9535
|
+
});
|
|
9536
|
+
}
|
|
9537
|
+
let bytesTransferred = 0;
|
|
9538
|
+
let finalItem;
|
|
9539
|
+
try {
|
|
9540
|
+
while (bytesTransferred < total) {
|
|
9541
|
+
request.throwIfAborted();
|
|
9542
|
+
const chunkEnd = Math.min(bytesTransferred + partSize, total);
|
|
9543
|
+
const chunk = buffered.subarray(bytesTransferred, chunkEnd);
|
|
9544
|
+
const response = await graphSessionFetch(this.options, uploadUrl, {
|
|
9545
|
+
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
9546
|
+
body: chunk,
|
|
9547
|
+
extraHeaders: {
|
|
9548
|
+
"content-length": String(chunk.byteLength),
|
|
9549
|
+
"content-range": `bytes ${String(bytesTransferred)}-${String(chunkEnd - 1)}/${String(total)}`,
|
|
9550
|
+
"content-type": "application/octet-stream"
|
|
9551
|
+
}
|
|
9552
|
+
});
|
|
9553
|
+
if (response.status === 202) {
|
|
9554
|
+
bytesTransferred = chunkEnd;
|
|
9555
|
+
request.reportProgress(bytesTransferred, total);
|
|
9556
|
+
continue;
|
|
9557
|
+
}
|
|
9558
|
+
if (response.status === 200 || response.status === 201) {
|
|
9559
|
+
bytesTransferred = chunkEnd;
|
|
9560
|
+
request.reportProgress(bytesTransferred, total);
|
|
9561
|
+
finalItem = await response.json();
|
|
9562
|
+
break;
|
|
9563
|
+
}
|
|
9564
|
+
throw mapOneDriveResponseError(response, normalized, await safeReadText3(response));
|
|
9565
|
+
}
|
|
9566
|
+
} catch (error) {
|
|
9567
|
+
void graphSessionFetch(this.options, uploadUrl, { method: "DELETE" }).catch(() => void 0);
|
|
9568
|
+
throw error;
|
|
9569
|
+
}
|
|
9570
|
+
if (finalItem === void 0) {
|
|
9571
|
+
throw new ConnectionError({
|
|
9572
|
+
details: { path: normalized },
|
|
9573
|
+
message: "OneDrive upload session did not return a final DriveItem",
|
|
9574
|
+
retryable: true
|
|
9575
|
+
});
|
|
9576
|
+
}
|
|
9577
|
+
const result = {
|
|
9578
|
+
bytesTransferred,
|
|
9579
|
+
totalBytes: bytesTransferred
|
|
9580
|
+
};
|
|
9581
|
+
const checksum = preferHash(finalItem.file?.hashes);
|
|
9582
|
+
if (checksum !== void 0) result.checksum = checksum;
|
|
9583
|
+
return result;
|
|
9584
|
+
}
|
|
9465
9585
|
async fetchItem(normalized) {
|
|
9466
9586
|
const url = `${this.options.driveBaseUrl}${itemSegment(normalized)}`;
|
|
9467
9587
|
const response = await graphFetch(this.options, "GET", url);
|
|
@@ -9508,6 +9628,45 @@ async function graphFetch(options, method, url, fetchOptions = {}) {
|
|
|
9508
9628
|
if (timer !== void 0) clearTimeout(timer);
|
|
9509
9629
|
}
|
|
9510
9630
|
}
|
|
9631
|
+
async function graphSessionFetch(options, uploadUrl, fetchOptions = {}) {
|
|
9632
|
+
const headers = {
|
|
9633
|
+
...fetchOptions.extraHeaders ?? {}
|
|
9634
|
+
};
|
|
9635
|
+
const init = { headers, method: fetchOptions.method ?? "PUT" };
|
|
9636
|
+
if (fetchOptions.body !== void 0) {
|
|
9637
|
+
init.body = fetchOptions.body;
|
|
9638
|
+
}
|
|
9639
|
+
const controller = new AbortController();
|
|
9640
|
+
const upstream = fetchOptions.signal ?? null;
|
|
9641
|
+
if (upstream !== null) {
|
|
9642
|
+
if (upstream.aborted) controller.abort(upstream.reason);
|
|
9643
|
+
else upstream.addEventListener("abort", () => controller.abort(upstream.reason));
|
|
9644
|
+
}
|
|
9645
|
+
let timer;
|
|
9646
|
+
if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
|
|
9647
|
+
timer = setTimeout(
|
|
9648
|
+
() => controller.abort(new Error("OneDrive upload session request timed out")),
|
|
9649
|
+
options.timeoutMs
|
|
9650
|
+
);
|
|
9651
|
+
}
|
|
9652
|
+
try {
|
|
9653
|
+
return await options.fetch(uploadUrl, { ...init, signal: controller.signal });
|
|
9654
|
+
} catch (error) {
|
|
9655
|
+
const safeUrl = redactSessionUrl(uploadUrl);
|
|
9656
|
+
throw new ConnectionError({
|
|
9657
|
+
cause: error,
|
|
9658
|
+
details: { url: safeUrl },
|
|
9659
|
+
message: `OneDrive upload session request to ${safeUrl} failed`,
|
|
9660
|
+
retryable: true
|
|
9661
|
+
});
|
|
9662
|
+
} finally {
|
|
9663
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
9664
|
+
}
|
|
9665
|
+
}
|
|
9666
|
+
function redactSessionUrl(url) {
|
|
9667
|
+
const queryStart = url.indexOf("?");
|
|
9668
|
+
return queryStart === -1 ? url : `${url.slice(0, queryStart)}?<redacted>`;
|
|
9669
|
+
}
|
|
9511
9670
|
function mapOneDriveResponseError(response, contextPath, bodyText) {
|
|
9512
9671
|
const details = {
|
|
9513
9672
|
bodyText: bodyText.slice(0, 500),
|
|
@@ -9629,8 +9788,12 @@ async function collectChunks3(source) {
|
|
|
9629
9788
|
}
|
|
9630
9789
|
|
|
9631
9790
|
// src/providers/cloud/AzureBlobProvider.ts
|
|
9791
|
+
var import_node_crypto11 = require("crypto");
|
|
9632
9792
|
var AZURE_BLOB_API_VERSION = "2023-11-03";
|
|
9633
9793
|
var AZURE_CHECKSUM_CAPABILITIES = ["md5"];
|
|
9794
|
+
var DEFAULT_AZURE_PART_SIZE = 8 * 1024 * 1024;
|
|
9795
|
+
var DEFAULT_AZURE_THRESHOLD = 8 * 1024 * 1024;
|
|
9796
|
+
var AZURE_MAX_PART_SIZE = 4e3 * 1024 * 1024;
|
|
9634
9797
|
function createAzureBlobProviderFactory(options) {
|
|
9635
9798
|
if (typeof options.container !== "string" || options.container === "") {
|
|
9636
9799
|
throw new ConfigurationError({
|
|
@@ -9648,6 +9811,37 @@ function createAzureBlobProviderFactory(options) {
|
|
|
9648
9811
|
}
|
|
9649
9812
|
const endpoint = resolveAzureEndpoint(options);
|
|
9650
9813
|
const apiVersion = options.apiVersion ?? AZURE_BLOB_API_VERSION;
|
|
9814
|
+
const multipartEnabled = options.multipart?.enabled ?? true;
|
|
9815
|
+
const partSizeBytes = options.multipart?.partSizeBytes ?? DEFAULT_AZURE_PART_SIZE;
|
|
9816
|
+
const thresholdBytes = options.multipart?.thresholdBytes ?? DEFAULT_AZURE_THRESHOLD;
|
|
9817
|
+
if (multipartEnabled) {
|
|
9818
|
+
if (!Number.isInteger(partSizeBytes) || partSizeBytes <= 0) {
|
|
9819
|
+
throw new ConfigurationError({
|
|
9820
|
+
details: { partSizeBytes },
|
|
9821
|
+
message: "AzureBlobMultipartOptions.partSizeBytes must be a positive integer",
|
|
9822
|
+
retryable: false
|
|
9823
|
+
});
|
|
9824
|
+
}
|
|
9825
|
+
if (partSizeBytes > AZURE_MAX_PART_SIZE) {
|
|
9826
|
+
throw new ConfigurationError({
|
|
9827
|
+
details: { maxBytes: AZURE_MAX_PART_SIZE, partSizeBytes },
|
|
9828
|
+
message: `AzureBlobMultipartOptions.partSizeBytes must not exceed ${String(AZURE_MAX_PART_SIZE)} bytes (4000 MiB)`,
|
|
9829
|
+
retryable: false
|
|
9830
|
+
});
|
|
9831
|
+
}
|
|
9832
|
+
if (!Number.isInteger(thresholdBytes) || thresholdBytes < 0) {
|
|
9833
|
+
throw new ConfigurationError({
|
|
9834
|
+
details: { thresholdBytes },
|
|
9835
|
+
message: "AzureBlobMultipartOptions.thresholdBytes must be a non-negative integer",
|
|
9836
|
+
retryable: false
|
|
9837
|
+
});
|
|
9838
|
+
}
|
|
9839
|
+
}
|
|
9840
|
+
const multipart = {
|
|
9841
|
+
enabled: multipartEnabled,
|
|
9842
|
+
partSizeBytes,
|
|
9843
|
+
thresholdBytes
|
|
9844
|
+
};
|
|
9651
9845
|
const capabilities = {
|
|
9652
9846
|
atomicRename: false,
|
|
9653
9847
|
authentication: ["token", "oauth"],
|
|
@@ -9657,13 +9851,17 @@ function createAzureBlobProviderFactory(options) {
|
|
|
9657
9851
|
list: true,
|
|
9658
9852
|
maxConcurrency: 4,
|
|
9659
9853
|
metadata: ["modifiedAt", "uniqueId"],
|
|
9660
|
-
notes: [
|
|
9661
|
-
|
|
9854
|
+
notes: multipartEnabled ? [
|
|
9855
|
+
`Azure Blob staged-block upload enabled by default (partSize=${String(multipart.partSizeBytes)}B, threshold=${String(multipart.thresholdBytes)}B).`,
|
|
9856
|
+
"Payloads at or below the threshold automatically fall back to single-shot block-blob PUT.",
|
|
9857
|
+
"Pass `multipart: { enabled: false }` to force the legacy single-shot behaviour."
|
|
9858
|
+
] : [
|
|
9859
|
+
"Azure Blob provider performs single-shot block-blob uploads via PUT; entire object is buffered in memory before transmission."
|
|
9662
9860
|
],
|
|
9663
9861
|
provider: id,
|
|
9664
9862
|
readStream: true,
|
|
9665
9863
|
resumeDownload: true,
|
|
9666
|
-
resumeUpload:
|
|
9864
|
+
resumeUpload: multipartEnabled,
|
|
9667
9865
|
serverSideCopy: false,
|
|
9668
9866
|
serverSideMove: false,
|
|
9669
9867
|
stat: true,
|
|
@@ -9680,6 +9878,7 @@ function createAzureBlobProviderFactory(options) {
|
|
|
9680
9878
|
endpoint,
|
|
9681
9879
|
fetch: fetchImpl,
|
|
9682
9880
|
id,
|
|
9881
|
+
multipart,
|
|
9683
9882
|
...options.sasToken !== void 0 ? { sasToken: options.sasToken } : {}
|
|
9684
9883
|
}),
|
|
9685
9884
|
id
|
|
@@ -9725,7 +9924,8 @@ var AzureBlobProvider = class {
|
|
|
9725
9924
|
defaultHeaders: this.internals.defaultHeaders,
|
|
9726
9925
|
endpoint: this.internals.endpoint,
|
|
9727
9926
|
fetch: this.internals.fetch,
|
|
9728
|
-
id: this.internals.id
|
|
9927
|
+
id: this.internals.id,
|
|
9928
|
+
multipart: this.internals.multipart
|
|
9729
9929
|
};
|
|
9730
9930
|
if (bearerToken !== void 0) sessionOptions.bearerToken = bearerToken;
|
|
9731
9931
|
if (this.internals.sasToken !== void 0) sessionOptions.sasToken = this.internals.sasToken;
|
|
@@ -9860,12 +10060,19 @@ var AzureBlobTransferOperations = class {
|
|
|
9860
10060
|
if (request.offset !== void 0 && request.offset > 0) {
|
|
9861
10061
|
throw new UnsupportedFeatureError({
|
|
9862
10062
|
details: { offset: request.offset },
|
|
9863
|
-
message: "Azure Blob provider does not yet support staged-block
|
|
10063
|
+
message: "Azure Blob provider does not yet support cross-attempt resume of staged-block uploads",
|
|
9864
10064
|
retryable: false
|
|
9865
10065
|
});
|
|
9866
10066
|
}
|
|
9867
10067
|
const normalized = normalizeRemotePath(request.endpoint.path);
|
|
9868
|
-
const
|
|
10068
|
+
const multipart = this.options.multipart;
|
|
10069
|
+
if (!multipart.enabled) {
|
|
10070
|
+
const buffered = await collectChunks4(request.content);
|
|
10071
|
+
return this.singleShotPut(request, normalized, buffered);
|
|
10072
|
+
}
|
|
10073
|
+
return this.writeStagedBlocks(request, normalized);
|
|
10074
|
+
}
|
|
10075
|
+
async singleShotPut(request, normalized, buffered) {
|
|
9869
10076
|
const url = buildBlobUrl(this.options, normalized);
|
|
9870
10077
|
const response = await azureFetch(this.options, "PUT", url, {
|
|
9871
10078
|
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
@@ -9887,6 +10094,92 @@ var AzureBlobTransferOperations = class {
|
|
|
9887
10094
|
if (md5 !== null && md5 !== "") result.checksum = md5;
|
|
9888
10095
|
return result;
|
|
9889
10096
|
}
|
|
10097
|
+
async writeStagedBlocks(request, normalized) {
|
|
10098
|
+
const multipart = this.options.multipart;
|
|
10099
|
+
const partSize = multipart.partSizeBytes;
|
|
10100
|
+
const iterator = request.content[Symbol.asyncIterator]();
|
|
10101
|
+
const uploadNonce = generateUploadNonce();
|
|
10102
|
+
const initialBuffer = [];
|
|
10103
|
+
let initialSize = 0;
|
|
10104
|
+
while (initialSize <= multipart.thresholdBytes) {
|
|
10105
|
+
const next = await iterator.next();
|
|
10106
|
+
if (next.done === true) break;
|
|
10107
|
+
const chunk = next.value;
|
|
10108
|
+
if (chunk.byteLength === 0) continue;
|
|
10109
|
+
initialBuffer.push(chunk);
|
|
10110
|
+
initialSize += chunk.byteLength;
|
|
10111
|
+
}
|
|
10112
|
+
if (initialSize <= multipart.thresholdBytes) {
|
|
10113
|
+
return this.singleShotPut(request, normalized, concatChunks2(initialBuffer, initialSize));
|
|
10114
|
+
}
|
|
10115
|
+
const blockIds = [];
|
|
10116
|
+
let bytesTransferred = 0;
|
|
10117
|
+
let partNumber = 1;
|
|
10118
|
+
let buffer = [...initialBuffer];
|
|
10119
|
+
let bufferSize = initialSize;
|
|
10120
|
+
const flushBlocks = async (final) => {
|
|
10121
|
+
while (bufferSize >= partSize || final && bufferSize > 0) {
|
|
10122
|
+
const take = Math.min(bufferSize, partSize);
|
|
10123
|
+
const sliced = sliceFromBuffers(buffer, take);
|
|
10124
|
+
buffer = sliced.remaining;
|
|
10125
|
+
bufferSize -= sliced.bytes.byteLength;
|
|
10126
|
+
const blockId = encodeBlockId(uploadNonce, partNumber);
|
|
10127
|
+
const blockUrl = buildBlobUrl(this.options, normalized, {
|
|
10128
|
+
blockid: blockId,
|
|
10129
|
+
comp: "block"
|
|
10130
|
+
});
|
|
10131
|
+
const response = await azureFetch(this.options, "PUT", blockUrl, {
|
|
10132
|
+
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
10133
|
+
body: sliced.bytes,
|
|
10134
|
+
extraHeaders: { "content-type": "application/octet-stream" }
|
|
10135
|
+
});
|
|
10136
|
+
if (!response.ok) {
|
|
10137
|
+
throw mapAzureResponseError(response, normalized, await safeReadText4(response));
|
|
10138
|
+
}
|
|
10139
|
+
blockIds.push(blockId);
|
|
10140
|
+
bytesTransferred += sliced.bytes.byteLength;
|
|
10141
|
+
request.reportProgress(bytesTransferred, void 0);
|
|
10142
|
+
partNumber += 1;
|
|
10143
|
+
}
|
|
10144
|
+
};
|
|
10145
|
+
await flushBlocks(false);
|
|
10146
|
+
while (true) {
|
|
10147
|
+
request.throwIfAborted();
|
|
10148
|
+
const next = await iterator.next();
|
|
10149
|
+
if (next.done === true) break;
|
|
10150
|
+
if (next.value.byteLength === 0) continue;
|
|
10151
|
+
buffer.push(next.value);
|
|
10152
|
+
bufferSize += next.value.byteLength;
|
|
10153
|
+
await flushBlocks(false);
|
|
10154
|
+
}
|
|
10155
|
+
await flushBlocks(true);
|
|
10156
|
+
if (blockIds.length === 0) {
|
|
10157
|
+
throw new ConnectionError({
|
|
10158
|
+
message: "Azure Blob staged-block upload completed with zero blocks",
|
|
10159
|
+
retryable: false
|
|
10160
|
+
});
|
|
10161
|
+
}
|
|
10162
|
+
const commitUrl = buildBlobUrl(this.options, normalized, { comp: "blocklist" });
|
|
10163
|
+
const xmlBody = buildBlockListXml(blockIds);
|
|
10164
|
+
const commitResponse = await azureFetch(this.options, "PUT", commitUrl, {
|
|
10165
|
+
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
10166
|
+
body: new TextEncoder().encode(xmlBody),
|
|
10167
|
+
extraHeaders: {
|
|
10168
|
+
"content-type": "application/xml",
|
|
10169
|
+
"x-ms-blob-content-type": "application/octet-stream"
|
|
10170
|
+
}
|
|
10171
|
+
});
|
|
10172
|
+
if (!commitResponse.ok) {
|
|
10173
|
+
throw mapAzureResponseError(commitResponse, normalized, await safeReadText4(commitResponse));
|
|
10174
|
+
}
|
|
10175
|
+
const result = {
|
|
10176
|
+
bytesTransferred,
|
|
10177
|
+
totalBytes: bytesTransferred
|
|
10178
|
+
};
|
|
10179
|
+
const md5 = commitResponse.headers.get("content-md5");
|
|
10180
|
+
if (md5 !== null && md5 !== "") result.checksum = md5;
|
|
10181
|
+
return result;
|
|
10182
|
+
}
|
|
9890
10183
|
};
|
|
9891
10184
|
async function azureFetch(options, method, url, fetchOptions = {}) {
|
|
9892
10185
|
const headers = {
|
|
@@ -9932,14 +10225,17 @@ function buildContainerUrl(options, params) {
|
|
|
9932
10225
|
appendSas(search, options.sasToken);
|
|
9933
10226
|
return `${options.endpoint}/${encodeURIComponent(options.container)}?${search.toString()}`;
|
|
9934
10227
|
}
|
|
9935
|
-
function buildBlobUrl(options, normalized) {
|
|
10228
|
+
function buildBlobUrl(options, normalized, extraParams) {
|
|
9936
10229
|
const blobPath = normalized.replace(/^\/+/u, "");
|
|
9937
10230
|
const encoded = blobPath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
9938
10231
|
const base = `${options.endpoint}/${encodeURIComponent(options.container)}/${encoded}`;
|
|
9939
|
-
|
|
9940
|
-
|
|
10232
|
+
const search = new URLSearchParams();
|
|
10233
|
+
if (extraParams !== void 0) {
|
|
10234
|
+
for (const [k, v] of Object.entries(extraParams)) search.set(k, v);
|
|
9941
10235
|
}
|
|
9942
|
-
|
|
10236
|
+
appendSas(search, options.sasToken);
|
|
10237
|
+
const query = search.toString();
|
|
10238
|
+
return query === "" ? base : `${base}?${query}`;
|
|
9943
10239
|
}
|
|
9944
10240
|
function appendSas(search, sasToken) {
|
|
9945
10241
|
if (sasToken === void 0 || sasToken === "") return;
|
|
@@ -10091,11 +10387,63 @@ async function collectChunks4(source) {
|
|
|
10091
10387
|
}
|
|
10092
10388
|
return out;
|
|
10093
10389
|
}
|
|
10390
|
+
function concatChunks2(chunks, totalSize) {
|
|
10391
|
+
const out = new Uint8Array(totalSize);
|
|
10392
|
+
let offset = 0;
|
|
10393
|
+
for (const chunk of chunks) {
|
|
10394
|
+
out.set(chunk, offset);
|
|
10395
|
+
offset += chunk.byteLength;
|
|
10396
|
+
}
|
|
10397
|
+
return out;
|
|
10398
|
+
}
|
|
10399
|
+
function sliceFromBuffers(buffers, size) {
|
|
10400
|
+
const out = new Uint8Array(size);
|
|
10401
|
+
let offset = 0;
|
|
10402
|
+
let i = 0;
|
|
10403
|
+
while (offset < size && i < buffers.length) {
|
|
10404
|
+
const chunk = buffers[i];
|
|
10405
|
+
if (chunk === void 0) {
|
|
10406
|
+
i += 1;
|
|
10407
|
+
continue;
|
|
10408
|
+
}
|
|
10409
|
+
const remaining = size - offset;
|
|
10410
|
+
if (chunk.byteLength <= remaining) {
|
|
10411
|
+
out.set(chunk, offset);
|
|
10412
|
+
offset += chunk.byteLength;
|
|
10413
|
+
i += 1;
|
|
10414
|
+
} else {
|
|
10415
|
+
out.set(chunk.subarray(0, remaining), offset);
|
|
10416
|
+
const leftover = chunk.subarray(remaining);
|
|
10417
|
+
const next = buffers.slice(i + 1);
|
|
10418
|
+
next.unshift(leftover);
|
|
10419
|
+
return { bytes: out, remaining: next };
|
|
10420
|
+
}
|
|
10421
|
+
}
|
|
10422
|
+
return { bytes: out.subarray(0, offset), remaining: buffers.slice(i) };
|
|
10423
|
+
}
|
|
10424
|
+
function encodeBlockId(nonce, partNumber) {
|
|
10425
|
+
const padded = String(partNumber).padStart(9, "0");
|
|
10426
|
+
const raw = `${nonce}-${padded}`;
|
|
10427
|
+
return Buffer.from(raw, "utf8").toString("base64");
|
|
10428
|
+
}
|
|
10429
|
+
function generateUploadNonce() {
|
|
10430
|
+
return (0, import_node_crypto11.randomBytes)(4).toString("hex");
|
|
10431
|
+
}
|
|
10432
|
+
function buildBlockListXml(blockIds) {
|
|
10433
|
+
const items = blockIds.map((id) => `<Latest>${escapeXml(id)}</Latest>`).join("");
|
|
10434
|
+
return `<?xml version="1.0" encoding="utf-8"?><BlockList>${items}</BlockList>`;
|
|
10435
|
+
}
|
|
10436
|
+
function escapeXml(value) {
|
|
10437
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
10438
|
+
}
|
|
10094
10439
|
|
|
10095
10440
|
// src/providers/cloud/GcsProvider.ts
|
|
10096
10441
|
var GCS_JSON_API_BASE = "https://storage.googleapis.com/storage/v1";
|
|
10097
10442
|
var GCS_UPLOAD_API_BASE = "https://storage.googleapis.com/upload/storage/v1";
|
|
10098
10443
|
var GCS_CHECKSUM_CAPABILITIES = ["md5", "crc32c"];
|
|
10444
|
+
var GCS_CHUNK_ALIGNMENT = 256 * 1024;
|
|
10445
|
+
var DEFAULT_GCS_PART_SIZE = 8 * 1024 * 1024;
|
|
10446
|
+
var DEFAULT_GCS_THRESHOLD = 8 * 1024 * 1024;
|
|
10099
10447
|
function createGcsProviderFactory(options) {
|
|
10100
10448
|
if (typeof options.bucket !== "string" || options.bucket === "") {
|
|
10101
10449
|
throw new ConfigurationError({
|
|
@@ -10113,6 +10461,37 @@ function createGcsProviderFactory(options) {
|
|
|
10113
10461
|
}
|
|
10114
10462
|
const apiBaseUrl = (options.apiBaseUrl ?? GCS_JSON_API_BASE).replace(/\/+$/u, "");
|
|
10115
10463
|
const uploadBaseUrl = (options.uploadBaseUrl ?? GCS_UPLOAD_API_BASE).replace(/\/+$/u, "");
|
|
10464
|
+
const multipartEnabled = options.multipart?.enabled ?? true;
|
|
10465
|
+
const partSizeBytes = options.multipart?.partSizeBytes ?? DEFAULT_GCS_PART_SIZE;
|
|
10466
|
+
const thresholdBytes = options.multipart?.thresholdBytes ?? DEFAULT_GCS_THRESHOLD;
|
|
10467
|
+
if (multipartEnabled) {
|
|
10468
|
+
if (!Number.isInteger(partSizeBytes) || partSizeBytes <= 0) {
|
|
10469
|
+
throw new ConfigurationError({
|
|
10470
|
+
details: { partSizeBytes },
|
|
10471
|
+
message: "GcsMultipartOptions.partSizeBytes must be a positive integer",
|
|
10472
|
+
retryable: false
|
|
10473
|
+
});
|
|
10474
|
+
}
|
|
10475
|
+
if (partSizeBytes % GCS_CHUNK_ALIGNMENT !== 0) {
|
|
10476
|
+
throw new ConfigurationError({
|
|
10477
|
+
details: { partSizeBytes },
|
|
10478
|
+
message: `GCS multipart partSizeBytes must be a multiple of ${String(GCS_CHUNK_ALIGNMENT)} bytes (256 KiB)`,
|
|
10479
|
+
retryable: false
|
|
10480
|
+
});
|
|
10481
|
+
}
|
|
10482
|
+
if (!Number.isInteger(thresholdBytes) || thresholdBytes < 0) {
|
|
10483
|
+
throw new ConfigurationError({
|
|
10484
|
+
details: { thresholdBytes },
|
|
10485
|
+
message: "GcsMultipartOptions.thresholdBytes must be a non-negative integer",
|
|
10486
|
+
retryable: false
|
|
10487
|
+
});
|
|
10488
|
+
}
|
|
10489
|
+
}
|
|
10490
|
+
const multipart = {
|
|
10491
|
+
enabled: multipartEnabled,
|
|
10492
|
+
partSizeBytes,
|
|
10493
|
+
thresholdBytes
|
|
10494
|
+
};
|
|
10116
10495
|
const capabilities = {
|
|
10117
10496
|
atomicRename: false,
|
|
10118
10497
|
authentication: ["token", "oauth"],
|
|
@@ -10122,13 +10501,17 @@ function createGcsProviderFactory(options) {
|
|
|
10122
10501
|
list: true,
|
|
10123
10502
|
maxConcurrency: 4,
|
|
10124
10503
|
metadata: ["modifiedAt", "createdAt", "uniqueId"],
|
|
10125
|
-
notes: [
|
|
10126
|
-
|
|
10504
|
+
notes: multipartEnabled ? [
|
|
10505
|
+
`GCS resumable-upload session enabled by default (partSize=${String(multipart.partSizeBytes)}B, threshold=${String(multipart.thresholdBytes)}B).`,
|
|
10506
|
+
"Payloads at or below the threshold automatically fall back to single-shot uploadType=media POST.",
|
|
10507
|
+
"Pass `multipart: { enabled: false }` to force the legacy single-shot behaviour."
|
|
10508
|
+
] : [
|
|
10509
|
+
"GCS provider performs single-shot media uploads via /upload?uploadType=media; resumable upload sessions are disabled."
|
|
10127
10510
|
],
|
|
10128
10511
|
provider: id,
|
|
10129
10512
|
readStream: true,
|
|
10130
10513
|
resumeDownload: true,
|
|
10131
|
-
resumeUpload:
|
|
10514
|
+
resumeUpload: multipartEnabled,
|
|
10132
10515
|
serverSideCopy: false,
|
|
10133
10516
|
serverSideMove: false,
|
|
10134
10517
|
stat: true,
|
|
@@ -10144,6 +10527,7 @@ function createGcsProviderFactory(options) {
|
|
|
10144
10527
|
defaultHeaders: { ...options.defaultHeaders ?? {} },
|
|
10145
10528
|
fetch: fetchImpl,
|
|
10146
10529
|
id,
|
|
10530
|
+
multipart,
|
|
10147
10531
|
uploadBaseUrl
|
|
10148
10532
|
}),
|
|
10149
10533
|
id
|
|
@@ -10179,6 +10563,7 @@ var GcsProvider = class {
|
|
|
10179
10563
|
defaultHeaders: this.internals.defaultHeaders,
|
|
10180
10564
|
fetch: this.internals.fetch,
|
|
10181
10565
|
id: this.internals.id,
|
|
10566
|
+
multipart: this.internals.multipart,
|
|
10182
10567
|
token,
|
|
10183
10568
|
uploadBaseUrl: this.internals.uploadBaseUrl
|
|
10184
10569
|
};
|
|
@@ -10300,13 +10685,20 @@ var GcsTransferOperations = class {
|
|
|
10300
10685
|
if (request.offset !== void 0 && request.offset > 0) {
|
|
10301
10686
|
throw new UnsupportedFeatureError({
|
|
10302
10687
|
details: { offset: request.offset },
|
|
10303
|
-
message: "GCS provider does not yet support
|
|
10688
|
+
message: "GCS provider does not yet support cross-attempt resume of upload sessions",
|
|
10304
10689
|
retryable: false
|
|
10305
10690
|
});
|
|
10306
10691
|
}
|
|
10307
10692
|
const normalized = normalizeRemotePath(request.endpoint.path);
|
|
10308
10693
|
const objectName = toGcsObjectName(normalized);
|
|
10309
|
-
const
|
|
10694
|
+
const multipart = this.options.multipart;
|
|
10695
|
+
if (!multipart.enabled) {
|
|
10696
|
+
const buffered = await collectChunks5(request.content);
|
|
10697
|
+
return this.singleShotMedia(request, normalized, objectName, buffered);
|
|
10698
|
+
}
|
|
10699
|
+
return this.writeResumableSession(request, normalized, objectName);
|
|
10700
|
+
}
|
|
10701
|
+
async singleShotMedia(request, normalized, objectName, buffered) {
|
|
10310
10702
|
const url = `${this.options.uploadBaseUrl}/b/${encodeURIComponent(this.options.bucket)}/o?uploadType=media&name=${encodeURIComponent(objectName)}`;
|
|
10311
10703
|
const response = await gcsFetch(this.options, "POST", url, {
|
|
10312
10704
|
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
@@ -10325,6 +10717,124 @@ var GcsTransferOperations = class {
|
|
|
10325
10717
|
if (typeof item.md5Hash === "string" && item.md5Hash !== "") result.checksum = item.md5Hash;
|
|
10326
10718
|
return result;
|
|
10327
10719
|
}
|
|
10720
|
+
async writeResumableSession(request, normalized, objectName) {
|
|
10721
|
+
const multipart = this.options.multipart;
|
|
10722
|
+
const partSize = multipart.partSizeBytes;
|
|
10723
|
+
const iterator = request.content[Symbol.asyncIterator]();
|
|
10724
|
+
const initialBuffer = [];
|
|
10725
|
+
let initialSize = 0;
|
|
10726
|
+
while (initialSize <= multipart.thresholdBytes) {
|
|
10727
|
+
const next = await iterator.next();
|
|
10728
|
+
if (next.done === true) break;
|
|
10729
|
+
const chunk = next.value;
|
|
10730
|
+
if (chunk.byteLength === 0) continue;
|
|
10731
|
+
initialBuffer.push(chunk);
|
|
10732
|
+
initialSize += chunk.byteLength;
|
|
10733
|
+
}
|
|
10734
|
+
if (initialSize <= multipart.thresholdBytes) {
|
|
10735
|
+
return this.singleShotMedia(
|
|
10736
|
+
request,
|
|
10737
|
+
normalized,
|
|
10738
|
+
objectName,
|
|
10739
|
+
concatChunks3(initialBuffer, initialSize)
|
|
10740
|
+
);
|
|
10741
|
+
}
|
|
10742
|
+
const initiateUrl = `${this.options.uploadBaseUrl}/b/${encodeURIComponent(this.options.bucket)}/o?uploadType=resumable&name=${encodeURIComponent(objectName)}`;
|
|
10743
|
+
const initiateResponse = await gcsFetch(this.options, "POST", initiateUrl, {
|
|
10744
|
+
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
10745
|
+
body: new TextEncoder().encode("{}"),
|
|
10746
|
+
extraHeaders: {
|
|
10747
|
+
"content-type": "application/json; charset=UTF-8",
|
|
10748
|
+
"x-upload-content-type": "application/octet-stream"
|
|
10749
|
+
}
|
|
10750
|
+
});
|
|
10751
|
+
if (!initiateResponse.ok) {
|
|
10752
|
+
throw mapGcsResponseError(initiateResponse, normalized, await safeReadText5(initiateResponse));
|
|
10753
|
+
}
|
|
10754
|
+
const sessionUri = initiateResponse.headers.get("location");
|
|
10755
|
+
if (sessionUri === null || sessionUri === "") {
|
|
10756
|
+
throw new ConnectionError({
|
|
10757
|
+
details: { path: normalized },
|
|
10758
|
+
message: "GCS resumable session initiation returned no Location header",
|
|
10759
|
+
retryable: true
|
|
10760
|
+
});
|
|
10761
|
+
}
|
|
10762
|
+
let bytesTransferred = 0;
|
|
10763
|
+
let buffer = [...initialBuffer];
|
|
10764
|
+
let bufferSize = initialSize;
|
|
10765
|
+
let sourceExhausted = false;
|
|
10766
|
+
let finalItem;
|
|
10767
|
+
const flushChunks = async (final) => {
|
|
10768
|
+
while (bufferSize >= partSize || final && bufferSize > 0) {
|
|
10769
|
+
const take = final ? bufferSize : partSize;
|
|
10770
|
+
const sliced = sliceFromBuffers2(buffer, take);
|
|
10771
|
+
buffer = sliced.remaining;
|
|
10772
|
+
bufferSize -= sliced.bytes.byteLength;
|
|
10773
|
+
const chunkStart = bytesTransferred;
|
|
10774
|
+
const chunkEnd = chunkStart + sliced.bytes.byteLength - 1;
|
|
10775
|
+
const totalRange = final ? String(chunkEnd + 1) : "*";
|
|
10776
|
+
const headers = {
|
|
10777
|
+
"content-length": String(sliced.bytes.byteLength),
|
|
10778
|
+
"content-range": `bytes ${String(chunkStart)}-${String(chunkEnd)}/${totalRange}`,
|
|
10779
|
+
"content-type": "application/octet-stream"
|
|
10780
|
+
};
|
|
10781
|
+
const response = await gcsSessionFetch(this.options, sessionUri, {
|
|
10782
|
+
...request.signal !== void 0 ? { signal: request.signal } : {},
|
|
10783
|
+
body: sliced.bytes,
|
|
10784
|
+
extraHeaders: headers
|
|
10785
|
+
});
|
|
10786
|
+
if (response.status === 308) {
|
|
10787
|
+
bytesTransferred += sliced.bytes.byteLength;
|
|
10788
|
+
request.reportProgress(bytesTransferred, void 0);
|
|
10789
|
+
continue;
|
|
10790
|
+
}
|
|
10791
|
+
if (response.status === 200 || response.status === 201) {
|
|
10792
|
+
bytesTransferred += sliced.bytes.byteLength;
|
|
10793
|
+
request.reportProgress(bytesTransferred, bytesTransferred);
|
|
10794
|
+
finalItem = await response.json();
|
|
10795
|
+
return;
|
|
10796
|
+
}
|
|
10797
|
+
throw mapGcsResponseError(response, normalized, await safeReadText5(response));
|
|
10798
|
+
}
|
|
10799
|
+
};
|
|
10800
|
+
try {
|
|
10801
|
+
await flushChunks(false);
|
|
10802
|
+
while (!sourceExhausted) {
|
|
10803
|
+
request.throwIfAborted();
|
|
10804
|
+
const next = await iterator.next();
|
|
10805
|
+
if (next.done === true) {
|
|
10806
|
+
sourceExhausted = true;
|
|
10807
|
+
break;
|
|
10808
|
+
}
|
|
10809
|
+
if (next.value.byteLength === 0) continue;
|
|
10810
|
+
buffer.push(next.value);
|
|
10811
|
+
bufferSize += next.value.byteLength;
|
|
10812
|
+
await flushChunks(false);
|
|
10813
|
+
if (finalItem !== void 0) break;
|
|
10814
|
+
}
|
|
10815
|
+
if (finalItem === void 0) {
|
|
10816
|
+
await flushChunks(true);
|
|
10817
|
+
}
|
|
10818
|
+
} catch (error) {
|
|
10819
|
+
void gcsSessionFetch(this.options, sessionUri, { method: "DELETE" }).catch(() => void 0);
|
|
10820
|
+
throw error;
|
|
10821
|
+
}
|
|
10822
|
+
if (finalItem === void 0) {
|
|
10823
|
+
throw new ConnectionError({
|
|
10824
|
+
details: { path: normalized },
|
|
10825
|
+
message: "GCS resumable upload did not return a final object",
|
|
10826
|
+
retryable: true
|
|
10827
|
+
});
|
|
10828
|
+
}
|
|
10829
|
+
const result = {
|
|
10830
|
+
bytesTransferred,
|
|
10831
|
+
totalBytes: bytesTransferred
|
|
10832
|
+
};
|
|
10833
|
+
if (typeof finalItem.md5Hash === "string" && finalItem.md5Hash !== "") {
|
|
10834
|
+
result.checksum = finalItem.md5Hash;
|
|
10835
|
+
}
|
|
10836
|
+
return result;
|
|
10837
|
+
}
|
|
10328
10838
|
};
|
|
10329
10839
|
async function gcsFetch(options, method, url, fetchOptions = {}) {
|
|
10330
10840
|
const headers = {
|
|
@@ -10483,6 +10993,81 @@ async function collectChunks5(source) {
|
|
|
10483
10993
|
}
|
|
10484
10994
|
return out;
|
|
10485
10995
|
}
|
|
10996
|
+
function concatChunks3(chunks, totalSize) {
|
|
10997
|
+
const out = new Uint8Array(totalSize);
|
|
10998
|
+
let offset = 0;
|
|
10999
|
+
for (const chunk of chunks) {
|
|
11000
|
+
out.set(chunk, offset);
|
|
11001
|
+
offset += chunk.byteLength;
|
|
11002
|
+
}
|
|
11003
|
+
return out;
|
|
11004
|
+
}
|
|
11005
|
+
function sliceFromBuffers2(buffers, size) {
|
|
11006
|
+
const out = new Uint8Array(size);
|
|
11007
|
+
let offset = 0;
|
|
11008
|
+
let i = 0;
|
|
11009
|
+
while (offset < size && i < buffers.length) {
|
|
11010
|
+
const chunk = buffers[i];
|
|
11011
|
+
if (chunk === void 0) {
|
|
11012
|
+
i += 1;
|
|
11013
|
+
continue;
|
|
11014
|
+
}
|
|
11015
|
+
const remaining = size - offset;
|
|
11016
|
+
if (chunk.byteLength <= remaining) {
|
|
11017
|
+
out.set(chunk, offset);
|
|
11018
|
+
offset += chunk.byteLength;
|
|
11019
|
+
i += 1;
|
|
11020
|
+
} else {
|
|
11021
|
+
out.set(chunk.subarray(0, remaining), offset);
|
|
11022
|
+
const leftover = chunk.subarray(remaining);
|
|
11023
|
+
const next = buffers.slice(i + 1);
|
|
11024
|
+
next.unshift(leftover);
|
|
11025
|
+
return { bytes: out, remaining: next };
|
|
11026
|
+
}
|
|
11027
|
+
}
|
|
11028
|
+
return { bytes: out.subarray(0, offset), remaining: buffers.slice(i) };
|
|
11029
|
+
}
|
|
11030
|
+
async function gcsSessionFetch(options, sessionUri, fetchOptions = {}) {
|
|
11031
|
+
const headers = {
|
|
11032
|
+
...options.defaultHeaders,
|
|
11033
|
+
...fetchOptions.extraHeaders ?? {},
|
|
11034
|
+
authorization: `Bearer ${options.token}`
|
|
11035
|
+
};
|
|
11036
|
+
const init = { headers, method: fetchOptions.method ?? "PUT" };
|
|
11037
|
+
if (fetchOptions.body !== void 0) {
|
|
11038
|
+
init.body = fetchOptions.body;
|
|
11039
|
+
}
|
|
11040
|
+
const controller = new AbortController();
|
|
11041
|
+
const upstream = fetchOptions.signal ?? null;
|
|
11042
|
+
if (upstream !== null) {
|
|
11043
|
+
if (upstream.aborted) controller.abort(upstream.reason);
|
|
11044
|
+
else upstream.addEventListener("abort", () => controller.abort(upstream.reason));
|
|
11045
|
+
}
|
|
11046
|
+
let timer;
|
|
11047
|
+
if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
|
|
11048
|
+
timer = setTimeout(
|
|
11049
|
+
() => controller.abort(new Error("GCS resumable session request timed out")),
|
|
11050
|
+
options.timeoutMs
|
|
11051
|
+
);
|
|
11052
|
+
}
|
|
11053
|
+
try {
|
|
11054
|
+
return await options.fetch(sessionUri, { ...init, signal: controller.signal });
|
|
11055
|
+
} catch (error) {
|
|
11056
|
+
const safeUrl = redactSessionUrl2(sessionUri);
|
|
11057
|
+
throw new ConnectionError({
|
|
11058
|
+
cause: error,
|
|
11059
|
+
details: { url: safeUrl },
|
|
11060
|
+
message: `GCS resumable session request to ${safeUrl} failed`,
|
|
11061
|
+
retryable: true
|
|
11062
|
+
});
|
|
11063
|
+
} finally {
|
|
11064
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
11065
|
+
}
|
|
11066
|
+
}
|
|
11067
|
+
function redactSessionUrl2(url) {
|
|
11068
|
+
const queryStart = url.indexOf("?");
|
|
11069
|
+
return queryStart === -1 ? url : `${url.slice(0, queryStart)}?<redacted>`;
|
|
11070
|
+
}
|
|
10486
11071
|
|
|
10487
11072
|
// src/providers/local/LocalProvider.ts
|
|
10488
11073
|
var import_node_fs = require("fs");
|
|
@@ -10693,9 +11278,9 @@ async function collectTransferContent(request) {
|
|
|
10693
11278
|
byteLength += clonedChunk.byteLength;
|
|
10694
11279
|
request.reportProgress(byteLength, request.totalBytes);
|
|
10695
11280
|
}
|
|
10696
|
-
return
|
|
11281
|
+
return concatChunks4(chunks, byteLength);
|
|
10697
11282
|
}
|
|
10698
|
-
function
|
|
11283
|
+
function concatChunks4(chunks, byteLength) {
|
|
10699
11284
|
const content = new Uint8Array(byteLength);
|
|
10700
11285
|
let offset = 0;
|
|
10701
11286
|
for (const chunk of chunks) {
|
|
@@ -11269,9 +11854,9 @@ async function collectTransferContent2(request) {
|
|
|
11269
11854
|
byteLength += clonedChunk.byteLength;
|
|
11270
11855
|
request.reportProgress(byteLength, request.totalBytes);
|
|
11271
11856
|
}
|
|
11272
|
-
return
|
|
11857
|
+
return concatChunks5(chunks, byteLength);
|
|
11273
11858
|
}
|
|
11274
|
-
function
|
|
11859
|
+
function concatChunks5(chunks, byteLength) {
|
|
11275
11860
|
const content = new Uint8Array(byteLength);
|
|
11276
11861
|
let offset = 0;
|
|
11277
11862
|
for (const chunk of chunks) {
|
|
@@ -11954,12 +12539,12 @@ function parentOf(path2) {
|
|
|
11954
12539
|
}
|
|
11955
12540
|
|
|
11956
12541
|
// src/providers/web/S3Provider.ts
|
|
11957
|
-
var
|
|
12542
|
+
var import_node_crypto13 = require("crypto");
|
|
11958
12543
|
var import_promises3 = require("fs/promises");
|
|
11959
12544
|
var import_node_path3 = require("path");
|
|
11960
12545
|
|
|
11961
12546
|
// src/providers/web/awsSigv4.ts
|
|
11962
|
-
var
|
|
12547
|
+
var import_node_crypto12 = require("crypto");
|
|
11963
12548
|
function signSigV4(input) {
|
|
11964
12549
|
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
11965
12550
|
const amzDate = formatAmzDate(now);
|
|
@@ -12029,13 +12614,13 @@ function encodeRfc3986(value) {
|
|
|
12029
12614
|
);
|
|
12030
12615
|
}
|
|
12031
12616
|
function sha256Hex(data) {
|
|
12032
|
-
return (0,
|
|
12617
|
+
return (0, import_node_crypto12.createHash)("sha256").update(data).digest("hex");
|
|
12033
12618
|
}
|
|
12034
12619
|
function hmac(key, data) {
|
|
12035
|
-
return (0,
|
|
12620
|
+
return (0, import_node_crypto12.createHmac)("sha256", key).update(data, "utf8").digest();
|
|
12036
12621
|
}
|
|
12037
12622
|
function hmacHex(key, data) {
|
|
12038
|
-
return (0,
|
|
12623
|
+
return (0, import_node_crypto12.createHmac)("sha256", key).update(data, "utf8").digest("hex");
|
|
12039
12624
|
}
|
|
12040
12625
|
|
|
12041
12626
|
// src/providers/web/S3Provider.ts
|
|
@@ -12061,7 +12646,7 @@ function createFileSystemS3MultipartResumeStore(options) {
|
|
|
12061
12646
|
});
|
|
12062
12647
|
}
|
|
12063
12648
|
const fileFor = (key) => {
|
|
12064
|
-
const hash = (0,
|
|
12649
|
+
const hash = (0, import_node_crypto13.createHash)("sha256").update(`${key.bucket}\0${key.jobId}\0${key.path}`).digest("hex");
|
|
12065
12650
|
return (0, import_node_path3.join)(directory, `${hash}.json`);
|
|
12066
12651
|
};
|
|
12067
12652
|
return {
|
|
@@ -12438,7 +13023,7 @@ var S3TransferOperations = class {
|
|
|
12438
13023
|
const flushPart = async (final) => {
|
|
12439
13024
|
while (bufferSize >= partSize || final && bufferSize > 0) {
|
|
12440
13025
|
const take = final ? bufferSize : partSize;
|
|
12441
|
-
const partBytes =
|
|
13026
|
+
const partBytes = sliceFromBuffers3(buffer, take);
|
|
12442
13027
|
buffer = partBytes.remaining;
|
|
12443
13028
|
bufferSize -= partBytes.bytes.byteLength;
|
|
12444
13029
|
const partUrl = new URL(objectUrl.toString());
|
|
@@ -12627,7 +13212,7 @@ function concat(chunks, totalSize) {
|
|
|
12627
13212
|
}
|
|
12628
13213
|
return out;
|
|
12629
13214
|
}
|
|
12630
|
-
function
|
|
13215
|
+
function sliceFromBuffers3(buffers, size) {
|
|
12631
13216
|
const out = new Uint8Array(size);
|
|
12632
13217
|
let offset = 0;
|
|
12633
13218
|
let i = 0;
|
|
@@ -12659,11 +13244,11 @@ async function abortMultipart(options, objectUrl, uploadId) {
|
|
|
12659
13244
|
}
|
|
12660
13245
|
function buildCompleteMultipartBody(parts) {
|
|
12661
13246
|
const partsXml = parts.map(
|
|
12662
|
-
(part) => `<Part><PartNumber>${String(part.partNumber)}</PartNumber><ETag>${
|
|
13247
|
+
(part) => `<Part><PartNumber>${String(part.partNumber)}</PartNumber><ETag>${escapeXml2(part.etag)}</ETag></Part>`
|
|
12663
13248
|
).join("");
|
|
12664
13249
|
return `<?xml version="1.0" encoding="UTF-8"?><CompleteMultipartUpload>${partsXml}</CompleteMultipartUpload>`;
|
|
12665
13250
|
}
|
|
12666
|
-
function
|
|
13251
|
+
function escapeXml2(value) {
|
|
12667
13252
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
12668
13253
|
}
|
|
12669
13254
|
function parseListObjectsV2(xml, prefix) {
|
|
@@ -12812,8 +13397,8 @@ function formatCapabilityMatrixMarkdown(matrix = getBuiltinCapabilityMatrix()) {
|
|
|
12812
13397
|
const c = entry.capabilities;
|
|
12813
13398
|
const yesNo = (value) => value ? "\u2705" : "\u274C";
|
|
12814
13399
|
const sideways = `${yesNo(c.serverSideCopy)} / ${yesNo(c.serverSideMove)}`;
|
|
12815
|
-
const checksums = c.checksum.length === 0 ? "
|
|
12816
|
-
const auth = c.authentication.length === 0 ? "
|
|
13400
|
+
const checksums = c.checksum.length === 0 ? "-" : c.checksum.join(", ");
|
|
13401
|
+
const auth = c.authentication.length === 0 ? "-" : c.authentication.join(", ");
|
|
12817
13402
|
return `| ${entry.label} | ${yesNo(c.list)} | ${yesNo(c.stat)} | ${yesNo(c.readStream)} | ${yesNo(c.writeStream)} | ${yesNo(c.resumeDownload)} | ${yesNo(c.resumeUpload)} | ${sideways} | ${checksums} | ${auth} |`;
|
|
12818
13403
|
});
|
|
12819
13404
|
return [header, divider, ...rows].join("\n");
|
|
@@ -13399,6 +13984,84 @@ function mapFtp550(details) {
|
|
|
13399
13984
|
return new PermissionDeniedError(details);
|
|
13400
13985
|
}
|
|
13401
13986
|
|
|
13987
|
+
// src/protocols/ssh/runSshCommand.ts
|
|
13988
|
+
var import_node_net3 = require("net");
|
|
13989
|
+
var DEFAULT_PORT = 22;
|
|
13990
|
+
var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
|
|
13991
|
+
var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
13992
|
+
var DEFAULT_MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
13993
|
+
async function runSshCommand(options) {
|
|
13994
|
+
const {
|
|
13995
|
+
host,
|
|
13996
|
+
port = DEFAULT_PORT,
|
|
13997
|
+
command,
|
|
13998
|
+
auth,
|
|
13999
|
+
transport: transportOptions,
|
|
14000
|
+
connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS,
|
|
14001
|
+
maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES
|
|
14002
|
+
} = options;
|
|
14003
|
+
const socket = await openTcpSocket(host, port, connectTimeoutMs);
|
|
14004
|
+
const transport = new SshTransportConnection({
|
|
14005
|
+
handshakeTimeoutMs: DEFAULT_HANDSHAKE_TIMEOUT_MS,
|
|
14006
|
+
...transportOptions
|
|
14007
|
+
});
|
|
14008
|
+
try {
|
|
14009
|
+
const handshake = await transport.connect(socket);
|
|
14010
|
+
const authSession = new SshAuthSession(transport);
|
|
14011
|
+
await authSession.authenticate({
|
|
14012
|
+
credential: auth,
|
|
14013
|
+
sessionId: handshake.keyExchange.sessionId
|
|
14014
|
+
});
|
|
14015
|
+
const conn = new SshConnectionManager(transport);
|
|
14016
|
+
const channel = await conn.openExecChannel(command);
|
|
14017
|
+
const pump = conn.start();
|
|
14018
|
+
pump.catch(() => {
|
|
14019
|
+
});
|
|
14020
|
+
const chunks = [];
|
|
14021
|
+
let bytesReceived = 0;
|
|
14022
|
+
try {
|
|
14023
|
+
for await (const chunk of channel.receiveData()) {
|
|
14024
|
+
bytesReceived += chunk.length;
|
|
14025
|
+
if (bytesReceived > maxOutputBytes) {
|
|
14026
|
+
throw new Error(
|
|
14027
|
+
`runSshCommand: stdout exceeded ${maxOutputBytes} bytes (set maxOutputBytes to allow more)`
|
|
14028
|
+
);
|
|
14029
|
+
}
|
|
14030
|
+
chunks.push(chunk);
|
|
14031
|
+
}
|
|
14032
|
+
} finally {
|
|
14033
|
+
channel.close();
|
|
14034
|
+
}
|
|
14035
|
+
const stdout = Buffer.concat(chunks);
|
|
14036
|
+
return {
|
|
14037
|
+
stdout,
|
|
14038
|
+
stdoutText: stdout.toString("utf8"),
|
|
14039
|
+
bytesReceived
|
|
14040
|
+
};
|
|
14041
|
+
} finally {
|
|
14042
|
+
transport.disconnect();
|
|
14043
|
+
}
|
|
14044
|
+
}
|
|
14045
|
+
function openTcpSocket(host, port, timeoutMs) {
|
|
14046
|
+
return new Promise((resolve, reject) => {
|
|
14047
|
+
const socket = (0, import_node_net3.connect)({ host, port });
|
|
14048
|
+
const timer = setTimeout(() => {
|
|
14049
|
+
socket.destroy();
|
|
14050
|
+
reject(
|
|
14051
|
+
new Error(`runSshCommand: TCP connect to ${host}:${port} timed out after ${timeoutMs}ms`)
|
|
14052
|
+
);
|
|
14053
|
+
}, timeoutMs);
|
|
14054
|
+
socket.once("connect", () => {
|
|
14055
|
+
clearTimeout(timer);
|
|
14056
|
+
resolve(socket);
|
|
14057
|
+
});
|
|
14058
|
+
socket.once("error", (error) => {
|
|
14059
|
+
clearTimeout(timer);
|
|
14060
|
+
reject(error);
|
|
14061
|
+
});
|
|
14062
|
+
});
|
|
14063
|
+
}
|
|
14064
|
+
|
|
13402
14065
|
// src/transfers/TransferPlan.ts
|
|
13403
14066
|
function createTransferPlan(input) {
|
|
13404
14067
|
const plan = {
|
|
@@ -14856,9 +15519,9 @@ function deepFreeze(value) {
|
|
|
14856
15519
|
}
|
|
14857
15520
|
|
|
14858
15521
|
// src/mft/webhooks.ts
|
|
14859
|
-
var
|
|
15522
|
+
var import_node_crypto14 = require("crypto");
|
|
14860
15523
|
function signWebhookPayload(payload, secret, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
14861
|
-
const mac = (0,
|
|
15524
|
+
const mac = (0, import_node_crypto14.createHmac)("sha256", secret);
|
|
14862
15525
|
mac.update(`${timestamp}.${payload}`);
|
|
14863
15526
|
return { digest: mac.digest("hex"), timestamp };
|
|
14864
15527
|
}
|
|
@@ -15437,6 +16100,19 @@ var defaultRunner = ({ client, route, signal }) => {
|
|
|
15437
16100
|
const options = { client, route, signal };
|
|
15438
16101
|
return runRoute(options);
|
|
15439
16102
|
};
|
|
16103
|
+
|
|
16104
|
+
// src/utils/mainModule.ts
|
|
16105
|
+
var import_node_url = require("url");
|
|
16106
|
+
function isMainModule(importMetaUrl) {
|
|
16107
|
+
if (typeof process === "undefined" || !process.argv || process.argv.length < 2) {
|
|
16108
|
+
return false;
|
|
16109
|
+
}
|
|
16110
|
+
try {
|
|
16111
|
+
return process.argv[1] === (0, import_node_url.fileURLToPath)(importMetaUrl);
|
|
16112
|
+
} catch {
|
|
16113
|
+
return false;
|
|
16114
|
+
}
|
|
16115
|
+
}
|
|
15440
16116
|
// Annotate the CommonJS export names for ESM import in node:
|
|
15441
16117
|
0 && (module.exports = {
|
|
15442
16118
|
AbortError,
|
|
@@ -15537,6 +16213,7 @@ var defaultRunner = ({ client, route, signal }) => {
|
|
|
15537
16213
|
inboxFailedPath,
|
|
15538
16214
|
inboxProcessedPath,
|
|
15539
16215
|
isClassicProviderId,
|
|
16216
|
+
isMainModule,
|
|
15540
16217
|
isSensitiveKey,
|
|
15541
16218
|
joinRemotePath,
|
|
15542
16219
|
matchKnownHosts,
|
|
@@ -15569,6 +16246,7 @@ var defaultRunner = ({ client, route, signal }) => {
|
|
|
15569
16246
|
resolveSecret,
|
|
15570
16247
|
runConnectionDiagnostics,
|
|
15571
16248
|
runRoute,
|
|
16249
|
+
runSshCommand,
|
|
15572
16250
|
serializeRemoteManifest,
|
|
15573
16251
|
signWebhookPayload,
|
|
15574
16252
|
sortRemoteEntries,
|