@socketsecurity/lib 5.21.0 → 5.23.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,26 @@ 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
+ ## [5.23.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.23.0) - 2026-04-22
9
+
10
+ ### Added
11
+
12
+ - `@socketsecurity/lib/errors` `isError(value)` — spec-compliant ES2025 [`Error.isError`](https://tc39.es/ecma262/#sec-error.iserror) with an `@@toStringTag`-based shim for older engines. Recognizes cross-realm Errors (worker threads, vm contexts, iframes) that same-realm `instanceof Error` misses
13
+ - `@socketsecurity/lib/errors` `errorMessage(value)` — extracts a readable message from any caught value (Error with cause chain via `messageWithCauses`, primitive, plain object, or nullish) with the shared `UNKNOWN_ERROR` (`'Unknown error'`) fallback. Replaces the `e instanceof Error ? e.message : String(e)` pattern
14
+ - `@socketsecurity/lib/errors` `errorStack(value)` — companion helper returning the cause-aware stack for Error instances (via `stackWithCauses`) and `undefined` otherwise
15
+ - `@socketsecurity/lib/errors` `isErrnoException(value)` — narrows to `NodeJS.ErrnoException` (an Error with a non-empty uppercase-prefixed `.code`, matching the libuv `UV_E*` / Node `ERR_*` conventions), cross-realm safe
16
+ - `@socketsecurity/lib/errors` re-exports `UNKNOWN_ERROR` from `constants/core` so callers don't need a separate import
17
+
18
+ ### Changed
19
+
20
+ - `@socketsecurity/lib/errors` pony-cause `messageWithCauses` / `stackWithCauses` / `findCauseByReference` / `getErrorCause` — patched to use `isError` internally so cross-realm Errors are recognized (previously returned `''` for any Error thrown in a different realm)
21
+
22
+ ## [5.22.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.22.0) - 2026-04-21
23
+
24
+ ### Changed
25
+
26
+ - `@socketsecurity/lib/releases/socket-btm` `getPlatformArch()` / `getBinaryAssetName()` — aligned with pnpm pack-app's `<os>-<arch>[-<libc>]` target format. The Windows OS segment is now `win32` (was `win`); `getPlatformArch('win32', 'x64')` returns `'win32-x64'` and `getBinaryAssetName('node', 'win32', 'x64')` returns `'node-win32-x64.exe'`. Callers that string-match on the output need updates
27
+
8
28
  ## [5.21.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.21.0) - 2026-04-20
9
29
 
10
30
  ### Added
package/README.md CHANGED
@@ -7,219 +7,62 @@
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)
9
9
 
