@skydiveai/git-cache 0.1.0-beta.2982 → 0.1.0-beta.3057

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/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { f as startServer } from "./server-Cu_B_yai.mjs";
2
+ import { f as startServer } from "./server-DjJs3KcN.mjs";
3
3
  //#region src/cli.ts
4
4
  /**
5
5
  * `git-cache` CLI — the published binary for @skydiveai/git-cache.
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as isProtocolV2Request, c as parseAdvertisement, d as shouldBackfillAfterPassthrough, f as startServer, i as explainRejection, l as parseUpstream, n as classifyUploadRequest, o as metricsSnapshot, r as copyRequestHeaders, s as mirrorHasObjects, t as SidebandPackExtractor, u as recordColdBuildOutcome } from "./server-Cu_B_yai.mjs";
1
+ import { a as isProtocolV2Request, c as parseAdvertisement, d as shouldBackfillAfterPassthrough, f as startServer, i as explainRejection, l as parseUpstream, n as classifyUploadRequest, o as metricsSnapshot, r as copyRequestHeaders, s as mirrorHasObjects, t as SidebandPackExtractor, u as recordColdBuildOutcome } from "./server-DjJs3KcN.mjs";
2
2
  export { SidebandPackExtractor, classifyUploadRequest, copyRequestHeaders, explainRejection, isProtocolV2Request, metricsSnapshot, mirrorHasObjects, parseAdvertisement, parseUpstream, recordColdBuildOutcome, shouldBackfillAfterPassthrough, startServer };
@@ -78,6 +78,7 @@ const config = {
78
78
  maxPackBytes: Number(process.env.GIT_CACHE_MAX_PACK_BYTES || 8 * 1024 * 1024 * 1024),
79
79
  maxImporterQueueBytes: Number(process.env.GIT_CACHE_MAX_IMPORTER_QUEUE_BYTES || 256 * 1024 * 1024),
80
80
  maxCacheBytes: Number(process.env.GIT_CACHE_MAX_CACHE_BYTES || 0),
81
+ upstreamTimeoutMs: Number(process.env.GIT_CACHE_UPSTREAM_TIMEOUT_MS || 3e4),
81
82
  hostAllowlist: (process.env.GIT_CACHE_HOST_ALLOWLIST || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean),
82
83
  logLevel: process.env.GIT_CACHE_LOG_LEVEL || "info",
83
84
  testOriginBase: (process.env.GIT_CACHE_TEST_ORIGIN_BASE || "").replace(/\/+$/, "")
@@ -94,7 +95,9 @@ const GIT_ENV = {
94
95
  GIT_ASKPASS: "/bin/true",
95
96
  SSH_ASKPASS: "/bin/true",
96
97
  GCM_INTERACTIVE: "never",
97
- GIT_CONFIG_NOSYSTEM: process.env.GIT_CONFIG_NOSYSTEM || "1"
98
+ GIT_CONFIG_NOSYSTEM: process.env.GIT_CONFIG_NOSYSTEM || "1",
99
+ GIT_HTTP_LOW_SPEED_LIMIT: "1024",
100
+ GIT_HTTP_LOW_SPEED_TIME: "60"
98
101
  };
99
102
  const activeLevel = LOG_LEVELS[config.logLevel] ?? LOG_LEVELS.info;
100
103
  function log(level, event, fields = {}) {
@@ -676,12 +679,35 @@ function withCredential(headers, credHeader) {
676
679
  function requestUpstream(target, options, callback) {
677
680
  return (target.protocol === "http:" ? http : https).request(target, options, callback);
678
681
  }
682
+ /**
683
+ * Arm the idle-socket ceiling on an upstream request. Fires only when the
684
+ * socket is SILENT for config.upstreamTimeoutMs (any activity resets it), so a
685
+ * healthy transfer — even a slow one — is never interrupted. Must be armed on
686
+ * EVERY upstream request: a hung upstream surfaces as a fast error to the
687
+ * caller instead of a client `git fetch` that never returns.
688
+ */
689
+ function armUpstreamIdleTimeout(up, onTimeout) {
690
+ up.setTimeout(config.upstreamTimeoutMs, () => {
691
+ log("error", "upstream_idle_timeout", { ms: config.upstreamTimeoutMs });
692
+ onTimeout();
693
+ });
694
+ }
679
695
  function openUpstream(req, upstreamUrl, operation, callback, credHeader) {
680
- const up = requestUpstream(new URL(upstreamUrl + (operation === "info/refs" ? "/info/refs?service=git-upload-pack" : "/git-upload-pack")), {
696
+ const target = new URL(upstreamUrl + (operation === "info/refs" ? "/info/refs?service=git-upload-pack" : "/git-upload-pack"));
697
+ let settled = false;
698
+ const settle = (resOrNull, error) => {
699
+ if (settled) return;
700
+ settled = true;
701
+ callback(resOrNull, error);
702
+ };
703
+ const up = requestUpstream(target, {
681
704
  method: req.method,
682
705
  headers: withCredential(copyRequestHeaders(req.headers), credHeader)
683
- }, (res) => callback(res, null));
684
- up.on("error", (error) => callback(null, error));
706
+ }, (res) => settle(res, null));
707
+ up.on("error", (error) => settle(null, error));
708
+ armUpstreamIdleTimeout(up, () => {
709
+ up.destroy(/* @__PURE__ */ new Error(`upstream idle for ${config.upstreamTimeoutMs}ms`));
710
+ });
685
711
  if (req.method === "POST") req.pipe(up);
686
712
  else up.end();
687
713
  return up;
@@ -1269,6 +1295,13 @@ function startColdStream(req, res, upstreamUrl, repo, requestBody, credHeader) {
1269
1295
  });
