@socketsecurity/lib 5.19.1 → 5.21.0

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +105 -74
  2. package/dist/archives.js +13 -0
  3. package/dist/cacache.js +6 -8
  4. package/dist/cache-with-ttl.d.ts +7 -0
  5. package/dist/cache-with-ttl.js +27 -8
  6. package/dist/constants/socket.js +1 -1
  7. package/dist/dlx/detect.js +25 -8
  8. package/dist/dlx/lockfile.js +4 -1
  9. package/dist/dlx/manifest.d.ts +10 -4
  10. package/dist/dlx/package.d.ts +1 -1
  11. package/dist/dlx/package.js +19 -3
  12. package/dist/external/@npmcli/package-json/lib/read-package.js +40 -32
  13. package/dist/external/@npmcli/package-json/lib/sort.js +104 -92
  14. package/dist/external/@npmcli/package-json.js +9 -3968
  15. package/dist/external/@sinclair/typebox/value.js +9007 -0
  16. package/dist/external/@sinclair/typebox.js +7891 -0
  17. package/dist/external/debug.js +162 -328
  18. package/dist/external/npm-pack.js +13935 -33342
  19. package/dist/fs.js +8 -2
  20. package/dist/globs.js +5 -1
  21. package/dist/http-request.d.ts +0 -25
  22. package/dist/http-request.js +6 -5
  23. package/dist/ipc.js +43 -10
  24. package/dist/json/edit.d.ts +1 -1
  25. package/dist/json/parse.d.ts +47 -2
  26. package/dist/json/parse.js +40 -2
  27. package/dist/json/types.d.ts +49 -0
  28. package/dist/memoization.d.ts +4 -23
  29. package/dist/memoization.js +15 -49
  30. package/dist/packages/specs.js +9 -2
  31. package/dist/paths/packages.js +6 -2
  32. package/dist/process-lock.js +1 -6
  33. package/dist/promise-queue.d.ts +9 -4
  34. package/dist/promise-queue.js +10 -8
  35. package/dist/promises.d.ts +41 -0
  36. package/dist/promises.js +19 -2
  37. package/dist/regexps.d.ts +4 -13
  38. package/dist/regexps.js +60 -3
  39. package/dist/schema/parse.d.ts +26 -0
  40. package/dist/{zod.js → schema/parse.js} +14 -6
  41. package/dist/schema/types.d.ts +121 -0
  42. package/dist/schema/validate.d.ts +35 -0
  43. package/dist/schema/validate.js +98 -0
  44. package/dist/stdio/progress.js +1 -1
  45. package/dist/suppress-warnings.js +0 -2
  46. package/dist/tables.js +2 -3
  47. package/dist/url.js +5 -1
  48. package/dist/versions.js +2 -2
  49. package/dist/words.js +4 -7
  50. package/package.json +15 -14
  51. package/dist/external/zod.js +0 -15223
  52. package/dist/validation/json-parser.d.ts +0 -58
  53. package/dist/validation/json-parser.js +0 -63
  54. package/dist/validation/types.d.ts +0 -118
  55. package/dist/zod.d.ts +0 -5
  56. /package/dist/{validation → schema}/types.js +0 -0