10
- Core infrastructure library for [Socket.dev](https://socket.dev/) security tools. Provides utilities for file system operations, process spawning, HTTP requests, environment detection, logging, spinners, and more.
11
-
12
- ## Prerequisites
13
-
14
- **Node.js 22 or higher** is required.
10
+ Core utilities for [Socket.dev](https://socket.dev/) tools: file system, processes, HTTP, env detection, logging, spinners, and more. Tree-shakeable, TypeScript-first, cross-platform.
15
11
 
16
12
  ## Install
17
13
 
18
14
  ```bash
19
- # Using pnpm (recommended)
20
15
  pnpm add @socketsecurity/lib
21
-
22
- # Using npm
23
- npm install @socketsecurity/lib
24
-
25
- # Using yarn
26
- yarn add @socketsecurity/lib
27
16
  ```
28
17
 
29
18
  ## Quick Start
30
19
 
31
20
  ```typescript
32
21
  import { Spinner } from '@socketsecurity/lib/spinner'
33
- import { getDefaultLogger } from '@socketsecurity/lib/logger'
34
22
  import { readJson } from '@socketsecurity/lib/fs'
35
23
 
36
- const logger = getDefaultLogger()
37
- const spinner = Spinner({ text: 'Loading package.json...' })
38
-
24
+ const spinner = Spinner({ text: 'Loading…' })
39
25
  spinner.start()
40
26
  const pkg = await readJson('./package.json')
41
- spinner.successAndStop('Loaded successfully')
42
-
43
- logger.success(`Package: ${pkg.name}@${pkg.version}`)
27
+ spinner.successAndStop(`Loaded ${pkg.name}@${pkg.version}`)
44
28
  ```
45
29
 
46
- ## Documentation
47
-
48
- - [API Index](./docs/api-index.md) - Every subpath export with a one-line description (start here)
49
- - [Getting Started](./docs/getting-started.md) - Prerequisites, installation, and first examples
50
- - [Visual Effects](./docs/visual-effects.md) - Spinners, loggers, themes, and progress indicators
51
- - [File System](./docs/file-system.md) - File operations, globs, paths, and safe deletion
52
- - [HTTP Utilities](./docs/http-utilities.md) - Making requests, downloading files, and retry logic
53
- - [Process Utilities](./docs/process-utilities.md) - Spawning processes, IPC, and locks
54
- - [Package Management](./docs/package-management.md) - npm/pnpm/yarn detection and operations
55
- - [Environment](./docs/environment.md) - CI detection, env getters, and platform checks
56
- - [Constants](./docs/constants.md) - Node versions, npm URLs, and platform values
57
- - [Examples](./docs/examples.md) - Real-world usage patterns
58
- - [Troubleshooting](./docs/troubleshooting.md) - Common issues and solutions
59
-
60
- ## What's Inside
61
-
62
- ### Visual Effects
63
-
64
- Spinners, colored loggers, themes, progress bars, and terminal output formatting.
65
-
66
- - `Spinner` - Animated CLI spinners with progress tracking
67
- - `getDefaultLogger()` - Colored console logger with symbols
68
- - `LOG_SYMBOLS` - Colored terminal symbols (✓, ✗, ⚠, ℹ, →)
69
- - `setTheme()` - Customize colors across the library
70
-
71
- ### File System
72
-
73
- Cross-platform file operations with safe deletion and convenient wrappers.
74
-
75
- - `readFileUtf8()`, `readFileBinary()` - Read files as text or binary
76
- - `readJson()`, `writeJson()` - Parse and format JSON files
77
- - `safeDelete()` - Protected deletion with safety checks
78
- - `findUp()`, `findUpSync()` - Traverse up to find files
79
- - `safeMkdir()` - Create directories without EEXIST errors
80
- - `validateFiles()` - Check file readability (useful for Yarn PnP, pnpm)
81
-
82
- ### HTTP Utilities
83
-
84
- Native Node.js HTTP/HTTPS requests with retry logic and redirects.
85
-
86
- - `httpJson()` - Fetch and parse JSON from APIs
87
- - `httpText()` - Fetch text/HTML content
88
- - `httpDownload()` - Download files with progress callbacks
89
- - `httpRequest()` - Full control over requests and responses
90
- - Automatic redirects, exponential backoff retries, timeout support
91
-
92
- ### Process Management
93
-
94
- Spawn child processes safely with cross-platform support.
95
-
96
- - `spawn()` - Promise-based process spawning with output capture
97
- - `spawnSync()` - Synchronous version for blocking operations
98
- - Array-based arguments prevent command injection
99
- - Automatic Windows `.cmd`/`.bat` handling
100
- - `processLock.withLock()` / `processLock.acquire()` / `processLock.release()` - Ensure only one instance runs at a time
101
- - `writeIpcStub()` / `getIpcStubPath()` - Filesystem-based inter-process data handoff
102
-
103
- ### Environment Detection
104
-
105
- Type-safe environment variable access and platform detection.
106
-
107
- - `getCI()` - Detect CI environment
108
- - `getNodeEnv()` - Get NODE_ENV value
109
- - `isTest()` - Check if running tests
110
- - `getHome()` - Home directory (cross-platform, with Windows `USERPROFILE` fallback)
111
- - Test rewiring with `setEnv()`, `resetEnv()`
112
-
113
- ### Package Management
114
-
115
- Detect and work with npm, pnpm, and yarn.
116
-
117
- - `detectPackageManager()` - Identify running package manager from `npm_config_user_agent` / binary path
118
- - Package manifest operations
119
- - Lock file management
120
-
121
- ### Constants
122
-
123
- Pre-defined values for Node.js, npm, and platform detection.
124
-
125
- - `getNodeMajorVersion()` - Get current Node.js major version
126
- - `WIN32`, `DARWIN` - Platform booleans (use `!WIN32 && !DARWIN` for Linux)
127
- - `getAbortSignal()` - Global abort signal
128
-
129
- ### Utilities
130
-
131
- Helpers for arrays, objects, strings, promises, sorting, and more.
132
-
133
- - Arrays, objects, strings manipulation
134
- - Promise utilities and queues
135
- - Natural sorting
136
- - Version comparison
137
- - Error handling with causes
138
-
139
- ## Features
140
-
141
- - **Tree-shakeable exports** - Import only what you need
142
- - **Cross-platform** - Works on Windows, macOS, and Linux
143
- - **TypeScript-first** - Full type safety with .d.ts files
144
- - **Zero dependencies** (for core HTTP - uses Node.js native modules)
145
- - **Well-tested** - 6000+ tests across 139+ test files
146
- - **Security-focused** - Safe defaults, command injection protection
147
- - **CommonJS output** - Compatible with Node.js tooling
148
-
149
- ## Common Use Cases
150
-
151
- ### Running Shell Commands
30
+ Every export lives under a subpath — pick what you need:
152
31
 
153
32
  ```typescript
154
33
  import { spawn } from '@socketsecurity/lib/spawn'
155
-
156
- const result = await spawn('git', ['status'])
157
- console.log(result.stdout)
158
- ```
159
-
160
- ### Making API Requests
161
-
162
- ```typescript
163
34
  import { httpJson } from '@socketsecurity/lib/http-request'
164
-
165
- const data = await httpJson('https://api.example.com/data')
166
- ```
167
-
168
- ### Visual Feedback
169
-
170
- ```typescript
171
- import { Spinner } from '@socketsecurity/lib/spinner'
172
-
173
- const spinner = Spinner({ text: 'Processing...' })
174
- spinner.start()
175
- // ... do work ...
176
- spinner.successAndStop('Complete!')
177
- ```
178
-
179
- ### Safe File Deletion
180
-
181
- ```typescript
182
35
  import { safeDelete } from '@socketsecurity/lib/fs'
183
-
184
- // Protected against deleting parent directories
185
- await safeDelete('./build')
186
- ```
187
-
188
- ## Troubleshooting
189
-
190
- **Module not found**: Verify you're importing from the correct path:
191
-
192
- ```typescript
193
- // Correct
194
- import { Spinner } from '@socketsecurity/lib/spinner'
195
-
196
- // Wrong
197
- import { Spinner } from '@socketsecurity/lib'
198
36
  ```
199
37
 
200
- **Node version error**: This library requires Node.js 22+. Check your version:
38
+ ## Documentation
201
39
 
202
- ```bash
203
- node --version
204
- ```
40
+ Start with the [API Index](./docs/api-index.md) — every subpath export with a one-line description.
205
41
 
206
- For more issues, see the [Troubleshooting Guide](./docs/troubleshooting.md).
42
+ - [Getting Started](./docs/getting-started.md) – install + first examples
43
+ - [Visual Effects](./docs/visual-effects.md) – spinners, loggers, themes
44
+ - [File System](./docs/file-system.md) – files, globs, paths, safe deletion
45
+ - [HTTP Utilities](./docs/http-utilities.md) – requests, downloads, retries
46
+ - [Process Utilities](./docs/process-utilities.md) – spawn, IPC, locks
47
+ - [Package Management](./docs/package-management.md) – npm/pnpm/yarn detection
48
+ - [Environment](./docs/environment.md) – CI/platform detection, env getters
49
+ - [Constants](./docs/constants.md) – Node versions, npm URLs, platform values
50
+ - [Examples](./docs/examples.md) – real-world patterns
51
+ - [Troubleshooting](./docs/troubleshooting.md) – common issues
207
52
 
208
53
  ## Development
209
54
 
210
55
  ```bash
211
- pnpm install # Install dependencies
212
- pnpm build # Build the library
213
- pnpm test # Run tests
214
- pnpm run cover # Run tests with coverage
215
- pnpm dev # Watch mode
216
- pnpm run lint # Check code style
217
- pnpm run fix # Fix formatting issues
56
+ pnpm install # install
57
+ pnpm build # build
58
+ pnpm test # run tests
59
+ pnpm run cover # tests with coverage
60
+ pnpm dev # watch mode
61
+ pnpm run lint # check style
62
+ pnpm run fix # auto-fix formatting
218
63
  ```
219
64
 
220
- ## Contributing
221
-
222
- Contributions are welcome! Please read the [CLAUDE.md](./CLAUDE.md) file for development guidelines and coding standards.
65
+ See [CLAUDE.md](./CLAUDE.md) for contributor guidelines.
223
66
 
224
67
  ## License
225
68
 
@@ -77,7 +77,7 @@ const SOCKET_FIREWALL_APP_NAME = "sfw";
77
77
  const SOCKET_REGISTRY_APP_NAME = "registry";
78
78
  const SOCKET_APP_PREFIX = "_";
79
79
  const SOCKET_LIB_NAME = "@socketsecurity/lib";
80
- const SOCKET_LIB_VERSION = "5.21.0";
80
+ const SOCKET_LIB_VERSION = "5.23.0";
81
81
  const SOCKET_LIB_URL = "https://github.com/SocketDev/socket-lib";
82
82
  const SOCKET_LIB_USER_AGENT = `socketsecurity-lib/${SOCKET_LIB_VERSION} (${SOCKET_LIB_URL})`;
83
83
  const SOCKET_IPC_HANDSHAKE = "SOCKET_IPC_HANDSHAKE";
@@ -26,6 +26,7 @@ __export(manifest_exports, {
26
26
  isPackageEntry: () => isPackageEntry
27
27
  });
28
28
  module.exports = __toCommonJS(manifest_exports);
29
+ var import_errors = require("../errors");
29
30
  var import_fs = require("../fs");
30
31
  var import_logger = require("../logger");
31
32
  var import_socket = require("../paths/socket");
@@ -79,9 +80,7 @@ class DlxManifest {
79
80
  }
80
81
  return JSON.parse(content);
81
82
  } catch (error) {
82
- logger.warn(
83
- `Failed to read manifest: ${error instanceof Error ? error.message : String(error)}`
84
- );
83
+ logger.warn(`Failed to read manifest: ${(0, import_errors.errorMessage)(error)}`);
85
84
  return { __proto__: null };
86
85
  }
87
86
  }
