@socketsecurity/lib 4.2.0 → 4.4.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 CHANGED
@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.4.0](https://github.com/SocketDev/socket-lib/releases/tag/v4.4.0) - 2025-11-25
9
+
10
+ ### Added
11
+
12
+ - **fs**: Exported `normalizeEncoding()` function for robust encoding string normalization
13
+ - Handles case-insensitive encoding names (e.g., 'UTF-8', 'utf8', 'UTF8')
14
+ - Supports encoding aliases (e.g., 'binary' → 'latin1', 'ucs-2' → 'utf16le')
15
+ - Fast-path optimization for common encodings
16
+ - Defaults to 'utf8' for invalid or null encodings
17
+ - Export: `@socketsecurity/lib/fs`
18
+
19
+ ### Fixed
20
+
21
+ - **fs**: `safeReadFile()` and `safeReadFileSync()` type signatures and encoding handling
22
+ - Corrected type overloads: `encoding: null` → `Buffer | undefined`, no encoding → `string | undefined` (UTF-8 default)
23
+ - Fixed implementation to properly handle `encoding: null` for Buffer returns
24
+
25
+ - **suppress-warnings**: `withSuppressedWarnings()` now properly restores warning state
26
+ - Fixed state restoration to only remove warning types that were added by the function
27
+ - Prevents accidental removal of warnings that were already suppressed
28
+ - Ensures correct cleanup behavior when warning types are nested or reused
29
+
30
+ ## [4.3.0](https://github.com/SocketDev/socket-lib/releases/tag/v4.3.0) - 2025-11-20
31
+
32
+ ### Added
33
+
34
+ - **globs**: New `glob()` and `globSync()` wrapper functions for fast-glob
35
+ - Provides convenient wrappers around fast-glob with normalized options
36
+ - Maintains consistent API with existing glob functionality
37
+ - Export: `@socketsecurity/lib/globs`
38
+
8
39
  ## [4.1.0](https://github.com/SocketDev/socket-lib/releases/tag/v4.1.0) - 2025-11-17
9
40
 
10
41
  ### Added
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Socket Badge](https://socket.dev/api/badge/npm/package/@socketsecurity/lib)](https://socket.dev/npm/package/@socketsecurity/lib)
4
4
  [![CI](https://github.com/SocketDev/socket-lib/actions/workflows/ci.yml/badge.svg)](https://github.com/SocketDev/socket-lib/actions/workflows/ci.yml)
5
- ![Coverage](https://img.shields.io/badge/coverage-83.95%25-brightgreen)
5
+ ![Coverage](https://img.shields.io/badge/coverage-83.78%25-brightgreen)
6
6
 
7
7
  [![Follow @SocketSecurity](https://img.shields.io/twitter/follow/SocketSecurity?style=social)](https://twitter.com/SocketSecurity)
8
8
  [![Follow @socket.dev on Bluesky](https://img.shields.io/badge/Follow-@socket.dev-1DA1F2?style=social&logo=bluesky)](https://bsky.app/profile/socket.dev)
@@ -5723,7 +5723,11 @@ var require_out4 = __commonJS({
5723
5723
 
5724
5724
  // src/external/fast-glob.js
5725
5725
  var fastGlob = require_out4();
5726
- module.exports = fastGlob.globStream ? { globStream: fastGlob.globStream } : fastGlob;
5726
+ module.exports = fastGlob.globStream ? {
5727
+ glob: fastGlob,
5728
+ globStream: fastGlob.globStream,
5729
+ globSync: fastGlob.sync
5730
+ } : fastGlob;
5727
5731
  /*! Bundled license information:
5728
5732
 
5729
5733
  is-extglob/index.js:
package/dist/fs.d.ts CHANGED
@@ -282,6 +282,13 @@ export declare function findUp(name: string | string[] | readonly string[], opti
282
282
  */
283
283
  /*@__NO_SIDE_EFFECTS__*/
284
284
  export declare function findUpSync(name: string | string[] | readonly string[], options?: FindUpSyncOptions | undefined): string;
285
+ /**
286
+ * Invalidate the cached allowed directories.
287
+ * Called automatically by the paths/rewire module when paths are overridden in tests.
288
+ *
289
+ * @internal Used for test rewiring
290
+ */
291
+ export declare function invalidatePathCache(): void;
285
292
  /**
286
293
  * Check if a path is a directory asynchronously.
287
294
  * Returns `true` for directories, `false` for files or non-existent paths.
@@ -351,53 +358,37 @@ export declare function isDirEmptySync(dirname: PathLike, options?: IsDirEmptyOp
351
358
  /*@__NO_SIDE_EFFECTS__*/
352
359
  export declare function isSymLinkSync(filepath: PathLike): boolean;
353
360
  /**
354
- * Result of file readability validation.
355
- * Contains lists of valid and invalid file paths.
356
- */
357
- export interface ValidateFilesResult {
358
- /**
359
- * File paths that passed validation and are readable.
360
- */
361
- validPaths: string[];
362
- /**
363
- * File paths that failed validation (unreadable, permission denied, or non-existent).
364
- * Common with Yarn Berry PnP virtual filesystem, pnpm symlinks, or filesystem race conditions.
365
- */
366
- invalidPaths: string[];
367
- }
368
- /**
369
- * Validate that file paths are readable before processing.
370
- * Filters out files from glob results that cannot be accessed (common with
371
- * Yarn Berry PnP virtual filesystem, pnpm content-addressable store symlinks,
372
- * or filesystem race conditions in CI/CD environments).
361
+ * Normalize encoding string to canonical form.
362
+ * Handles common encodings inline for performance, delegates to slowCases for others.
373
363
  *
374
- * This defensive pattern prevents ENOENT errors when files exist in glob
375
- * results but are not accessible via standard filesystem operations.
364
+ * Based on Node.js internal/util.js normalizeEncoding implementation.
365
+ * @see https://github.com/nodejs/node/blob/ae62b36d442b7bf987e85ae6e0df0f02cc1bb17f/lib/internal/util.js#L247-L310
376
366
  *
377
- * @param filepaths - Array of file paths to validate
378
- * @returns Object with `validPaths` (readable) and `invalidPaths` (unreadable)
367
+ * @param enc - Encoding to normalize (can be null/undefined)
368
+ * @returns Normalized encoding string, defaults to 'utf8'
379
369
  *
380
370
  * @example
381
371
  * ```ts
382
- * import { validateFiles } from '@socketsecurity/lib/fs'
383
- *
384
- * const files = ['package.json', '.pnp.cjs/virtual-file.json']
385
- * const { validPaths, invalidPaths } = validateFiles(files)
386
- *
387
- * console.log(`Valid: ${validPaths.length}`)
388
- * console.log(`Invalid: ${invalidPaths.length}`)
372
+ * normalizeEncoding('UTF-8') // Returns 'utf8'
373
+ * normalizeEncoding('binary') // Returns 'latin1'
374
+ * normalizeEncoding('ucs-2') // Returns 'utf16le'
375
+ * normalizeEncoding(null) // Returns 'utf8'
389
376
  * ```
377
+ */
378
+ /*@__NO_SIDE_EFFECTS__*/
379
+ export declare function normalizeEncoding(enc: BufferEncoding | string | null | undefined): BufferEncoding;
380
+ /**
381
+ * Move the "slow cases" to a separate function to make sure this function gets
382
+ * inlined properly. That prioritizes the common case.
390
383
  *
391
- * @example
392
- * ```ts
393
- * // Typical usage in Socket CLI commands
394
- * const packagePaths = await getPackageFilesForScan(targets)
395
- * const { validPaths } = validateFiles(packagePaths)
396
- * await sdk.uploadManifestFiles(orgSlug, validPaths)
397
- * ```
384
+ * Based on Node.js internal/util.js normalizeEncoding implementation.
385
+ * @see https://github.com/nodejs/node/blob/ae62b36d442b7bf987e85ae6e0df0f02cc1bb17f/lib/internal/util.js#L247-L310
386
+ *
387
+ * @param enc - Encoding to normalize
388
+ * @returns Normalized encoding string, defaults to 'utf8' for unknown encodings
398
389
  */
399
390
  /*@__NO_SIDE_EFFECTS__*/
400
- export declare function validateFiles(filepaths: string[] | readonly string[]): ValidateFilesResult;
391
+ export declare function normalizeEncodingSlow(enc: string): BufferEncoding;
401
392
  /**
402
393
  * Read directory names asynchronously with filtering and sorting.
403
394
  * Returns only directory names (not files), with optional filtering for empty directories
@@ -585,13 +576,6 @@ export declare function readJson(filepath: PathLike, options?: ReadJsonOptions |
585
576
  */
586
577
  /*@__NO_SIDE_EFFECTS__*/
587
578
  export declare function readJsonSync(filepath: PathLike, options?: ReadJsonOptions | string | undefined): import("./json").JsonValue;
588
- /**
589
- * Invalidate the cached allowed directories.
590
- * Called automatically by the paths/rewire module when paths are overridden in tests.
591
- *
592
- * @internal Used for test rewiring
593
- */
594
- export declare function invalidatePathCache(): void;
595
579
  /**
596
580
  * Safely delete a file or directory asynchronously with built-in protections.
597
581
  * Uses `del` for safer deletion that prevents removing cwd and above by default.
@@ -707,14 +691,15 @@ export declare function safeMkdirSync(path: PathLike, options?: MakeDirectoryOpt
707
691
  * Safely read a file asynchronously, returning undefined on error.
708
692
  * Useful when you want to attempt reading a file without handling errors explicitly.
709
693
  * Returns undefined for any error (file not found, permission denied, etc.).
694
+ * Defaults to UTF-8 encoding, returning a string unless encoding is explicitly set to null.
710
695
  *
711
696
  * @param filepath - Path to file
712
697
  * @param options - Read options including encoding and default value
713
- * @returns Promise resolving to file contents, or undefined on error
698
+ * @returns Promise resolving to file contents (string by default), or undefined on error
714
699
  *
715
700
  * @example
716
701
  * ```ts
717
- * // Try to read a file, get undefined if it doesn't exist
702
+ * // Try to read a file as UTF-8 string (default), get undefined if it doesn't exist
718
703
  * const content = await safeReadFile('./optional-config.txt')
719
704
  * if (content) {
720
705
  * console.log('Config found:', content)
@@ -722,33 +707,48 @@ export declare function safeMkdirSync(path: PathLike, options?: MakeDirectoryOpt
722
707
  *
723
708
  * // Read with specific encoding
724
709
  * const data = await safeReadFile('./data.txt', { encoding: 'utf8' })
710
+ *
711
+ * // Read as Buffer by setting encoding to null
712
+ * const buffer = await safeReadFile('./binary.dat', { encoding: null })
725
713
  * ```
726
714
  */
727
715
  /*@__NO_SIDE_EFFECTS__*/
728
- export declare function safeReadFile(filepath: PathLike, options?: SafeReadOptions | undefined): Promise<NonSharedBuffer>;
716
+ export declare function safeReadFile(filepath: PathLike, options: SafeReadOptions & {
717
+ encoding: null;
718
+ }): Promise<Buffer | undefined>;
719
+ /*@__NO_SIDE_EFFECTS__*/
720
+ export declare function safeReadFile(filepath: PathLike, options?: SafeReadOptions | undefined): Promise<string | undefined>;
729
721
  /**
730
722
  * Safely read a file synchronously, returning undefined on error.
731
723
  * Useful when you want to attempt reading a file without handling errors explicitly.
732
724
  * Returns undefined for any error (file not found, permission denied, etc.).
725
+ * Defaults to UTF-8 encoding, returning a string unless encoding is explicitly set to null.
733
726
  *
734
727
  * @param filepath - Path to file
735
728
  * @param options - Read options including encoding and default value
736
- * @returns File contents, or undefined on error
729
+ * @returns File contents (string by default), or undefined on error
737
730
  *
738
731
  * @example
739
732
  * ```ts
740
- * // Try to read a config file
733
+ * // Try to read a config file as UTF-8 string (default)
741
734
  * const config = safeReadFileSync('./config.txt')
742
735
  * if (config) {
743
736
  * console.log('Config loaded successfully')
744
737
  * }
745
738
  *
746
- * // Read binary file safely
739
+ * // Read with explicit encoding
740
+ * const data = safeReadFileSync('./data.txt', { encoding: 'utf8' })
741
+ *
742
+ * // Read binary file by setting encoding to null
747
743
  * const buffer = safeReadFileSync('./image.png', { encoding: null })
748
744
  * ```
749
745
  */
750
746
  /*@__NO_SIDE_EFFECTS__*/
751
- export declare function safeReadFileSync(filepath: PathLike, options?: SafeReadOptions | undefined): string | NonSharedBuffer;
747
+ export declare function safeReadFileSync(filepath: PathLike, options: SafeReadOptions & {
748
+ encoding: null;
749
+ }): Buffer | undefined;
750
+ /*@__NO_SIDE_EFFECTS__*/
751
+ export declare function safeReadFileSync(filepath: PathLike, options?: SafeReadOptions | undefined): string | undefined;
752
752
  /**
753
753
  * Safely get file stats asynchronously, returning undefined on error.
754
754
  * Useful for checking file existence and properties without error handling.
@@ -812,6 +812,54 @@ export declare function safeStatsSync(filepath: PathLike, options?: ReadFileOpti
812
812
  */
813
813
  /*@__NO_SIDE_EFFECTS__*/
814
814
  export declare function uniqueSync(filepath: PathLike): string;
815
+ /**
816
+ * Result of file readability validation.
817
+ * Contains lists of valid and invalid file paths.
818
+ */
819
+ export interface ValidateFilesResult {
820
+ /**
821
+ * File paths that passed validation and are readable.
822
+ */
823
+ validPaths: string[];
824
+ /**
825
+ * File paths that failed validation (unreadable, permission denied, or non-existent).
826
+ * Common with Yarn Berry PnP virtual filesystem, pnpm symlinks, or filesystem race conditions.
827
+ */
828
+ invalidPaths: string[];
829
+ }
830
+ /**
831
+ * Validate that file paths are readable before processing.
832
+ * Filters out files from glob results that cannot be accessed (common with
833
+ * Yarn Berry PnP virtual filesystem, pnpm content-addressable store symlinks,
834
+ * or filesystem race conditions in CI/CD environments).
835
+ *
836
+ * This defensive pattern prevents ENOENT errors when files exist in glob
837
+ * results but are not accessible via standard filesystem operations.
838
+ *
839
+ * @param filepaths - Array of file paths to validate
840
+ * @returns Object with `validPaths` (readable) and `invalidPaths` (unreadable)
841
+ *
842
+ * @example
843
+ * ```ts
844
+ * import { validateFiles } from '@socketsecurity/lib/fs'
845
+ *
846
+ * const files = ['package.json', '.pnp.cjs/virtual-file.json']
847
+ * const { validPaths, invalidPaths } = validateFiles(files)
848
+ *
849
+ * console.log(`Valid: ${validPaths.length}`)
850
+ * console.log(`Invalid: ${invalidPaths.length}`)
851
+ * ```
852
+ *
853
+ * @example
854
+ * ```ts
855
+ * // Typical usage in Socket CLI commands
856
+ * const packagePaths = await getPackageFilesForScan(targets)
857
+ * const { validPaths } = validateFiles(packagePaths)
858
+ * await sdk.uploadManifestFiles(orgSlug, validPaths)
859
+ * ```
860
+ */
861
+ /*@__NO_SIDE_EFFECTS__*/
862
+ export declare function validateFiles(filepaths: string[] | readonly string[]): ValidateFilesResult;
815
863
  /**
816
864
  * Write JSON content to a file asynchronously with formatting.
817
865
  * Stringifies the value with configurable indentation and line endings.
package/dist/fs.js CHANGED
@@ -26,6 +26,8 @@ __export(fs_exports, {
26
26
  isDirEmptySync: () => isDirEmptySync,
27
27
  isDirSync: () => isDirSync,
28
28
  isSymLinkSync: () => isSymLinkSync,
29
+ normalizeEncoding: () => normalizeEncoding,
30
+ normalizeEncodingSlow: () => normalizeEncodingSlow,
29
31
  readDirNames: () => readDirNames,
30
32
  readDirNamesSync: () => readDirNamesSync,
31
33
  readFileBinary: () => readFileBinary,
@@ -66,6 +68,26 @@ const defaultRemoveOptions = (0, import_objects.objectFreeze)({
66
68
  recursive: true,
67
69
  retryDelay: 200
68
70
  });
71
+ let _cachedAllowedDirs;
72
+ function getAllowedDirectories() {
73
+ if (_cachedAllowedDirs === void 0) {
74
+ const path = /* @__PURE__ */ getPath();
75
+ _cachedAllowedDirs = [
76
+ path.resolve((0, import_socket.getOsTmpDir)()),
77
+ path.resolve((0, import_socket.getSocketCacacheDir)()),
78
+ path.resolve((0, import_socket.getSocketUserDir)())
79
+ ];
80
+ }
81
+ return _cachedAllowedDirs;
82
+ }
83
+ let _buffer;
84
+ // @__NO_SIDE_EFFECTS__
85
+ function getBuffer() {
86
+ if (_buffer === void 0) {
87
+ _buffer = require("node:buffer");
88
+ }
89
+ return _buffer;
90
+ }
69
91
  let _fs;
70
92
  // @__NO_SIDE_EFFECTS__
71
93
  function getFs() {
@@ -201,6 +223,10 @@ function findUpSync(name, options) {
201
223
  }
202
224
  return void 0;
203
225
  }
226
+ function invalidatePathCache() {
227
+ _cachedAllowedDirs = void 0;
228
+ }
229
+ (0, import_rewire.registerCacheInvalidation)(invalidatePathCache);
204
230
  // @__NO_SIDE_EFFECTS__
205
231
  async function isDir(filepath) {
206
232
  return !!(await /* @__PURE__ */ safeStats(filepath))?.isDirectory();
@@ -250,20 +276,75 @@ function isSymLinkSync(filepath) {
250
276
  return false;
251
277
  }
252
278
  // @__NO_SIDE_EFFECTS__
253
- function validateFiles(filepaths) {
254
- const fs = /* @__PURE__ */ getFs();
255
- const validPaths = [];
256
- const invalidPaths = [];
257
- const { R_OK } = fs.constants;
258
- for (const filepath of filepaths) {
259
- try {
260
- fs.accessSync(filepath, R_OK);
261
- validPaths.push(filepath);
262
- } catch {
263
- invalidPaths.push(filepath);
279
+ function normalizeEncoding(enc) {
280
+ return enc == null || enc === "utf8" || enc === "utf-8" ? "utf8" : /* @__PURE__ */ normalizeEncodingSlow(enc);
281
+ }
282
+ // @__NO_SIDE_EFFECTS__
283
+ function normalizeEncodingSlow(enc) {
284
+ const { length } = enc;
285
+ if (length === 4) {
286
+ if (enc === "ucs2" || enc === "UCS2") {
287
+ return "utf16le";
288
+ }
289
+ if (enc.toLowerCase() === "ucs2") {
290
+ return "utf16le";
291
+ }
292
+ } else if (length === 3 && enc === "hex" || enc === "HEX" || enc.toLowerCase() === "hex") {
293
+ return "hex";
294
+ } else if (length === 5) {
295
+ if (enc === "ascii") {
296
+ return "ascii";
297
+ }
298
+ if (enc === "ucs-2") {
299
+ return "utf16le";
300
+ }
301
+ if (enc === "ASCII") {
302
+ return "ascii";
303
+ }
304
+ if (enc === "UCS-2") {
305
+ return "utf16le";
306
+ }
307
+ enc = enc.toLowerCase();
308
+ if (enc === "ascii") {
309
+ return "ascii";
310
+ }
311
+ if (enc === "ucs-2") {
312
+ return "utf16le";
313
+ }
314
+ } else if (length === 6) {
315
+ if (enc === "base64") {
316
+ return "base64";
317
+ }
318
+ if (enc === "latin1" || enc === "binary") {
319
+ return "latin1";
320
+ }
321
+ if (enc === "BASE64") {
322
+ return "base64";
323
+ }
324
+ if (enc === "LATIN1" || enc === "BINARY") {
325
+ return "latin1";
326
+ }
327
+ enc = enc.toLowerCase();
328
+ if (enc === "base64") {
329
+ return "base64";
330
+ }
331
+ if (enc === "latin1" || enc === "binary") {
332
+ return "latin1";
333
+ }
334
+ } else if (length === 7) {
335
+ if (enc === "utf16le" || enc === "UTF16LE" || enc.toLowerCase() === "utf16le") {
336
+ return "utf16le";
337
+ }
338
+ } else if (length === 8) {
339
+ if (enc === "utf-16le" || enc === "UTF-16LE" || enc.toLowerCase() === "utf-16le") {
340
+ return "utf16le";
341
+ }
342
+ } else if (length === 9) {
343
+ if (enc === "base64url" || enc === "BASE64URL" || enc.toLowerCase() === "base64url") {
344
+ return "base64url";
264
345
  }
265
346
  }
266
- return { __proto__: null, validPaths, invalidPaths };
347
+ return "utf8";
267
348
  }
268
349
  // @__NO_SIDE_EFFECTS__
269
350
  async function readDirNames(dirname, options) {
@@ -350,8 +431,8 @@ async function readJson(filepath, options) {
350
431
  try {
351
432
  content = await fs.promises.readFile(filepath, {
352
433
  __proto__: null,
353
- encoding: "utf8",
354
- ...fsOptions
434
+ ...fsOptions,
435
+ encoding: "utf8"
355
436
  });
356
437
  } catch (e) {
357
438
  if (shouldThrow) {
@@ -393,8 +474,8 @@ function readJsonSync(filepath, options) {
393
474
  try {
394
475
  content = fs.readFileSync(filepath, {
395
476
  __proto__: null,
396
- encoding: "utf8",
397
- ...fsOptions
477
+ ...fsOptions,
478
+ encoding: "utf8"
398
479
  });
399
480
  } catch (e) {
400
481
  if (shouldThrow) {
@@ -423,22 +504,6 @@ Check file permissions or run with appropriate access.`,
423
504
  throws: shouldThrow
424
505
  });
425
506
  }
426
- let _cachedAllowedDirs;
427
- function getAllowedDirectories() {
428
- if (_cachedAllowedDirs === void 0) {
429
- const path = /* @__PURE__ */ getPath();
430
- _cachedAllowedDirs = [
431
- path.resolve((0, import_socket.getOsTmpDir)()),
432
- path.resolve((0, import_socket.getSocketCacacheDir)()),
433
- path.resolve((0, import_socket.getSocketUserDir)())
434
- ];
435
- }
436
- return _cachedAllowedDirs;
437
- }
438
- function invalidatePathCache() {
439
- _cachedAllowedDirs = void 0;
440
- }
441
- (0, import_rewire.registerCacheInvalidation)(invalidatePathCache);
442
507
  async function safeDelete(filepath, options) {
443
508
  const opts = { __proto__: null, ...options };
444
509
  const patterns = (0, import_arrays.isArray)(filepath) ? filepath.map(import_normalize.pathLikeToString) : [(0, import_normalize.pathLikeToString)(filepath)];
@@ -523,29 +588,54 @@ function safeMkdirSync(path, options) {
523
588
  }
524
589
  // @__NO_SIDE_EFFECTS__
525
590
  async function safeReadFile(filepath, options) {
526
- const opts = typeof options === "string" ? { encoding: options } : options;
591
+ const opts = typeof options === "string" ? { __proto__: null, encoding: options } : { __proto__: null, ...options };
592
+ const { defaultValue, ...rawReadOpts } = opts;
593
+ const readOpts = { __proto__: null, ...rawReadOpts };
594
+ const shouldReturnBuffer = readOpts.encoding === null;
595
+ const encoding = shouldReturnBuffer ? null : /* @__PURE__ */ normalizeEncoding(readOpts.encoding);
527
596
  const fs = /* @__PURE__ */ getFs();
528
597
  try {
529
598
  return await fs.promises.readFile(filepath, {
599
+ __proto__: null,
530
600
  signal: abortSignal,
531
- ...opts
601
+ ...readOpts,
602
+ encoding
532
603
  });
533
604
  } catch {
534
605
  }
535
- return void 0;
606
+ if (defaultValue === void 0) {
607
+ return void 0;
608
+ }
609
+ if (shouldReturnBuffer) {
610
+ const { Buffer: Buffer2 } = /* @__PURE__ */ getBuffer();
611
+ return Buffer2.isBuffer(defaultValue) ? defaultValue : void 0;
612
+ }
613
+ return typeof defaultValue === "string" ? defaultValue : String(defaultValue);
536
614
  }
537
615
  // @__NO_SIDE_EFFECTS__
538
616
  function safeReadFileSync(filepath, options) {
539
- const opts = typeof options === "string" ? { encoding: options } : options;
617
+ const opts = typeof options === "string" ? { __proto__: null, encoding: options } : { __proto__: null, ...options };
618
+ const { defaultValue, ...rawReadOpts } = opts;
619
+ const readOpts = { __proto__: null, ...rawReadOpts };
620
+ const shouldReturnBuffer = readOpts.encoding === null;
621
+ const encoding = shouldReturnBuffer ? null : /* @__PURE__ */ normalizeEncoding(readOpts.encoding);
540
622
  const fs = /* @__PURE__ */ getFs();
541
623
  try {
542
624
  return fs.readFileSync(filepath, {
543
625
  __proto__: null,
544
- ...opts
626
+ ...readOpts,
627
+ encoding
545
628
  });
546
629
  } catch {
547
630
  }
548
- return void 0;
631
+ if (defaultValue === void 0) {
632
+ return void 0;
633
+ }
634
+ if (shouldReturnBuffer) {
635
+ const { Buffer: Buffer2 } = /* @__PURE__ */ getBuffer();
636
+ return Buffer2.isBuffer(defaultValue) ? defaultValue : void 0;
637
+ }
638
+ return typeof defaultValue === "string" ? defaultValue : String(defaultValue);
549
639
  }
550
640
  // @__NO_SIDE_EFFECTS__
551
641
  async function safeStats(filepath) {
@@ -589,6 +679,22 @@ function uniqueSync(filepath) {
589
679
  } while (fs.existsSync(uniquePath));
590
680
  return (0, import_normalize.normalizePath)(uniquePath);
591
681
  }
682
+ // @__NO_SIDE_EFFECTS__
683
+ function validateFiles(filepaths) {
684
+ const fs = /* @__PURE__ */ getFs();
685
+ const validPaths = [];
686
+ const invalidPaths = [];
687
+ const { R_OK } = fs.constants;
688
+ for (const filepath of filepaths) {
689
+ try {
690
+ fs.accessSync(filepath, R_OK);
691
+ validPaths.push(filepath);
692
+ } catch {
693
+ invalidPaths.push(filepath);
694
+ }
695
+ }
696
+ return { __proto__: null, validPaths, invalidPaths };
697
+ }
592
698
  async function writeJson(filepath, jsonContent, options) {
593
699
  const opts = typeof options === "string" ? { encoding: options } : options;
594
700
  const { EOL, finalEOL, replacer, spaces, ...fsOptions } = {
@@ -638,6 +744,8 @@ function writeJsonSync(filepath, jsonContent, options) {
638
744
  isDirEmptySync,
639
745
  isDirSync,
640
746
  isSymLinkSync,
747
+ normalizeEncoding,
748
+ normalizeEncodingSlow,
641
749
  readDirNames,
642
750
  readDirNamesSync,
643
751
  readFileBinary,
package/dist/globs.d.ts CHANGED
@@ -44,3 +44,15 @@ export declare function getGlobMatcher(glob: Pattern | Pattern[], options?: {
44
44
  nocase?: boolean;
45
45
  ignore?: string[];
46
46
  }): (path: string) => boolean;
47
+ /**
48
+ * Asynchronously find files matching glob patterns.
49
+ * Wrapper around fast-glob.
50
+ */
51
+ /*@__NO_SIDE_EFFECTS__*/
52
+ export declare function glob(patterns: Pattern | Pattern[], options?: FastGlobOptions): Promise<string[]>;
53
+ /**
54
+ * Synchronously find files matching glob patterns.
55
+ * Wrapper around fast-glob.sync.
56
+ */
57
+ /*@__NO_SIDE_EFFECTS__*/
58
+ export declare function globSync(patterns: Pattern | Pattern[], options?: FastGlobOptions): string[];
package/dist/globs.js CHANGED
@@ -31,7 +31,9 @@ var globs_exports = {};
31
31
  __export(globs_exports, {
32
32
  defaultIgnore: () => defaultIgnore,
33
33
  getGlobMatcher: () => getGlobMatcher,
34
- globStreamLicenses: () => globStreamLicenses
34
+ glob: () => glob,
35
+ globStreamLicenses: () => globStreamLicenses,
36
+ globSync: () => globSync
35
37
  });
36
38
  module.exports = __toCommonJS(globs_exports);
37
39
  var fastGlob = __toESM(require("./external/fast-glob.js"));
@@ -107,8 +109,8 @@ function globStreamLicenses(dirname, options) {
107
109
  }
108
110
  const matcherCache = /* @__PURE__ */ new Map();
109
111
  // @__NO_SIDE_EFFECTS__
110
- function getGlobMatcher(glob, options) {
111
- const patterns = Array.isArray(glob) ? glob : [glob];
112
+ function getGlobMatcher(glob2, options) {
113
+ const patterns = Array.isArray(glob2) ? glob2 : [glob2];
112
114
  const key = JSON.stringify({ patterns, options });
113
115
  let matcher = matcherCache.get(key);
114
116
  if (matcher) {
@@ -129,9 +131,19 @@ function getGlobMatcher(glob, options) {
129
131
  matcherCache.set(key, matcher);
130
132
  return matcher;
131
133
  }
134
+ // @__NO_SIDE_EFFECTS__
135
+ function glob(patterns, options) {
136
+ return fastGlob.glob(patterns, options);
137
+ }
138
+ // @__NO_SIDE_EFFECTS__
139
+ function globSync(patterns, options) {
140
+ return fastGlob.globSync(patterns, options);
141
+ }
132
142
  // Annotate the CommonJS export names for ESM import in node:
133
143
  0 && (module.exports = {
134
144
  defaultIgnore,
135
145
  getGlobMatcher,
136
- globStreamLicenses
146
+ glob,
147
+ globStreamLicenses,
148
+ globSync
137
149
  });
package/dist/objects.d.ts CHANGED
@@ -55,33 +55,6 @@ type SortedObject<T> = {
55
55
  [key: PropertyKey]: T;
56
56
  };
57
57
  export type { GetterDefObj, LazyGetterStats, ConstantsObjectOptions, Remap };
58
- /**
59
- * Create a lazy getter function that memoizes its result.
60
- *
61
- * The returned function will only call the getter once, caching the result
62
- * for subsequent calls. This is useful for expensive computations or
63
- * operations that should only happen when needed.
64
- *
65
- * @param name - The property key name for the getter (used for debugging and stats)
66
- * @param getter - Function that computes the value on first access
67
- * @param stats - Optional stats object to track initialization
68
- * @returns A memoized getter function
69
- *
70
- * @example
71
- * ```ts
72
- * const stats = { initialized: new Set() }
73
- * const getLargeData = createLazyGetter('data', () => {
74
- * console.log('Computing expensive data...')
75
- * return { large: 'dataset' }
76
- * }, stats)
77
- *
78
- * getLargeData() // Logs "Computing expensive data..." and returns data
79
- * getLargeData() // Returns cached data without logging
80
- * console.log(stats.initialized.has('data')) // true
81
- * ```
82
- */
83
- /*@__NO_SIDE_EFFECTS__*/
84
- export declare function createLazyGetter<T>(name: PropertyKey, getter: () => T, stats?: LazyGetterStats | undefined): () => T;
85
58
  /**
86
59
  * Create a frozen constants object with lazy getters and internal properties.
87
60
  *
@@ -120,6 +93,33 @@ export declare function createLazyGetter<T>(name: PropertyKey, getter: () => T,
120
93
  */
121
94
  /*@__NO_SIDE_EFFECTS__*/
122
95
  export declare function createConstantsObject(props: object, options_?: ConstantsObjectOptions | undefined): Readonly<object>;
96
+ /**
97
+ * Create a lazy getter function that memoizes its result.
98
+ *
99
+ * The returned function will only call the getter once, caching the result
100
+ * for subsequent calls. This is useful for expensive computations or
101
+ * operations that should only happen when needed.
102
+ *
103
+ * @param name - The property key name for the getter (used for debugging and stats)
104
+ * @param getter - Function that computes the value on first access
105
+ * @param stats - Optional stats object to track initialization
106
+ * @returns A memoized getter function
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * const stats = { initialized: new Set() }
111
+ * const getLargeData = createLazyGetter('data', () => {
112
+ * console.log('Computing expensive data...')
113
+ * return { large: 'dataset' }
114
+ * }, stats)
115
+ *
116
+ * getLargeData() // Logs "Computing expensive data..." and returns data
117
+ * getLargeData() // Returns cached data without logging
118
+ * console.log(stats.initialized.has('data')) // true
119
+ * ```
120
+ */
121
+ /*@__NO_SIDE_EFFECTS__*/
122
+ export declare function createLazyGetter<T>(name: PropertyKey, getter: () => T, stats?: LazyGetterStats | undefined): () => T;
123
123
  /**
124
124
  * Define a getter property on an object.
125
125
  *
@@ -393,6 +393,40 @@ export declare const objectAssign: {
393
393
  <T extends {}, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W;
394
394
  (target: object, ...sources: any[]): any;
395
395
  };
396
+ /**
397
+ * Deep merge source object into target object.
398
+ *
399
+ * Recursively merges properties from `source` into `target`. Arrays in source
400
+ * completely replace arrays in target (no element-wise merging). Objects are
401
+ * merged recursively. Includes infinite loop detection for safety.
402
+ *
403
+ * @param target - The object to merge into (will be modified)
404
+ * @param source - The object to merge from
405
+ * @returns The modified target object
406
+ *
407
+ * @example
408
+ * ```ts
409
+ * const target = { a: { x: 1 }, b: [1, 2] }
410
+ * const source = { a: { y: 2 }, b: [3, 4, 5], c: 3 }
411
+ * merge(target, source)
412
+ * // { a: { x: 1, y: 2 }, b: [3, 4, 5], c: 3 }
413
+ * ```
414
+ *
415
+ * @example
416
+ * ```ts
417
+ * // Arrays are replaced, not merged
418
+ * merge({ arr: [1, 2] }, { arr: [3] }) // { arr: [3] }
419
+ *
420
+ * // Deep object merging
421
+ * merge(
422
+ * { config: { api: 'v1', timeout: 1000 } },
423
+ * { config: { api: 'v2', retries: 3 } }
424
+ * )
425
+ * // { config: { api: 'v2', timeout: 1000, retries: 3 } }
426
+ * ```
427
+ */
428
+ /*@__NO_SIDE_EFFECTS__*/
429
+ export declare function merge<T extends object, U extends object>(target: T, source: U): T & U;
396
430
  /**
397
431
  * Get all own property entries (key-value pairs) from an object.
398
432
  *
@@ -438,40 +472,6 @@ export declare const objectFreeze: {
438
472
  }, U extends string | number | bigint | symbol | boolean>(o: T): Readonly<T>;
439
473
  <T>(o: T): Readonly<T>;
440
474
  };
441
- /**
442
- * Deep merge source object into target object.
443
- *
444
- * Recursively merges properties from `source` into `target`. Arrays in source
445
- * completely replace arrays in target (no element-wise merging). Objects are
446
- * merged recursively. Includes infinite loop detection for safety.
447
- *
448
- * @param target - The object to merge into (will be modified)
449
- * @param source - The object to merge from
450
- * @returns The modified target object
451
- *
452
- * @example
453
- * ```ts
454
- * const target = { a: { x: 1 }, b: [1, 2] }
455
- * const source = { a: { y: 2 }, b: [3, 4, 5], c: 3 }
456
- * merge(target, source)
457
- * // { a: { x: 1, y: 2 }, b: [3, 4, 5], c: 3 }
458
- * ```
459
- *
460
- * @example
461
- * ```ts
462
- * // Arrays are replaced, not merged
463
- * merge({ arr: [1, 2] }, { arr: [3] }) // { arr: [3] }
464
- *
465
- * // Deep object merging
466
- * merge(
467
- * { config: { api: 'v1', timeout: 1000 } },
468
- * { config: { api: 'v2', retries: 3 } }
469
- * )
470
- * // { config: { api: 'v2', timeout: 1000, retries: 3 } }
471
- * ```
472
- */
473
- /*@__NO_SIDE_EFFECTS__*/
474
- export declare function merge<T extends object, U extends object>(target: T, source: U): T & U;
475
475
  /**
476
476
  * Convert an object to a new object with sorted keys.
477
477
  *
package/dist/objects.js CHANGED
@@ -56,20 +56,6 @@ const ObjectPrototype = Object.prototype;
56
56
  const ObjectSetPrototypeOf = Object.setPrototypeOf;
57
57
  const ReflectOwnKeys = Reflect.ownKeys;
58
58
  // @__NO_SIDE_EFFECTS__
59
- function createLazyGetter(name, getter, stats) {
60
- let lazyValue = import_core.UNDEFINED_TOKEN;
61
- const { [name]: lazyGetter } = {
62
- [name]() {
63
- if (lazyValue === import_core.UNDEFINED_TOKEN) {
64
- stats?.initialized?.add(name);
65
- lazyValue = getter();
66
- }
67
- return lazyValue;
68
- }
69
- };
70
- return lazyGetter;
71
- }
72
- // @__NO_SIDE_EFFECTS__
73
59
  function createConstantsObject(props, options_) {
74
60
  const options = { __proto__: null, ...options_ };
75
61
  const attributes = ObjectFreeze({
@@ -123,6 +109,20 @@ function createConstantsObject(props, options_) {
123
109
  }
124
110
  return ObjectFreeze(object);
125
111
  }
112
+ // @__NO_SIDE_EFFECTS__
113
+ function createLazyGetter(name, getter, stats) {
114
+ let lazyValue = import_core.UNDEFINED_TOKEN;
115
+ const { [name]: lazyGetter } = {
116
+ [name]() {
117
+ if (lazyValue === import_core.UNDEFINED_TOKEN) {
118
+ stats?.initialized?.add(name);
119
+ lazyValue = getter();
120
+ }
121
+ return lazyValue;
122
+ }
123
+ };
124
+ return lazyGetter;
125
+ }
126
126
  function defineGetter(object, propKey, getter) {
127
127
  ObjectDefineProperty(object, propKey, {
128
128
  get: getter,
@@ -209,22 +209,6 @@ function isObjectObject(value) {
209
209
  }
210
210
  const objectAssign = Object.assign;
211
211
  // @__NO_SIDE_EFFECTS__
212
- function objectEntries(obj) {
213
- if (obj === null || obj === void 0) {
214
- return [];
215
- }
216
- const keys = ReflectOwnKeys(obj);
217
- const { length } = keys;
218
- const entries = Array(length);
219
- const record = obj;
220
- for (let i = 0; i < length; i += 1) {
221
- const key = keys[i];
222
- entries[i] = [key, record[key]];
223
- }
224
- return entries;
225
- }
226
- const objectFreeze = Object.freeze;
227
- // @__NO_SIDE_EFFECTS__
228
212
  function merge(target, source) {
229
213
  if (!/* @__PURE__ */ isObject(target) || !/* @__PURE__ */ isObject(source)) {
230
214
  return target;
@@ -266,6 +250,22 @@ function merge(target, source) {
266
250
  return target;
267
251
  }
268
252
  // @__NO_SIDE_EFFECTS__
253
+ function objectEntries(obj) {
254
+ if (obj === null || obj === void 0) {
255
+ return [];
256
+ }
257
+ const keys = ReflectOwnKeys(obj);
258
+ const { length } = keys;
259
+ const entries = Array(length);
260
+ const record = obj;
261
+ for (let i = 0; i < length; i += 1) {
262
+ const key = keys[i];
263
+ entries[i] = [key, record[key]];
264
+ }
265
+ return entries;
266
+ }
267
+ const objectFreeze = Object.freeze;
268
+ // @__NO_SIDE_EFFECTS__
269
269
  function toSortedObject(obj) {
270
270
  return /* @__PURE__ */ toSortedObjectFromEntries(/* @__PURE__ */ objectEntries(obj));
271
271
  }
@@ -1,2 +1,3 @@
1
+ /* c8 ignore next - External semver call */
1
2
  declare const packageDefaultNodeRange: string;
2
3
  export { packageDefaultNodeRange };
@@ -36,6 +36,7 @@ var yarnPkgExtensions = __toESM(require("./external/@yarnpkg/extensions.js"));
36
36
  const { freeze: ObjectFreeze } = Object;
37
37
  const packageExtensions = ObjectFreeze(
38
38
  [
39
+ /* c8 ignore next - External @yarnpkg/extensions data */
39
40
  ...yarnPkgExtensions.packageExtensions,
40
41
  [
41
42
  "@yarnpkg/extensions@>=1.1.0",
@@ -208,25 +208,6 @@ export declare function normalizeIterationOptions(options?: number | IterationOp
208
208
  */
209
209
  /*@__NO_SIDE_EFFECTS__*/
210
210
  export declare function normalizeRetryOptions(options?: number | RetryOptions | undefined): RetryOptions;
211
- /**
212
- * Resolve retry options from various input formats.
213
- *
214
- * Converts shorthand and partial options into a base configuration that can be
215
- * further normalized. This is an internal helper for option processing.
216
- *
217
- * @param options - Retry count as number, or partial options object, or undefined
218
- * @returns Resolved retry options with defaults for basic properties
219
- *
220
- * @example
221
- * resolveRetryOptions(3)
222
- * // => { retries: 3, minTimeout: 200, maxTimeout: 10000, factor: 2 }
223
- *
224
- * @example
225
- * resolveRetryOptions({ retries: 5, maxTimeout: 5000 })
226
- * // => { retries: 5, minTimeout: 200, maxTimeout: 5000, factor: 2 }
227
- */
228
- /*@__NO_SIDE_EFFECTS__*/
229
- export declare function resolveRetryOptions(options?: number | RetryOptions | undefined): RetryOptions;
230
211
  /**
231
212
  * Execute an async function for each array element with concurrency control.
232
213
  *
@@ -455,3 +436,22 @@ export declare function pFilterChunk<T>(chunks: T[][], callbackFn: (value: T) =>
455
436
  */
456
437
  /*@__NO_SIDE_EFFECTS__*/
457
438
  export declare function pRetry<T>(callbackFn: (...args: unknown[]) => Promise<T>, options?: number | RetryOptions | undefined): Promise<T | undefined>;
439
+ /**
440
+ * Resolve retry options from various input formats.
441
+ *
442
+ * Converts shorthand and partial options into a base configuration that can be
443
+ * further normalized. This is an internal helper for option processing.
444
+ *
445
+ * @param options - Retry count as number, or partial options object, or undefined
446
+ * @returns Resolved retry options with defaults for basic properties
447
+ *
448
+ * @example
449
+ * resolveRetryOptions(3)
450
+ * // => { retries: 3, minTimeout: 200, maxTimeout: 10000, factor: 2 }
451
+ *
452
+ * @example
453
+ * resolveRetryOptions({ retries: 5, maxTimeout: 5000 })
454
+ * // => { retries: 5, minTimeout: 200, maxTimeout: 5000, factor: 2 }
455
+ */
456
+ /*@__NO_SIDE_EFFECTS__*/
457
+ export declare function resolveRetryOptions(options?: number | RetryOptions | undefined): RetryOptions;
package/dist/promises.js CHANGED
@@ -101,20 +101,6 @@ function normalizeRetryOptions(options) {
101
101
  };
102
102
  }
103
103
  // @__NO_SIDE_EFFECTS__
104
- function resolveRetryOptions(options) {
105
- const defaults = {
106
- __proto__: null,
107
- retries: 0,
108
- baseDelayMs: 200,
109
- maxDelayMs: 1e4,
110
- backoffFactor: 2
111
- };
112
- if (typeof options === "number") {
113
- return { ...defaults, retries: options };
114
- }
115
- return options ? { ...defaults, ...options } : defaults;
116
- }
117
- // @__NO_SIDE_EFFECTS__
118
104
  async function pEach(array, callbackFn, options) {
119
105
  const iterOpts = /* @__PURE__ */ normalizeIterationOptions(options);
120
106
  const { concurrency, retries, signal } = iterOpts;
@@ -259,6 +245,20 @@ async function pRetry(callbackFn, options) {
259
245
  }
260
246
  return void 0;
261
247
  }
248
+ // @__NO_SIDE_EFFECTS__
249
+ function resolveRetryOptions(options) {
250
+ const defaults = {
251
+ __proto__: null,
252
+ retries: 0,
253
+ baseDelayMs: 200,
254
+ maxDelayMs: 1e4,
255
+ backoffFactor: 2
256
+ };
257
+ if (typeof options === "number") {
258
+ return { ...defaults, retries: options };
259
+ }
260
+ return options ? { ...defaults, ...options } : defaults;
261
+ }
262
262
  // Annotate the CommonJS export names for ESM import in node:
263
263
  0 && (module.exports = {
264
264
  normalizeIterationOptions,
package/dist/sorts.d.ts CHANGED
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Compare semantic versions.
3
+ */
4
+ /*@__NO_SIDE_EFFECTS__*/
5
+ export declare function compareSemver(a: string, b: string): number;
6
+ /**
7
+ * Simple string comparison.
8
+ */
9
+ /*@__NO_SIDE_EFFECTS__*/
10
+ export declare function compareStr(a: string, b: string): number;
1
11
  /**
2
12
  * Compare two strings using locale-aware comparison.
3
13
  */
@@ -15,14 +25,4 @@ type FastSortFunction = ReturnType<typeof import('fast-sort').createNewSortInsta
15
25
  */
16
26
  /*@__NO_SIDE_EFFECTS__*/
17
27
  export declare function naturalSorter<T>(arrayToSort: T[]): ReturnType<FastSortFunction>;
18
- /**
19
- * Simple string comparison.
20
- */
21
- /*@__NO_SIDE_EFFECTS__*/
22
- export declare function compareStr(a: string, b: string): number;
23
- /**
24
- * Compare semantic versions.
25
- */
26
- /*@__NO_SIDE_EFFECTS__*/
27
- export declare function compareSemver(a: string, b: string): number;
28
28
  export {};
package/dist/sorts.js CHANGED
@@ -38,6 +38,25 @@ __export(sorts_exports, {
38
38
  module.exports = __toCommonJS(sorts_exports);
39
39
  var fastSort = __toESM(require("./external/fast-sort.js"));
40
40
  var semver = __toESM(require("./external/semver.js"));
41
+ // @__NO_SIDE_EFFECTS__
42
+ function compareSemver(a, b) {
43
+ const validA = semver.valid(a);
44
+ const validB = semver.valid(b);
45
+ if (!validA && !validB) {
46
+ return 0;
47
+ }
48
+ if (!validA) {
49
+ return -1;
50
+ }
51
+ if (!validB) {
52
+ return 1;
53
+ }
54
+ return semver.compare(a, b);
55
+ }
56
+ // @__NO_SIDE_EFFECTS__
57
+ function compareStr(a, b) {
58
+ return a < b ? -1 : a > b ? 1 : 0;
59
+ }
41
60
  let _localeCompare;
42
61
  // @__NO_SIDE_EFFECTS__
43
62
  function localeCompare(x, y) {
@@ -77,25 +96,6 @@ function naturalSorter(arrayToSort) {
77
96
  }
78
97
  return _naturalSorter(arrayToSort);
79
98
  }
80
- // @__NO_SIDE_EFFECTS__
81
- function compareStr(a, b) {
82
- return a < b ? -1 : a > b ? 1 : 0;
83
- }
84
- // @__NO_SIDE_EFFECTS__
85
- function compareSemver(a, b) {
86
- const validA = semver.valid(a);
87
- const validB = semver.valid(b);
88
- if (!validA && !validB) {
89
- return 0;
90
- }
91
- if (!validA) {
92
- return -1;
93
- }
94
- if (!validB) {
95
- return 1;
96
- }
97
- return semver.compare(a, b);
98
- }
99
99
  // Annotate the CommonJS export names for ESM import in node:
100
100
  0 && (module.exports = {
101
101
  compareSemver,
package/dist/strings.d.ts CHANGED
@@ -85,6 +85,41 @@ export declare function applyLinePrefix(str: string, options?: ApplyLinePrefixOp
85
85
  */
86
86
  /*@__NO_SIDE_EFFECTS__*/
87
87
  export declare function camelToKebab(str: string): string;
88
+ /**
89
+ * Center text within a given width.
90
+ *
91
+ * Adds spaces before and after the text to center it within the specified width.
92
+ * Distributes padding evenly on both sides. When the padding is odd, the extra
93
+ * space is added to the right side. Strips ANSI codes before calculating text
94
+ * length to ensure accurate centering of colored text.
95
+ *
96
+ * If the text is already wider than or equal to the target width, returns the
97
+ * original text unchanged (no truncation occurs).
98
+ *
99
+ * @param text - The text to center (may include ANSI codes)
100
+ * @param width - The target width in columns
101
+ * @returns The centered text with padding
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * centerText('hello', 11)
106
+ * // Returns: ' hello ' (3 spaces on each side)
107
+ *
108
+ * centerText('hi', 10)
109
+ * // Returns: ' hi ' (4 spaces on each side)
110
+ *
111
+ * centerText('odd', 8)
112
+ * // Returns: ' odd ' (2 left, 3 right)
113
+ *
114
+ * centerText('\x1b[31mred\x1b[0m', 7)
115
+ * // Returns: ' \x1b[31mred\x1b[0m ' (ANSI codes preserved, 'red' centered)
116
+ *
117
+ * centerText('too long text', 5)
118
+ * // Returns: 'too long text' (no truncation, returned as-is)
119
+ * ```
120
+ */
121
+ /*@__NO_SIDE_EFFECTS__*/
122
+ export declare function centerText(text: string, width: number): string;
88
123
  export interface IndentStringOptions {
89
124
  /**
90
125
  * Number of spaces to indent each line.
@@ -181,6 +216,33 @@ export declare function isBlankString(value: unknown): value is BlankString;
181
216
  */
182
217
  /*@__NO_SIDE_EFFECTS__*/
183
218
  export declare function isNonEmptyString(value: unknown): value is Exclude<string, EmptyString>;
219
+ /**
220
+ * Repeat a string a specified number of times.
221
+ *
222
+ * Creates a new string by repeating the input string `count` times.
223
+ * Returns an empty string if count is 0 or negative.
224
+ *
225
+ * @param str - The string to repeat
226
+ * @param count - The number of times to repeat the string
227
+ * @returns The repeated string, or empty string if count <= 0
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * repeatString('hello', 3)
232
+ * // Returns: 'hellohellohello'
233
+ *
234
+ * repeatString('x', 5)
235
+ * // Returns: 'xxxxx'
236
+ *
237
+ * repeatString('hello', 0)
238
+ * // Returns: ''
239
+ *
240
+ * repeatString('hello', -1)
241
+ * // Returns: ''
242
+ * ```
243
+ */
244
+ /*@__NO_SIDE_EFFECTS__*/
245
+ export declare function repeatString(str: string, count: number): string;
184
246
  export interface SearchOptions {
185
247
  /**
186
248
  * The position in the string to begin searching from.
@@ -252,6 +314,7 @@ export declare function search(str: string, regexp: RegExp, options?: SearchOpti
252
314
  */
253
315
  /*@__NO_SIDE_EFFECTS__*/
254
316
  export declare function stripBom(str: string): string;
317
+ /* c8 ignore stop */
255
318
  /**
256
319
  * Get the visual width of a string in terminal columns.
257
320
  *
@@ -403,66 +466,3 @@ export declare function toKebabCase(str: string): string;
403
466
  */
404
467
  /*@__NO_SIDE_EFFECTS__*/
405
468
  export declare function trimNewlines(str: string): string;
406
- /**
407
- * Repeat a string n times.
408
- *
409
- * Creates a new string by repeating the input string the specified number of times.
410
- * Returns an empty string if count is zero or negative. This is a simple wrapper
411
- * around `String.prototype.repeat()` with guard for non-positive counts.
412
- *
413
- * @param str - The string to repeat
414
- * @param count - The number of times to repeat the string
415
- * @returns The repeated string, or empty string if count <= 0
416
- *
417
- * @example
418
- * ```ts
419
- * repeatString('hello', 3)
420
- * // Returns: 'hellohellohello'
421
- *
422
- * repeatString('x', 5)
423
- * // Returns: 'xxxxx'
424
- *
425
- * repeatString('hello', 0)
426
- * // Returns: ''
427
- *
428
- * repeatString('hello', -1)
429
- * // Returns: ''
430
- * ```
431
- */
432
- /*@__NO_SIDE_EFFECTS__*/
433
- export declare function repeatString(str: string, count: number): string;
434
- /**
435
- * Center text within a given width.
436
- *
437
- * Adds spaces before and after the text to center it within the specified width.
438
- * Distributes padding evenly on both sides. When the padding is odd, the extra
439
- * space is added to the right side. Strips ANSI codes before calculating text
440
- * length to ensure accurate centering of colored text.
441
- *
442
- * If the text is already wider than or equal to the target width, returns the
443
- * original text unchanged (no truncation occurs).
444
- *
445
- * @param text - The text to center (may include ANSI codes)
446
- * @param width - The target width in columns
447
- * @returns The centered text with padding
448
- *
449
- * @example
450
- * ```ts
451
- * centerText('hello', 11)
452
- * // Returns: ' hello ' (3 spaces on each side)
453
- *
454
- * centerText('hi', 10)
455
- * // Returns: ' hi ' (4 spaces on each side)
456
- *
457
- * centerText('odd', 8)
458
- * // Returns: ' odd ' (2 left, 3 right)
459
- *
460
- * centerText('\x1b[31mred\x1b[0m', 7)
461
- * // Returns: ' \x1b[31mred\x1b[0m ' (ANSI codes preserved, 'red' centered)
462
- *
463
- * centerText('too long text', 5)
464
- * // Returns: 'too long text' (no truncation, returned as-is)
465
- * ```
466
- */
467
- /*@__NO_SIDE_EFFECTS__*/
468
- export declare function centerText(text: string, width: number): string;
package/dist/strings.js CHANGED
@@ -92,6 +92,17 @@ function camelToKebab(str) {
92
92
  return result;
93
93
  }
94
94
  // @__NO_SIDE_EFFECTS__
95
+ function centerText(text, width) {
96
+ const textLength = (0, import_ansi.stripAnsi)(text).length;
97
+ if (textLength >= width) {
98
+ return text;
99
+ }
100
+ const padding = width - textLength;
101
+ const leftPad = Math.floor(padding / 2);
102
+ const rightPad = padding - leftPad;
103
+ return " ".repeat(leftPad) + text + " ".repeat(rightPad);
104
+ }
105
+ // @__NO_SIDE_EFFECTS__
95
106
  function indentString(str, options) {
96
107
  const { count = 1 } = { __proto__: null, ...options };
97
108
  return str.replace(/^(?!\s*$)/gm, " ".repeat(count));
@@ -105,6 +116,13 @@ function isNonEmptyString(value) {
105
116
  return typeof value === "string" && value.length > 0;
106
117
  }
107
118
  // @__NO_SIDE_EFFECTS__
119
+ function repeatString(str, count) {
120
+ if (count <= 0) {
121
+ return "";
122
+ }
123
+ return str.repeat(count);
124
+ }
125
+ // @__NO_SIDE_EFFECTS__
108
126
  function search(str, regexp, options) {
109
127
  const { fromIndex = 0 } = { __proto__: null, ...options };
110
128
  const { length } = str;
@@ -215,24 +233,6 @@ function trimNewlines(str) {
215
233
  }
216
234
  return start === 0 && end === length ? str : str.slice(start, end);
217
235
  }
218
- // @__NO_SIDE_EFFECTS__
219
- function repeatString(str, count) {
220
- if (count <= 0) {
221
- return "";
222
- }
223
- return str.repeat(count);
224
- }
225
- // @__NO_SIDE_EFFECTS__
226
- function centerText(text, width) {
227
- const textLength = (0, import_ansi.stripAnsi)(text).length;
228
- if (textLength >= width) {
229
- return text;
230
- }
231
- const padding = width - textLength;
232
- const leftPad = Math.floor(padding / 2);
233
- const rightPad = padding - leftPad;
234
- return " ".repeat(leftPad) + text + " ".repeat(rightPad);
235
- }
236
236
  // Annotate the CommonJS export names for ESM import in node:
237
237
  0 && (module.exports = {
238
238
  ansiRegex,
@@ -82,11 +82,15 @@ function restoreWarnings() {
82
82
  }
83
83
  }
84
84
  async function withSuppressedWarnings(warningType, callback) {
85
+ const wasAlreadySuppressed = suppressedWarnings.has(warningType);
85
86
  const original = process.emitWarning;
86
87
  suppressWarningType(warningType);
87
88
  try {
88
89
  return await callback();
89
90
  } finally {
91
+ if (!wasAlreadySuppressed) {
92
+ suppressedWarnings.delete(warningType);
93
+ }
90
94
  process.emitWarning = original;
91
95
  }
92
96
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socketsecurity/lib",
3
- "version": "4.2.0",
3
+ "version": "4.4.0",
4
4
  "packageManager": "pnpm@10.22.0",
5
5
  "license": "MIT",
6
6
  "description": "Core utilities and infrastructure for Socket.dev security tools",
@@ -690,7 +690,7 @@
690
690
  "@socketregistry/is-unicode-supported": "1.0.5",
691
691
  "@socketregistry/packageurl-js": "1.3.5",
692
692
  "@socketregistry/yocto-spinner": "1.0.25",
693
- "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@4.1.0",
693
+ "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@4.3.0",
694
694
  "@types/node": "24.9.2",
695
695
  "@typescript/native-preview": "7.0.0-dev.20250920.1",
696
696
  "@vitest/coverage-v8": "4.0.3",