1270
1296
  });
1271
1297
  up.on("error", (error) => settle.reject(error));
1298
+ armUpstreamIdleTimeout(up, () => {
1299
+ if (!res.headersSent) {
1300
+ res.writeHead(502, { "Content-Type": "text/plain" });
1301
+ res.end("unable to serve repository\n");
1302
+ }
1303
+ up.destroy(/* @__PURE__ */ new Error(`upstream idle for ${config.upstreamTimeoutMs}ms`));
1304
+ });
1272
1305
  up.end(requestBody);
1273
1306
  })().catch(settle.reject);
1274
1307
  done.catch(async (error) => {
@@ -1488,6 +1521,9 @@ function proxyUploadPackWithBody(res, upstreamUrl, reqHeaders, body, credHeader)
1488
1521
  if (!res.headersSent) res.writeHead(502);
1489
1522
  res.end();
1490
1523
  });
1524
+ armUpstreamIdleTimeout(up, () => {
1525
+ up.destroy(/* @__PURE__ */ new Error(`upstream idle for ${config.upstreamTimeoutMs}ms`));
1526
+ });
1491
1527
  up.end(body);
1492
1528
  }
1493
1529
  function serveUploadPack(req, res, local, advertise, cacheState, body = null) {
@@ -1816,7 +1852,7 @@ async function handle(req, res) {
1816
1852
  op: up.operation,
1817
1853
  state: warm.cacheState
1818
1854
  });
1819
- return proxyInfoRefs(req, res, up.repo, up.upstreamUrl, host, clientCred);
1855
+ return await proxyInfoRefs(req, res, up.repo, up.upstreamUrl, host, clientCred);
1820
1856
  }
1821
1857
  if (up.operation === "git-upload-pack" && !await mirrorHasObjects(warm.local, uploadWants)) {
1822
1858
  await refreshMirror(up.repo, warm.local, clientCred).catch((error) => {
@@ -1840,7 +1876,7 @@ async function handle(req, res) {
1840
1876
  });
1841
1877
  return serveUploadPack(req, res, warm.local, up.operation === "info/refs", warm.cacheState, uploadBody);
1842
1878
  }
1843
- if (up.operation === "info/refs") return proxyInfoRefs(req, res, up.repo, up.upstreamUrl, host, clientCred);
1879
+ if (up.operation === "info/refs") return await proxyInfoRefs(req, res, up.repo, up.upstreamUrl, host, clientCred);
1844
1880
  const body = uploadBody ?? Buffer.alloc(0);
1845
1881
  const credHeader = clientCred ?? await fetchCredentialHeader(host);
1846
1882
  if (coldLeaders.has(up.repo)) return proxyColdFollowerWithBody(res, up.upstreamUrl, req.headers, body, up.repo, credHeader);
@@ -1868,6 +1904,9 @@ function proxyColdFollowerWithBody(res, upstreamUrl, reqHeaders, body, repo, cre
1868
1904
  if (!res.headersSent) res.writeHead(502);
1869
1905
  res.end();
1870
1906
  });
1907
+ armUpstreamIdleTimeout(up, () => {
1908
+ up.destroy(/* @__PURE__ */ new Error(`upstream idle for ${config.upstreamTimeoutMs}ms`));
1909
+ });
1871
1910
  up.end(body);
1872
1911
  }
1873
1912
  async function startServer(options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skydiveai/git-cache",
3
- "version": "0.1.0-beta.2982",
3
+ "version": "0.1.0-beta.3057",
4
4
  "description": "Sandbox-local Git smart-HTTP clone/fetch cache: a loopback daemon that transparently mirrors HTTPS git reads so repeated clone/fetch/ls-remote skip the origin round-trip. Runs inside an agent sandbox; pushes stay direct.",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",