@socketsecurity/lib 5.11.2 → 5.11.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.
@@ -29,6 +29,7 @@ __export(memoization_exports, {
29
29
  });
30
30
  module.exports = __toCommonJS(memoization_exports);
31
31
  var import_debug = require("./debug");
32
+ const cacheRegistry = [];
32
33
  function memoize(fn, options = {}) {
33
34
  const {
34
35
  keyGen = (...args) => JSON.stringify(args),
@@ -41,6 +42,10 @@ function memoize(fn, options = {}) {
41
42
  }
42
43
  const cache = /* @__PURE__ */ new Map();
43
44
  const accessOrder = [];
45
+ cacheRegistry.push(() => {
46
+ cache.clear();
47
+ accessOrder.length = 0;
48
+ });
44
49
  function evictLRU() {
45
50
  if (cache.size >= maxSize && accessOrder.length > 0) {
46
51
  const oldest = accessOrder.shift();
@@ -62,15 +67,22 @@ function memoize(fn, options = {}) {
62
67
  return function memoized(...args) {
63
68
  const key = keyGen(...args);
64
69
  const cached = cache.get(key);
65
- if (cached && !isExpired(cached)) {
66
- cached.hits++;
70
+ if (cached) {
71
+ if (!isExpired(cached)) {
72
+ cached.hits++;
73
+ const index2 = accessOrder.indexOf(key);
74
+ if (index2 !== -1) {
75
+ accessOrder.splice(index2, 1);
76
+ }
77
+ accessOrder.push(key);
78
+ (0, import_debug.debugLog)(`[memoize:${name}] hit`, { key, hits: cached.hits });
79
+ return cached.value;
80
+ }
81
+ cache.delete(key);
67
82
  const index = accessOrder.indexOf(key);
68
83
  if (index !== -1) {
69
84
  accessOrder.splice(index, 1);
70
85
  }
71
- accessOrder.push(key);
72
- (0, import_debug.debugLog)(`[memoize:${name}] hit`, { key, hits: cached.hits });
73
- return cached.value;
74
86
  }
75
87
  (0, import_debug.debugLog)(`[memoize:${name}] miss`, { key });
76
88
  const value = fn(...args);
@@ -94,6 +106,10 @@ function memoizeAsync(fn, options = {}) {
94
106
  } = options;
95
107
  const cache = /* @__PURE__ */ new Map();
96
108
  const accessOrder = [];
109
+ cacheRegistry.push(() => {
110
+ cache.clear();
111
+ accessOrder.length = 0;
112
+ });
97
113
  function evictLRU() {
98
114
  if (cache.size >= maxSize && accessOrder.length > 0) {
99
115
  const oldest = accessOrder.shift();
@@ -112,22 +128,36 @@ function memoizeAsync(fn, options = {}) {
112
128
  }
113
129
  return Date.now() - entry.timestamp > ttl;
114
130
  }
131
+ const refreshing = /* @__PURE__ */ new Set();
115
132
  return async function memoized(...args) {
116
133
  const key = keyGen(...args);
117
134
  const cached = cache.get(key);
118
- if (cached && !isExpired(cached)) {
119
- cached.hits++;
135
+ if (cached) {
136
+ if (!isExpired(cached)) {
137
+ cached.hits++;
138
+ const index2 = accessOrder.indexOf(key);
139
+ if (index2 !== -1) {
140
+ accessOrder.splice(index2, 1);
141
+ }
142
+ accessOrder.push(key);
143
+ (0, import_debug.debugLog)(`[memoizeAsync:${name}] hit`, { key, hits: cached.hits });
144
+ return await cached.value;
145
+ }
146
+ if (refreshing.has(key)) {
147
+ (0, import_debug.debugLog)(`[memoizeAsync:${name}] stale-dedup`, { key });
148
+ return await cached.value;
149
+ }
150
+ cache.delete(key);
120
151
  const index = accessOrder.indexOf(key);
121
152
  if (index !== -1) {
122
153
  accessOrder.splice(index, 1);
123
154
  }
124
- accessOrder.push(key);
125
- (0, import_debug.debugLog)(`[memoizeAsync:${name}] hit`, { key, hits: cached.hits });
126
- return await cached.value;
127
155
  }
128
156
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] miss`, { key });
157
+ refreshing.add(key);
129
158
  const promise = fn(...args).then(
130
159
  (result) => {
160
+ refreshing.delete(key);
131
161
  const entry = cache.get(key);
132
162
  if (entry) {
133
163
  entry.value = Promise.resolve(result);
@@ -135,6 +165,7 @@ function memoizeAsync(fn, options = {}) {
135
165
  return result;
136
166
  },
137
167
  (error) => {
168
+ refreshing.delete(key);
138
169
  cache.delete(key);
139
170
  const index = accessOrder.indexOf(key);
140
171
  if (index !== -1) {
@@ -167,14 +198,16 @@ function Memoize(options = {}) {
167
198
  }
168
199
  function clearAllMemoizationCaches() {
169
200
  (0, import_debug.debugLog)("[memoize:all] clear", { action: "clear-all-caches" });
201
+ for (const clear of cacheRegistry) {
202
+ clear();
203
+ }
170
204
  }
171
205
  function memoizeWeak(fn) {
172
206
  const cache = /* @__PURE__ */ new WeakMap();
173
207
  return function memoized(key) {
174
- const cached = cache.get(key);
175
- if (cached !== void 0) {
208
+ if (cache.has(key)) {
176
209
  (0, import_debug.debugLog)(`[memoizeWeak:${fn.name}] hit`);
177
- return cached;
210
+ return cache.get(key);
178
211
  }
179
212
  (0, import_debug.debugLog)(`[memoizeWeak:${fn.name}] miss`);
180
213
  const result = fn(key);
@@ -69,7 +69,15 @@ async function resolveRealPath(pathStr) {
69
69
  }
70
70
  async function mergePackageJson(pkgJsonPath, originalPkgJson) {
71
71
  const fs = /* @__PURE__ */ getFs();
72
- const pkgJson = JSON.parse(await fs.promises.readFile(pkgJsonPath, "utf8"));
72
+ let pkgJson;
73
+ try {
74
+ pkgJson = JSON.parse(await fs.promises.readFile(pkgJsonPath, "utf8"));
75
+ } catch (error) {
76
+ throw new Error(
77
+ `Failed to parse ${pkgJsonPath}: ${error instanceof Error ? error.message : String(error)}`,
78
+ { cause: error }
79
+ );
80
+ }
73
81
  const mergedPkgJson = originalPkgJson ? { ...originalPkgJson, ...pkgJson } : pkgJson;
74
82
  return mergedPkgJson;
75
83
  }
@@ -179,7 +179,14 @@ class ProcessLockManager {
179
179
  if (existsSync(lockPath)) {
180
180
  throw new Error(`Lock already exists: ${lockPath}`);
181
181
  }
182
- mkdirSync(lockPath, { recursive: true });
182
+ const lastSlash = Math.max(
183
+ lockPath.lastIndexOf("/"),
184
+ lockPath.lastIndexOf("\\")
185
+ );
186
+ if (lastSlash > 0) {
187
+ mkdirSync(lockPath.slice(0, lastSlash), { recursive: true });
188
+ }
189
+ mkdirSync(lockPath);
183
190
  this.activeLocks.add(lockPath);
184
191
  this.startTouchTimer(lockPath, touchIntervalMs);
185
192
  return () => this.release(lockPath);
@@ -204,7 +211,10 @@ class ProcessLockManager {
204
211
  );
205
212
  }
206
213
  if (code === "ENOTDIR") {
207
- const lastSlashIndex = lockPath.lastIndexOf("/");
214
+ const lastSlashIndex = Math.max(
215
+ lockPath.lastIndexOf("/"),
216
+ lockPath.lastIndexOf("\\")
217
+ );
208
218
  const parentDir = lastSlashIndex === -1 ? "." : lockPath.slice(0, lastSlashIndex);
209
219
  throw new Error(
210
220
  `Cannot create lock directory: ${lockPath}
@@ -218,7 +228,10 @@ To resolve:
218
228
  );
219
229
  }
220
230
  if (code === "ENOENT") {
221
- const lastSlashIndex = lockPath.lastIndexOf("/");
231
+ const lastSlashIndex = Math.max(
232
+ lockPath.lastIndexOf("/"),
233
+ lockPath.lastIndexOf("\\")
234
+ );
222
235
  const parentDir = lastSlashIndex === -1 ? "." : lockPath.slice(0, lastSlashIndex);
223
236
  throw new Error(
224
237
  `Cannot create lock directory: ${lockPath}
@@ -1,6 +1,7 @@
1
1
  export declare class PromiseQueue {
2
2
  private queue;
3
3
  private running;
4
+ private idleResolvers;
4
5
  private readonly maxConcurrency;
5
6
  private readonly maxQueueLength;
6
7
  /**
@@ -16,6 +17,7 @@ export declare class PromiseQueue {
16
17
  */
17
18
  add<T>(fn: () => Promise<T>): Promise<T>;
18
19
  private runNext;
20
+ private notifyIdleIfNeeded;
19
21
  /**
20
22
  * Wait for all queued and running tasks to complete
21
23
  */
@@ -25,6 +25,7 @@ module.exports = __toCommonJS(promise_queue_exports);
25
25
  class PromiseQueue {
26
26
  queue = [];
27
27
  running = 0;
28
+ idleResolvers = [];
28
29
  maxConcurrency;
29
30
  maxQueueLength;
30
31
  /**
@@ -47,7 +48,7 @@ class PromiseQueue {
47
48
  async add(fn) {
48
49
  return await new Promise((resolve, reject) => {
49
50
  const task = { fn, resolve, reject };
50
- if (this.maxQueueLength && this.queue.length >= this.maxQueueLength) {
51
+ if (this.maxQueueLength !== void 0 && this.queue.length >= this.maxQueueLength) {
51
52
  const droppedTask = this.queue.shift();
52
53
  if (droppedTask) {
53
54
  droppedTask.reject(new Error("Task dropped: queue length exceeded"));
@@ -59,6 +60,7 @@ class PromiseQueue {
59
60
  }
60
61
  runNext() {
61
62
  if (this.running >= this.maxConcurrency || this.queue.length === 0) {
63
+ this.notifyIdleIfNeeded();
62
64
  return;
63
65
  }
64
66
  const task = this.queue.shift();
@@ -71,19 +73,23 @@ class PromiseQueue {
71
73
  this.runNext();
72
74
  });
73
75
  }
76
+ notifyIdleIfNeeded() {
77
+ if (this.running === 0 && this.queue.length === 0) {
78
+ for (const resolve of this.idleResolvers) {
79
+ resolve();
80
+ }
81
+ this.idleResolvers = [];
82
+ }
83
+ }
74
84
  /**
75
85
  * Wait for all queued and running tasks to complete
76
86
  */
77
87
  async onIdle() {
88
+ if (this.running === 0 && this.queue.length === 0) {
89
+ return;
90
+ }
78
91
  return await new Promise((resolve) => {
79
- const check = () => {
80
- if (this.running === 0 && this.queue.length === 0) {
81
- resolve();
82
- } else {
83
- setImmediate(check);
84
- }
85
- };
86
- check();
92
+ this.idleResolvers.push(resolve);
87
93
  });
88
94
  }
89
95
  /**
@@ -102,7 +108,12 @@ class PromiseQueue {
102
108
  * Clear all pending tasks from the queue (does not affect running tasks)
103
109
  */
104
110
  clear() {
111
+ const pending = this.queue;
105
112
  this.queue = [];
113
+ for (const task of pending) {
114
+ task.reject(new Error("Task cancelled: queue cleared"));
115
+ }
116
+ this.notifyIdleIfNeeded();
106
117
  }
107
118
  }
108
119
  // Annotate the CommonJS export names for ESM import in node:
package/dist/promises.js CHANGED
@@ -203,9 +203,7 @@ async function pRetry(callbackFn, options) {
203
203
  try {
204
204
  return await callbackFn(...args || [], { signal });
205
205
  } catch (e) {
206
- if (error === import_core.UNDEFINED_TOKEN) {
207
- error = e;
208
- }
206
+ error = e;
209
207
  if (attempts < 0) {
210
208
  break;
211
209
  }
@@ -294,7 +294,11 @@ async function getReleaseAssetUrl(tag, assetPattern, repoConfig, options = {}) {
294
294
  { cause }
295
295
  );
296
296
  }
297
- const asset = release.assets.find((a) => isMatch(a.name));
297
+ const assets = release.assets;
298
+ if (!Array.isArray(assets)) {
299
+ throw new Error(`Release ${tag} has no assets`);
300
+ }
301
+ const asset = assets.find((a) => isMatch(a.name));
298
302
  if (!asset) {
299
303
  const patternDesc = typeof assetPattern === "string" ? assetPattern : "matching pattern";
300
304
  throw new Error(`Asset ${patternDesc} not found in release ${tag}`);
@@ -196,7 +196,7 @@ class ProgressBar {
196
196
  }
197
197
  }
198
198
  function createProgressIndicator(current, total, label) {
199
- const percent = Math.floor(current / total * 100);
199
+ const percent = total === 0 ? 0 : Math.floor(current / total * 100);
200
200
  const progress = `${current}/${total}`;
201
201
  let output = "";
202
202
  if (label) {
@@ -27,7 +27,15 @@ __export(json_parser_exports, {
27
27
  tryJsonParse: () => tryJsonParse
28
28
  });
29
29
  module.exports = __toCommonJS(json_parser_exports);
30
- const { hasOwn: ObjectHasOwn } = Object;
30
+ const DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
31
+ function prototypePollutionReviver(key, value) {
32
+ if (DANGEROUS_KEYS.has(key)) {
33
+ throw new Error(
34
+ "JSON contains potentially malicious prototype pollution keys"
35
+ );
36
+ }
37
+ return value;
38
+ }
31
39
  function safeJsonParse(jsonString, schema, options = {}) {
32
40
  const { allowPrototype = false, maxSize = 10 * 1024 * 1024 } = options;
33
41
  const byteLength = Buffer.byteLength(jsonString, "utf8");
@@ -38,20 +46,10 @@ function safeJsonParse(jsonString, schema, options = {}) {
38
46
  }
39
47
  let parsed;
40
48
  try {
41
- parsed = JSON.parse(jsonString);
49
+ parsed = allowPrototype ? JSON.parse(jsonString) : JSON.parse(jsonString, prototypePollutionReviver);
42
50
  } catch (error) {
43
51
  throw new Error(`Failed to parse JSON: ${error}`);
44
52
  }
45
- if (!allowPrototype && typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
46
- const dangerous = ["__proto__", "constructor", "prototype"];
47
- for (const key of dangerous) {
48
- if (ObjectHasOwn(parsed, key)) {
49
- throw new Error(
50
- "JSON contains potentially malicious prototype pollution keys"
51
- );
52
- }
53
- }
54
- }
55
53
  if (schema) {
56
54
  const result = schema.safeParse(parsed);
57
55
  if (!result.success) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socketsecurity/lib",
3
- "version": "5.11.2",
3
+ "version": "5.11.3",
4
4
  "packageManager": "pnpm@10.33.0",
5
5
  "license": "MIT",
6
6
  "description": "Core utilities and infrastructure for Socket.dev security tools",
@@ -734,7 +734,7 @@
734
734
  "@socketregistry/is-unicode-supported": "1.0.5",
735
735
  "@socketregistry/packageurl-js": "1.3.5",
736
736
  "@socketregistry/yocto-spinner": "1.0.25",
737
- "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.11.1",
737
+ "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.11.2",
738
738
  "@types/node": "24.9.2",
739
739
  "@typescript/native-preview": "7.0.0-dev.20250920.1",
740
740
  "@vitest/coverage-v8": "4.0.3",
@@ -764,15 +764,16 @@
764
764
  "npm-package-arg": "13.0.0",
765
765
  "oxfmt": "^0.37.0",
766
766
  "oxlint": "1.53.0",
767
+ "p-map": "7.0.4",
767
768
  "pacote": "21.0.1",
768
- "picomatch": "2.3.1",
769
+ "picomatch": "4.0.4",
769
770
  "pony-cause": "2.1.11",
770
771
  "semver": "7.7.2",
771
772
  "signal-exit": "4.1.0",
772
773
  "spdx-correct": "3.2.0",
773
774
  "spdx-expression-parse": "4.0.0",
774
775
  "streaming-iterables": "8.0.1",
775
- "supports-color": "10.0.0",
776
+ "supports-color": "10.2.2",
776
777
  "tar-fs": "3.1.2",
777
778
  "tar-stream": "3.1.8",
778
779
  "taze": "19.9.2",
@@ -821,7 +822,8 @@
821
822
  "minizlib": "3.1.0",
822
823
  "npm-package-arg": "12.0.2",
823
824
  "npm-pick-manifest": "10.0.0",
824
- "picomatch": "4.0.3",
825
+ "p-map": "7.0.4",
826
+ "picomatch": "4.0.4",
825
827
  "proc-log": "6.1.0",
826
828
  "semver": "7.7.2",
827
829
  "signal-exit": "4.1.0",
@@ -829,7 +831,7 @@
829
831
  "ssri": "12.0.0",
830
832
  "string-width": "8.1.0",
831
833
  "strip-ansi": "7.1.2",
832
- "supports-color": "10.0.0",
834
+ "supports-color": "10.2.2",
833
835
  "tar": "7.5.11",
834
836
  "which": "5.0.0",
835
837
  "wrap-ansi": "9.0.2",