@socketsecurity/lib 5.0.0 → 5.0.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ 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.0.1](https://github.com/SocketDev/socket-lib/releases/tag/v5.0.1) - 2025-12-11
9
+
10
+ ### Added
11
+
12
+ - **http-request**: Enhanced `httpDownload()` with automatic progress logging via Logger integration
13
+ - New `logger` option: Pass a Logger instance for automatic progress tracking
14
+ - New `progressInterval` option: Configure progress reporting frequency (default: 10%)
15
+ - Progress format: `Progress: XX% (Y.Y MB / Z.Z MB)`
16
+ - `onProgress` callback takes precedence over `logger` when both are provided
17
+ - Commit: [`91e5db5`](https://github.com/SocketDev/socket-lib/commit/91e5db5)
18
+
8
19
  ## [5.0.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.0.0) - 2025-12-04
9
20
 
10
21
  ### Added
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Socket Badge](https://socket.dev/api/badge/npm/package/@socketsecurity/lib)](https://socket.dev/npm/package/@socketsecurity/lib)
4
4
  [![CI](https://github.com/SocketDev/socket-lib/actions/workflows/ci.yml/badge.svg)](https://github.com/SocketDev/socket-lib/actions/workflows/ci.yml)
5
- ![Coverage](https://img.shields.io/badge/coverage-83.78%25-brightgreen)
5
+ ![Coverage](https://img.shields.io/badge/coverage-84.10%25-brightgreen)
6
6
 
7
7
  [![Follow @SocketSecurity](https://img.shields.io/twitter/follow/SocketSecurity?style=social)](https://twitter.com/SocketSecurity)
8
8
  [![Follow @socket.dev on Bluesky](https://img.shields.io/badge/Follow-@socket.dev-1DA1F2?style=social&logo=bluesky)](https://bsky.app/profile/socket.dev)
package/dist/fs.d.ts CHANGED
@@ -578,51 +578,63 @@ export declare function readJson(filepath: PathLike, options?: ReadJsonOptions |
578
578
  export declare function readJsonSync(filepath: PathLike, options?: ReadJsonOptions | string | undefined): import("./json/types").JsonValue;
579
579
  /**
580
580
  * Safely delete a file or directory asynchronously with built-in protections.
581
- * Uses `del` for safer deletion that prevents removing cwd and above by default.
582
- * Automatically uses force: true for temp directory, cacache, and ~/.socket subdirectories.
581
+ *
582
+ * Uses [`del`](https://socket.dev/npm/package/del/overview/8.0.1) for safer deletion with these safety features:
583
+ * - By default, prevents deleting the current working directory (cwd) and above
584
+ * - Allows deleting within cwd (descendant paths) without force option
585
+ * - Automatically uses force: true for temp directory, cacache, and ~/.socket subdirectories
586
+ * - Protects against accidental deletion of parent directories via `../` paths
583
587
  *
584
588
  * @param filepath - Path or array of paths to delete (supports glob patterns)
585
589
  * @param options - Deletion options including force, retries, and recursion
590
+ * @param options.force - Set to true to allow deleting cwd and above (use with caution)
586
591
  * @throws {Error} When attempting to delete protected paths without force option
587
592
  *
588
593
  * @example
589
594
  * ```ts
590
- * // Delete a single file
591
- * await safeDelete('./temp-file.txt')
592
- *
593
- * // Delete a directory recursively
594
- * await safeDelete('./build', { recursive: true })
595
+ * // Delete files within cwd (safe by default)
596
+ * await safeDelete('./build')
597
+ * await safeDelete('./dist')
595
598
  *
596
- * // Delete multiple paths
597
- * await safeDelete(['./dist', './coverage'])
599
+ * // Delete with glob patterns
600
+ * await safeDelete(['./temp/**', '!./temp/keep.txt'])
598
601
  *
599
602
  * // Delete with custom retry settings
600
603
  * await safeDelete('./flaky-dir', { maxRetries: 5, retryDelay: 500 })
604
+ *
605
+ * // Force delete cwd or above (requires explicit force: true)
606
+ * await safeDelete('../parent-dir', { force: true })
601
607
  * ```
602
608
  */
603
609
  export declare function safeDelete(filepath: PathLike | PathLike[], options?: RemoveOptions | undefined): Promise<void>;
604
610
  /**
605
611
  * Safely delete a file or directory synchronously with built-in protections.
606
- * Uses `del` for safer deletion that prevents removing cwd and above by default.
607
- * Automatically uses force: true for temp directory, cacache, and ~/.socket subdirectories.
612
+ *
613
+ * Uses [`del`](https://socket.dev/npm/package/del/overview/8.0.1) for safer deletion with these safety features:
614
+ * - By default, prevents deleting the current working directory (cwd) and above
615
+ * - Allows deleting within cwd (descendant paths) without force option
616
+ * - Automatically uses force: true for temp directory, cacache, and ~/.socket subdirectories
617
+ * - Protects against accidental deletion of parent directories via `../` paths
608
618
  *
609
619
  * @param filepath - Path or array of paths to delete (supports glob patterns)
610
620
  * @param options - Deletion options including force, retries, and recursion
621
+ * @param options.force - Set to true to allow deleting cwd and above (use with caution)
611
622
  * @throws {Error} When attempting to delete protected paths without force option
612
623
  *
613
624
  * @example
614
625
  * ```ts
615
- * // Delete a single file
616
- * safeDeleteSync('./temp-file.txt')
626
+ * // Delete files within cwd (safe by default)
627
+ * safeDeleteSync('./build')
628
+ * safeDeleteSync('./dist')
617
629
  *
618
- * // Delete a directory recursively
619
- * safeDeleteSync('./build', { recursive: true })
630
+ * // Delete with glob patterns
631
+ * safeDeleteSync(['./temp/**', '!./temp/keep.txt'])
620
632
  *
621
- * // Delete multiple paths with globs
622
- * safeDeleteSync(['./dist/**', './coverage/**'])
633
+ * // Delete multiple paths
634
+ * safeDeleteSync(['./coverage', './reports'])
623
635
  *
624
- * // Force delete a protected path (use with caution)
625
- * safeDeleteSync('./important', { force: true })
636
+ * // Force delete cwd or above (requires explicit force: true)
637
+ * safeDeleteSync('../parent-dir', { force: true })
626
638
  * ```
627
639
  */
628
640
  export declare function safeDeleteSync(filepath: PathLike | PathLike[], options?: RemoveOptions | undefined): void;
@@ -1,3 +1,4 @@
1
+ import type { Logger } from './logger.js';
1
2
  /**
2
3
  * Configuration options for HTTP/HTTPS requests.
3
4
  */
@@ -270,9 +271,31 @@ export interface HttpDownloadOptions {
270
271
  * ```
271
272
  */
272
273
  headers?: Record<string, string> | undefined;
274
+ /**
275
+ * Logger instance for automatic progress logging.
276
+ * When provided with `progressInterval`, will automatically log download progress.
277
+ * If both `onProgress` and `logger` are provided, `onProgress` takes precedence.
278
+ *
279
+ * @example
280
+ * ```ts
281
+ * import { getDefaultLogger } from '@socketsecurity/lib/logger'
282
+ *
283
+ * const logger = getDefaultLogger()
284
+ * await httpDownload('https://example.com/file.zip', '/tmp/file.zip', {
285
+ * logger,
286
+ * progressInterval: 10 // Log every 10%
287
+ * })
288
+ * // Output:
289
+ * // Progress: 10% (5.2 MB / 52.0 MB)
290
+ * // Progress: 20% (10.4 MB / 52.0 MB)
291
+ * // ...
292
+ * ```
293
+ */
294
+ logger?: Logger | undefined;
273
295
  /**
274
296
  * Callback for tracking download progress.
275
297
  * Called periodically as data is received.
298
+ * Takes precedence over `logger` if both are provided.
276
299
  *
277
300
  * @param downloaded - Number of bytes downloaded so far
278
301
  * @param total - Total file size in bytes (from Content-Length header)
@@ -288,6 +311,29 @@ export interface HttpDownloadOptions {
288
311
  * ```
289
312
  */
290
313
  onProgress?: ((downloaded: number, total: number) => void) | undefined;
314
+ /**
315
+ * Progress reporting interval as a percentage (0-100).
316
+ * Only used when `logger` is provided.
317
+ * Progress will be logged each time the download advances by this percentage.
318
+ *
319
+ * @default 10
320
+ *
321
+ * @example
322
+ * ```ts
323
+ * // Log every 10%
324
+ * await httpDownload('https://example.com/file.zip', '/tmp/file.zip', {
325
+ * logger: getDefaultLogger(),
326
+ * progressInterval: 10
327
+ * })
328
+ *
329
+ * // Log every 25%
330
+ * await httpDownload('https://example.com/file.zip', '/tmp/file.zip', {
331
+ * logger: getDefaultLogger(),
332
+ * progressInterval: 25
333
+ * })
334
+ * ```
335
+ */
336
+ progressInterval?: number | undefined;
291
337
  /**
292
338
  * Number of retry attempts for failed downloads.
293
339
  * Uses exponential backoff: delay = `retryDelay` * 2^attempt.
@@ -184,17 +184,34 @@ async function httpRequestAttempt(url, options) {
184
184
  async function httpDownload(url, destPath, options) {
185
185
  const {
186
186
  headers = {},
187
+ logger,
187
188
  onProgress,
189
+ progressInterval = 10,
188
190
  retries = 0,
189
191
  retryDelay = 1e3,
190
192
  timeout = 12e4
191
193
  } = { __proto__: null, ...options };
194
+ let progressCallback;
195
+ if (onProgress) {
196
+ progressCallback = onProgress;
197
+ } else if (logger) {
198
+ let lastPercent = 0;
199
+ progressCallback = (downloaded, total) => {
200
+ const percent = Math.floor(downloaded / total * 100);
201
+ if (percent >= lastPercent + progressInterval) {
202
+ logger.log(
203
+ ` Progress: ${percent}% (${(downloaded / 1024 / 1024).toFixed(1)} MB / ${(total / 1024 / 1024).toFixed(1)} MB)`
204
+ );
205
+ lastPercent = percent;
206
+ }
207
+ };
208
+ }
192
209
  let lastError;
193
210
  for (let attempt = 0; attempt <= retries; attempt++) {
194
211
  try {
195
212
  return await httpDownloadAttempt(url, destPath, {
196
213
  headers,
197
- onProgress,
214
+ onProgress: progressCallback,
198
215
  timeout
199
216
  });
200
217
  } catch (e) {
package/dist/json/edit.js CHANGED
@@ -22,6 +22,7 @@ __export(edit_exports, {
22
22
  getEditableJsonClass: () => getEditableJsonClass
23
23
  });
24
24
  module.exports = __toCommonJS(edit_exports);
25
+ var import_promises = require("node:timers/promises");
25
26
  var import_format = require("./format");
26
27
  const identSymbol = import_format.INDENT_SYMBOL;
27
28
  const newlineSymbol = import_format.NEWLINE_SYMBOL;
@@ -40,15 +41,31 @@ async function retryWrite(filepath, content, retries = 3, baseDelay = 10) {
40
41
  for (let attempt = 0; attempt <= retries; attempt++) {
41
42
  try {
42
43
  await fsPromises.writeFile(filepath, content);
44
+ if (process.platform === "win32") {
45
+ await (0, import_promises.setTimeout)(50);
46
+ let accessRetries = 0;
47
+ const maxAccessRetries = 5;
48
+ while (accessRetries < maxAccessRetries) {
49
+ try {
50
+ await fsPromises.access(filepath);
51
+ await (0, import_promises.setTimeout)(10);
52
+ break;
53
+ } catch {
54
+ const delay = 20 * (accessRetries + 1);
55
+ await (0, import_promises.setTimeout)(delay);
56
+ accessRetries++;
57
+ }
58
+ }
59
+ }
43
60
  return;
44
61
  } catch (err) {
45
62
  const isLastAttempt = attempt === retries;
46
- const isEperm = err instanceof Error && "code" in err && (err.code === "EPERM" || err.code === "EBUSY");
47
- if (!isEperm || isLastAttempt) {
63
+ const isRetriableError = err instanceof Error && "code" in err && (err.code === "EPERM" || err.code === "EBUSY" || err.code === "ENOENT");
64
+ if (!isRetriableError || isLastAttempt) {
48
65
  throw err;
49
66
  }
50
67
  const delay = baseDelay * 2 ** attempt;
51
- await new Promise((resolve) => setTimeout(resolve, delay));
68
+ await (0, import_promises.setTimeout)(delay);
52
69
  }
53
70
  }
54
71
  }
@@ -57,7 +74,21 @@ function parseJson(content) {
57
74
  }
58
75
  async function readFile(filepath) {
59
76
  const { promises: fsPromises } = /* @__PURE__ */ getFs();
60
- return await fsPromises.readFile(filepath, "utf8");
77
+ const maxRetries = process.platform === "win32" ? 5 : 1;
78
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
79
+ try {
80
+ return await fsPromises.readFile(filepath, "utf8");
81
+ } catch (err) {
82
+ const isLastAttempt = attempt === maxRetries;
83
+ const isEnoent = err instanceof Error && "code" in err && err.code === "ENOENT";
84
+ if (!isEnoent || isLastAttempt) {
85
+ throw err;
86
+ }
87
+ const delay = process.platform === "win32" ? 50 * (attempt + 1) : 20;
88
+ await (0, import_promises.setTimeout)(delay);
89
+ }
90
+ }
91
+ throw new Error("Unreachable code");
61
92
  }
62
93
  // @__NO_SIDE_EFFECTS__
63
94
  function getEditableJsonClass() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@socketsecurity/lib",
3
- "version": "5.0.0",
4
- "packageManager": "pnpm@10.22.0",
3
+ "version": "5.0.1",
4
+ "packageManager": "pnpm@10.25.0",
5
5
  "license": "MIT",
6
6
  "description": "Core utilities and infrastructure for Socket.dev security tools",
7
7
  "keywords": [
@@ -677,7 +677,7 @@
677
677
  ],
678
678
  "engines": {
679
679
  "node": ">=22",
680
- "pnpm": ">=10.22.0"
680
+ "pnpm": ">=10.25.0"
681
681
  },
682
682
  "sideEffects": false,
683
683
  "scripts": {
@@ -742,6 +742,7 @@
742
742
  "lint-staged": "15.2.11",
743
743
  "magic-string": "0.30.17",
744
744
  "make-fetch-happen": "15.0.2",
745
+ "nock": "14.0.10",
745
746
  "normalize-package-data": "8.0.0",
746
747
  "npm-package-arg": "13.0.0",
747
748
  "pacote": "21.0.1",
@@ -750,7 +751,7 @@
750
751
  "spdx-correct": "3.2.0",
751
752
  "spdx-expression-parse": "4.0.0",
752
753
  "streaming-iterables": "8.0.1",
753
- "taze": "19.6.0",
754
+ "taze": "19.9.2",
754
755
  "trash": "10.0.0",
755
756
  "type-coverage": "2.29.7",
756
757
  "typescript": "5.9.2",