@@ -94,9 +93,7 @@ class DlxManifest {
94
93
  try {
95
94
  (0, import_fs.safeMkdirSync)(manifestDir, { recursive: true });
96
95
  } catch (error) {
97
- logger.warn(
98
- `Failed to create manifest directory: ${error instanceof Error ? error.message : String(error)}`
99
- );
96
+ logger.warn(`Failed to create manifest directory: ${(0, import_errors.errorMessage)(error)}`);
100
97
  }
101
98
  const content = JSON.stringify(data, null, 2);
102
99
  const tempPath = `${this.manifestPath}.tmp`;
@@ -130,9 +127,7 @@ class DlxManifest {
130
127
  delete data[name];
131
128
  await this.writeManifest(data);
132
129
  } catch (error) {
133
- logger.warn(
134
- `Failed to clear cache for ${name}: ${error instanceof Error ? error.message : String(error)}`
135
- );
130
+ logger.warn(`Failed to clear cache for ${name}: ${(0, import_errors.errorMessage)(error)}`);
136
131
  }
137
132
  });
138
133
  }
@@ -146,9 +141,7 @@ class DlxManifest {
146
141
  fs.unlinkSync(this.manifestPath);
147
142
  }
148
143
  } catch (error) {
149
- logger.warn(
150
- `Failed to clear all cache: ${error instanceof Error ? error.message : String(error)}`
151
- );
144
+ logger.warn(`Failed to clear all cache: ${(0, import_errors.errorMessage)(error)}`);
152
145
  }
153
146
  });
154
147
  }
@@ -180,9 +173,7 @@ class DlxManifest {
180
173
  const data = JSON.parse(content);
181
174
  return Object.keys(data);
182
175
  } catch (error) {
183
- logger.warn(
184
- `Failed to get package list: ${error instanceof Error ? error.message : String(error)}`
185
- );
176
+ logger.warn(`Failed to get package list: ${(0, import_errors.errorMessage)(error)}`);
186
177
  return [];
187
178
  }
188
179
  }
@@ -224,9 +215,7 @@ class DlxManifest {
224
215
  }
225
216
  }
226
217
  } catch (error) {
227
- logger.warn(
228
- `Failed to read existing manifest: ${error instanceof Error ? error.message : String(error)}`
229
- );
218
+ logger.warn(`Failed to read existing manifest: ${(0, import_errors.errorMessage)(error)}`);
230
219
  }
231
220
  data[name] = record;
232
221
  const manifestDir = path.dirname(this.manifestPath);
@@ -234,7 +223,7 @@ class DlxManifest {
234
223
  (0, import_fs.safeMkdirSync)(manifestDir, { recursive: true });
235
224
  } catch (error) {
236
225
  logger.warn(
237
- `Failed to create manifest directory: ${error instanceof Error ? error.message : String(error)}`
226
+ `Failed to create manifest directory: ${(0, import_errors.errorMessage)(error)}`
238
227
  );
239
228
  }
240
229
  const content = JSON.stringify(data, null, 2);
