@socketsecurity/lib
Advanced tools
+22
-0
@@ -8,2 +8,24 @@ # Changelog | ||
| ## [4.4.0](https://github.com/SocketDev/socket-lib/releases/tag/v4.4.0) - 2025-11-25 | ||
| ### Added | ||
| - **fs**: Exported `normalizeEncoding()` function for robust encoding string normalization | ||
| - Handles case-insensitive encoding names (e.g., 'UTF-8', 'utf8', 'UTF8') | ||
| - Supports encoding aliases (e.g., 'binary' → 'latin1', 'ucs-2' → 'utf16le') | ||
| - Fast-path optimization for common encodings | ||
| - Defaults to 'utf8' for invalid or null encodings | ||
| - Export: `@socketsecurity/lib/fs` | ||
| ### Fixed | ||
| - **fs**: `safeReadFile()` and `safeReadFileSync()` type signatures and encoding handling | ||
| - Corrected type overloads: `encoding: null` → `Buffer | undefined`, no encoding → `string | undefined` (UTF-8 default) | ||
| - Fixed implementation to properly handle `encoding: null` for Buffer returns | ||
| - **suppress-warnings**: `withSuppressedWarnings()` now properly restores warning state | ||
| - Fixed state restoration to only remove warning types that were added by the function | ||
| - Prevents accidental removal of warnings that were already suppressed | ||
| - Ensures correct cleanup behavior when warning types are nested or reused | ||
| ## [4.3.0](https://github.com/SocketDev/socket-lib/releases/tag/v4.3.0) - 2025-11-20 | ||
@@ -10,0 +32,0 @@ |
+100
-52
@@ -286,2 +286,9 @@ /** | ||
| /** | ||
| * Invalidate the cached allowed directories. | ||
| * Called automatically by the paths/rewire module when paths are overridden in tests. | ||
| * | ||
| * @internal Used for test rewiring | ||
| */ | ||
| export declare function invalidatePathCache(): void; | ||
| /** | ||
| * Check if a path is a directory asynchronously. | ||
@@ -355,49 +362,33 @@ * Returns `true` for directories, `false` for files or non-existent paths. | ||
| /** | ||
| * Result of file readability validation. | ||
| * Contains lists of valid and invalid file paths. | ||
| */ | ||
| export interface ValidateFilesResult { | ||
| /** | ||
| * File paths that passed validation and are readable. | ||
| */ | ||
| validPaths: string[]; | ||
| /** | ||
| * File paths that failed validation (unreadable, permission denied, or non-existent). | ||
| * Common with Yarn Berry PnP virtual filesystem, pnpm symlinks, or filesystem race conditions. | ||
| */ | ||
| invalidPaths: string[]; | ||
| } | ||
| /** | ||
| * Validate that file paths are readable before processing. | ||
| * Filters out files from glob results that cannot be accessed (common with | ||
| * Yarn Berry PnP virtual filesystem, pnpm content-addressable store symlinks, | ||
| * or filesystem race conditions in CI/CD environments). | ||
| * Normalize encoding string to canonical form. | ||
| * Handles common encodings inline for performance, delegates to slowCases for others. | ||
| * | ||
| * This defensive pattern prevents ENOENT errors when files exist in glob | ||
| * results but are not accessible via standard filesystem operations. | ||
| * Based on Node.js internal/util.js normalizeEncoding implementation. | ||
| * @see https://github.com/nodejs/node/blob/ae62b36d442b7bf987e85ae6e0df0f02cc1bb17f/lib/internal/util.js#L247-L310 | ||
| * | ||
| * @param filepaths - Array of file paths to validate | ||
| * @returns Object with `validPaths` (readable) and `invalidPaths` (unreadable) | ||
| * @param enc - Encoding to normalize (can be null/undefined) | ||
| * @returns Normalized encoding string, defaults to 'utf8' | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { validateFiles } from '@socketsecurity/lib/fs' | ||
| * normalizeEncoding('UTF-8') // Returns 'utf8' | ||
| * normalizeEncoding('binary') // Returns 'latin1' | ||
| * normalizeEncoding('ucs-2') // Returns 'utf16le' | ||
| * normalizeEncoding(null) // Returns 'utf8' | ||
| * ``` | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function normalizeEncoding(enc: BufferEncoding | string | null | undefined): BufferEncoding; | ||
| /** | ||
| * Move the "slow cases" to a separate function to make sure this function gets | ||
| * inlined properly. That prioritizes the common case. | ||
| * | ||
| * const files = ['package.json', '.pnp.cjs/virtual-file.json'] | ||
| * const { validPaths, invalidPaths } = validateFiles(files) | ||
| * Based on Node.js internal/util.js normalizeEncoding implementation. | ||
| * @see https://github.com/nodejs/node/blob/ae62b36d442b7bf987e85ae6e0df0f02cc1bb17f/lib/internal/util.js#L247-L310 | ||
| * | ||
| * console.log(`Valid: ${validPaths.length}`) | ||
| * console.log(`Invalid: ${invalidPaths.length}`) | ||
| * ``` | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * // Typical usage in Socket CLI commands | ||
| * const packagePaths = await getPackageFilesForScan(targets) | ||
| * const { validPaths } = validateFiles(packagePaths) | ||
| * await sdk.uploadManifestFiles(orgSlug, validPaths) | ||
| * ``` | ||
| * @param enc - Encoding to normalize | ||
| * @returns Normalized encoding string, defaults to 'utf8' for unknown encodings | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function validateFiles(filepaths: string[] | readonly string[]): ValidateFilesResult; | ||
| export declare function normalizeEncodingSlow(enc: string): BufferEncoding; | ||
| /** | ||
@@ -591,9 +582,2 @@ * Read directory names asynchronously with filtering and sorting. | ||
| /** | ||
| * Invalidate the cached allowed directories. | ||
| * Called automatically by the paths/rewire module when paths are overridden in tests. | ||
| * | ||
| * @internal Used for test rewiring | ||
| */ | ||
| export declare function invalidatePathCache(): void; | ||
| /** | ||
| * Safely delete a file or directory asynchronously with built-in protections. | ||
@@ -713,10 +697,11 @@ * Uses `del` for safer deletion that prevents removing cwd and above by default. | ||
| * Returns undefined for any error (file not found, permission denied, etc.). | ||
| * Defaults to UTF-8 encoding, returning a string unless encoding is explicitly set to null. | ||
| * | ||
| * @param filepath - Path to file | ||
| * @param options - Read options including encoding and default value | ||
| * @returns Promise resolving to file contents, or undefined on error | ||
| * @returns Promise resolving to file contents (string by default), or undefined on error | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * // Try to read a file, get undefined if it doesn't exist | ||
| * // Try to read a file as UTF-8 string (default), get undefined if it doesn't exist | ||
| * const content = await safeReadFile('./optional-config.txt') | ||
@@ -729,6 +714,13 @@ * if (content) { | ||
| * const data = await safeReadFile('./data.txt', { encoding: 'utf8' }) | ||
| * | ||
| * // Read as Buffer by setting encoding to null | ||
| * const buffer = await safeReadFile('./binary.dat', { encoding: null }) | ||
| * ``` | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function safeReadFile(filepath: PathLike, options?: SafeReadOptions | undefined): Promise<NonSharedBuffer>; | ||
| export declare function safeReadFile(filepath: PathLike, options: SafeReadOptions & { | ||
| encoding: null; | ||
| }): Promise<Buffer | undefined>; | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function safeReadFile(filepath: PathLike, options?: SafeReadOptions | undefined): Promise<string | undefined>; | ||
| /** | ||
@@ -738,10 +730,11 @@ * Safely read a file synchronously, returning undefined on error. | ||
| * Returns undefined for any error (file not found, permission denied, etc.). | ||
| * Defaults to UTF-8 encoding, returning a string unless encoding is explicitly set to null. | ||
| * | ||
| * @param filepath - Path to file | ||
| * @param options - Read options including encoding and default value | ||
| * @returns File contents, or undefined on error | ||
| * @returns File contents (string by default), or undefined on error | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * // Try to read a config file | ||
| * // Try to read a config file as UTF-8 string (default) | ||
| * const config = safeReadFileSync('./config.txt') | ||
@@ -752,3 +745,6 @@ * if (config) { | ||
| * | ||
| * // Read binary file safely | ||
| * // Read with explicit encoding | ||
| * const data = safeReadFileSync('./data.txt', { encoding: 'utf8' }) | ||
| * | ||
| * // Read binary file by setting encoding to null | ||
| * const buffer = safeReadFileSync('./image.png', { encoding: null }) | ||
@@ -758,3 +754,7 @@ * ``` | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function safeReadFileSync(filepath: PathLike, options?: SafeReadOptions | undefined): string | NonSharedBuffer; | ||
| export declare function safeReadFileSync(filepath: PathLike, options: SafeReadOptions & { | ||
| encoding: null; | ||
| }): Buffer | undefined; | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function safeReadFileSync(filepath: PathLike, options?: SafeReadOptions | undefined): string | undefined; | ||
| /** | ||
@@ -824,2 +824,50 @@ * Safely get file stats asynchronously, returning undefined on error. | ||
| /** | ||
| * Result of file readability validation. | ||
| * Contains lists of valid and invalid file paths. | ||
| */ | ||
| export interface ValidateFilesResult { | ||
| /** | ||
| * File paths that passed validation and are readable. | ||
| */ | ||
| validPaths: string[]; | ||
| /** | ||
| * File paths that failed validation (unreadable, permission denied, or non-existent). | ||
| * Common with Yarn Berry PnP virtual filesystem, pnpm symlinks, or filesystem race conditions. | ||
| */ | ||
| invalidPaths: string[]; | ||
| } | ||
| /** | ||
| * Validate that file paths are readable before processing. | ||
| * Filters out files from glob results that cannot be accessed (common with | ||
| * Yarn Berry PnP virtual filesystem, pnpm content-addressable store symlinks, | ||
| * or filesystem race conditions in CI/CD environments). | ||
| * | ||
| * This defensive pattern prevents ENOENT errors when files exist in glob | ||
| * results but are not accessible via standard filesystem operations. | ||
| * | ||
| * @param filepaths - Array of file paths to validate | ||
| * @returns Object with `validPaths` (readable) and `invalidPaths` (unreadable) | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { validateFiles } from '@socketsecurity/lib/fs' | ||
| * | ||
| * const files = ['package.json', '.pnp.cjs/virtual-file.json'] | ||
| * const { validPaths, invalidPaths } = validateFiles(files) | ||
| * | ||
| * console.log(`Valid: ${validPaths.length}`) | ||
| * console.log(`Invalid: ${invalidPaths.length}`) | ||
| * ``` | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * // Typical usage in Socket CLI commands | ||
| * const packagePaths = await getPackageFilesForScan(targets) | ||
| * const { validPaths } = validateFiles(packagePaths) | ||
| * await sdk.uploadManifestFiles(orgSlug, validPaths) | ||
| * ``` | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function validateFiles(filepaths: string[] | readonly string[]): ValidateFilesResult; | ||
| /** | ||
| * Write JSON content to a file asynchronously with formatting. | ||
@@ -826,0 +874,0 @@ * Stringifies the value with configurable indentation and line endings. |
+146
-38
@@ -29,2 +29,4 @@ "use strict"; | ||
| isSymLinkSync: () => isSymLinkSync, | ||
| normalizeEncoding: () => normalizeEncoding, | ||
| normalizeEncodingSlow: () => normalizeEncodingSlow, | ||
| readDirNames: () => readDirNames, | ||
@@ -70,2 +72,22 @@ readDirNamesSync: () => readDirNamesSync, | ||
| }); | ||
| let _cachedAllowedDirs; | ||
| function getAllowedDirectories() { | ||
| if (_cachedAllowedDirs === void 0) { | ||
| const path = /* @__PURE__ */ getPath(); | ||
| _cachedAllowedDirs = [ | ||
| path.resolve((0, import_socket.getOsTmpDir)()), | ||
| path.resolve((0, import_socket.getSocketCacacheDir)()), | ||
| path.resolve((0, import_socket.getSocketUserDir)()) | ||
| ]; | ||
| } | ||
| return _cachedAllowedDirs; | ||
| } | ||
| let _buffer; | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function getBuffer() { | ||
| if (_buffer === void 0) { | ||
| _buffer = require("node:buffer"); | ||
| } | ||
| return _buffer; | ||
| } | ||
| let _fs; | ||
@@ -206,2 +228,6 @@ // @__NO_SIDE_EFFECTS__ | ||
| } | ||
| function invalidatePathCache() { | ||
| _cachedAllowedDirs = void 0; | ||
| } | ||
| (0, import_rewire.registerCacheInvalidation)(invalidatePathCache); | ||
| // @__NO_SIDE_EFFECTS__ | ||
@@ -256,16 +282,71 @@ async function isDir(filepath) { | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function validateFiles(filepaths) { | ||
| const fs = /* @__PURE__ */ getFs(); | ||
| const validPaths = []; | ||
| const invalidPaths = []; | ||
| const { R_OK } = fs.constants; | ||
| for (const filepath of filepaths) { | ||
| try { | ||
| fs.accessSync(filepath, R_OK); | ||
| validPaths.push(filepath); | ||
| } catch { | ||
| invalidPaths.push(filepath); | ||
| function normalizeEncoding(enc) { | ||
| return enc == null || enc === "utf8" || enc === "utf-8" ? "utf8" : /* @__PURE__ */ normalizeEncodingSlow(enc); | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function normalizeEncodingSlow(enc) { | ||
| const { length } = enc; | ||
| if (length === 4) { | ||
| if (enc === "ucs2" || enc === "UCS2") { | ||
| return "utf16le"; | ||
| } | ||
| if (enc.toLowerCase() === "ucs2") { | ||
| return "utf16le"; | ||
| } | ||
| } else if (length === 3 && enc === "hex" || enc === "HEX" || enc.toLowerCase() === "hex") { | ||
| return "hex"; | ||
| } else if (length === 5) { | ||
| if (enc === "ascii") { | ||
| return "ascii"; | ||
| } | ||
| if (enc === "ucs-2") { | ||
| return "utf16le"; | ||
| } | ||
| if (enc === "ASCII") { | ||
| return "ascii"; | ||
| } | ||
| if (enc === "UCS-2") { | ||
| return "utf16le"; | ||
| } | ||
| enc = enc.toLowerCase(); | ||
| if (enc === "ascii") { | ||
| return "ascii"; | ||
| } | ||
| if (enc === "ucs-2") { | ||
| return "utf16le"; | ||
| } | ||
| } else if (length === 6) { | ||
| if (enc === "base64") { | ||
| return "base64"; | ||
| } | ||
| if (enc === "latin1" || enc === "binary") { | ||
| return "latin1"; | ||
| } | ||
| if (enc === "BASE64") { | ||
| return "base64"; | ||
| } | ||
| if (enc === "LATIN1" || enc === "BINARY") { | ||
| return "latin1"; | ||
| } | ||
| enc = enc.toLowerCase(); | ||
| if (enc === "base64") { | ||
| return "base64"; | ||
| } | ||
| if (enc === "latin1" || enc === "binary") { | ||
| return "latin1"; | ||
| } | ||
| } else if (length === 7) { | ||
| if (enc === "utf16le" || enc === "UTF16LE" || enc.toLowerCase() === "utf16le") { | ||
| return "utf16le"; | ||
| } | ||
| } else if (length === 8) { | ||
| if (enc === "utf-16le" || enc === "UTF-16LE" || enc.toLowerCase() === "utf-16le") { | ||
| return "utf16le"; | ||
| } | ||
| } else if (length === 9) { | ||
| if (enc === "base64url" || enc === "BASE64URL" || enc.toLowerCase() === "base64url") { | ||
| return "base64url"; | ||
| } | ||
| } | ||
| return { __proto__: null, validPaths, invalidPaths }; | ||
| return "utf8"; | ||
| } | ||
@@ -357,4 +438,4 @@ // @__NO_SIDE_EFFECTS__ | ||
| __proto__: null, | ||
| encoding: "utf8", | ||
| ...fsOptions | ||
| ...fsOptions, | ||
| encoding: "utf8" | ||
| }); | ||
@@ -401,4 +482,4 @@ } catch (e) { | ||
| __proto__: null, | ||
| encoding: "utf8", | ||
| ...fsOptions | ||
| ...fsOptions, | ||
| encoding: "utf8" | ||
| }); | ||
@@ -432,18 +513,2 @@ } catch (e) { | ||
| } | ||
| let _cachedAllowedDirs; | ||
| function getAllowedDirectories() { | ||
| if (_cachedAllowedDirs === void 0) { | ||
| const path = /* @__PURE__ */ getPath(); | ||
| _cachedAllowedDirs = [ | ||
| path.resolve((0, import_socket.getOsTmpDir)()), | ||
| path.resolve((0, import_socket.getSocketCacacheDir)()), | ||
| path.resolve((0, import_socket.getSocketUserDir)()) | ||
| ]; | ||
| } | ||
| return _cachedAllowedDirs; | ||
| } | ||
| function invalidatePathCache() { | ||
| _cachedAllowedDirs = void 0; | ||
| } | ||
| (0, import_rewire.registerCacheInvalidation)(invalidatePathCache); | ||
| async function safeDelete(filepath, options) { | ||
@@ -533,16 +598,33 @@ const opts = { __proto__: null, ...options }; | ||
| async function safeReadFile(filepath, options) { | ||
| const opts = typeof options === "string" ? { encoding: options } : options; | ||
| const opts = typeof options === "string" ? { __proto__: null, encoding: options } : { __proto__: null, ...options }; | ||
| const { defaultValue, ...rawReadOpts } = opts; | ||
| const readOpts = { __proto__: null, ...rawReadOpts }; | ||
| const shouldReturnBuffer = readOpts.encoding === null; | ||
| const encoding = shouldReturnBuffer ? null : /* @__PURE__ */ normalizeEncoding(readOpts.encoding); | ||
| const fs = /* @__PURE__ */ getFs(); | ||
| try { | ||
| return await fs.promises.readFile(filepath, { | ||
| __proto__: null, | ||
| signal: abortSignal, | ||
| ...opts | ||
| ...readOpts, | ||
| encoding | ||
| }); | ||
| } catch { | ||
| } | ||
| return void 0; | ||
| if (defaultValue === void 0) { | ||
| return void 0; | ||
| } | ||
| if (shouldReturnBuffer) { | ||
| const { Buffer: Buffer2 } = /* @__PURE__ */ getBuffer(); | ||
| return Buffer2.isBuffer(defaultValue) ? defaultValue : void 0; | ||
| } | ||
| return typeof defaultValue === "string" ? defaultValue : String(defaultValue); | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function safeReadFileSync(filepath, options) { | ||
| const opts = typeof options === "string" ? { encoding: options } : options; | ||
| const opts = typeof options === "string" ? { __proto__: null, encoding: options } : { __proto__: null, ...options }; | ||
| const { defaultValue, ...rawReadOpts } = opts; | ||
| const readOpts = { __proto__: null, ...rawReadOpts }; | ||
| const shouldReturnBuffer = readOpts.encoding === null; | ||
| const encoding = shouldReturnBuffer ? null : /* @__PURE__ */ normalizeEncoding(readOpts.encoding); | ||
| const fs = /* @__PURE__ */ getFs(); | ||
@@ -552,7 +634,15 @@ try { | ||
| __proto__: null, | ||
| ...opts | ||
| ...readOpts, | ||
| encoding | ||
| }); | ||
| } catch { | ||
| } | ||
| return void 0; | ||
| if (defaultValue === void 0) { | ||
| return void 0; | ||
| } | ||
| if (shouldReturnBuffer) { | ||
| const { Buffer: Buffer2 } = /* @__PURE__ */ getBuffer(); | ||
| return Buffer2.isBuffer(defaultValue) ? defaultValue : void 0; | ||
| } | ||
| return typeof defaultValue === "string" ? defaultValue : String(defaultValue); | ||
| } | ||
@@ -601,2 +691,18 @@ // @__NO_SIDE_EFFECTS__ | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function validateFiles(filepaths) { | ||
| const fs = /* @__PURE__ */ getFs(); | ||
| const validPaths = []; | ||
| const invalidPaths = []; | ||
| const { R_OK } = fs.constants; | ||
| for (const filepath of filepaths) { | ||
| try { | ||
| fs.accessSync(filepath, R_OK); | ||
| validPaths.push(filepath); | ||
| } catch { | ||
| invalidPaths.push(filepath); | ||
| } | ||
| } | ||
| return { __proto__: null, validPaths, invalidPaths }; | ||
| } | ||
| async function writeJson(filepath, jsonContent, options) { | ||
@@ -651,2 +757,4 @@ const opts = typeof options === "string" ? { encoding: options } : options; | ||
| isSymLinkSync, | ||
| normalizeEncoding, | ||
| normalizeEncodingSlow, | ||
| readDirNames, | ||
@@ -653,0 +761,0 @@ readDirNamesSync, |
+61
-61
@@ -59,29 +59,2 @@ // Type definitions | ||
| /** | ||
| * Create a lazy getter function that memoizes its result. | ||
| * | ||
| * The returned function will only call the getter once, caching the result | ||
| * for subsequent calls. This is useful for expensive computations or | ||
| * operations that should only happen when needed. | ||
| * | ||
| * @param name - The property key name for the getter (used for debugging and stats) | ||
| * @param getter - Function that computes the value on first access | ||
| * @param stats - Optional stats object to track initialization | ||
| * @returns A memoized getter function | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const stats = { initialized: new Set() } | ||
| * const getLargeData = createLazyGetter('data', () => { | ||
| * console.log('Computing expensive data...') | ||
| * return { large: 'dataset' } | ||
| * }, stats) | ||
| * | ||
| * getLargeData() // Logs "Computing expensive data..." and returns data | ||
| * getLargeData() // Returns cached data without logging | ||
| * console.log(stats.initialized.has('data')) // true | ||
| * ``` | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function createLazyGetter<T>(name: PropertyKey, getter: () => T, stats?: LazyGetterStats | undefined): () => T; | ||
| /** | ||
| * Create a frozen constants object with lazy getters and internal properties. | ||
@@ -125,2 +98,29 @@ * | ||
| /** | ||
| * Create a lazy getter function that memoizes its result. | ||
| * | ||
| * The returned function will only call the getter once, caching the result | ||
| * for subsequent calls. This is useful for expensive computations or | ||
| * operations that should only happen when needed. | ||
| * | ||
| * @param name - The property key name for the getter (used for debugging and stats) | ||
| * @param getter - Function that computes the value on first access | ||
| * @param stats - Optional stats object to track initialization | ||
| * @returns A memoized getter function | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const stats = { initialized: new Set() } | ||
| * const getLargeData = createLazyGetter('data', () => { | ||
| * console.log('Computing expensive data...') | ||
| * return { large: 'dataset' } | ||
| * }, stats) | ||
| * | ||
| * getLargeData() // Logs "Computing expensive data..." and returns data | ||
| * getLargeData() // Returns cached data without logging | ||
| * console.log(stats.initialized.has('data')) // true | ||
| * ``` | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function createLazyGetter<T>(name: PropertyKey, getter: () => T, stats?: LazyGetterStats | undefined): () => T; | ||
| /** | ||
| * Define a getter property on an object. | ||
@@ -399,2 +399,36 @@ * | ||
| /** | ||
| * Deep merge source object into target object. | ||
| * | ||
| * Recursively merges properties from `source` into `target`. Arrays in source | ||
| * completely replace arrays in target (no element-wise merging). Objects are | ||
| * merged recursively. Includes infinite loop detection for safety. | ||
| * | ||
| * @param target - The object to merge into (will be modified) | ||
| * @param source - The object to merge from | ||
| * @returns The modified target object | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const target = { a: { x: 1 }, b: [1, 2] } | ||
| * const source = { a: { y: 2 }, b: [3, 4, 5], c: 3 } | ||
| * merge(target, source) | ||
| * // { a: { x: 1, y: 2 }, b: [3, 4, 5], c: 3 } | ||
| * ``` | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * // Arrays are replaced, not merged | ||
| * merge({ arr: [1, 2] }, { arr: [3] }) // { arr: [3] } | ||
| * | ||
| * // Deep object merging | ||
| * merge( | ||
| * { config: { api: 'v1', timeout: 1000 } }, | ||
| * { config: { api: 'v2', retries: 3 } } | ||
| * ) | ||
| * // { config: { api: 'v2', timeout: 1000, retries: 3 } } | ||
| * ``` | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function merge<T extends object, U extends object>(target: T, source: U): T & U; | ||
| /** | ||
| * Get all own property entries (key-value pairs) from an object. | ||
@@ -445,36 +479,2 @@ * | ||
| /** | ||
| * Deep merge source object into target object. | ||
| * | ||
| * Recursively merges properties from `source` into `target`. Arrays in source | ||
| * completely replace arrays in target (no element-wise merging). Objects are | ||
| * merged recursively. Includes infinite loop detection for safety. | ||
| * | ||
| * @param target - The object to merge into (will be modified) | ||
| * @param source - The object to merge from | ||
| * @returns The modified target object | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const target = { a: { x: 1 }, b: [1, 2] } | ||
| * const source = { a: { y: 2 }, b: [3, 4, 5], c: 3 } | ||
| * merge(target, source) | ||
| * // { a: { x: 1, y: 2 }, b: [3, 4, 5], c: 3 } | ||
| * ``` | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * // Arrays are replaced, not merged | ||
| * merge({ arr: [1, 2] }, { arr: [3] }) // { arr: [3] } | ||
| * | ||
| * // Deep object merging | ||
| * merge( | ||
| * { config: { api: 'v1', timeout: 1000 } }, | ||
| * { config: { api: 'v2', retries: 3 } } | ||
| * ) | ||
| * // { config: { api: 'v2', timeout: 1000, retries: 3 } } | ||
| * ``` | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function merge<T extends object, U extends object>(target: T, source: U): T & U; | ||
| /** | ||
| * Convert an object to a new object with sorted keys. | ||
@@ -481,0 +481,0 @@ * |
+30
-30
@@ -59,16 +59,2 @@ "use strict"; | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function createLazyGetter(name, getter, stats) { | ||
| let lazyValue = import_core.UNDEFINED_TOKEN; | ||
| const { [name]: lazyGetter } = { | ||
| [name]() { | ||
| if (lazyValue === import_core.UNDEFINED_TOKEN) { | ||
| stats?.initialized?.add(name); | ||
| lazyValue = getter(); | ||
| } | ||
| return lazyValue; | ||
| } | ||
| }; | ||
| return lazyGetter; | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function createConstantsObject(props, options_) { | ||
@@ -127,2 +113,16 @@ const options = { __proto__: null, ...options_ }; | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function createLazyGetter(name, getter, stats) { | ||
| let lazyValue = import_core.UNDEFINED_TOKEN; | ||
| const { [name]: lazyGetter } = { | ||
| [name]() { | ||
| if (lazyValue === import_core.UNDEFINED_TOKEN) { | ||
| stats?.initialized?.add(name); | ||
| lazyValue = getter(); | ||
| } | ||
| return lazyValue; | ||
| } | ||
| }; | ||
| return lazyGetter; | ||
| } | ||
| function defineGetter(object, propKey, getter) { | ||
@@ -214,18 +214,2 @@ ObjectDefineProperty(object, propKey, { | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function objectEntries(obj) { | ||
| if (obj === null || obj === void 0) { | ||
| return []; | ||
| } | ||
| const keys = ReflectOwnKeys(obj); | ||
| const { length } = keys; | ||
| const entries = Array(length); | ||
| const record = obj; | ||
| for (let i = 0; i < length; i += 1) { | ||
| const key = keys[i]; | ||
| entries[i] = [key, record[key]]; | ||
| } | ||
| return entries; | ||
| } | ||
| const objectFreeze = Object.freeze; | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function merge(target, source) { | ||
@@ -272,2 +256,18 @@ if (!/* @__PURE__ */ isObject(target) || !/* @__PURE__ */ isObject(source)) { | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function objectEntries(obj) { | ||
| if (obj === null || obj === void 0) { | ||
| return []; | ||
| } | ||
| const keys = ReflectOwnKeys(obj); | ||
| const { length } = keys; | ||
| const entries = Array(length); | ||
| const record = obj; | ||
| for (let i = 0; i < length; i += 1) { | ||
| const key = keys[i]; | ||
| entries[i] = [key, record[key]]; | ||
| } | ||
| return entries; | ||
| } | ||
| const objectFreeze = Object.freeze; | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function toSortedObject(obj) { | ||
@@ -274,0 +274,0 @@ return /* @__PURE__ */ toSortedObjectFromEntries(/* @__PURE__ */ objectEntries(obj)); |
+19
-19
@@ -212,21 +212,2 @@ /** | ||
| /** | ||
| * Resolve retry options from various input formats. | ||
| * | ||
| * Converts shorthand and partial options into a base configuration that can be | ||
| * further normalized. This is an internal helper for option processing. | ||
| * | ||
| * @param options - Retry count as number, or partial options object, or undefined | ||
| * @returns Resolved retry options with defaults for basic properties | ||
| * | ||
| * @example | ||
| * resolveRetryOptions(3) | ||
| * // => { retries: 3, minTimeout: 200, maxTimeout: 10000, factor: 2 } | ||
| * | ||
| * @example | ||
| * resolveRetryOptions({ retries: 5, maxTimeout: 5000 }) | ||
| * // => { retries: 5, minTimeout: 200, maxTimeout: 5000, factor: 2 } | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function resolveRetryOptions(options?: number | RetryOptions | undefined): RetryOptions; | ||
| /** | ||
| * Execute an async function for each array element with concurrency control. | ||
@@ -459,1 +440,20 @@ * | ||
| export declare function pRetry<T>(callbackFn: (...args: unknown[]) => Promise<T>, options?: number | RetryOptions | undefined): Promise<T | undefined>; | ||
| /** | ||
| * Resolve retry options from various input formats. | ||
| * | ||
| * Converts shorthand and partial options into a base configuration that can be | ||
| * further normalized. This is an internal helper for option processing. | ||
| * | ||
| * @param options - Retry count as number, or partial options object, or undefined | ||
| * @returns Resolved retry options with defaults for basic properties | ||
| * | ||
| * @example | ||
| * resolveRetryOptions(3) | ||
| * // => { retries: 3, minTimeout: 200, maxTimeout: 10000, factor: 2 } | ||
| * | ||
| * @example | ||
| * resolveRetryOptions({ retries: 5, maxTimeout: 5000 }) | ||
| * // => { retries: 5, minTimeout: 200, maxTimeout: 5000, factor: 2 } | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function resolveRetryOptions(options?: number | RetryOptions | undefined): RetryOptions; |
+14
-14
@@ -104,16 +104,2 @@ "use strict"; | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function resolveRetryOptions(options) { | ||
| const defaults = { | ||
| __proto__: null, | ||
| retries: 0, | ||
| baseDelayMs: 200, | ||
| maxDelayMs: 1e4, | ||
| backoffFactor: 2 | ||
| }; | ||
| if (typeof options === "number") { | ||
| return { ...defaults, retries: options }; | ||
| } | ||
| return options ? { ...defaults, ...options } : defaults; | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| async function pEach(array, callbackFn, options) { | ||
@@ -263,2 +249,16 @@ const iterOpts = /* @__PURE__ */ normalizeIterationOptions(options); | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function resolveRetryOptions(options) { | ||
| const defaults = { | ||
| __proto__: null, | ||
| retries: 0, | ||
| baseDelayMs: 200, | ||
| maxDelayMs: 1e4, | ||
| backoffFactor: 2 | ||
| }; | ||
| if (typeof options === "number") { | ||
| return { ...defaults, retries: options }; | ||
| } | ||
| return options ? { ...defaults, ...options } : defaults; | ||
| } | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
@@ -265,0 +265,0 @@ 0 && (module.exports = { |
+10
-10
| /** | ||
| * Compare semantic versions. | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function compareSemver(a: string, b: string): number; | ||
| /** | ||
| * Simple string comparison. | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function compareStr(a: string, b: string): number; | ||
| /** | ||
| * Compare two strings using locale-aware comparison. | ||
@@ -18,12 +28,2 @@ */ | ||
| export declare function naturalSorter<T>(arrayToSort: T[]): ReturnType<FastSortFunction>; | ||
| /** | ||
| * Simple string comparison. | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function compareStr(a: string, b: string): number; | ||
| /** | ||
| * Compare semantic versions. | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function compareSemver(a: string, b: string): number; | ||
| export {}; |
+19
-19
@@ -41,2 +41,21 @@ "use strict"; | ||
| var semver = __toESM(require("./external/semver.js")); | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function compareSemver(a, b) { | ||
| const validA = semver.valid(a); | ||
| const validB = semver.valid(b); | ||
| if (!validA && !validB) { | ||
| return 0; | ||
| } | ||
| if (!validA) { | ||
| return -1; | ||
| } | ||
| if (!validB) { | ||
| return 1; | ||
| } | ||
| return semver.compare(a, b); | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function compareStr(a, b) { | ||
| return a < b ? -1 : a > b ? 1 : 0; | ||
| } | ||
| let _localeCompare; | ||
@@ -81,21 +100,2 @@ // @__NO_SIDE_EFFECTS__ | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function compareStr(a, b) { | ||
| return a < b ? -1 : a > b ? 1 : 0; | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function compareSemver(a, b) { | ||
| const validA = semver.valid(a); | ||
| const validB = semver.valid(b); | ||
| if (!validA && !validB) { | ||
| return 0; | ||
| } | ||
| if (!validA) { | ||
| return -1; | ||
| } | ||
| if (!validB) { | ||
| return 1; | ||
| } | ||
| return semver.compare(a, b); | ||
| } | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
@@ -102,0 +102,0 @@ 0 && (module.exports = { |
+63
-63
@@ -88,2 +88,37 @@ /** | ||
| export declare function camelToKebab(str: string): string; | ||
| /** | ||
| * Center text within a given width. | ||
| * | ||
| * Adds spaces before and after the text to center it within the specified width. | ||
| * Distributes padding evenly on both sides. When the padding is odd, the extra | ||
| * space is added to the right side. Strips ANSI codes before calculating text | ||
| * length to ensure accurate centering of colored text. | ||
| * | ||
| * If the text is already wider than or equal to the target width, returns the | ||
| * original text unchanged (no truncation occurs). | ||
| * | ||
| * @param text - The text to center (may include ANSI codes) | ||
| * @param width - The target width in columns | ||
| * @returns The centered text with padding | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * centerText('hello', 11) | ||
| * // Returns: ' hello ' (3 spaces on each side) | ||
| * | ||
| * centerText('hi', 10) | ||
| * // Returns: ' hi ' (4 spaces on each side) | ||
| * | ||
| * centerText('odd', 8) | ||
| * // Returns: ' odd ' (2 left, 3 right) | ||
| * | ||
| * centerText('\x1b[31mred\x1b[0m', 7) | ||
| * // Returns: ' \x1b[31mred\x1b[0m ' (ANSI codes preserved, 'red' centered) | ||
| * | ||
| * centerText('too long text', 5) | ||
| * // Returns: 'too long text' (no truncation, returned as-is) | ||
| * ``` | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function centerText(text: string, width: number): string; | ||
| export interface IndentStringOptions { | ||
@@ -185,2 +220,29 @@ /** | ||
| export declare function isNonEmptyString(value: unknown): value is Exclude<string, EmptyString>; | ||
| /** | ||
| * Repeat a string a specified number of times. | ||
| * | ||
| * Creates a new string by repeating the input string `count` times. | ||
| * Returns an empty string if count is 0 or negative. | ||
| * | ||
| * @param str - The string to repeat | ||
| * @param count - The number of times to repeat the string | ||
| * @returns The repeated string, or empty string if count <= 0 | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * repeatString('hello', 3) | ||
| * // Returns: 'hellohellohello' | ||
| * | ||
| * repeatString('x', 5) | ||
| * // Returns: 'xxxxx' | ||
| * | ||
| * repeatString('hello', 0) | ||
| * // Returns: '' | ||
| * | ||
| * repeatString('hello', -1) | ||
| * // Returns: '' | ||
| * ``` | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function repeatString(str: string, count: number): string; | ||
| export interface SearchOptions { | ||
@@ -257,2 +319,3 @@ /** | ||
| export declare function stripBom(str: string): string; | ||
| /* c8 ignore stop */ | ||
| /** | ||
@@ -409,64 +472,1 @@ * Get the visual width of a string in terminal columns. | ||
| export declare function trimNewlines(str: string): string; | ||
| /** | ||
| * Repeat a string n times. | ||
| * | ||
| * Creates a new string by repeating the input string the specified number of times. | ||
| * Returns an empty string if count is zero or negative. This is a simple wrapper | ||
| * around `String.prototype.repeat()` with guard for non-positive counts. | ||
| * | ||
| * @param str - The string to repeat | ||
| * @param count - The number of times to repeat the string | ||
| * @returns The repeated string, or empty string if count <= 0 | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * repeatString('hello', 3) | ||
| * // Returns: 'hellohellohello' | ||
| * | ||
| * repeatString('x', 5) | ||
| * // Returns: 'xxxxx' | ||
| * | ||
| * repeatString('hello', 0) | ||
| * // Returns: '' | ||
| * | ||
| * repeatString('hello', -1) | ||
| * // Returns: '' | ||
| * ``` | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function repeatString(str: string, count: number): string; | ||
| /** | ||
| * Center text within a given width. | ||
| * | ||
| * Adds spaces before and after the text to center it within the specified width. | ||
| * Distributes padding evenly on both sides. When the padding is odd, the extra | ||
| * space is added to the right side. Strips ANSI codes before calculating text | ||
| * length to ensure accurate centering of colored text. | ||
| * | ||
| * If the text is already wider than or equal to the target width, returns the | ||
| * original text unchanged (no truncation occurs). | ||
| * | ||
| * @param text - The text to center (may include ANSI codes) | ||
| * @param width - The target width in columns | ||
| * @returns The centered text with padding | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * centerText('hello', 11) | ||
| * // Returns: ' hello ' (3 spaces on each side) | ||
| * | ||
| * centerText('hi', 10) | ||
| * // Returns: ' hi ' (4 spaces on each side) | ||
| * | ||
| * centerText('odd', 8) | ||
| * // Returns: ' odd ' (2 left, 3 right) | ||
| * | ||
| * centerText('\x1b[31mred\x1b[0m', 7) | ||
| * // Returns: ' \x1b[31mred\x1b[0m ' (ANSI codes preserved, 'red' centered) | ||
| * | ||
| * centerText('too long text', 5) | ||
| * // Returns: 'too long text' (no truncation, returned as-is) | ||
| * ``` | ||
| */ | ||
| /*@__NO_SIDE_EFFECTS__*/ | ||
| export declare function centerText(text: string, width: number): string; |
+18
-18
@@ -95,2 +95,13 @@ "use strict"; | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function centerText(text, width) { | ||
| const textLength = (0, import_ansi.stripAnsi)(text).length; | ||
| if (textLength >= width) { | ||
| return text; | ||
| } | ||
| const padding = width - textLength; | ||
| const leftPad = Math.floor(padding / 2); | ||
| const rightPad = padding - leftPad; | ||
| return " ".repeat(leftPad) + text + " ".repeat(rightPad); | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function indentString(str, options) { | ||
@@ -109,2 +120,9 @@ const { count = 1 } = { __proto__: null, ...options }; | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function repeatString(str, count) { | ||
| if (count <= 0) { | ||
| return ""; | ||
| } | ||
| return str.repeat(count); | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function search(str, regexp, options) { | ||
@@ -220,20 +238,2 @@ const { fromIndex = 0 } = { __proto__: null, ...options }; | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function repeatString(str, count) { | ||
| if (count <= 0) { | ||
| return ""; | ||
| } | ||
| return str.repeat(count); | ||
| } | ||
| // @__NO_SIDE_EFFECTS__ | ||
| function centerText(text, width) { | ||
| const textLength = (0, import_ansi.stripAnsi)(text).length; | ||
| if (textLength >= width) { | ||
| return text; | ||
| } | ||
| const padding = width - textLength; | ||
| const leftPad = Math.floor(padding / 2); | ||
| const rightPad = padding - leftPad; | ||
| return " ".repeat(leftPad) + text + " ".repeat(rightPad); | ||
| } | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
@@ -240,0 +240,0 @@ 0 && (module.exports = { |
@@ -85,2 +85,3 @@ "use strict"; | ||
| async function withSuppressedWarnings(warningType, callback) { | ||
| const wasAlreadySuppressed = suppressedWarnings.has(warningType); | ||
| const original = process.emitWarning; | ||
@@ -91,2 +92,5 @@ suppressWarningType(warningType); | ||
| } finally { | ||
| if (!wasAlreadySuppressed) { | ||
| suppressedWarnings.delete(warningType); | ||
| } | ||
| process.emitWarning = original; | ||
@@ -93,0 +97,0 @@ } |
+2
-2
| { | ||
| "name": "@socketsecurity/lib", | ||
| "version": "4.3.0", | ||
| "version": "4.4.0", | ||
| "packageManager": "pnpm@10.22.0", | ||
@@ -693,3 +693,3 @@ "license": "MIT", | ||
| "@socketregistry/yocto-spinner": "1.0.25", | ||
| "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@4.1.0", | ||
| "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@4.3.0", | ||
| "@types/node": "24.9.2", | ||
@@ -696,0 +696,0 @@ "@typescript/native-preview": "7.0.0-dev.20250920.1", |
+1
-1
@@ -5,3 +5,3 @@ # @socketsecurity/lib | ||
| [](https://github.com/SocketDev/socket-lib/actions/workflows/ci.yml) | ||
|  | ||
|  | ||
@@ -8,0 +8,0 @@ [](https://twitter.com/SocketSecurity) |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 20 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 20 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
6646027
0.1%180168
0.09%307
-0.65%