maven-proxy 1.1.1 → 1.2.1

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.
@@ -0,0 +1,396 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ function toPositiveInt(value, fallback) {
5
+ const parsed = Number.parseInt(value, 10);
6
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
7
+ }
8
+
9
+ function toBool(value, fallback) {
10
+ if (value == null || value === "") {
11
+ return fallback;
12
+ }
13
+
14
+ return ["1", "true", "yes", "on"].includes(String(value).toLowerCase());
15
+ }
16
+
17
+ function nowMs() {
18
+ return Date.now();
19
+ }
20
+
21
+ function readJsonFile(filePath, fallback) {
22
+ if (!fs.existsSync(filePath)) {
23
+ return fallback;
24
+ }
25
+
26
+ try {
27
+ const text = fs.readFileSync(filePath, "utf8");
28
+ return JSON.parse(text);
29
+ } catch {
30
+ return fallback;
31
+ }
32
+ }
33
+
34
+ function serializeSnapshot(positiveMap, negativeMap, conflictMap) {
35
+ return {
36
+ version: 1,
37
+ generatedAt: new Date().toISOString(),
38
+ positive: [...positiveMap.entries()],
39
+ negative: [...negativeMap.entries()],
40
+ conflicts: [...conflictMap.entries()],
41
+ };
42
+ }
43
+
44
+ function normalizeRequestPath(pathname) {
45
+ return String(pathname || "")
46
+ .replace(/\\/g, "/")
47
+ .replace(/\/+/g, "/")
48
+ .replace(/^\/+/, "")
49
+ .trim();
50
+ }
51
+
52
+ function buildNegativeScope(urlObj) {
53
+ if (!urlObj || typeof urlObj !== "object") {
54
+ return "";
55
+ }
56
+
57
+ const protocol = urlObj.protocol === "https:" ? "https:" : "http:";
58
+ const host = String(urlObj.host || "").toLowerCase();
59
+ const pathname = normalizeRequestPath(urlObj.pathname || "");
60
+ return `${protocol}//${host}/${pathname}`;
61
+ }
62
+
63
+ function buildNegativeKey(scope, canonicalKey) {
64
+ return `${String(scope || "").toLowerCase()}|${canonicalKey}`;
65
+ }
66
+
67
+ export class MavenAffinityIndex {
68
+ constructor(config) {
69
+ this.enabled = toBool(config.mavenAffinityEnabled, true);
70
+ this.indexDir = config.mavenAffinityIndexDir;
71
+ this.negativeTtlMs = toPositiveInt(config.mavenNegativeCacheTtlMs, 24 * 60 * 60 * 1000);
72
+ this.flushIntervalMs = toPositiveInt(config.mavenAffinityFlushIntervalMs, 5000);
73
+ this.maxEventBytes = toPositiveInt(config.mavenAffinityEventMaxBytes, 8 * 1024 * 1024);
74
+
75
+ this.snapshotPath = path.join(this.indexDir, "maven-affinity.snapshot.json");
76
+ this.eventLogPath = path.join(this.indexDir, "maven-affinity.events.log");
77
+
78
+ // Positive entries are persistent and have no TTL. They are removed only
79
+ // when the cache file disappears or a conflict is detected.
80
+ this.positive = new Map();
81
+ this.negative = new Map();
82
+ this.conflicts = new Map();
83
+
84
+ this.pendingEvents = [];
85
+ this.flushTimer = null;
86
+ this.flushing = false;
87
+ this.dirtySinceSnapshot = false;
88
+ }
89
+
90
+ async init() {
91
+ if (!this.enabled) {
92
+ return;
93
+ }
94
+
95
+ await fs.promises.mkdir(this.indexDir, { recursive: true });
96
+ this.#loadSnapshot();
97
+ this.#replayEventLog();
98
+
99
+ this.flushTimer = setInterval(() => {
100
+ this.flush().catch((error) => {
101
+ console.error(`[affinity] flush failed: ${error.message}`);
102
+ });
103
+ }, this.flushIntervalMs);
104
+
105
+ if (typeof this.flushTimer.unref === "function") {
106
+ this.flushTimer.unref();
107
+ }
108
+ }
109
+
110
+ #loadSnapshot() {
111
+ const snapshot = readJsonFile(this.snapshotPath, null);
112
+ if (!snapshot || typeof snapshot !== "object") {
113
+ return;
114
+ }
115
+
116
+ for (const [key, value] of snapshot.positive || []) {
117
+ this.positive.set(key, value);
118
+ }
119
+
120
+ const currentTime = nowMs();
121
+ for (const [key, value] of snapshot.negative || []) {
122
+ if (value?.expireAt && value.expireAt > currentTime) {
123
+ this.negative.set(key, value);
124
+ }
125
+ }
126
+
127
+ for (const [key, value] of snapshot.conflicts || []) {
128
+ this.conflicts.set(key, value);
129
+ }
130
+ }
131
+
132
+ #replayEventLog() {
133
+ if (!fs.existsSync(this.eventLogPath)) {
134
+ return;
135
+ }
136
+
137
+ let raw = "";
138
+ try {
139
+ raw = fs.readFileSync(this.eventLogPath, "utf8");
140
+ } catch {
141
+ return;
142
+ }
143
+
144
+ const lines = raw.split(/\r?\n/).filter(Boolean);
145
+ for (const line of lines) {
146
+ try {
147
+ const event = JSON.parse(line);
148
+ this.#applyEvent(event, false);
149
+ } catch {
150
+ // ignore invalid line
151
+ }
152
+ }
153
+ }
154
+
155
+ #enqueueEvent(type, payload) {
156
+ this.pendingEvents.push(JSON.stringify({ t: nowMs(), type, payload }));
157
+ this.dirtySinceSnapshot = true;
158
+ }
159
+
160
+ #applyEvent(event, append = true) {
161
+ if (!event || typeof event !== "object") {
162
+ return;
163
+ }
164
+
165
+ const { type, payload } = event;
166
+ if (!type || !payload || typeof payload !== "object") {
167
+ return;
168
+ }
169
+
170
+ if (type === "positive_upsert") {
171
+ this.positive.set(payload.key, payload.value);
172
+ if (append) {
173
+ this.#enqueueEvent(type, payload);
174
+ }
175
+ return;
176
+ }
177
+
178
+ if (type === "positive_remove") {
179
+ this.positive.delete(payload.key);
180
+ if (append) {
181
+ this.#enqueueEvent(type, payload);
182
+ }
183
+ return;
184
+ }
185
+
186
+ if (type === "negative_upsert") {
187
+ if (payload.value?.expireAt > nowMs()) {
188
+ this.negative.set(payload.key, payload.value);
189
+ } else {
190
+ this.negative.delete(payload.key);
191
+ }
192
+ if (append) {
193
+ this.#enqueueEvent(type, payload);
194
+ }
195
+ return;
196
+ }
197
+
198
+ if (type === "negative_remove") {
199
+ this.negative.delete(payload.key);
200
+ if (append) {
201
+ this.#enqueueEvent(type, payload);
202
+ }
203
+ return;
204
+ }
205
+
206
+ if (type === "conflict_set") {
207
+ this.conflicts.set(payload.key, payload.value);
208
+ if (append) {
209
+ this.#enqueueEvent(type, payload);
210
+ }
211
+ return;
212
+ }
213
+
214
+ if (type === "conflict_clear") {
215
+ this.conflicts.delete(payload.key);
216
+ if (append) {
217
+ this.#enqueueEvent(type, payload);
218
+ }
219
+ }
220
+ }
221
+
222
+ async resolvePreferredCachePath(canonicalKey) {
223
+ if (!this.enabled || !canonicalKey) {
224
+ return "";
225
+ }
226
+
227
+ if (this.conflicts.has(canonicalKey)) {
228
+ return "";
229
+ }
230
+
231
+ const existing = this.positive.get(canonicalKey);
232
+ if (!existing?.cachePath) {
233
+ return "";
234
+ }
235
+
236
+ try {
237
+ const stats = await fs.promises.stat(existing.cachePath);
238
+ if (!stats.isFile()) {
239
+ throw new Error("not-file");
240
+ }
241
+ return existing.cachePath;
242
+ } catch {
243
+ this.#applyEvent({
244
+ type: "positive_remove",
245
+ payload: { key: canonicalKey },
246
+ });
247
+ return "";
248
+ }
249
+ }
250
+
251
+ shouldSkipRequest(canonicalKey, urlObj) {
252
+ const scope = buildNegativeScope(urlObj);
253
+ if (!this.enabled || !canonicalKey || !scope) {
254
+ return false;
255
+ }
256
+
257
+ const key = buildNegativeKey(scope, canonicalKey);
258
+ const entry = this.negative.get(key);
259
+ if (!entry) {
260
+ return false;
261
+ }
262
+
263
+ if (entry.expireAt <= nowMs()) {
264
+ this.#applyEvent({
265
+ type: "negative_remove",
266
+ payload: { key },
267
+ });
268
+ return false;
269
+ }
270
+
271
+ return true;
272
+ }
273
+
274
+ recordSuccess({ canonicalKey, host, cachePath, fileName, urlObj = null }) {
275
+ if (!this.enabled || !canonicalKey || !cachePath || !fileName) {
276
+ return;
277
+ }
278
+
279
+ const previous = this.positive.get(canonicalKey);
280
+ if (previous && previous.fileName !== fileName) {
281
+ this.#applyEvent({
282
+ type: "conflict_set",
283
+ payload: {
284
+ key: canonicalKey,
285
+ value: {
286
+ reason: "file-name-mismatch",
287
+ updatedAt: nowMs(),
288
+ previousFileName: previous.fileName,
289
+ currentFileName: fileName,
290
+ },
291
+ },
292
+ });
293
+
294
+ this.#applyEvent({
295
+ type: "positive_remove",
296
+ payload: { key: canonicalKey },
297
+ });
298
+ return;
299
+ }
300
+
301
+ this.#applyEvent({
302
+ type: "positive_upsert",
303
+ payload: {
304
+ key: canonicalKey,
305
+ value: {
306
+ cachePath,
307
+ fileName,
308
+ host: String(host || "").toLowerCase(),
309
+ updatedAt: nowMs(),
310
+ },
311
+ },
312
+ });
313
+
314
+ const successScope = buildNegativeScope(urlObj);
315
+ if (successScope) {
316
+ const negativeKey = buildNegativeKey(successScope, canonicalKey);
317
+ if (this.negative.has(negativeKey)) {
318
+ this.#applyEvent({
319
+ type: "negative_remove",
320
+ payload: { key: negativeKey },
321
+ });
322
+ }
323
+ }
324
+ }
325
+
326
+ recordNegative({ canonicalKey, urlObj, statusCode = 404, ttlMs = this.negativeTtlMs }) {
327
+ const scope = buildNegativeScope(urlObj);
328
+ if (!this.enabled || !canonicalKey || !scope) {
329
+ return;
330
+ }
331
+
332
+ const expireAt = nowMs() + toPositiveInt(ttlMs, this.negativeTtlMs);
333
+ const key = buildNegativeKey(scope, canonicalKey);
334
+ this.#applyEvent({
335
+ type: "negative_upsert",
336
+ payload: {
337
+ key,
338
+ value: {
339
+ scope,
340
+ statusCode,
341
+ expireAt,
342
+ updatedAt: nowMs(),
343
+ },
344
+ },
345
+ });
346
+ }
347
+
348
+ async flush() {
349
+ if (!this.enabled || this.flushing) {
350
+ return;
351
+ }
352
+
353
+ this.flushing = true;
354
+ try {
355
+ if (this.pendingEvents.length > 0) {
356
+ const text = `${this.pendingEvents.join("\n")}\n`;
357
+ this.pendingEvents = [];
358
+ await fs.promises.appendFile(this.eventLogPath, text, "utf8");
359
+ }
360
+
361
+ const stats = await fs.promises.stat(this.eventLogPath).catch(() => null);
362
+ const needsSnapshot = this.dirtySinceSnapshot && (!stats || stats.size >= this.maxEventBytes);
363
+
364
+ if (needsSnapshot) {
365
+ await this.#writeSnapshotAndResetEventLog();
366
+ }
367
+ } finally {
368
+ this.flushing = false;
369
+ }
370
+ }
371
+
372
+ async #writeSnapshotAndResetEventLog() {
373
+ const snapshot = serializeSnapshot(this.positive, this.negative, this.conflicts);
374
+ const tempPath = `${this.snapshotPath}.tmp`;
375
+ await fs.promises.writeFile(tempPath, `${JSON.stringify(snapshot, null, 2)}\n`, "utf8");
376
+ await fs.promises.rename(tempPath, this.snapshotPath);
377
+ await fs.promises.writeFile(this.eventLogPath, "", "utf8");
378
+ this.dirtySinceSnapshot = false;
379
+ }
380
+
381
+ async destroy() {
382
+ if (!this.enabled) {
383
+ return;
384
+ }
385
+
386
+ if (this.flushTimer) {
387
+ clearInterval(this.flushTimer);
388
+ this.flushTimer = null;
389
+ }
390
+
391
+ await this.flush();
392
+ if (this.dirtySinceSnapshot) {
393
+ await this.#writeSnapshotAndResetEventLog();
394
+ }
395
+ }
396
+ }
@@ -4,45 +4,75 @@ import { DailyLogFile } from "./daily-log-file.js";
4
4
  const MIRROR_INSTALLED = Symbol.for("maven-proxy.console-log-file.installed");