package/dist/fs.js CHANGED
@@ -167,7 +167,7 @@ async function findUp(name, options) {
167
167
  let dir = path.resolve(cwd);
168
168
  const { root } = path.parse(dir);
169
169
  const names = (0, import_arrays.isArray)(name) ? name : [name];
170
- while (dir && dir !== root) {
170
+ while (dir) {
171
171
  for (const n of names) {
172
172
  if (signal?.aborted) {
173
173
  return void 0;
@@ -184,6 +184,9 @@ async function findUp(name, options) {
184
184
  } catch {
185
185
  }
186
186
  }
187
+ if (dir === root) {
188
+ break;
189
+ }
187
190
  dir = path.dirname(dir);
188
191
  }
189
192
  return void 0;
@@ -210,7 +213,7 @@ function findUpSync(name, options) {
210
213
  const { root } = path.parse(dir);
211
214
  const stopDir = stopAt ? path.resolve(stopAt) : void 0;
212
215
  const names = (0, import_arrays.isArray)(name) ? name : [name];
213
- while (dir && dir !== root) {
216
+ while (dir) {
214
217
  if (stopDir && dir === stopDir) {
215
218
  for (const n of names) {
216
219
  const thePath = path.join(dir, n);
@@ -240,6 +243,9 @@ function findUpSync(name, options) {
240
243
  } catch {
241
244
  }
242
245
  }
246
+ if (dir === root) {
247
+ break;
248
+ }
243
249
  dir = path.dirname(dir);
244
250
  }
245
251
  return void 0;
package/dist/globs.js CHANGED
@@ -91,7 +91,11 @@ function getPicomatch() {
91
91
  function getGlobMatcher(glob2, options) {
92
92
  const patterns = Array.isArray(glob2) ? glob2 : [glob2];
93
93
  const sortedPatterns = [...patterns].sort();
94
- const sortedOptions = options ? Object.keys(options).sort().map((k) => `${k}:${JSON.stringify(options[k])}`).join(",") : "";
94
+ const sortedOptions = options ? Object.keys(options).sort().map((k) => {
95
+ const value = options[k];
96
+ const normalized = Array.isArray(value) ? [...value].sort() : value;
97
+ return `${k}:${JSON.stringify(normalized)}`;
98
+ }).join(",") : "";
95
99
  const key = `${sortedPatterns.join("|")}:${sortedOptions}`;
96
100
  const existing = matcherCache.get(key);
97
101
  if (existing) {
@@ -890,31 +890,6 @@ export declare function httpRequest(url: string, options?: HttpRequestOptions |
890
890
  * ```
891
891
  */
892
892
  export declare function httpText(url: string, options?: HttpRequestOptions | undefined): Promise<string>;
893
- /**
894
- * Parse a checksums file text into a filename-to-hash map.
895
- *
896
- * Supports standard checksums file formats:
897
- * - BSD style: "SHA256 (filename) = hash"
898
- * - GNU style: "hash filename" (two spaces)
899
- * - Simple style: "hash filename" (single space)
900
- *
901
- * Lines starting with '#' are treated as comments and ignored.
902
- * Empty lines are ignored.
903
- *
904
- * @param text - Raw text content of a checksums file
905
- * @returns Map of filenames to lowercase SHA256 hashes
906
- *
907
- * @example
908
- * ```ts
909
- * const text = `
910
- * # SHA256 checksums
911
- * e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 file.zip
912
- * abc123def456... other.tar.gz
913
- * `
914
- * const checksums = parseChecksums(text)
915
- * console.log(checksums['file.zip']) // 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
916
- * ```
917
- */
918
893
  export declare function parseChecksums(text: string): Checksums;
919
894
  /**
920
895
  * Parse a `Retry-After` HTTP header value into milliseconds.
@@ -644,6 +644,9 @@ async function httpText(url, options) {
644
644
  }
645
645
  return response.text();
646
646
  }
647
+ const CHECKSUM_BSD_RE = /^SHA256\s+\((.+)\)\s+=\s+([a-fA-F0-9]{64})$/;
648
+ const CHECKSUM_GNU_RE = /^([a-fA-F0-9]{64})\s+(.+)$/;
649
+ const RETRY_AFTER_INT_RE = /^\d+$/;
647
650
  function parseChecksums(text) {
648
651
  const checksums = { __proto__: null };
649
652
  for (const line of text.split("\n")) {
@@ -651,14 +654,12 @@ function parseChecksums(text) {
651
654
  if (!trimmed || trimmed.startsWith("#")) {
652
655
  continue;
653
656
  }
654
- const bsdMatch = trimmed.match(
655
- /^SHA256\s+\((.+)\)\s+=\s+([a-fA-F0-9]{64})$/
656
- );
657
+ const bsdMatch = CHECKSUM_BSD_RE.exec(trimmed);
657
658
  if (bsdMatch) {
658
659
  checksums[bsdMatch[1]] = bsdMatch[2].toLowerCase();
659
660
  continue;
660
661
  }
661
- const gnuMatch = trimmed.match(/^([a-fA-F0-9]{64})\s+(.+)$/);
662
+ const gnuMatch = CHECKSUM_GNU_RE.exec(trimmed);
662
663
  if (gnuMatch) {
663
664
  checksums[gnuMatch[2]] = gnuMatch[1].toLowerCase();
664
665
  }
@@ -674,7 +675,7 @@ function parseRetryAfterHeader(value) {
674
675
  return void 0;
675
676
  }
676
677
  const trimmed = raw.trim();
677
- if (/^\d+$/.test(trimmed)) {
678
+ if (RETRY_AFTER_INT_RE.test(trimmed)) {
678
679
  const seconds = Number(trimmed);
679
680
  return seconds * 1e3;
680
681
  }
package/dist/ipc.js CHANGED
@@ -35,15 +35,16 @@ __export(ipc_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(ipc_exports);
37
37
  var import_node_process = __toESM(require("node:process"));
38
+ var import_typebox = require("./external/@sinclair/typebox");
38
39
  var import_socket = require("./paths/socket");
39
- var import_zod = require("./zod");
40
- const IpcStubSchema = import_zod.z.object({
40
+ var import_parse = require("./schema/parse");
41
+ const IpcStubSchema = import_typebox.Type.Object({
41
42
  /** Process ID that created the stub. */
42
- pid: import_zod.z.number().int().positive(),
43
+ pid: import_typebox.Type.Integer({ minimum: 1 }),
43
44
  /** Creation timestamp for age validation. */
44
- timestamp: import_zod.z.number().positive(),
45
+ timestamp: import_typebox.Type.Number({ exclusiveMinimum: 0 }),
45
46
  /** The actual data payload. */
46
- data: import_zod.z.unknown()
47
+ data: import_typebox.Type.Unknown()
47
48
  });
48
49
  let _fs;
49
50
  let _path;
@@ -52,6 +53,24 @@ async function ensureIpcDirectory(filePath) {
52
53
  const path = /* @__PURE__ */ getPath();
53
54
  const dir = path.dirname(filePath);
54
55
  await fs.promises.mkdir(dir, { recursive: true, mode: 448 });
56
+ if (import_node_process.default.platform === "win32") {
57
+ return;
58
+ }
59
+ const stats = await fs.promises.lstat(dir);
60
+ if (!stats.isDirectory()) {
61
+ throw new Error(`IPC path is not a directory: ${dir}`);
62
+ }
63
+ const getuid = import_node_process.default.getuid;
64
+ const ownUid = typeof getuid === "function" ? getuid.call(import_node_process.default) : -1;
65
+ if (ownUid !== -1 && stats.uid !== ownUid) {
66
+ throw new Error(
67
+ `IPC directory ${dir} is owned by another user (uid ${stats.uid}); refusing to use it.`
68
+ );
69
+ }
70
+ const mode = stats.mode & 511;
71
+ if ((mode & 63) !== 0) {
72
+ await fs.promises.chmod(dir, 448);
73
+ }
55
74
  }
56
75
  // @__NO_SIDE_EFFECTS__
57
76
  function getFs() {
@@ -81,12 +100,26 @@ async function writeIpcStub(appName, data) {
81
100
  pid: import_node_process.default.pid,
82
101
  timestamp: Date.now()
83
102
  };
84
- const validated = IpcStubSchema.parse(ipcData);
103
+ const validated = (0, import_parse.parseSchema)(IpcStubSchema, ipcData);
85
104
  const fs = /* @__PURE__ */ getFs();
86
- await fs.promises.writeFile(stubPath, JSON.stringify(validated, null, 2), {
87
- encoding: "utf8",
88
- mode: 384
89
- });
105
+ const flags = fs.constants.O_CREAT | fs.constants.O_WRONLY | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW;
106
+ let handle;
107
+ try {
108
+ handle = await fs.promises.open(stubPath, flags, 384);
109
+ } catch (e) {
110
+ const err = e;
111
+ if (err.code === "EEXIST") {
112
+ await fs.promises.unlink(stubPath);
113
+ handle = await fs.promises.open(stubPath, flags, 384);
114
+ } else {
115
+ throw err;
116
+ }
117
+ }
118
+ try {
119
+ await handle.writeFile(JSON.stringify(validated, null, 2), "utf8");
120
+ } finally {
121
+ await handle.close();
122
+ }
90
123
  return stubPath;
91
124
  }
92
125
  // Annotate the CommonJS export names for ESM import in node:
@@ -7,7 +7,7 @@ import type { EditableJsonConstructor } from './types';
7
7
  *
8
8
  * @example
9
9
  * ```ts
10
- * import { getEditableJsonClass } from '@socketsecurity/lib/json'
10
+ * import { getEditableJsonClass } from '@socketsecurity/lib/json/edit'
11
11
  *
12
12
  * const EditableJson = getEditableJsonClass<MyConfigType>()
13
13
  * const config = await EditableJson.load('./config.json')
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * @fileoverview JSON parsing utilities with Buffer detection and BOM stripping.
3
- * Provides safe JSON parsing with automatic encoding handling.
3
+ * Provides safe JSON parsing with automatic encoding handling, plus
4
+ * `safeJsonParse` for untrusted input (prototype-pollution protection +
5
+ * size limits + optional schema validation).
4
6
  */
5
- import type { JsonParseOptions, JsonPrimitive, JsonValue } from './types';
7
+ import type { Schema } from '../schema/types';
8
+ import type { JsonParseOptions, JsonPrimitive, JsonValue, SafeJsonParseOptions } from './types';
6
9
  /**
7
10
  * Check if a value is a JSON primitive type.
8
11
  * JSON primitives are: `null`, `boolean`, `number`, or `string`.
@@ -76,3 +79,45 @@ export declare function isJsonPrimitive(value: unknown): value is JsonPrimitive;
76
79
  * ```
77
80
  */
78
81
  export declare function jsonParse(content: string | Buffer, options?: JsonParseOptions | undefined): JsonValue | undefined;
82
+ /**
83
+ * Safely parse JSON with optional schema validation and security controls.
84
+ * Throws on parse failure, validation failure, or security violation.
85
+ *
86
+ * Recommended for parsing untrusted JSON (user input, network payloads,
87
+ * anything beyond a trust boundary). Layers:
88
+ * 1. Size cap (default 10 MB) prevents memory exhaustion.
89
+ * 2. Prototype-pollution reviver rejects `__proto__` / `constructor` /
90
+ * `prototype` keys at any depth (unless `allowPrototype: true`).
91
+ * 3. Optional Zod-shaped schema validation via
92
+ * `@socketsecurity/lib/schema/validate`.
93
+ *
94
+ * For trusted-source reads (package.json, local config files), prefer
95
+ * `jsonParse()` — it offers Buffer/BOM handling and filepath-aware error
96
+ * messages, without the untrusted-input overhead.
97
+ *
98
+ * @throws {Error} When `jsonString` exceeds `maxSize`.
99
+ * @throws {Error} When JSON parsing fails.
100
+ * @throws {Error} When prototype-pollution keys are detected (and
101
+ * `allowPrototype` is not `true`).
102
+ * @throws {Error} When schema validation fails.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * // Basic parsing with type inference.
107
+ * const data = safeJsonParse<User>('{"name":"Alice","age":30}')
108
+ *
109
+ * // With schema validation.
110
+ * import { z } from 'zod'
111
+ * const userSchema = z.object({ name: z.string(), age: z.number() })
112
+ * const user = safeJsonParse('{"name":"Alice","age":30}', userSchema)
113
+ *
114
+ * // With size limit.
115
+ * const data = safeJsonParse(jsonString, undefined, { maxSize: 1024 })
116
+ *
117
+ * // Allow prototype keys (DANGEROUS — only for trusted sources).
118
+ * const data = safeJsonParse('{"__proto__":{}}', undefined, {
119
+ * allowPrototype: true,
120
+ * })
121
+ * ```
122
+ */
123
+ export declare function safeJsonParse<T = unknown>(jsonString: string, schema?: Schema<T> | undefined, options?: SafeJsonParseOptions): T;
@@ -21,9 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var parse_exports = {};
22
22
  __export(parse_exports, {
23
23
  isJsonPrimitive: () => isJsonPrimitive,
24
- jsonParse: () => jsonParse
24
+ jsonParse: () => jsonParse,
25
+ safeJsonParse: () => safeJsonParse
25
26
  });
26
27
  module.exports = __toCommonJS(parse_exports);
28
+ var import_validate = require("../schema/validate");
27
29
  var import_strings = require("../strings");
28
30
  const JSONParse = JSON.parse;
29
31
  // @__NO_SIDE_EFFECTS__
@@ -69,8 +71,44 @@ function jsonParse(content, options) {
69
71
  }
70
72
  return void 0;
71
73
  }
74
+ const DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
75
+ function prototypePollutionReviver(key, value) {
76
+ if (DANGEROUS_KEYS.has(key)) {
77
+ throw new Error(
78
+ "JSON contains potentially malicious prototype pollution keys"
79
+ );
80
+ }
81
+ return value;
82
+ }
83
+ const DEFAULT_MAX_SIZE = 10 * 1024 * 1024;
84
+ // @__NO_SIDE_EFFECTS__
85
+ function safeJsonParse(jsonString, schema, options = {}) {
86
+ const { allowPrototype = false, maxSize = DEFAULT_MAX_SIZE } = options;
87
+ const byteLength = Buffer.byteLength(jsonString, "utf8");
88
+ if (byteLength > maxSize) {
89
+ throw new Error(
90
+ `JSON string exceeds maximum size limit${maxSize !== DEFAULT_MAX_SIZE ? ` of ${maxSize} bytes` : ""}`
91
+ );
92
+ }
93
+ let parsed;
94
+ try {
95
+ parsed = allowPrototype ? JSONParse(jsonString) : JSONParse(jsonString, prototypePollutionReviver);
96
+ } catch (error) {
97
+ throw new Error(`Failed to parse JSON: ${error}`);
98
+ }
99
+ if (schema) {
100
+ const result = (0, import_validate.validateSchema)(schema, parsed);
101
+ if (!result.ok) {
102
+ const summary = result.errors.map((e) => `${e.path.join(".") || "(root)"}: ${e.message}`).join(", ");
103
+ throw new Error(`Validation failed: ${summary}`);
104
+ }
105
+ return result.value;
106
+ }
107
+ return parsed;
108
+ }
72
109
  // Annotate the CommonJS export names for ESM import in node:
73
110
  0 && (module.exports = {
74
111
  isJsonPrimitive,
75
- jsonParse
112
+ jsonParse,
113
+ safeJsonParse
76
114
  });
@@ -72,6 +72,55 @@ export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
72
72
  * ```
73
73
  */
74
74
  export type JsonReviver = (key: string, value: unknown) => unknown;
75
+ /**
76
+ * Options for `safeJsonParse`: security controls for untrusted JSON.
77
+ *
78
+ * Distinct from `JsonParseOptions` (which is scoped to reviver /
79
+ * error-handling for trusted-source fs reads). Use this type when
80
+ * parsing user input, network payloads, or anything beyond a trust
81
+ * boundary.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const options: SafeJsonParseOptions = {
86
+ * maxSize: 1024 * 1024, // 1MB limit
87
+ * allowPrototype: false // Block prototype pollution
88
+ * }
89
+ * ```
90
+ */
91
+ export interface SafeJsonParseOptions {
92
+ /**
93
+ * Allow dangerous prototype pollution keys (`__proto__`, `constructor`, `prototype`).
94
+ * Set to `true` only if you trust the JSON source completely.
95
+ *
96
+ * @default false
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * // Will throw error by default
101
+ * safeJsonParse('{"__proto__": {"polluted": true}}')
102
+ *
103
+ * // Allows the parse (dangerous!)
104
+ * safeJsonParse('{"__proto__": {"polluted": true}}', undefined, {
105
+ * allowPrototype: true
106
+ * })
107
+ * ```
108
+ */
109
+ allowPrototype?: boolean | undefined;
110
+ /**
111
+ * Maximum allowed size of JSON string in bytes.
112
+ * Prevents memory exhaustion from extremely large payloads.
113
+ *
114
+ * @default 10_485_760 (10 MB)
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * // Limit to 1KB
119
+ * safeJsonParse(jsonString, undefined, { maxSize: 1024 })
120
+ * ```
121
+ */
122
+ maxSize?: number | undefined;
123
+ }
75
124
  /**
76
125
  * Options for JSON parsing operations.
77
126
  */
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Options for memoization behavior.
7
7
  */
8
- export type MemoizeOptions<Args extends unknown[], _Result = unknown> = {
8
+ export type MemoizeOptions<Args extends unknown[]> = {
9
9
  /** Custom cache key generator (defaults to JSON.stringify) */
10
10
  keyGen?: (...args: Args) => string;
11
11
  /** Maximum cache size (LRU eviction when exceeded) */
@@ -49,7 +49,7 @@ export declare function clearAllMemoizationCaches(): void;
49
49
  * }
50
50
  * }
51
51
  */
52
- export declare function Memoize(options?: MemoizeOptions<unknown[], unknown>): (_target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
52
+ export declare function Memoize(options?: MemoizeOptions<unknown[]>): (_target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
53
53
  /**
54
54
  * Memoize a function with configurable caching behavior.
55
55
  * Caches function results to avoid repeated computation.
@@ -69,7 +69,7 @@ export declare function Memoize(options?: MemoizeOptions<unknown[], unknown>): (
69
69
  * expensiveOperation(1000) // Computed
70
70
  * expensiveOperation(1000) // Cached
71
71
  */
72
- export declare function memoize<Args extends unknown[], Result>(fn: (...args: Args) => Result, options?: MemoizeOptions<Args, Result>): (...args: Args) => Result;
72
+ export declare function memoize<Args extends unknown[], Result>(fn: (...args: Args) => Result, options?: MemoizeOptions<Args>): (...args: Args) => Result;
73
73
  /**
74
74
  * Memoize an async function.
75
75
  * Similar to memoize() but handles promises properly.
@@ -89,26 +89,7 @@ export declare function memoize<Args extends unknown[], Result>(fn: (...args: Ar
89
89
  * await fetchUser('123') // Fetches from API
90
90
  * await fetchUser('123') // Returns cached result
91
91
  */
92
- export declare function memoizeAsync<Args extends unknown[], Result>(fn: (...args: Args) => Promise<Result>, options?: MemoizeOptions<Args, Result>): (...args: Args) => Promise<Result>;
93
- /**
94
- * Create a debounced memoized function.
95
- * Combines memoization with debouncing for expensive operations.
96
- *
97
- * @param fn - Function to memoize and debounce
98
- * @param wait - Debounce wait time in milliseconds
99
- * @param options - Memoization options
100
- * @returns Debounced memoized function
101
- *
102
- * @example
103
- * import { memoizeDebounced } from '@socketsecurity/lib/memoization'
104
- *
105
- * const search = memoizeDebounced(
106
- * (query: string) => performSearch(query),
107
- * 300,
108
- * { name: 'search' }
109
- * )
110
- */
111
- export declare function memoizeDebounced<Args extends unknown[], Result>(fn: (...args: Args) => Result, wait: number, options?: MemoizeOptions<Args, Result>): (...args: Args) => Result;
92
+ export declare function memoizeAsync<Args extends unknown[], Result>(fn: (...args: Args) => Promise<Result>, options?: MemoizeOptions<Args>): (...args: Args) => Promise<Result>;
112
93
  /**
113
94
  * Memoize with WeakMap for object keys.
114
95
  * Allows garbage collection when objects are no longer referenced.
@@ -24,7 +24,6 @@ __export(memoization_exports, {
24
24
  clearAllMemoizationCaches: () => clearAllMemoizationCaches,
25
25
  memoize: () => memoize,
26
26
  memoizeAsync: () => memoizeAsync,
27
- memoizeDebounced: () => memoizeDebounced,
28
27
  memoizeWeak: () => memoizeWeak,
29
28
  once: () => once
30
29
  });
@@ -78,15 +77,13 @@ function memoize(fn, options = {}) {
78
77
  throw new TypeError("TTL must be non-negative");
79
78
  }
80
79
  const cache = /* @__PURE__ */ new Map();
81
- const accessOrder = [];
82
80
  cacheRegistry.push(() => {
83
81
  cache.clear();
84
- accessOrder.length = 0;
85
82
  });
86
83
  function evictLRU() {
87
- if (cache.size >= maxSize && accessOrder.length > 0) {
88
- const oldest = accessOrder.shift();
89
- if (oldest) {
84
+ if (cache.size >= maxSize) {
85
+ const oldest = cache.keys().next().value;
86
+ if (oldest !== void 0) {
90
87
  cache.delete(oldest);
91
88
  (0, import_debug.debugLog)(`[memoize:${name}] clear`, {
92
89
  key: oldest,
@@ -107,19 +104,12 @@ function memoize(fn, options = {}) {
107
104
  if (cached) {
108
105
  if (!isExpired(cached)) {
109
106
  cached.hits++;
110
- const index2 = accessOrder.indexOf(key);
111
- if (index2 !== -1) {
112
- accessOrder.splice(index2, 1);
113
- }
114
- accessOrder.push(key);
107
+ cache.delete(key);
108
+ cache.set(key, cached);
115
109
  (0, import_debug.debugLog)(`[memoize:${name}] hit`, { key, hits: cached.hits });
116
110
  return cached.value;
117
111
  }
118
112
  cache.delete(key);
119
- const index = accessOrder.indexOf(key);
120
- if (index !== -1) {
121
- accessOrder.splice(index, 1);
122
- }
123
113
  }
124
114
  (0, import_debug.debugLog)(`[memoize:${name}] miss`, { key });
125
115
  const value = fn(...args);
@@ -129,7 +119,6 @@ function memoize(fn, options = {}) {
129
119
  timestamp: Date.now(),
130
120
  hits: 0
131
121
  });
132
- accessOrder.push(key);
133
122
  (0, import_debug.debugLog)(`[memoize:${name}] set`, { key, cacheSize: cache.size });
134
123
  return value;
135
124
  };
@@ -142,15 +131,13 @@ function memoizeAsync(fn, options = {}) {
142
131
  ttl = Number.POSITIVE_INFINITY
143
132
  } = options;
144
133
  const cache = /* @__PURE__ */ new Map();
145
- const accessOrder = [];
146
134
  cacheRegistry.push(() => {
147
135
  cache.clear();
148
- accessOrder.length = 0;
149
136
  });
150
137
  function evictLRU() {
151
- if (cache.size >= maxSize && accessOrder.length > 0) {
152
- const oldest = accessOrder.shift();
153
- if (oldest) {
138
+ if (cache.size >= maxSize) {
139
+ const oldest = cache.keys().next().value;
140
+ if (oldest !== void 0) {
154
141
  cache.delete(oldest);
155
142
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] clear`, {
156
143
  key: oldest,
@@ -165,6 +152,10 @@ function memoizeAsync(fn, options = {}) {
165
152
  }
166
153
  return Date.now() - entry.timestamp > ttl;
167
154
  }
155
+ function bumpRecency(key, entry) {
156
+ cache.delete(key);
157
+ cache.set(key, entry);
158
+ }
168
159
  const refreshing = /* @__PURE__ */ new Map();
169
160
  return async function memoized(...args) {
170
161
  const key = keyGen(...args);
@@ -172,24 +163,17 @@ function memoizeAsync(fn, options = {}) {
172
163
  if (cached) {
173
164
  if (!isExpired(cached)) {
174
165
  cached.hits++;
175
- const index2 = accessOrder.indexOf(key);
176
- if (index2 !== -1) {
177
- accessOrder.splice(index2, 1);
178
- }
179
- accessOrder.push(key);
166
+ bumpRecency(key, cached);
180
167
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] hit`, { key, hits: cached.hits });
181
168
  return await cached.value;
182
169
  }
183
170
  const inflight = refreshing.get(key);
184
171
  if (inflight) {
185
172
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] stale-dedup`, { key });
173
+ bumpRecency(key, cached);
186
174
  return await inflight;
187
175
  }
188
176
  cache.delete(key);
189
- const index = accessOrder.indexOf(key);
190
- if (index !== -1) {
191
- accessOrder.splice(index, 1);
192
- }
193
177
  }
194
178
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] miss`, { key });
195
179
  const promise = fn(...args).then(
@@ -198,16 +182,13 @@ function memoizeAsync(fn, options = {}) {
198
182
  const entry = cache.get(key);
199
183
  if (entry) {
200
184
  entry.value = Promise.resolve(result);
185
+ entry.timestamp = Date.now();
201
186
  }
202
187
  return result;
203
188
  },
204
189
  (error) => {
205
190
  refreshing.delete(key);
206
191
  cache.delete(key);
207
- const index = accessOrder.indexOf(key);
208
- if (index !== -1) {
209
- accessOrder.splice(index, 1);
210
- }
211
192
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] error`, { key, error });
212
193
  throw error;
213
194
  }
@@ -219,24 +200,10 @@ function memoizeAsync(fn, options = {}) {
219
200
  timestamp: Date.now(),
220
201
  hits: 0
221
202
  });
222
- accessOrder.push(key);
223
203
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] set`, { key, cacheSize: cache.size });
224
204
  return await promise;
225
205
  };
226
206
  }
227
- function memoizeDebounced(fn, wait, options = {}) {
228
- const memoized = memoize(fn, options);
229
- let timeoutId;
230
- return function debounced(...args) {
231
- if (timeoutId) {
232
- clearTimeout(timeoutId);
233
- }
234
- timeoutId = setTimeout(() => {
235
- memoized(...args);
236
- }, wait);
237
- return memoized(...args);
238
- };
239
- }
240
207
  function memoizeWeak(fn) {
241
208
  const cache = /* @__PURE__ */ new WeakMap();
242
209
  return function memoized(key) {
@@ -270,7 +237,6 @@ function once(fn) {
270
237
  clearAllMemoizationCaches,
271
238
  memoize,
272
239
  memoizeAsync,
273
- memoizeDebounced,
274
240
  memoizeWeak,
275
241
  once
276
242
  });
@@ -42,9 +42,16 @@ var import_objects = require("../objects");
42
42
  var import_strings = require("../strings");
43
43
  // @__NO_SIDE_EFFECTS__
44
44
  function getRepoUrlDetails(repoUrl = "") {
45
- const userAndRepo = repoUrl.replace(/^.+github.com\//, "").split("/");
45
+ const match = /^(?:[a-z][a-z+]*:\/\/)(?:[^/@]+@)?github\.com\/([^?#]+)(?:[?#]|$)/i.exec(
46
+ repoUrl
47
+ );
48
+ if (!match || !match[1]) {
49
+ return { user: "", project: "" };
50
+ }
51
+ const userAndRepo = match[1].split("/");
46
52
  const user = userAndRepo[0] || "";
47
- const project = userAndRepo.length > 1 ? (userAndRepo[1]?.endsWith(".git") ? userAndRepo[1].slice(0, -4) : userAndRepo[1]) || "" : "";
53
+ const rawProject = userAndRepo[1] ?? "";
54
+ const project = rawProject.endsWith(".git") ? rawProject.slice(0, -4) : rawProject;
48
55
  return { user, project };
49
56
  }
50
57
  // @__NO_SIDE_EFFECTS__
@@ -34,8 +34,12 @@ function getPath() {
34
34
  return _path;
35
35
  }
36
36
  // @__NO_SIDE_EFFECTS__
37
+ function isPackageJsonFile(filepath) {
38
+ return filepath === "package.json" || filepath.endsWith("/package.json") || filepath.endsWith("\\package.json");
39
+ }
40
+ // @__NO_SIDE_EFFECTS__
37
41
  function resolvePackageJsonDirname(filepath) {
38
- if (filepath.endsWith("package.json")) {
42
+ if (/* @__PURE__ */ isPackageJsonFile(filepath)) {
39
43
  const path = /* @__PURE__ */ getPath();
40
44
  return (0, import_normalize.normalizePath)(path.dirname(filepath));
41
45
  }
@@ -43,7 +47,7 @@ function resolvePackageJsonDirname(filepath) {
43
47
  }
44
48
  // @__NO_SIDE_EFFECTS__
45
49
  function resolvePackageJsonPath(filepath) {
46
- if (filepath.endsWith("package.json")) {
50
+ if (/* @__PURE__ */ isPackageJsonFile(filepath)) {
47
51
  return (0, import_normalize.normalizePath)(filepath);
48
52
  }
49
53
  const path = /* @__PURE__ */ getPath();
@@ -136,9 +136,7 @@ class ProcessLockManager {
136
136
  if (!stats) {
137
137
  return false;
138
138
  }
139
- const ageSeconds = Math.floor((Date.now() - stats.mtime.getTime()) / 1e3);
140
- const staleSeconds = Math.floor(staleMs / 1e3);
141
- return ageSeconds > staleSeconds;
139
+ return Date.now() - stats.mtime.getTime() > staleMs;
142
140
  } catch {
143
141
  return false;
144
142
  }
@@ -186,9 +184,6 @@ class ProcessLockManager {
186
184
  }
187
185
  }
188
186
  const fs = /* @__PURE__ */ getFs();
189
- if (fs.existsSync(lockPath)) {
190
- throw new Error(`Lock already exists: ${lockPath}`);
191
- }
192
187
  const parent = (/* @__PURE__ */ getPath()).dirname(lockPath);
193
188
  if (parent && parent !== "." && parent !== lockPath) {
194
189
  fs.mkdirSync(parent, { recursive: true });