hillclimb 0.9.6 → 0.9.8
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/main.js +139 -24
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -115,6 +115,7 @@ function formatError(err) {
|
|
|
115
115
|
import fs2 from "fs";
|
|
116
116
|
import http from "http";
|
|
117
117
|
import https from "https";
|
|
118
|
+
import net from "net";
|
|
118
119
|
var DEFAULT_SOCKET_IDLE_TIMEOUT_MS = 12e4;
|
|
119
120
|
var DEFAULT_DEADLINE_MS = 60 * 6e4;
|
|
120
121
|
var MAX_RESPONSE_BODY_BYTES = 64 * 1024;
|
|
@@ -129,18 +130,33 @@ async function putToPresignedUrl(url, headers, body, options) {
|
|
|
129
130
|
let inputStream;
|
|
130
131
|
let progressTimer;
|
|
131
132
|
let phase = "connecting";
|
|
133
|
+
let requestFinished = false;
|
|
134
|
+
const seen = {};
|
|
135
|
+
const snapshot = () => ({
|
|
136
|
+
elapsedMs: Date.now() - start,
|
|
137
|
+
phase,
|
|
138
|
+
requestFinished,
|
|
139
|
+
totalBodyBytes: contentLength,
|
|
140
|
+
...inputStream ? { bodyBytesRead: inputStream.bytesRead } : {},
|
|
141
|
+
...seen,
|
|
142
|
+
// Bun fills the peer address in only after 'connect'.
|
|
143
|
+
remoteAddress: seen.remoteAddress ?? req.socket?.remoteAddress
|
|
144
|
+
});
|
|
132
145
|
let settled = false;
|
|
133
146
|
const settleResolve = (result) => {
|
|
134
147
|
if (settled) return;
|
|
135
148
|
settled = true;
|
|
149
|
+
const attempt = snapshot();
|
|
136
150
|
clearTimeout(deadlineTimer);
|
|
137
151
|
clearInterval(progressTimer);
|
|
138
152
|
inputStream?.destroy();
|
|
139
|
-
resolve(result);
|
|
153
|
+
resolve({ ...result, attempt });
|
|
140
154
|
};
|
|
141
155
|
const settleReject = (err) => {
|
|
142
156
|
if (settled) return;
|
|
143
157
|
settled = true;
|
|
158
|
+
err.message = redactLocalAddress(err.message);
|
|
159
|
+
err.putAttempt = snapshot();
|
|
144
160
|
clearTimeout(deadlineTimer);
|
|
145
161
|
clearInterval(progressTimer);
|
|
146
162
|
inputStream?.destroy();
|
|
@@ -158,6 +174,13 @@ async function putToPresignedUrl(url, headers, body, options) {
|
|
|
158
174
|
},
|
|
159
175
|
(res) => {
|
|
160
176
|
phase = "receiving-response";
|
|
177
|
+
seen.status = res.statusCode;
|
|
178
|
+
const header2 = (name) => {
|
|
179
|
+
const value = res.headers[name];
|
|
180
|
+
return Array.isArray(value) ? value[0] : value;
|
|
181
|
+
};
|
|
182
|
+
seen.requestId = header2("x-amz-request-id");
|
|
183
|
+
seen.hostId = header2("x-amz-id-2");
|
|
161
184
|
const chunks = [];
|
|
162
185
|
let buffered = 0;
|
|
163
186
|
res.on("data", (chunk) => {
|
|
@@ -191,11 +214,32 @@ async function putToPresignedUrl(url, headers, body, options) {
|
|
|
191
214
|
const connected = () => {
|
|
192
215
|
if (phase === "connecting") phase = "sending";
|
|
193
216
|
};
|
|
194
|
-
|
|
195
|
-
|
|
217
|
+
const tcpConnected = () => {
|
|
218
|
+
seen.connectMs = Date.now() - start;
|
|
219
|
+
seen.remoteAddress = socket.remoteAddress;
|
|
220
|
+
seen.localNet = addressClass(socket.localAddress);
|
|
221
|
+
};
|
|
222
|
+
if (!socket.connecting) tcpConnected();
|
|
223
|
+
else socket.once("connect", tcpConnected);
|
|
224
|
+
if (parsed.protocol === "https:") {
|
|
225
|
+
socket.once("secureConnect", () => {
|
|
226
|
+
seen.tlsMs = Date.now() - start;
|
|
227
|
+
const secure = socket;
|
|
228
|
+
try {
|
|
229
|
+
seen.tlsProtocol = secure.getProtocol?.() ?? void 0;
|
|
230
|
+
const issuer = secure.getPeerCertificate?.()?.issuer;
|
|
231
|
+
const name = [issuer?.O, issuer?.CN].filter(Boolean).join("/");
|
|
232
|
+
if (name) seen.tlsIssuer = name;
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
connected();
|
|
236
|
+
});
|
|
237
|
+
} else if (!socket.connecting) connected();
|
|
196
238
|
else socket.once("connect", connected);
|
|
197
239
|
});
|
|
198
240
|
req.on("finish", () => {
|
|
241
|
+
if (req.socket?.errored) return;
|
|
242
|
+
requestFinished = true;
|
|
199
243
|
if (phase !== "receiving-response") phase = "awaiting-response";
|
|
200
244
|
});
|
|
201
245
|
if (options?.onProgress) {
|
|
@@ -204,7 +248,8 @@ async function putToPresignedUrl(url, headers, body, options) {
|
|
|
204
248
|
elapsedMs: Date.now() - start,
|
|
205
249
|
phase,
|
|
206
250
|
socketBytesWritten: req.socket?.bytesWritten ?? 0,
|
|
207
|
-
totalBodyBytes: contentLength
|
|
251
|
+
totalBodyBytes: contentLength,
|
|
252
|
+
requestFinished
|
|
208
253
|
});
|
|
209
254
|
}, options.progressIntervalMs ?? 3e4);
|
|
210
255
|
progressTimer.unref();
|
|
@@ -229,10 +274,36 @@ async function putToPresignedUrl(url, headers, body, options) {
|
|
|
229
274
|
}
|
|
230
275
|
});
|
|
231
276
|
}
|
|
277
|
+
function addressClass(address) {
|
|
278
|
+
if (!address) return void 0;
|
|
279
|
+
const ip = address.startsWith("::ffff:") ? address.slice(7) : address;
|
|
280
|
+
if (net.isIPv4(ip)) {
|
|
281
|
+
const [a, b] = ip.split(".").map(Number);
|
|
282
|
+
if (a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168)
|
|
283
|
+
return "private";
|
|
284
|
+
if (a === 100 && b >= 64 && b <= 127) return "cgnat";
|
|
285
|
+
if (a === 169 && b === 254) return "link-local";
|
|
286
|
+
if (a === 127) return "loopback";
|
|
287
|
+
return "public";
|
|
288
|
+
}
|
|
289
|
+
const lower = ip.toLowerCase();
|
|
290
|
+
if (lower === "::1") return "loopback";
|
|
291
|
+
if (/^fe[89ab]/.test(lower)) return "link-local";
|
|
292
|
+
if (/^f[cd]/.test(lower)) return "private";
|
|
293
|
+
return "public";
|
|
294
|
+
}
|
|
295
|
+
function redactLocalAddress(message) {
|
|
296
|
+
return message.replace(/ - Local \([^)]*\)/g, "");
|
|
297
|
+
}
|
|
232
298
|
|
|
233
299
|
// src/platform/retry.ts
|
|
234
300
|
var MAX_ATTEMPTS = 3;
|
|
235
|
-
var
|
|
301
|
+
var LARGE_PUT_BYTES = 8 * 1024 * 1024;
|
|
302
|
+
var LARGE_PUT_MAX_ATTEMPTS = 5;
|
|
303
|
+
function putMaxAttempts(bodyBytes) {
|
|
304
|
+
return bodyBytes >= LARGE_PUT_BYTES ? LARGE_PUT_MAX_ATTEMPTS : MAX_ATTEMPTS;
|
|
305
|
+
}
|
|
306
|
+
var TRANSIENT_DELAYS_MS = [1e3, 3e3, 1e4, 3e4];
|
|
236
307
|
var RATE_LIMIT_DELAYS_MS = [5e3, 15e3];
|
|
237
308
|
var MAX_RETRY_AFTER_MS = 3e4;
|
|
238
309
|
var CONNECT_FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
@@ -301,7 +372,7 @@ function apiRetryDelayMs(input) {
|
|
|
301
372
|
return null;
|
|
302
373
|
}
|
|
303
374
|
function putRetryDelayMs(input) {
|
|
304
|
-
if (input.attempt >= MAX_ATTEMPTS) return null;
|
|
375
|
+
if (input.attempt >= (input.maxAttempts ?? MAX_ATTEMPTS)) return null;
|
|
305
376
|
const delayIndex = input.attempt - 1;
|
|
306
377
|
if (input.status !== void 0) {
|
|
307
378
|
if (input.status === 429 || input.status === 500 || input.status === 502 || input.status === 503 || input.status === 504) {
|
|
@@ -599,6 +670,7 @@ var PlatformClient = class {
|
|
|
599
670
|
const context = {
|
|
600
671
|
contributionId,
|
|
601
672
|
uploadId: target2.upload.id,
|
|
673
|
+
filename: input.originalFilename,
|
|
602
674
|
requireVerificationOnConflict: true
|
|
603
675
|
};
|
|
604
676
|
try {
|
|
@@ -790,6 +862,12 @@ var PlatformClient = class {
|
|
|
790
862
|
}
|
|
791
863
|
})();
|
|
792
864
|
const sizeBytes = body.kind === "buffer" ? body.buffer.byteLength : body.sizeBytes;
|
|
865
|
+
const maxAttempts = putMaxAttempts(sizeBytes);
|
|
866
|
+
const label = [
|
|
867
|
+
refresh?.filename && `file=${refresh.filename}`,
|
|
868
|
+
refresh?.uploadId && `upload=${refresh.uploadId}`,
|
|
869
|
+
`size=${sizeBytes}`
|
|
870
|
+
].filter(Boolean).join(" ");
|
|
793
871
|
let hadNetworkError = false;
|
|
794
872
|
let refreshed = false;
|
|
795
873
|
for (let attempt = 1; ; attempt++) {
|
|
@@ -799,52 +877,56 @@ var PlatformClient = class {
|
|
|
799
877
|
res = await putToPresignedUrl(presignedUrl, headers, body, {
|
|
800
878
|
onProgress: (progress) => appendLog(
|
|
801
879
|
"info",
|
|
802
|
-
`[PUT ${host}] progress (attempt=${attempt}, phase=${progress.phase}, elapsedMs=${progress.elapsedMs}, socketBytesWritten=${progress.socketBytesWritten}, totalBodyBytes=${progress.totalBodyBytes})`
|
|
880
|
+
`[PUT ${host}] progress (attempt=${attempt}, phase=${progress.phase}, elapsedMs=${progress.elapsedMs}, requestFinished=${progress.requestFinished}, socketBytesWritten=${progress.socketBytesWritten}, totalBodyBytes=${progress.totalBodyBytes}) | ${label}`
|
|
803
881
|
)
|
|
804
882
|
});
|
|
805
883
|
} catch (err) {
|
|
806
884
|
hadNetworkError = true;
|
|
885
|
+
const diagnostics2 = `| ${label} ${formatPutAttempt(err.putAttempt)}`;
|
|
807
886
|
const delayMs2 = putRetryDelayMs({
|
|
808
887
|
attempt,
|
|
809
|
-
networkErrorCode: networkErrorCode(err)
|
|
888
|
+
networkErrorCode: networkErrorCode(err),
|
|
889
|
+
maxAttempts
|
|
810
890
|
});
|
|
811
891
|
if (delayMs2 !== null) {
|
|
812
892
|
appendLog(
|
|
813
893
|
"warn",
|
|
814
|
-
`[PUT ${host}] network error, retrying in ${delayMs2}ms (attempt ${attempt}/${
|
|
894
|
+
`[PUT ${host}] network error, retrying in ${delayMs2}ms (attempt ${attempt}/${maxAttempts}) ${formatError(err)} ${diagnostics2}`
|
|
815
895
|
);
|
|
816
896
|
await sleep(delayMs2);
|
|
817
897
|
continue;
|
|
818
898
|
}
|
|
819
899
|
appendLog(
|
|
820
900
|
"error",
|
|
821
|
-
`[PUT ${host}] FAILED ${Date.now() - start}ms ${formatError(err)}`
|
|
901
|
+
`[PUT ${host}] FAILED ${Date.now() - start}ms ${formatError(err)} (attempt ${attempt}/${maxAttempts}) ${diagnostics2}`
|
|
822
902
|
);
|
|
823
903
|
throw err;
|
|
824
904
|
}
|
|
825
905
|
const elapsedMs = Date.now() - start;
|
|
906
|
+
const diagnostics = `| ${label} ${formatPutAttempt(res.attempt)}`;
|
|
826
907
|
if (res.ok) {
|
|
827
908
|
appendLog(
|
|
828
909
|
"info",
|
|
829
|
-
`[PUT ${host}] ${res.status} ${elapsedMs}ms (${sizeBytes} bytes)`
|
|
910
|
+
`[PUT ${host}] ${res.status} ${elapsedMs}ms (${sizeBytes} bytes) (attempt ${attempt}/${maxAttempts}) | ${label}${sizeBytes >= LARGE_PUT_BYTES ? ` ${formatPutAttempt(res.attempt)}` : ""}`
|
|
830
911
|
);
|
|
831
912
|
return;
|
|
832
913
|
}
|
|
833
914
|
if (res.status === 412 && hadNetworkError && !refresh?.requireVerificationOnConflict) {
|
|
834
915
|
appendLog(
|
|
835
916
|
"info",
|
|
836
|
-
`[PUT ${host}] 412 after retry \u2014 object already stored (${sizeBytes} bytes)`
|
|
917
|
+
`[PUT ${host}] 412 after retry \u2014 object already stored (${sizeBytes} bytes) ${diagnostics}`
|
|
837
918
|
);
|
|
838
919
|
return;
|
|
839
920
|
}
|
|
840
921
|
const code = /<Code>\s*([A-Za-z][A-Za-z0-9]*)\s*<\/Code>/.exec(
|
|
841
922
|
res.bodyText
|
|
842
923
|
)?.[1];
|
|
843
|
-
|
|
924
|
+
const urlExpired = res.status === 403 && code === "AccessDenied" && /<Message>\s*Request has expired\s*<\/Message>/.test(res.bodyText);
|
|
925
|
+
if ((res.status === 400 || res.status === 403) && (code === "ExpiredToken" || urlExpired) && refresh && attempt < maxAttempts && !refreshed) {
|
|
844
926
|
refreshed = true;
|
|
845
927
|
appendLog(
|
|
846
928
|
"warn",
|
|
847
|
-
`[PUT ${host}] ${res.status} ${elapsedMs}ms (code
|
|
929
|
+
`[PUT ${host}] ${res.status} ${elapsedMs}ms (code=${code}${urlExpired ? ", request expired" : ""}); refreshing AWS upload URL once for the existing object ${diagnostics}`
|
|
848
930
|
);
|
|
849
931
|
const renewed = await this.request(
|
|
850
932
|
"POST",
|
|
@@ -864,19 +946,23 @@ var PlatformClient = class {
|
|
|
864
946
|
headers = renewed.headers;
|
|
865
947
|
continue;
|
|
866
948
|
}
|
|
867
|
-
const delayMs = putRetryDelayMs({
|
|
949
|
+
const delayMs = putRetryDelayMs({
|
|
950
|
+
attempt,
|
|
951
|
+
status: res.status,
|
|
952
|
+
maxAttempts
|
|
953
|
+
});
|
|
868
954
|
if (delayMs !== null) {
|
|
869
955
|
appendLog(
|
|
870
956
|
"warn",
|
|
871
|
-
`[PUT ${host}] ${res.status} ${elapsedMs}ms, retrying in ${delayMs}ms (attempt ${attempt}/${
|
|
957
|
+
`[PUT ${host}] ${res.status} ${elapsedMs}ms${code ? ` (code=${code})` : ""}, retrying in ${delayMs}ms (attempt ${attempt}/${maxAttempts}) ${diagnostics}`
|
|
872
958
|
);
|
|
873
959
|
await sleep(delayMs);
|
|
874
960
|
continue;
|
|
875
961
|
}
|
|
876
|
-
const detail = code ? `(code=${code})` : res.bodyText;
|
|
962
|
+
const detail = code ? `(code=${code})` : res.bodyText ? `(${Buffer.byteLength(res.bodyText)}-byte body without an S3 error code)` : "";
|
|
877
963
|
appendLog(
|
|
878
964
|
"warn",
|
|
879
|
-
`[PUT ${host}] ${res.status} ${elapsedMs}ms${code ? ` (code=${code})` : ""}`
|
|
965
|
+
`[PUT ${host}] ${res.status} ${elapsedMs}ms${code ? ` (code=${code})` : ""} (attempt ${attempt}/${maxAttempts}) ${diagnostics}`
|
|
880
966
|
);
|
|
881
967
|
throw new PlatformError(
|
|
882
968
|
`PUT to presigned URL failed: HTTP ${res.status}${detail ? ` ${detail}` : ""}`,
|
|
@@ -906,6 +992,31 @@ var PlatformClient = class {
|
|
|
906
992
|
);
|
|
907
993
|
}
|
|
908
994
|
};
|
|
995
|
+
function formatPutAttempt(attempt) {
|
|
996
|
+
const fields = [];
|
|
997
|
+
if (attempt) {
|
|
998
|
+
fields.push(
|
|
999
|
+
`elapsedMs=${attempt.elapsedMs}`,
|
|
1000
|
+
`phase=${attempt.phase}`,
|
|
1001
|
+
`requestFinished=${attempt.requestFinished}`
|
|
1002
|
+
);
|
|
1003
|
+
if (attempt.bodyBytesRead !== void 0)
|
|
1004
|
+
fields.push(`bodyBytesRead=${attempt.bodyBytesRead}`);
|
|
1005
|
+
if (attempt.connectMs !== void 0)
|
|
1006
|
+
fields.push(`connectMs=${attempt.connectMs}`);
|
|
1007
|
+
if (attempt.tlsMs !== void 0) fields.push(`tlsMs=${attempt.tlsMs}`);
|
|
1008
|
+
if (attempt.remoteAddress) fields.push(`remote=${attempt.remoteAddress}`);
|
|
1009
|
+
if (attempt.localNet) fields.push(`localNet=${attempt.localNet}`);
|
|
1010
|
+
if (attempt.tlsProtocol) fields.push(`tls=${attempt.tlsProtocol}`);
|
|
1011
|
+
if (attempt.tlsIssuer)
|
|
1012
|
+
fields.push(`issuer=${JSON.stringify(attempt.tlsIssuer)}`);
|
|
1013
|
+
if (attempt.status !== void 0) fields.push(`status=${attempt.status}`);
|
|
1014
|
+
if (attempt.requestId) fields.push(`requestId=${attempt.requestId}`);
|
|
1015
|
+
if (attempt.hostId) fields.push(`hostId=${attempt.hostId}`);
|
|
1016
|
+
}
|
|
1017
|
+
fields.push(`node=${process.version}`, `platform=${process.platform}`);
|
|
1018
|
+
return fields.join(" ");
|
|
1019
|
+
}
|
|
909
1020
|
|
|
910
1021
|
// src/platform/hooks.ts
|
|
911
1022
|
import { execFileSync } from "child_process";
|
|
@@ -3729,7 +3840,7 @@ var DEFAULT_STATE_DIR = path9.join(
|
|
|
3729
3840
|
"agent-uploads"
|
|
3730
3841
|
);
|
|
3731
3842
|
var DEFAULT_LOCK_WAIT_MS = 5 * 60 * 1e3;
|
|
3732
|
-
var DEFAULT_LIVE_OWNER_MAX_WAIT_MS =
|
|
3843
|
+
var DEFAULT_LIVE_OWNER_MAX_WAIT_MS = 6 * 60 * 60 * 1e3;
|
|
3733
3844
|
var DEFAULT_LOCK_RETRY_DELAY_MS = 500;
|
|
3734
3845
|
var DEFAULT_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3735
3846
|
var STALE_LOCK_TTL_MS = DEFAULT_LIVE_OWNER_MAX_WAIT_MS;
|
|
@@ -4376,7 +4487,7 @@ async function persistPreparedFile(directory, filename, source) {
|
|
|
4376
4487
|
const file = path10.join(directory, filename);
|
|
4377
4488
|
await fs11.copyFile(source, `${file}.tmp`);
|
|
4378
4489
|
await fs11.chmod(`${file}.tmp`, 384);
|
|
4379
|
-
const handle = await fs11.open(`${file}.tmp`, "r");
|
|
4490
|
+
const handle = await fs11.open(`${file}.tmp`, "r+");
|
|
4380
4491
|
try {
|
|
4381
4492
|
await handle.sync();
|
|
4382
4493
|
} finally {
|
|
@@ -14356,7 +14467,11 @@ var PlatformUploadOutput = class {
|
|
|
14356
14467
|
presigned.headers,
|
|
14357
14468
|
filePath,
|
|
14358
14469
|
sizeBytes,
|
|
14359
|
-
{
|
|
14470
|
+
{
|
|
14471
|
+
contributionId,
|
|
14472
|
+
uploadId: presigned.upload.id,
|
|
14473
|
+
filename: zipFilename
|
|
14474
|
+
}
|
|
14360
14475
|
);
|
|
14361
14476
|
appendLog("info", `PUT to presigned URL succeeded for ${zipFilename}`);
|
|
14362
14477
|
}
|
|
@@ -17850,13 +17965,13 @@ async function uploadArtifact(client, contributionId, filename, mimeType, buffer
|
|
|
17850
17965
|
presigned.presignedUrl,
|
|
17851
17966
|
presigned.headers,
|
|
17852
17967
|
buffer,
|
|
17853
|
-
{ contributionId, uploadId: presigned.upload.id }
|
|
17968
|
+
{ contributionId, uploadId: presigned.upload.id, filename }
|
|
17854
17969
|
);
|
|
17855
17970
|
appendLog("info", `PUT to presigned URL succeeded for ${filename}`);
|
|
17856
17971
|
}
|
|
17857
17972
|
|
|
17858
17973
|
// package.json
|
|
17859
|
-
var version = "0.9.
|
|
17974
|
+
var version = "0.9.8";
|
|
17860
17975
|
|
|
17861
17976
|
// src/version.ts
|
|
17862
17977
|
var CLI_VERSION = version;
|
|
@@ -19631,7 +19746,7 @@ async function uploadFile(client, contributionId, filename, mimeType, buffer) {
|
|
|
19631
19746
|
presigned.presignedUrl,
|
|
19632
19747
|
presigned.headers,
|
|
19633
19748
|
buffer,
|
|
19634
|
-
{ contributionId, uploadId: presigned.upload.id }
|
|
19749
|
+
{ contributionId, uploadId: presigned.upload.id, filename }
|
|
19635
19750
|
);
|
|
19636
19751
|
appendLog(
|
|
19637
19752
|
"info",
|