5
5
  const GLOBAL_ERROR_HOOK_INSTALLED = Symbol.for("maven-proxy.global-error-hook.installed");
6
6
 
7
- function mirrorConsoleMethod({ level, originalMethod, logFile }) {
7
+ function mirrorConsoleMethod({
8
+ level,
9
+ originalMethod,
10
+ appLogFile,
11
+ errorLogFile,
12
+ outputToConsole,
13
+ }) {
8
14
  return (...args) => {
9
- originalMethod(...args);
15
+ if (outputToConsole) {
16
+ originalMethod(...args);
17
+ }
10
18
 
11
19
  const line = `[${new Date().toISOString()}] [${level}] ${util.format(...args)}`;
12
- logFile.appendLine(line).catch((error) => {
20
+ appLogFile.appendLine(line).catch((error) => {
13
21
  process.stderr.write(`[maven-proxy] write console log failed: ${error.message}\n`);
14
22
  });
23
+
24
+ if (level === "ERROR") {
25
+ errorLogFile.appendLine(line).catch((error) => {
26
+ process.stderr.write(`[maven-proxy] write error log failed: ${error.message}\n`);
27
+ });
28
+ }
15
29
  };
16
30
  }
17
31
 
18
- export function installConsoleLogFileMirror({ logDir, retentionDays = 7 }) {
32
+ export function installConsoleLogFileMirror({
33
+ logDir,
34
+ retentionDays = 7,
35
+ outputToConsole = true,
36
+ }) {
19
37
  if (globalThis[MIRROR_INSTALLED]) {
20
38
  return;
21
39
  }
22
40
  globalThis[MIRROR_INSTALLED] = true;
23
41
 
24
- const logFile = new DailyLogFile({
42
+ const appLogFile = new DailyLogFile({
43
+ logDir,
44
+ filePrefix: "app",
45
+ retentionDays,
46
+ });
47
+
48
+ const errorLogFile = new DailyLogFile({
25
49
  logDir,
26
- filePrefix: "console",
50
+ filePrefix: "error",
27
51
  retentionDays,
28
52
  });
29
53
 
30
54
  console.log = mirrorConsoleMethod({
31
55
  level: "INFO",
32
56
  originalMethod: console.log.bind(console),
33
- logFile,
57
+ appLogFile,
58
+ errorLogFile,
59
+ outputToConsole,
34
60
  });
35
61
 
36
62
  console.warn = mirrorConsoleMethod({
37
63
  level: "WARN",
38
64
  originalMethod: console.warn.bind(console),
39
- logFile,
65
+ appLogFile,
66
+ errorLogFile,
67
+ outputToConsole,
40
68
  });
41
69
 
42
70
  console.error = mirrorConsoleMethod({
43
71
  level: "ERROR",
44
72
  originalMethod: console.error.bind(console),
45
- logFile,
73
+ appLogFile,
74
+ errorLogFile,
75
+ outputToConsole,
46
76
  });
47
77
  }
48
78
 
@@ -0,0 +1,151 @@
1
+ function safeDecode(pathname) {
2
+ try {
3
+ return decodeURIComponent(pathname || "/");
4
+ } catch {
5
+ return pathname || "/";
6
+ }
7
+ }
8
+
9
+ function escapeRegex(value) {
10
+ return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11
+ }
12
+
13
+ function normalizePathname(pathname) {
14
+ return String(pathname || "")
15
+ .replace(/\\/g, "/")
16
+ .replace(/^\/+/, "")
17
+ .replace(/\/+/g, "/")
18
+ .trim();
19
+ }
20
+
21
+ function stripKnownPrefixes(relativePath) {
22
+ const raw = normalizePathname(relativePath);
23
+ if (!raw) {
24
+ return [];
25
+ }
26
+
27
+ const candidates = new Set([raw]);
28
+
29
+ if (raw.toLowerCase().startsWith("maven2/")) {
30
+ candidates.add(raw.slice("maven2/".length));
31
+ }
32
+
33
+ if (raw.toLowerCase().startsWith("m2/")) {
34
+ candidates.add(raw.slice("m2/".length));
35
+ }
36
+
37
+ const maven2Marker = raw.toLowerCase().indexOf("/maven2/");
38
+ if (maven2Marker >= 0) {
39
+ candidates.add(raw.slice(maven2Marker + "/maven2/".length));
40
+ }
41
+
42
+ const m2Marker = raw.toLowerCase().indexOf("/m2/");
43
+ if (m2Marker >= 0) {
44
+ candidates.add(raw.slice(m2Marker + "/m2/".length));
45
+ }
46
+
47
+ const patterns = [
48
+ /^repository\/[^/]+\/(.+)$/i,
49
+ /^artifactory\/[^/]+\/(.+)$/i,
50
+ /^nexus\/content\/repositories\/[^/]+\/(.+)$/i,
51
+ /^repositories\/[^/]+\/(.+)$/i,
52
+ ];
53
+
54
+ for (const pattern of patterns) {
55
+ const match = raw.match(pattern);
56
+ if (match?.[1]) {
57
+ candidates.add(match[1]);
58
+ }
59
+ }
60
+
61
+ const normalizedCandidates = [...candidates].map((item) => normalizePathname(item)).filter(Boolean);
62
+ normalizedCandidates.sort((left, right) => left.split("/").length - right.split("/").length);
63
+ return normalizedCandidates;
64
+ }
65
+
66
+ function isSafePathSegment(segment) {
67
+ return /^[A-Za-z0-9_.+-]+$/.test(String(segment || ""));
68
+ }
69
+
70
+ function isReleaseVersion(version) {
71
+ return !String(version || "").toUpperCase().endsWith("-SNAPSHOT");
72
+ }
73
+
74
+ function matchReleaseFileName(artifact, version, fileName) {
75
+ if (/-SNAPSHOT(?=\.|-)/i.test(fileName)) {
76
+ return false;
77
+ }
78
+
79
+ const escapedArtifact = escapeRegex(artifact);
80
+ const escapedVersion = escapeRegex(version);
81
+ const pattern = new RegExp(
82
+ `^${escapedArtifact}-${escapedVersion}(?:-[A-Za-z0-9_.+:-]+)?\\.(pom|jar|module|aar|war)(?:\\.(sha1|sha256|sha512|md5|asc))?$`,
83
+ "i",
84
+ );
85
+
86
+ return pattern.test(fileName);
87
+ }
88
+
89
+ function tryParseCandidate(relativePath) {
90
+ const normalized = normalizePathname(relativePath);
91
+ const parts = normalized.split("/").filter(Boolean);
92
+ if (parts.length < 4) {
93
+ return null;
94
+ }
95
+
96
+ const fileName = parts[parts.length - 1];
97
+ const version = parts[parts.length - 2];
98
+ const artifact = parts[parts.length - 3];
99
+ const groupParts = parts.slice(0, -3);
100
+
101
+ if (!artifact || !version || !fileName || groupParts.length === 0) {
102
+ return null;
103
+ }
104
+
105
+ if (!isSafePathSegment(artifact) || !isSafePathSegment(version) || !isSafePathSegment(fileName)) {
106
+ return null;
107
+ }
108
+
109
+ if (!groupParts.every((segment) => isSafePathSegment(segment))) {
110
+ return null;
111
+ }
112
+
113
+ if (!isReleaseVersion(version)) {
114
+ return null;
115
+ }
116
+
117
+ if (!matchReleaseFileName(artifact, version, fileName)) {
118
+ return null;
119
+ }
120
+
121
+ const groupPath = groupParts.join("/");
122
+ const canonicalPath = `${groupPath}/${artifact}/${version}/${fileName}`;
123
+
124
+ return {
125
+ canonicalPath,
126
+ canonicalKey: canonicalPath,
127
+ groupPath,
128
+ artifact,
129
+ version,
130
+ fileName,
131
+ isRelease: true,
132
+ };
133
+ }
134
+
135
+ export function parseMavenReleaseCanonical(urlObj) {
136
+ if (!urlObj || typeof urlObj !== "object") {
137
+ return null;
138
+ }
139
+
140
+ const decodedPath = safeDecode(urlObj.pathname || "/");
141
+ const candidates = stripKnownPrefixes(decodedPath);
142
+
143
+ for (const candidate of candidates) {
144
+ const parsed = tryParseCandidate(candidate);
145
+ if (parsed) {
146
+ return parsed;
147
+ }
148
+ }
149
+
150
+ return null;
151
+ }
@@ -6,7 +6,7 @@ import { detectJavaHome } from "../common/java-home.js";
6
6
 
7
7
  const cwd = process.cwd();
8
8
  const userConfigDir = path.resolve(os.homedir(), "maven-proxy");
9
- const defaultUserConfigPath = path.join(userConfigDir, "config");
9
+ const defaultUserConfigPath = path.join(userConfigDir, "config.properties");
10
10
 
11
11
  function normalizeConfigMode(value) {
12
12
  const normalized = String(value || "").trim().toLowerCase();
@@ -55,8 +55,7 @@ function resolveConfigFilePath(configMode) {
55
55
 
56
56
  if (configMode === "development") {
57
57
  const devCandidates = [
58
- path.resolve(cwd, ".env"),
59
- path.resolve(cwd, ".evn"),
58
+ path.resolve(cwd, "config.properties"),
60
59
  ];
61
60
 
62
61
  for (const candidate of devCandidates) {
@@ -181,6 +180,13 @@ const defaultMavenRepoDomains = [
181
180
 
182
181
  const cacheDir = path.resolve(configBaseDir, process.env.CACHE_DIR || "data/cache");
183
182
 
183
+ const multiThreadMinSizeBytes = Math.max(0, toInt(process.env.MULTI_THREAD_MIN_SIZE_MB, 1)) * 1024 * 1024;
184
+ const downloadTimeoutMs = Math.max(1, toInt(process.env.DOWNLOAD_TIMEOUT_SECONDS, 60)) * 1000;
185
+ const outboundKeepAliveMsecs = Math.max(1, toInt(process.env.OUTBOUND_KEEP_ALIVE_SECONDS, 1)) * 1000;
186
+ const mavenNegativeCacheTtlMs = Math.max(1, toInt(process.env.MAVEN_NEGATIVE_CACHE_TTL_HOURS, 24)) * 60 * 60 * 1000;
187
+ const mavenAffinityFlushIntervalMs = Math.max(1, toInt(process.env.MAVEN_AFFINITY_FLUSH_INTERVAL_SECONDS, 5)) * 1000;
188
+ const mavenAffinityEventMaxBytes = Math.max(1, toInt(process.env.MAVEN_AFFINITY_EVENT_MAX_MB, 8)) * 1024 * 1024;
189
+
184
190
  export const config = {
185
191
  configMode,
186
192
  configBaseDir,
@@ -198,10 +204,21 @@ export const config = {
198
204
  mavenRepoDomains: toList(process.env.MAVEN_REPO_DOMAINS, [...new Set(defaultMavenRepoDomains)]),
199
205
  multiThreadDomains: toList(process.env.MULTI_THREAD_DOMAINS, ["repo1.maven.org"]),
200
206
  multiThreadCount: Math.max(1, toInt(process.env.MULTI_THREAD_COUNT, 4)),
201
- multiThreadMinSizeBytes: Math.max(0, toInt(process.env.MULTI_THREAD_MIN_SIZE_BYTES, 1024 * 1024)),
202
- downloadTimeoutMs: Math.max(1000, toInt(process.env.DOWNLOAD_TIMEOUT_MS, 60000)),
207
+ multiThreadMinSizeBytes,
208
+ downloadTimeoutMs,
209
+ outboundKeepAlive: toBool(process.env.OUTBOUND_KEEP_ALIVE, true),
210
+ outboundKeepAliveMsecs,
211
+ outboundMaxSockets: Math.max(1, toInt(process.env.OUTBOUND_MAX_SOCKETS, 64)),
212
+ outboundMaxFreeSockets: Math.max(1, toInt(process.env.OUTBOUND_MAX_FREE_SOCKETS, 16)),
213
+ mavenAffinityEnabled: toBool(process.env.MAVEN_AFFINITY_ENABLED, true),
214
+ mavenAffinityIndexDir: path.resolve(cacheDir, process.env.MAVEN_AFFINITY_INDEX_DIR || ".index"),
215
+ mavenNegativeCacheTtlMs,
216
+ mavenAffinityFlushIntervalMs,
217
+ mavenAffinityEventMaxBytes,
203
218
  downloadLogDir: path.resolve(configBaseDir, process.env.DOWNLOAD_LOG_DIR || "data/logs/downloads"),
204
219
  logRetentionDays: Math.max(1, toInt(process.env.LOG_RETENTION_DAYS, 7)),
220
+ logToStdout: toBool(process.env.LOG_TO_STDOUT, true),
221
+ logConnectEvents: toBool(process.env.LOG_CONNECT_EVENTS, false),
205
222
  certDir: path.resolve(configBaseDir, process.env.CERT_DIR || "data/certs"),
206
223
  rootCertPath: path.resolve(configBaseDir, process.env.ROOT_CERT_PATH || "data/certs/root-ca.crt"),
207
224
  rootKeyPath: path.resolve(configBaseDir, process.env.ROOT_KEY_PATH || "data/certs/root-ca.key.pem"),
@@ -214,7 +231,7 @@ export const config = {
214
231
  javaHome: javaHomeResolution.javaHome,
215
232
  javaHomeSource: javaHomeResolution.source,
216
233
  javaHomeConfigured: javaHomeResolution.configuredJavaHome || "",
217
- httpsPassthroughForUnmatched: toBool(process.env.HTTPS_PASSTHROUGH_FOR_UNMATCHED, true),
234
+ httpsPassthroughForUnmatched: toBool(process.env.HTTPS_PASSTHROUGH_FOR_UNMATCHED, false),
218
235
  upstreamProxyUrl: normalizeProxyUrl(process.env.UPSTREAM_PROXY_URL || process.env.ALL_PROXY || process.env.all_proxy || ""),
219
236
  upstreamHttpProxyUrl: normalizeProxyUrl(process.env.UPSTREAM_HTTP_PROXY_URL || process.env.HTTP_PROXY || process.env.http_proxy || ""),
220
237
  upstreamHttpsProxyUrl: normalizeProxyUrl(process.env.UPSTREAM_HTTPS_PROXY_URL || process.env.HTTPS_PROXY || process.env.https_proxy || ""),