@socketsecurity/lib 5.1.1 → 5.1.3

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/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [5.1.3](https://github.com/SocketDev/socket-lib/releases/tag/v5.1.3) - 2025-12-29
9
+
10
+ ### Fixed
11
+
12
+ - **http-request**: Fixed `httpDownload()` to properly handle HTTP redirects (3xx status codes)
13
+ - Added `followRedirects` option (default: `true`) to enable automatic redirect following
14
+ - Added `maxRedirects` option (default: `5`) to limit redirect chain length
15
+ - Now supports downloading from services that use CDN redirects, such as GitHub release assets
16
+ - Prevents GitHub API quota exhaustion by following `browser_download_url` redirects instead of using API endpoints
17
+ - Resolves "Request quota exhausted" errors when downloading GitHub release assets
18
+
19
+ ## [5.1.2](https://github.com/SocketDev/socket-lib/releases/tag/v5.1.2) - 2025-12-28
20
+
21
+ ### Fixed
22
+
23
+ - **paths**: Fixed missing `getPathValue()` caching in `getSocketDlxDir()`
24
+ - Now uses `getPathValue()` for performance, consistent with `getSocketUserDir()` and `getSocketCacacheDir()`
25
+ - Adds test override support via `setPath('socket-dlx-dir', ...)`
26
+ - Test helper `mockHomeDir()` now properly invalidates path cache with `resetPaths()` calls
27
+ - Resolves cache persistence issues in test environments
28
+
8
29
  ## [5.1.1](https://github.com/SocketDev/socket-lib/releases/tag/v5.1.1) - 2025-12-28
9
30
 
10
31
  ### Added
@@ -257,6 +257,28 @@ export interface HttpResponse {
257
257
  * Configuration options for file downloads.
258
258
  */
259
259
  export interface HttpDownloadOptions {
260
+ /**
261
+ * Whether to automatically follow HTTP redirects (3xx status codes).
262
+ * This is essential for downloading from services that use CDN redirects,
263
+ * such as GitHub release assets which return HTTP 302 to their CDN.
264
+ *
265
+ * @default true
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * // Follow redirects (default) - works with GitHub releases
270
+ * await httpDownload(
271
+ * 'https://github.com/org/repo/releases/download/v1.0.0/file.zip',
272
+ * '/tmp/file.zip'
273
+ * )
274
+ *
275
+ * // Don't follow redirects
276
+ * await httpDownload('https://example.com/file.zip', '/tmp/file.zip', {
277
+ * followRedirects: false
278
+ * })
279
+ * ```
280
+ */
281
+ followRedirects?: boolean | undefined;
260
282
  /**
261
283
  * HTTP headers to send with the download request.
262
284
  * A `User-Agent` header is automatically added if not provided.
@@ -292,6 +314,21 @@ export interface HttpDownloadOptions {
292
314
  * ```
293
315
  */
294
316
  logger?: Logger | undefined;
317
+ /**
318
+ * Maximum number of redirects to follow before throwing an error.
319
+ * Only relevant when `followRedirects` is `true`.
320
+ *
321
+ * @default 5
322
+ *
323
+ * @example
324
+ * ```ts
325
+ * // Allow up to 10 redirects
326
+ * await httpDownload('https://example.com/many-redirects/file.zip', '/tmp/file.zip', {
327
+ * maxRedirects: 10
328
+ * })
329
+ * ```
330
+ */
331
+ maxRedirects?: number | undefined;
295
332
  /**
296
333
  * Callback for tracking download progress.
297
334
  * Called periodically as data is received.
@@ -439,12 +476,15 @@ export interface HttpDownloadResult {
439
476
  */
440
477
  export declare function httpRequest(url: string, options?: HttpRequestOptions | undefined): Promise<HttpResponse>;
441
478
  /**
442
- * Download a file from a URL to a local path with retry logic and progress callbacks.
479
+ * Download a file from a URL to a local path with redirect support, retry logic, and progress callbacks.
443
480
  * Uses streaming to avoid loading entire file in memory.
444
481
  *
445
482
  * The download is streamed directly to disk, making it memory-efficient even for
446
483
  * large files. Progress callbacks allow for real-time download status updates.
447
484
  *
485
+ * Automatically follows HTTP redirects (3xx status codes) by default, making it suitable
486
+ * for downloading from services like GitHub releases that redirect to CDN URLs.
487
+ *
448
488
  * @param url - The URL to download from (must start with http:// or https://)
449
489
  * @param destPath - Absolute path where the file should be saved
450
490
  * @param options - Download configuration options
@@ -460,6 +500,12 @@ export declare function httpRequest(url: string, options?: HttpRequestOptions |
460
500
  * )
461
501
  * console.log(`Downloaded ${result.size} bytes to ${result.path}`)
462
502
  *
503
+ * // Download from GitHub releases (handles 302 redirect automatically)
504
+ * await httpDownload(
505
+ * 'https://github.com/org/repo/releases/download/v1.0.0/binary.tar.gz',
506
+ * '/tmp/binary.tar.gz'
507
+ * )
508
+ *
463
509
  * // With progress tracking
464
510
  * await httpDownload(
465
511
  * 'https://example.com/large-file.zip',
@@ -183,8 +183,10 @@ async function httpRequestAttempt(url, options) {
183
183
  }
184
184
  async function httpDownload(url, destPath, options) {
185
185
  const {
186
+ followRedirects = true,
186
187
  headers = {},
187
188
  logger,
189
+ maxRedirects = 5,
188
190
  onProgress,
189
191
  progressInterval = 10,
190
192
  retries = 0,
@@ -210,7 +212,9 @@ async function httpDownload(url, destPath, options) {
210
212
  for (let attempt = 0; attempt <= retries; attempt++) {
211
213
  try {
212
214
  return await httpDownloadAttempt(url, destPath, {
215
+ followRedirects,
213
216
  headers,
217
+ maxRedirects,
214
218
  onProgress: progressCallback,
215
219
  timeout
216
220
  });
@@ -227,7 +231,9 @@ async function httpDownload(url, destPath, options) {
227
231
  }
228
232
  async function httpDownloadAttempt(url, destPath, options) {
229
233
  const {
234
+ followRedirects = true,
230
235
  headers = {},
236
+ maxRedirects = 5,
231
237
  onProgress,
232
238
  timeout = 12e4
233
239
  } = { __proto__: null, ...options };
@@ -257,6 +263,27 @@ async function httpDownloadAttempt(url, destPath, options) {
257
263
  const request = httpModule.request(
258
264
  requestOptions,
259
265
  (res) => {
266
+ if (followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
267
+ if (maxRedirects <= 0) {
268
+ reject(
269
+ new Error(
270
+ `Too many redirects (exceeded maximum: ${maxRedirects})`
271
+ )
272
+ );
273
+ return;
274
+ }
275
+ const redirectUrl = res.headers.location.startsWith("http") ? res.headers.location : new URL(res.headers.location, url).toString();
276
+ resolve(
277
+ httpDownloadAttempt(redirectUrl, destPath, {
278
+ followRedirects,
279
+ headers,
280
+ maxRedirects: maxRedirects - 1,
281
+ onProgress,
282
+ timeout
283
+ })
284
+ );
285
+ return;
286
+ }
260
287
  if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
261
288
  closeStream();
262
289
  reject(
@@ -42,13 +42,15 @@ export declare function getSocketAppDir(appName: string): string;
42
42
  export declare function getSocketCacacheDir(): string;
43
43
  /**
44
44
  * Get the Socket DLX directory (~/.socket/_dlx).
45
- * Can be overridden with environment variables.
45
+ * Can be overridden with SOCKET_DLX_DIR environment variable or via setPath() for testing.
46
+ * Result is cached via getPathValue for performance.
46
47
  *
47
48
  * Priority order:
48
- * 1. SOCKET_DLX_DIR - Full override of DLX cache directory
49
- * 2. SOCKET_HOME/_dlx - Base directory override (inherits from getSocketUserDir)
50
- * 3. Default: $HOME/.socket/_dlx
51
- * 4. Fallback: /tmp/.socket/_dlx (Unix) or %TEMP%\.socket\_dlx (Windows)
49
+ * 1. Test override via setPath('socket-dlx-dir', ...)
50
+ * 2. SOCKET_DLX_DIR - Full override of DLX cache directory
51
+ * 3. SOCKET_HOME/_dlx - Base directory override (inherits from getSocketUserDir)
52
+ * 4. Default: $HOME/.socket/_dlx
53
+ * 5. Fallback: /tmp/.socket/_dlx (Unix) or %TEMP%\.socket\_dlx (Windows)
52
54
  */
53
55
  export declare function getSocketDlxDir(): string;
54
56
  /**
@@ -88,12 +88,17 @@ function getSocketCacacheDir() {
88
88
  });
89
89
  }
90
90
  function getSocketDlxDir() {
91
- if ((0, import_socket2.getSocketDlxDirEnv)()) {
92
- return (0, import_normalize.normalizePath)((0, import_socket2.getSocketDlxDirEnv)());
93
- }
94
- return (0, import_normalize.normalizePath)(
95
- path.join(getSocketUserDir(), `${import_socket.SOCKET_APP_PREFIX}${import_socket.SOCKET_DLX_APP_NAME}`)
96
- );
91
+ return (0, import_rewire.getPathValue)("socket-dlx-dir", () => {
92
+ if ((0, import_socket2.getSocketDlxDirEnv)()) {
93
+ return (0, import_normalize.normalizePath)((0, import_socket2.getSocketDlxDirEnv)());
94
+ }
95
+ return (0, import_normalize.normalizePath)(
96
+ path.join(
97
+ getSocketUserDir(),
98
+ `${import_socket.SOCKET_APP_PREFIX}${import_socket.SOCKET_DLX_APP_NAME}`
99
+ )
100
+ );
101
+ });
97
102
  }
98
103
  function getSocketAppCacheDir(appName) {
99
104
  return (0, import_normalize.normalizePath)(path.join(getSocketAppDir(appName), import_dirnames.CACHE_DIR));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socketsecurity/lib",
3
- "version": "5.1.1",
3
+ "version": "5.1.3",
4
4
  "packageManager": "pnpm@10.26.2",
5
5
  "license": "MIT",
6
6
  "description": "Core utilities and infrastructure for Socket.dev security tools",
@@ -714,7 +714,7 @@
714
714
  "@socketregistry/is-unicode-supported": "1.0.5",
715
715
  "@socketregistry/packageurl-js": "1.3.5",
716
716
  "@socketregistry/yocto-spinner": "1.0.25",
717
- "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.1.0",
717
+ "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.1.2",
718
718
  "@types/node": "24.9.2",
719
719
  "@typescript/native-preview": "7.0.0-dev.20250920.1",
720
720
  "@vitest/coverage-v8": "4.0.3",