@@ -43,6 +43,7 @@ __export(package_exports, {
43
43
  module.exports = __toCommonJS(package_exports);
44
44
  var import_platform = require("../constants/platform");
45
45
  var import_socket = require("../constants/socket");
46
+ var import_errors = require("../errors");
46
47
  var import_arborist = __toESM(require("../external/@npmcli/arborist"));
47
48
  var import_libnpmexec = __toESM(require("../external/libnpmexec"));
48
49
  var import_npm_package_arg = __toESM(require("../external/npm-package-arg"));
@@ -276,7 +277,7 @@ Ensure the filesystem is writable or set SOCKET_DLX_DIR to a writable location.`
276
277
  await checkFirewallPurls(arb, packageName);
277
278
  await arb.reify({ save: true });
278
279
  } catch (e) {
279
- if (e instanceof Error && e.message.startsWith("Socket Firewall blocked")) {
280
+ if ((0, import_errors.isError)(e) && e.message.startsWith("Socket Firewall blocked")) {
280
281
  throw e;
281
282
  }
282
283
  const code = e?.code;
@@ -63,9 +63,10 @@ export declare function getSocketCliApiProxy(): string | undefined;
63
63
  */
64
64
  export declare function getSocketCliApiTimeout(): number;
65
65
  /**
66
- * Socket CLI API authentication token (alternative name).
67
- * Checks SOCKET_CLI_API_TOKEN, SOCKET_CLI_API_KEY, SOCKET_SECURITY_API_TOKEN, SOCKET_SECURITY_API_KEY.
68
- * Maintains full v1.x backward compatibility.
66
+ * Socket CLI API authentication token.
67
+ * Checks SOCKET_API_TOKEN (canonical), then the legacy names
68
+ * SOCKET_CLI_API_TOKEN, SOCKET_CLI_API_KEY, SOCKET_SECURITY_API_TOKEN,
69
+ * SOCKET_SECURITY_API_KEY. Maintains full v1.x backward compatibility.
69
70
  *
70
71
  * @returns API token or undefined
71
72
  *
@@ -56,7 +56,7 @@ function getSocketCliApiTimeout() {
56
56
  }
57
57
  // @__NO_SIDE_EFFECTS__
58
58
  function getSocketCliApiToken() {
59
- return (0, import_rewire.getEnvValue)("SOCKET_CLI_API_TOKEN") || (0, import_rewire.getEnvValue)("SOCKET_CLI_API_KEY") || (0, import_rewire.getEnvValue)("SOCKET_SECURITY_API_TOKEN") || (0, import_rewire.getEnvValue)("SOCKET_SECURITY_API_KEY");
59
+ return (0, import_rewire.getEnvValue)("SOCKET_API_TOKEN") || (0, import_rewire.getEnvValue)("SOCKET_CLI_API_TOKEN") || (0, import_rewire.getEnvValue)("SOCKET_CLI_API_KEY") || (0, import_rewire.getEnvValue)("SOCKET_SECURITY_API_TOKEN") || (0, import_rewire.getEnvValue)("SOCKET_SECURITY_API_KEY");
60
60
  }
61
61
  // @__NO_SIDE_EFFECTS__
62
62
  function getSocketCliBootstrapCacheDir() {
package/dist/errors.d.ts CHANGED
@@ -1,6 +1,100 @@
1
1
  /**
2
2
  * @fileoverview Error utilities with cause chain support.
3
- * Provides helpers for working with error causes and stack traces.
3
+ *
4
+ * Provides:
5
+ * - `isError(value)` — cross-realm-safe Error check (ES2025 `Error.isError`
6
+ * when available, `@@toStringTag` fallback otherwise).
7
+ * - `errorMessage(value)` — read the message (with cause chain) from any
8
+ * caught value, falling back to the shared `UNKNOWN_ERROR` sentinel.
9
+ * - `errorStack(value)` — read the stack (with cause chain) from any
10
+ * caught value, or `undefined` for non-Errors.
11
+ *
12
+ * `messageWithCauses` / `stackWithCauses` are re-exported from pony-cause;
13
+ * a patched copy recognizes cross-realm Errors via `isError`.
4
14
  */
15
+ import { UNKNOWN_ERROR } from './constants/core';
5
16
  import { messageWithCauses, stackWithCauses } from './external/pony-cause';
6
- export { messageWithCauses, stackWithCauses };
17
+ export { UNKNOWN_ERROR, messageWithCauses, stackWithCauses };
18
+ /**
19
+ * Spec-compliant [`Error.isError`](https://tc39.es/ecma262/#sec-error.iserror)
20
+ * with a fallback shim for engines that don't ship it yet.
21
+ *
22
+ * Returns `true` for Errors from any realm (worker threads, vm contexts,
23
+ * iframes) — things same-realm `instanceof Error` misses. Plain objects
24
+ * with `name` + `message` properties are **not** recognized.
25
+ *
26
+ * @example
27
+ * try {
28
+ * await doWork()
29
+ * } catch (e) {
30
+ * if (isError(e)) {
31
+ * logger.error(e.message)
32
+ * } else {
33
+ * logger.error(String(e))
34
+ * }
35
+ * }
36
+ */
37
+ /**
38
+ * `Error.isError` fallback shim — the in-language approximation used
39
+ * when the native ES2025 method isn't available.
40
+ *
41
+ * Exported separately so test suites on engines that ship the native
42
+ * method can still exercise the shim branch directly. Consumers should
43
+ * prefer {@link isError}, which picks the native method when present.
44
+ */
45
+ export declare function isErrorShim(value: unknown): value is Error;
46
+ /**
47
+ * Reference to the native ES2025 `Error.isError` when the running
48
+ * engine ships it, otherwise `undefined`. Exposed separately so tests
49
+ * and callers can detect the fast-path without re-probing.
50
+ */
51
+ export declare const isErrorBuiltin: ((value: unknown) => value is Error) | undefined;
52
+ /**
53
+ * Prefer the native ES2025 `Error.isError` when available (exact
54
+ * `[[ErrorData]]` slot check, cross-realm-safe); fall back to
55
+ * {@link isErrorShim} otherwise.
56
+ */
57
+ export declare const isError: (value: unknown) => value is Error;
58
+ /**
59
+ * Narrow a caught value to a Node.js `ErrnoException` — an Error with a
60
+ * `.code` string set by libuv/syscall failures (e.g. `'ENOENT'`,
61
+ * `'EACCES'`, `'EBUSY'`, `'EPERM'`). Cross-realm safe (builds on
62
+ * {@link isError}), and checks that `code` is a string so a merely
63
+ * branded Error without a real errno code returns `false`.
64
+ *
65
+ * @example
66
+ * try {
67
+ * await fsPromises.readFile(path)
68
+ * } catch (e) {
69
+ * if (isErrnoException(e) && e.code === 'ENOENT') {
70
+ * // … retry, or return default …
71
+ * } else {
72
+ * throw e
73
+ * }
74
+ * }
75
+ */
76
+ export declare function isErrnoException(value: unknown): value is NodeJS.ErrnoException;
77
+ /**
78
+ * Extract a human-readable message from any caught value.
79
+ *
80
+ * Walks the `cause` chain for Errors (via {@link messageWithCauses});
81
+ * coerces primitives and objects to string; returns
82
+ * {@link UNKNOWN_ERROR} for `null`, `undefined`, empty strings,
83
+ * `[object Object]`, or Errors with no message.
84
+ *
85
+ * @example
86
+ * try {
87
+ * await readConfig(path)
88
+ * } catch (e) {
89
+ * throw new Error(`Failed to read ${path}: ${errorMessage(e)}`, { cause: e })
90
+ * }
91
+ */
92
+ export declare function errorMessage(value: unknown): string;
93
+ /**
94
+ * Extract a stack trace (with causes) from any caught value.
95
+ *
96
+ * Returns the cause-aware stack via {@link stackWithCauses} for Errors;
97
+ * returns `undefined` for non-Error values, so callers can
98
+ * `logger.error(msg, { stack: errorStack(e) })` safely.
99
+ */
100
+ export declare function errorStack(value: unknown): string | undefined;
package/dist/errors.js CHANGED
@@ -20,13 +20,68 @@ var __copyProps = (to, from, except, desc) => {
20
20
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
21
  var errors_exports = {};
22
22
  __export(errors_exports, {
23
+ UNKNOWN_ERROR: () => import_core.UNKNOWN_ERROR,
24
+ errorMessage: () => errorMessage,
25
+ errorStack: () => errorStack,
26
+ isErrnoException: () => isErrnoException,
27
+ isError: () => isError,
28
+ isErrorBuiltin: () => isErrorBuiltin,
29
+ isErrorShim: () => isErrorShim,
23
30
  messageWithCauses: () => import_pony_cause.messageWithCauses,
24
31
  stackWithCauses: () => import_pony_cause.stackWithCauses
25
32
  });
26
33
  module.exports = __toCommonJS(errors_exports);
34
+ var import_core = require("./constants/core");
27
35
  var import_pony_cause = require("./external/pony-cause");
36
+ const ObjectPrototypeToString = Object.prototype.toString;
37
+ const ReflectApply = Reflect.apply;
38
+ function isErrorShim(value) {
39
+ if (value === null || typeof value !== "object") {
40
+ return false;
41
+ }
42
+ return ReflectApply(ObjectPrototypeToString, value, []) === "[object Error]";
43
+ }
44
+ const isErrorBuiltin = Error.isError;
45
+ const isError = isErrorBuiltin ?? isErrorShim;
46
+ function isErrnoException(value) {
47
+ if (!isError(value)) {
48
+ return false;
49
+ }
50
+ const code = value.code;
51
+ if (typeof code !== "string" || code.length === 0) {
52
+ return false;
53
+ }
54
+ const first = code.charCodeAt(0);
55
+ return first >= 65 && first <= 90;
56
+ }
57
+ function errorMessage(value) {
58
+ if (isError(value)) {
59
+ return (0, import_pony_cause.messageWithCauses)(value) || import_core.UNKNOWN_ERROR;
60
+ }
61
+ if (value === null || value === void 0) {
62
+ return import_core.UNKNOWN_ERROR;
63
+ }
64
+ const s = String(value);
65
+ if (s === "" || s === "[object Object]") {
66
+ return import_core.UNKNOWN_ERROR;
67
+ }
68
+ return s;
69
+ }
70
+ function errorStack(value) {
71
+ if (isError(value)) {
72
+ return (0, import_pony_cause.stackWithCauses)(value);
73
+ }
74
+ return void 0;
75
+ }
28
76
  // Annotate the CommonJS export names for ESM import in node:
29
77
  0 && (module.exports = {
78
+ UNKNOWN_ERROR,
79
+ errorMessage,
80
+ errorStack,
81
+ isErrnoException,
82
+ isError,
83
+ isErrorBuiltin,
84
+ isErrorShim,
30
85
  messageWithCauses,
31
86
  stackWithCauses
32
87
  });
@@ -11,9 +11,9 @@ var __commonJS = (cb, mod) => function __require() {
11
11
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
12
  };
13
13
 
14
- // node_modules/.pnpm/pony-cause@2.1.11/node_modules/pony-cause/lib/error-with-cause.js
14
+ // node_modules/.pnpm/pony-cause@2.1.11_patch_hash=39b2eb2567818b7c60126d03e252dbbabd8738082fca850582300d45b0a8cfb8/node_modules/pony-cause/lib/error-with-cause.js
15
15
  var require_error_with_cause = __commonJS({
16
- "node_modules/.pnpm/pony-cause@2.1.11/node_modules/pony-cause/lib/error-with-cause.js"(exports2, module2) {
16
+ "node_modules/.pnpm/pony-cause@2.1.11_patch_hash=39b2eb2567818b7c60126d03e252dbbabd8738082fca850582300d45b0a8cfb8/node_modules/pony-cause/lib/error-with-cause.js"(exports2, module2) {
17
17
  "use strict";
18
18
  var ErrorWithCause = class _ErrorWithCause extends Error {
19
19
  static {
@@ -41,13 +41,14 @@ var require_error_with_cause = __commonJS({
41
41
  }
42
42
  });
43
43
 
44
- // node_modules/.pnpm/pony-cause@2.1.11/node_modules/pony-cause/lib/helpers.js
44
+ // node_modules/.pnpm/pony-cause@2.1.11_patch_hash=39b2eb2567818b7c60126d03e252dbbabd8738082fca850582300d45b0a8cfb8/node_modules/pony-cause/lib/helpers.js
45
45
  var require_helpers = __commonJS({
46
- "node_modules/.pnpm/pony-cause@2.1.11/node_modules/pony-cause/lib/helpers.js"(exports2, module2) {
46
+ "node_modules/.pnpm/pony-cause@2.1.11_patch_hash=39b2eb2567818b7c60126d03e252dbbabd8738082fca850582300d45b0a8cfb8/node_modules/pony-cause/lib/helpers.js"(exports2, module2) {
47
47
  "use strict";
48
+ var isError = typeof Error.isError === "function" ? Error.isError : (v) => v !== null && typeof v === "object" && Object.prototype.toString.call(v) === "[object Error]";
48
49
  var findCauseByReference = /* @__PURE__ */ __name((err, reference) => {
49
50
  if (!err || !reference) return;
50
- if (!(err instanceof Error)) return;
51
+ if (!isError(err)) return;
51
52
  if (!(reference.prototype instanceof Error) && // @ts-ignore
52
53
  reference !== Error) return;
53
54
  const seen = /* @__PURE__ */ new Set();
@@ -66,13 +67,13 @@ var require_helpers = __commonJS({
66
67
  }
67
68
  if (typeof err.cause === "function") {
68
69
  const causeResult = err.cause();
69
- return causeResult instanceof Error ? causeResult : void 0;
70
+ return isError(causeResult) ? causeResult : void 0;
70
71
  } else {
71
- return err.cause instanceof Error ? err.cause : void 0;
72
+ return isError(err.cause) ? err.cause : void 0;
72
73
  }
73
74
  }, "getErrorCause");
74
75
  var _stackWithCauses = /* @__PURE__ */ __name((err, seen) => {
75
- if (!(err instanceof Error)) return "";
76
+ if (!isError(err)) return "";
76
77
  const stack = err.stack || "";
77
78
  if (seen.has(err)) {
78
79
  return stack + "\ncauses have become circular...";
@@ -87,7 +88,7 @@ var require_helpers = __commonJS({
87
88
  }, "_stackWithCauses");
88
89
  var stackWithCauses = /* @__PURE__ */ __name((err) => _stackWithCauses(err, /* @__PURE__ */ new Set()), "stackWithCauses");
89
90
  var _messageWithCauses = /* @__PURE__ */ __name((err, seen, skip) => {
90
- if (!(err instanceof Error)) return "";
91
+ if (!isError(err)) return "";
91
92
  const message = skip ? "" : err.message || "";
92
93
  if (seen.has(err)) {
93
94
  return message + ": ...";
@@ -116,9 +117,9 @@ var require_helpers = __commonJS({
116
117
  }
117
118
  });
118
119
 
119
- // node_modules/.pnpm/pony-cause@2.1.11/node_modules/pony-cause/index.js
120
+ // node_modules/.pnpm/pony-cause@2.1.11_patch_hash=39b2eb2567818b7c60126d03e252dbbabd8738082fca850582300d45b0a8cfb8/node_modules/pony-cause/index.js
120
121
  var require_pony_cause = __commonJS({
121
- "node_modules/.pnpm/pony-cause@2.1.11/node_modules/pony-cause/index.js"(exports2, module2) {
122
+ "node_modules/.pnpm/pony-cause@2.1.11_patch_hash=39b2eb2567818b7c60126d03e252dbbabd8738082fca850582300d45b0a8cfb8/node_modules/pony-cause/index.js"(exports2, module2) {
122
123
  "use strict";
123
124
  var { ErrorWithCause } = require_error_with_cause();
124
125
  var {
package/dist/github.js CHANGED
@@ -45,6 +45,7 @@ var import_node_process = __toESM(require("node:process"));
45
45
  var import_cache_with_ttl = require("./cache-with-ttl");
46
46
  var import_github = require("./env/github");
47
47
  var import_socket_cli = require("./env/socket-cli");
48
+ var import_errors = require("./errors");
48
49
  var import_http_request = require("./http-request");
49
50
  var import_spawn = require("./spawn");
50
51
  const GITHUB_API_BASE_URL = "https://api.github.com";
@@ -80,7 +81,7 @@ async function fetchRefSha(owner, repo, ref, options) {
80
81
  return commitData.sha;
81
82
  } catch (e) {
82
83
  throw new Error(
83
- `failed to resolve ref "${ref}" for ${owner}/${repo}: ${e instanceof Error ? e.message : String(e)}`
84
+ `failed to resolve ref "${ref}" for ${owner}/${repo}: ${(0, import_errors.errorMessage)(e)}`
84
85
  );
85
86
  }
86
87
  }
@@ -165,7 +166,7 @@ async function fetchGitHub(url, options) {
165
166
  return JSON.parse(response.body.toString("utf8"));
166
167
  } catch (error) {
167
168
  throw new Error(
168
- `Failed to parse GitHub API response: ${error instanceof Error ? error.message : String(error)}
169
+ `Failed to parse GitHub API response: ${(0, import_errors.errorMessage)(error)}
169
170
  URL: ${url}
170
171
  Response may be malformed or incomplete.`,
171
172
  { cause: error }
package/dist/json/edit.js CHANGED
@@ -35,6 +35,7 @@ __export(edit_exports, {
35
35
  module.exports = __toCommonJS(edit_exports);
36
36
  var import_node_process = __toESM(require("node:process"));
37
37
  var import_promises = require("node:timers/promises");
38
+ var import_errors = require("../errors");
38
39
  var import_format = require("./format");
39
40
  const identSymbol = import_format.INDENT_SYMBOL;
40
41
  const newlineSymbol = import_format.NEWLINE_SYMBOL;
@@ -59,7 +60,7 @@ async function readFile(filepath) {
59
60
  return await fsPromises.readFile(filepath, "utf8");
60
61
  } catch (err) {
61
62
  const isLastAttempt = attempt === maxRetries;
62
- const isEnoent = err instanceof Error && "code" in err && err.code === "ENOENT";
63
+ const isEnoent = (0, import_errors.isErrnoException)(err) && err.code === "ENOENT";
63
64
  if (!isEnoent || isLastAttempt) {
64
65
  throw err;
65
66
  }
@@ -92,7 +93,7 @@ async function retryWrite(filepath, content, retries = 3, baseDelay = 10) {
92
93
  return;
93
94
  } catch (err) {
94
95
  const isLastAttempt = attempt === retries;
95
- const isRetriableError = err instanceof Error && "code" in err && (err.code === "EPERM" || err.code === "EBUSY" || err.code === "ENOENT");
96
+ const isRetriableError = (0, import_errors.isErrnoException)(err) && (err.code === "EPERM" || err.code === "EBUSY" || err.code === "ENOENT");
96
97
  if (!isRetriableError || isLastAttempt) {
97
98
  throw err;
98
99
  }
@@ -35,6 +35,7 @@ __export(isolation_exports, {
35
35
  module.exports = __toCommonJS(isolation_exports);
36
36
  var import_npm_package_arg = __toESM(require("../external/npm-package-arg"));
37
37
  var import_platform = require("../constants/platform");
38
+ var import_errors = require("../errors");
38
39
  var import_normalize = require("../paths/normalize");
39
40
  var import_socket = require("../paths/socket");
40
41
  var import_spawn = require("../spawn");
@@ -69,10 +70,9 @@ async function mergePackageJson(pkgJsonPath, originalPkgJson) {
69
70
  try {
70
71
  pkgJson = JSON.parse(await fs.promises.readFile(pkgJsonPath, "utf8"));
71
72
  } catch (error) {
72
- throw new Error(
73
- `Failed to parse ${pkgJsonPath}: ${error instanceof Error ? error.message : String(error)}`,
74
- { cause: error }
75
- );
73
+ throw new Error(`Failed to parse ${pkgJsonPath}: ${(0, import_errors.errorMessage)(error)}`, {
74
+ cause: error
75
+ });
76
76
  }
77
77
  const mergedPkgJson = originalPkgJson ? { ...originalPkgJson, ...pkgJson } : pkgJson;
78
78
  return mergedPkgJson;
@@ -44,6 +44,7 @@ __export(performance_exports, {
44
44
  module.exports = __toCommonJS(performance_exports);
45
45
  var import_node_process = __toESM(require("node:process"));
46
46
  var import_debug = require("./debug");
47
+ var import_errors = require("./errors");
47
48
  const performanceMetrics = [];
48
49
  function isPerfEnabled() {
49
50
  return import_node_process.default.env["DEBUG"]?.includes("perf") || false;
@@ -128,7 +129,7 @@ async function measure(operation, fn, metadata) {
128
129
  } catch (e) {
129
130
  stop({
130
131
  success: false,
131
- error: e instanceof Error ? e.message : "Unknown"
132
+ error: (0, import_errors.errorMessage)(e)
132
133
  });
133
134
  throw e;
134
135
  }
@@ -143,7 +144,7 @@ function measureSync(operation, fn, metadata) {
143
144
  } catch (e) {
144
145
  stop({
145
146
  success: false,
146
- error: e instanceof Error ? e.message : "Unknown"
147
+ error: (0, import_errors.errorMessage)(e)
147
148
  });
148
149
  throw e;
149
150
  }
@@ -23,6 +23,7 @@ __export(process_lock_exports, {
23
23
  processLock: () => processLock
24
24
  });
25
25
  module.exports = __toCommonJS(process_lock_exports);
26
+ var import_errors = require("./errors");
26
27
  var import_fs = require("./fs");
27
28
  var import_logger = require("./logger");
28
29
  var import_promises = require("./promises");
@@ -87,9 +88,7 @@ class ProcessLockManager {
87
88
  fs.utimesSync(lockPath, now, now);
88
89
  }
89
90
  } catch (error) {
90
- logger.warn(
91
- `Failed to touch lock ${lockPath}: ${error instanceof Error ? error.message : String(error)}`
92
- );
91
+ logger.warn(`Failed to touch lock ${lockPath}: ${(0, import_errors.errorMessage)(error)}`);
93
92
  }
94
93
  }
95
94
  /**
@@ -277,9 +276,7 @@ To resolve:
277
276
  }
278
277
  this.activeLocks.delete(lockPath);
279
278
  } catch (error) {
280
- logger.warn(
281
- `Failed to release lock ${lockPath}: ${error instanceof Error ? error.message : String(error)}`
282
- );
279
+ logger.warn(`Failed to release lock ${lockPath}: ${(0, import_errors.errorMessage)(error)}`);
283
280
  }
284
281
  }
285
282
  /**
@@ -43,6 +43,7 @@ module.exports = __toCommonJS(github_exports);
43
43
  var import_node_process = __toESM(require("node:process"));
44
44
  var import_picomatch = __toESM(require("../external/picomatch"));
45
45
  var import_archives = require("../archives");
46
+ var import_errors = require("../errors");
46
47
  var import_fs = require("../fs");
47
48
  var import_http_request = require("../http-request");
48
49
  var import_logger = require("../logger");
@@ -341,7 +342,7 @@ async function getLatestRelease(toolPrefix, repoConfig, options = {}) {
341
342
  `Retry attempt ${attempt + 1}/${RETRY_CONFIG.retries + 1} for ${toolPrefix} release...`
342
343
  );
343
344
  logger.warn(
344
- `Attempt ${attempt + 1}/${RETRY_CONFIG.retries + 1} failed: ${error instanceof Error ? error.message : String(error)}`
345
+ `Attempt ${attempt + 1}/${RETRY_CONFIG.retries + 1} failed: ${(0, import_errors.errorMessage)(error)}`
345
346
  );
346
347
  }
347
348
  return void 0;
@@ -395,7 +396,7 @@ async function getReleaseAssetUrl(tag, assetPattern, repoConfig, options = {}) {
395
396
  `Retry attempt ${attempt + 1}/${RETRY_CONFIG.retries + 1} for asset URL...`
396
397
  );
397
398
  logger.warn(
398
- `Attempt ${attempt + 1}/${RETRY_CONFIG.retries + 1} failed: ${error instanceof Error ? error.message : String(error)}`
399
+ `Attempt ${attempt + 1}/${RETRY_CONFIG.retries + 1} failed: ${(0, import_errors.errorMessage)(error)}`
399
400
  );
400
401
  }
401
402
  return void 0;
@@ -126,18 +126,74 @@ export declare function getBinaryAssetName(binaryBaseName: string, platform: Pla
126
126
  */
127
127
  export declare function getBinaryName(binaryBaseName: string, platform: Platform): string;
128
128
  /**
129
- * Get platform-arch identifier for directory structure.
130
- * Uses 'win' instead of 'win32' for file/folder names.
129
+ * Get platform-arch identifier for directory structure and asset names.
130
+ *
131
+ * # Format: `<os>-<arch>[-<libc>]`
132
+ *
133
+ * The OS segment is `process.platform` verbatim: `darwin` / `linux` /
134
+ * `win32`. The arch segment is `process.arch` verbatim: `x64` / `arm64`.
135
+ * The optional libc suffix is `-musl` (Linux only; the glibc default is
136
+ * unsuffixed to match Node.js's own linuxstatic convention).
137
+ *
138
+ * # Why these specific conventions
139
+ *
140
+ * ## Why `win32`, not `win`
141
+ *
142
+ * `win32` is what `process.platform` returns on every Windows host. Every
143
+ * npm package whose install-time platform filter uses the standard
144
+ * `os` / `cpu` / `libc` manifest fields must match `process.platform`
145
+ * strings exactly (npm compares them verbatim — there's no shorthand
146
+ * layer). Using `win` internally here would have forced a translation
147
+ * every time we constructed an install filter or a target triple, and
148
+ * reviewers would have to remember "we abbreviate on disk but not in
149
+ * package filters." Since the two now match, there's no translation
150
+ * step to get wrong.
151
+ *
152
+ * pnpm's pack-app (v11+) accepts `<os>-<arch>[-<libc>]` target strings
153
+ * and its shards are `@pnpm/exe.<os>-<arch>` (with `win32`, not `win` —
154
+ * see pnpm#11314). Our naming matches so asset names we emit can flow
155
+ * directly into pack-app's `--target` arg, `pnpm.app.targets` config,
156
+ * and sibling-package-name construction without a translation map.
157
+ *
158
+ * ## Why `-musl` is the suffix (and glibc is unsuffixed)
159
+ *
160
+ * Node.js's own linuxstatic tarballs historically used the unqualified
161
+ * `linux` for glibc and a separate download channel for musl. The pnpm
162
+ * ecosystem codified that as `linux-<arch>` (glibc, default) and
163
+ * `linux-<arch>-musl` (the libc outlier), matching the asymmetric
164
+ * reality of Linux distros — glibc is the majority case, musl is
165
+ * Alpine-and-similar. Adding `-glibc` for the default would be
166
+ * redundant noise in the name.
167
+ *
168
+ * ## Why libc is only appended for Linux
169
+ *
170
+ * macOS and Windows have exactly one system libc each (Apple libSystem,
171
+ * Microsoft UCRT). A hypothetical `darwin-arm64-libsystem` conveys no
172
+ * information. Node.js, npm, and pnpm all treat libc as a Linux-only
173
+ * axis; we follow the same convention so callers don't have to special-
174
+ * case `'darwin-arm64'.startsWith('darwin-arm64')` style matches.
175
+ *
176
+ * ## Why this function exists at all (vs. inlining)
177
+ *
178
+ * Two upstream APIs that socket-btm consumers end up calling — the
179
+ * npm manifest filter (`os`/`cpu`/`libc`) and pnpm's pack-app
180
+ * `--target` — both need the exact same triple format. Centralizing
181
+ * the construction here means a future schema change (e.g. Node
182
+ * introducing `riscv64`) gets one edit, and the error message for an
183
+ * unsupported platform is uniform across downloaders, pack-app
184
+ * invocations, and the `@socketbin/*` resolver logic.
131
185
  *
132
186
  * @param platform - Target platform
133
187
  * @param arch - Target architecture
134
- * @param libc - Linux libc variant (optional)
135
- * @returns Platform-arch identifier (e.g., 'darwin-arm64', 'linux-x64-musl', 'win-x64')
188
+ * @param libc - Linux libc variant (optional; non-linux platforms ignore)
189
+ * @returns Platform-arch identifier (e.g., 'darwin-arm64', 'linux-x64-musl', 'win32-x64')
136
190
  *
137
191
  * @example
138
192
  * ```typescript
139
193
  * getPlatformArch('linux', 'x64', 'musl') // 'linux-x64-musl'
140
- * getPlatformArch('darwin', 'arm64') // 'darwin-arm64'
194
+ * getPlatformArch('darwin', 'arm64') // 'darwin-arm64'
195
+ * getPlatformArch('win32', 'x64') // 'win32-x64'
196
+ * getPlatformArch('darwin', 'x64', 'musl') // 'darwin-x64' — libc ignored
141
197
  * ```
142
198
  */
143
199
  export declare function getPlatformArch(platform: Platform, arch: Arch, libc?: Libc | undefined): string;
@@ -38,7 +38,7 @@ const PLATFORM_MAP = {
38
38
  __proto__: null,
39
39
  darwin: "darwin",
40
40
  linux: "linux",
41
- win32: "win"
41
+ win32: "win32"
42
42
  };
43
43
  const ARCH_MAP = {
44
44
  __proto__: null,
@@ -185,7 +185,7 @@ function getBinaryAssetName(binaryBaseName, platform, arch, libc) {
185
185
  return `${binaryBaseName}-linux-${mappedArch}${muslSuffix}${ext}`;
186
186
  }
187
187
  if (platform === "win32") {
188
- return `${binaryBaseName}-win-${mappedArch}${ext}`;
188
+ return `${binaryBaseName}-win32-${mappedArch}${ext}`;
189
189
  }
190
190
  throw new Error(`Unsupported platform: ${platform}`);
191
191
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@socketsecurity/lib",
3
- "version": "5.21.0",
4
- "packageManager": "pnpm@11.0.0-rc.2",
3
+ "version": "5.23.0",
4
+ "packageManager": "pnpm@11.0.0-rc.5",
5
5
  "license": "MIT",
6
6
  "description": "Core utilities and infrastructure for Socket.dev security tools",
7
7
  "keywords": [
@@ -724,7 +724,7 @@
724
724
  "@socketregistry/is-unicode-supported": "1.0.5",
725
725
  "@socketregistry/packageurl-js": "1.4.2",
726
726
  "@socketregistry/yocto-spinner": "1.0.25",
727
- "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.20.1",
727
+ "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.21.0",
728
728
  "@types/node": "24.9.2",
729
729
  "@typescript/native-preview": "7.0.0-dev.20260415.1",
730
730
  "@vitest/coverage-v8": "4.0.3",