@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.
- package/CHANGELOG.md +105 -74
- package/dist/archives.js +13 -0
- package/dist/cacache.js +6 -8
- package/dist/cache-with-ttl.d.ts +7 -0
- package/dist/cache-with-ttl.js +27 -8
- package/dist/constants/socket.js +1 -1
- package/dist/dlx/detect.js +25 -8
- package/dist/dlx/lockfile.js +4 -1
- package/dist/dlx/manifest.d.ts +10 -4
- package/dist/dlx/package.d.ts +1 -1
- package/dist/dlx/package.js +19 -3
- package/dist/external/@npmcli/package-json/lib/read-package.js +40 -32
- package/dist/external/@npmcli/package-json/lib/sort.js +104 -92
- package/dist/external/@npmcli/package-json.js +9 -3968
- package/dist/external/@sinclair/typebox/value.js +9007 -0
- package/dist/external/@sinclair/typebox.js +7891 -0
- package/dist/external/debug.js +162 -328
- package/dist/external/npm-pack.js +13935 -33342
- package/dist/fs.js +8 -2
- package/dist/globs.js +5 -1
- package/dist/http-request.d.ts +0 -25
- package/dist/http-request.js +6 -5
- package/dist/ipc.js +43 -10
- package/dist/json/edit.d.ts +1 -1
- package/dist/json/parse.d.ts +47 -2
- package/dist/json/parse.js +40 -2
- package/dist/json/types.d.ts +49 -0
- package/dist/memoization.d.ts +4 -23
- package/dist/memoization.js +15 -49
- package/dist/packages/specs.js +9 -2
- package/dist/paths/packages.js +6 -2
- package/dist/process-lock.js +1 -6
- package/dist/promise-queue.d.ts +9 -4
- package/dist/promise-queue.js +10 -8
- package/dist/promises.d.ts +41 -0
- package/dist/promises.js +19 -2
- package/dist/regexps.d.ts +4 -13
- package/dist/regexps.js +60 -3
- package/dist/schema/parse.d.ts +26 -0
- package/dist/{zod.js → schema/parse.js} +14 -6
- package/dist/schema/types.d.ts +121 -0
- package/dist/schema/validate.d.ts +35 -0
- package/dist/schema/validate.js +98 -0
- package/dist/stdio/progress.js +1 -1
- package/dist/suppress-warnings.js +0 -2
- package/dist/tables.js +2 -3
- package/dist/url.js +5 -1
- package/dist/versions.js +2 -2
- package/dist/words.js +4 -7
- package/package.json +15 -14
- package/dist/external/zod.js +0 -15223
- package/dist/validation/json-parser.d.ts +0 -58
- package/dist/validation/json-parser.js +0 -63
- package/dist/validation/types.d.ts +0 -118
- package/dist/zod.d.ts +0 -5
- /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
|
|
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
|
|
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) =>
|
|
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) {
|
package/dist/http-request.d.ts
CHANGED
|
@@ -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.
|
package/dist/http-request.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 (
|
|
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
|
|
40
|
-
const IpcStubSchema =
|
|
40
|
+
var import_parse = require("./schema/parse");
|
|
41
|
+
const IpcStubSchema = import_typebox.Type.Object({
|
|
41
42
|
/** Process ID that created the stub. */
|
|
42
|
-
pid:
|
|
43
|
+
pid: import_typebox.Type.Integer({ minimum: 1 }),
|
|
43
44
|
/** Creation timestamp for age validation. */
|
|
44
|
-
timestamp:
|
|
45
|
+
timestamp: import_typebox.Type.Number({ exclusiveMinimum: 0 }),
|
|
45
46
|
/** The actual data payload. */
|
|
46
|
-
data:
|
|
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 =
|
|
103
|
+
const validated = (0, import_parse.parseSchema)(IpcStubSchema, ipcData);
|
|
85
104
|
const fs = /* @__PURE__ */ getFs();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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:
|
package/dist/json/edit.d.ts
CHANGED
|
@@ -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')
|
package/dist/json/parse.d.ts
CHANGED
|
@@ -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 {
|
|
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;
|
package/dist/json/parse.js
CHANGED
|
@@ -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
|
});
|
package/dist/json/types.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/memoization.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* Options for memoization behavior.
|
|
7
7
|
*/
|
|
8
|
-
export type MemoizeOptions<Args extends 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[]
|
|
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
|
|
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
|
|
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.
|
package/dist/memoization.js
CHANGED
|
@@ -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
|
|
88
|
-
const oldest =
|
|
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
|
-
|
|
111
|
-
|
|
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
|
|
152
|
-
const oldest =
|
|
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
|
-
|
|
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
|
});
|
package/dist/packages/specs.js
CHANGED
|
@@ -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
|
|
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
|
|
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__
|
package/dist/paths/packages.js
CHANGED
|
@@ -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
|
|
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
|
|
50
|
+
if (/* @__PURE__ */ isPackageJsonFile(filepath)) {
|
|
47
51
|
return (0, import_normalize.normalizePath)(filepath);
|
|
48
52
|
}
|
|
49
53
|
const path = /* @__PURE__ */ getPath();
|
package/dist/process-lock.js
CHANGED
|
@@ -136,9 +136,7 @@ class ProcessLockManager {
|
|
|
136
136
|
if (!stats) {
|
|
137
137
|
return false;
|
|
138
138
|
}
|
|
139
|
-
|
|
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 });
|