@socketsecurity/lib 3.2.0 → 3.2.1

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
+ ## [3.2.1](https://github.com/SocketDev/socket-lib/releases/tag/v3.2.1) - 2025-11-02
9
+
10
+ ### Changed
11
+
12
+ - **Logger/Spinner**: Use module-level constants to prevent duplicate and rogue spinner indicators
13
+ - Call `getDefaultLogger()` and `getDefaultSpinner()` once at module scope instead of repeated calls
14
+ - Prevents multiple spinner instances that can cause duplicate or lingering indicators in terminal output
15
+ - Applied in `src/dlx-manifest.ts`, `src/stdio/mask.ts`, and `src/spinner.ts`
16
+ - Follows DRY principle and aligns with socket-registry/socket-sdk-js patterns
17
+
18
+ ### Fixed
19
+
20
+ - **Scripts**: Fixed undefined logger variable in update script
21
+ - Replaced undefined `log` references with `_logger` throughout `scripts/update.mjs`
22
+ - Resolves ESLint errors that blocked test execution
23
+ - **Tests**: Improved stdout test stability by checking call delta instead of absolute counts
24
+ - Fixed flaky CI failures where spy call count was 101 instead of expected 100
25
+ - More robust approach handles potential state leakage between tests
26
+ - **Tests**: Removed unnecessary 10ms delay in cache-with-ttl test
27
+ - Cache with memoization enabled updates in-memory storage synchronously
28
+ - Delay was insufficient in CI and unnecessary given synchronous behavior
29
+ - Resolves flaky CI failures where cached values returned undefined
30
+
31
+ ## [3.2.0](https://github.com/SocketDev/socket-lib/releases/tag/v3.2.0) - 2025-11-02
32
+
33
+ ### Added
34
+
35
+ - **DLX**: Unified manifest for packages and binaries
36
+ - Centralized manifest system for tracking DLX-compatible packages
37
+ - Simplifies package and binary lookups for dependency-free execution
38
+
8
39
  ## [3.1.3](https://github.com/SocketDev/socket-lib/releases/tag/v3.1.3) - 2025-11-02
9
40
 
10
41
  ### Changed
@@ -40,6 +40,7 @@ var import_fs2 = require("./fs");
40
40
  var import_logger = require("./logger");
41
41
  var import_paths = require("./paths");
42
42
  var import_process_lock = require("./process-lock");
43
+ const logger = (0, import_logger.getDefaultLogger)();
43
44
  const MANIFEST_FILE_NAME = ".dlx-manifest.json";
44
45
  function isPackageEntry(entry) {
45
46
  return entry.type === "package";
@@ -69,7 +70,7 @@ class DlxManifest {
69
70
  }
70
71
  return JSON.parse(content);
71
72
  } catch (error) {
72
- (0, import_logger.getDefaultLogger)().warn(
73
+ logger.warn(
73
74
  `Failed to read manifest: ${error instanceof Error ? error.message : String(error)}`
74
75
  );
75
76
  return /* @__PURE__ */ Object.create(null);
@@ -136,7 +137,7 @@ class DlxManifest {
136
137
  try {
137
138
  (0, import_fs2.safeMkdirSync)(manifestDir, { recursive: true });
138
139
  } catch (error) {
139
- (0, import_logger.getDefaultLogger)().warn(
140
+ logger.warn(
140
141
  `Failed to create manifest directory: ${error instanceof Error ? error.message : String(error)}`
141
142
  );
142
143
  }
@@ -176,7 +177,7 @@ class DlxManifest {
176
177
  }
177
178
  }
178
179
  } catch (error) {
179
- (0, import_logger.getDefaultLogger)().warn(
180
+ logger.warn(
180
181
  `Failed to read existing manifest: ${error instanceof Error ? error.message : String(error)}`
181
182
  );
182
183
  }
@@ -185,7 +186,7 @@ class DlxManifest {
185
186
  try {
186
187
  (0, import_fs2.safeMkdirSync)(manifestDir, { recursive: true });
187
188
  } catch (error) {
188
- (0, import_logger.getDefaultLogger)().warn(
189
+ logger.warn(
189
190
  `Failed to create manifest directory: ${error instanceof Error ? error.message : String(error)}`
190
191
  );
191
192
  }
@@ -229,7 +230,7 @@ class DlxManifest {
229
230
  const updatedContent = JSON.stringify(data, null, 2);
230
231
  (0, import_fs.writeFileSync)(this.manifestPath, updatedContent, "utf8");
231
232
  } catch (error) {
232
- (0, import_logger.getDefaultLogger)().warn(
233
+ logger.warn(
233
234
  `Failed to clear cache for ${name}: ${error instanceof Error ? error.message : String(error)}`
234
235
  );
235
236
  }
@@ -245,7 +246,7 @@ class DlxManifest {
245
246
  (0, import_fs.unlinkSync)(this.manifestPath);
246
247
  }
247
248
  } catch (error) {
248
- (0, import_logger.getDefaultLogger)().warn(
249
+ logger.warn(
249
250
  `Failed to clear all cache: ${error instanceof Error ? error.message : String(error)}`
250
251
  );
251
252
  }
@@ -277,7 +278,7 @@ class DlxManifest {
277
278
  const data = JSON.parse(content);
278
279
  return Object.keys(data);
279
280
  } catch (error) {
280
- (0, import_logger.getDefaultLogger)().warn(
281
+ logger.warn(
281
282
  `Failed to get package list: ${error instanceof Error ? error.message : String(error)}`
282
283
  );
283
284
  return [];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/dlx-manifest.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview DLX manifest storage utilities.\n * Manages persistent caching of DLX package and binary metadata with TTL support\n * and atomic file operations.\n *\n * Key Functions:\n * - getManifestEntry: Retrieve manifest entry by spec\n * - setPackageEntry: Store npm package metadata\n * - setBinaryEntry: Store binary download metadata\n *\n * Features:\n * - TTL-based cache expiration\n * - Atomic file operations with locking\n * - JSON-based persistent storage\n * - Error-resistant implementation\n *\n * Storage Format:\n * - Stores in ~/.socket/_dlx/.dlx-manifest.json\n * - Per-spec manifest entries with timestamps\n * - Thread-safe operations using process lock utility\n *\n * Usage:\n * - Update check caching\n * - Binary metadata tracking\n * - Rate limiting registry requests\n */\n\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'fs'\nimport path from 'path'\n\nimport { readFileUtf8Sync, safeMkdirSync } from './fs'\nimport { getDefaultLogger } from './logger'\nimport { getSocketDlxDir } from './paths'\nimport { processLock } from './process-lock'\n\n/**\n * Manifest file name.\n */\nconst MANIFEST_FILE_NAME = '.dlx-manifest.json'\n\n/**\n * Details for npm package entries.\n */\nexport interface PackageDetails {\n installed_version: string\n size?: number\n update_check?: {\n last_check: number\n last_notification: number\n latest_known: string\n }\n}\n\n/**\n * Details for binary download entries.\n */\nexport interface BinaryDetails {\n checksum: string\n checksum_algorithm: 'sha256' | 'sha512'\n platform: string\n arch: string\n size: number\n source: {\n type: 'download'\n url: string\n }\n}\n\n/**\n * Unified manifest entry for all cached items (packages and binaries).\n * Shared fields at root, type-specific fields in details.\n */\nexport interface ManifestEntry {\n type: 'package' | 'binary'\n cache_key: string\n timestamp: number\n details: PackageDetails | BinaryDetails\n}\n\n/**\n * Type guard for package entries.\n */\nexport function isPackageEntry(\n entry: ManifestEntry,\n): entry is ManifestEntry & { details: PackageDetails } {\n return entry.type === 'package'\n}\n\n/**\n * Type guard for binary entries.\n */\nexport function isBinaryEntry(\n entry: ManifestEntry,\n): entry is ManifestEntry & { details: BinaryDetails } {\n return entry.type === 'binary'\n}\n\n/**\n * Legacy store record format (deprecated, for migration).\n */\nexport interface StoreRecord {\n timestampFetch: number\n timestampNotification: number\n version: string\n}\n\nexport interface DlxManifestOptions {\n /**\n * Custom manifest file path (defaults to ~/.socket/_dlx/.dlx-manifest.json).\n */\n manifestPath?: string\n}\n\n/**\n * DLX manifest storage manager with atomic operations.\n * Supports both legacy format (package name keys) and new unified manifest format (spec keys).\n */\nexport class DlxManifest {\n private readonly manifestPath: string\n private readonly lockPath: string\n\n constructor(options: DlxManifestOptions = {}) {\n this.manifestPath =\n options.manifestPath ?? path.join(getSocketDlxDir(), MANIFEST_FILE_NAME)\n this.lockPath = `${this.manifestPath}.lock`\n }\n\n /**\n * Read the entire manifest file.\n */\n private readManifest(): Record<string, ManifestEntry | StoreRecord> {\n try {\n if (!existsSync(this.manifestPath)) {\n return Object.create(null)\n }\n\n const rawContent = readFileUtf8Sync(this.manifestPath)\n const content = (\n typeof rawContent === 'string'\n ? rawContent\n : rawContent.toString('utf8')\n ).trim()\n\n if (!content) {\n return Object.create(null)\n }\n\n return JSON.parse(content) as Record<string, ManifestEntry | StoreRecord>\n } catch (error) {\n getDefaultLogger().warn(\n `Failed to read manifest: ${error instanceof Error ? error.message : String(error)}`,\n )\n return Object.create(null)\n }\n }\n\n /**\n * Get a manifest entry by spec (e.g., \"@socketsecurity/cli@^2.0.11\").\n */\n getManifestEntry(spec: string): ManifestEntry | undefined {\n const data = this.readManifest()\n const entry = data[spec]\n\n // Check if it's a new-format entry (has 'type' field).\n if (entry && 'type' in entry) {\n return entry as ManifestEntry\n }\n\n return undefined\n }\n\n /**\n * Get cached update information for a package (legacy format).\n * @deprecated Use getManifestEntry() for new code.\n */\n get(name: string): StoreRecord | undefined {\n const data = this.readManifest()\n const entry = data[name]\n\n // Return legacy format entries only.\n if (entry && !('type' in entry)) {\n return entry as StoreRecord\n }\n\n return undefined\n }\n\n /**\n * Set a package manifest entry.\n */\n async setPackageEntry(\n spec: string,\n cacheKey: string,\n details: PackageDetails,\n ): Promise<void> {\n await processLock.withLock(this.lockPath, async () => {\n const data = this.readManifest()\n\n data[spec] = {\n type: 'package',\n cache_key: cacheKey,\n timestamp: Date.now(),\n details,\n }\n\n await this.writeManifest(data)\n })\n }\n\n /**\n * Set a binary manifest entry.\n */\n async setBinaryEntry(\n spec: string,\n cacheKey: string,\n details: BinaryDetails,\n ): Promise<void> {\n await processLock.withLock(this.lockPath, async () => {\n const data = this.readManifest()\n\n data[spec] = {\n type: 'binary',\n cache_key: cacheKey,\n timestamp: Date.now(),\n details,\n }\n\n await this.writeManifest(data)\n })\n }\n\n /**\n * Write the manifest file atomically.\n */\n private async writeManifest(\n data: Record<string, ManifestEntry | StoreRecord>,\n ): Promise<void> {\n // Ensure directory exists.\n const manifestDir = path.dirname(this.manifestPath)\n try {\n safeMkdirSync(manifestDir, { recursive: true })\n } catch (error) {\n getDefaultLogger().warn(\n `Failed to create manifest directory: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n\n // Write atomically.\n const content = JSON.stringify(data, null, 2)\n const tempPath = `${this.manifestPath}.tmp`\n\n try {\n writeFileSync(tempPath, content, 'utf8')\n writeFileSync(this.manifestPath, content, 'utf8')\n\n // Clean up temp file.\n try {\n if (existsSync(tempPath)) {\n unlinkSync(tempPath)\n }\n } catch {\n // Cleanup failed, not critical.\n }\n } catch (error) {\n // Clean up temp file on error.\n try {\n if (existsSync(tempPath)) {\n unlinkSync(tempPath)\n }\n } catch {\n // Best effort cleanup.\n }\n throw error\n }\n }\n\n /**\n * Store update information for a package (legacy format).\n * @deprecated Use setPackageEntry() for new code.\n */\n async set(name: string, record: StoreRecord): Promise<void> {\n await processLock.withLock(this.lockPath, async () => {\n let data: Record<string, StoreRecord> = Object.create(null)\n\n // Read existing data.\n try {\n if (existsSync(this.manifestPath)) {\n const content = readFileSync(this.manifestPath, 'utf8')\n if (content.trim()) {\n data = JSON.parse(content) as Record<string, StoreRecord>\n }\n }\n } catch (error) {\n getDefaultLogger().warn(\n `Failed to read existing manifest: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n\n // Update record.\n data[name] = record\n\n // Ensure directory exists.\n const manifestDir = path.dirname(this.manifestPath)\n try {\n safeMkdirSync(manifestDir, { recursive: true })\n } catch (error) {\n getDefaultLogger().warn(\n `Failed to create manifest directory: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n\n // Write atomically.\n const content = JSON.stringify(data, null, 2)\n const tempPath = `${this.manifestPath}.tmp`\n\n try {\n writeFileSync(tempPath, content, 'utf8')\n writeFileSync(this.manifestPath, content, 'utf8')\n\n // Clean up temp file.\n try {\n if (existsSync(tempPath)) {\n unlinkSync(tempPath)\n }\n } catch {\n // Cleanup failed, not critical.\n }\n } catch (error) {\n // Clean up temp file on error.\n try {\n if (existsSync(tempPath)) {\n unlinkSync(tempPath)\n }\n } catch {\n // Best effort cleanup.\n }\n throw error\n }\n })\n }\n\n /**\n * Clear cached data for a specific entry.\n */\n async clear(name: string): Promise<void> {\n await processLock.withLock(this.lockPath, async () => {\n try {\n if (!existsSync(this.manifestPath)) {\n return\n }\n\n const content = readFileSync(this.manifestPath, 'utf8')\n if (!content.trim()) {\n return\n }\n\n const data = JSON.parse(content) as Record<string, StoreRecord>\n delete data[name]\n\n const updatedContent = JSON.stringify(data, null, 2)\n writeFileSync(this.manifestPath, updatedContent, 'utf8')\n } catch (error) {\n getDefaultLogger().warn(\n `Failed to clear cache for ${name}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n })\n }\n\n /**\n * Clear all cached data.\n */\n async clearAll(): Promise<void> {\n await processLock.withLock(this.lockPath, async () => {\n try {\n if (existsSync(this.manifestPath)) {\n unlinkSync(this.manifestPath)\n }\n } catch (error) {\n getDefaultLogger().warn(\n `Failed to clear all cache: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n })\n }\n\n /**\n * Check if cached data is fresh based on TTL.\n */\n isFresh(record: StoreRecord | undefined, ttlMs: number): boolean {\n if (!record) {\n return false\n }\n\n const age = Date.now() - record.timestampFetch\n return age < ttlMs\n }\n\n /**\n * Get all cached package names.\n */\n getAllPackages(): string[] {\n try {\n if (!existsSync(this.manifestPath)) {\n return []\n }\n\n const rawContent = readFileUtf8Sync(this.manifestPath)\n const content = (\n typeof rawContent === 'string'\n ? rawContent\n : rawContent.toString('utf8')\n ).trim()\n if (!content) {\n return []\n }\n\n const data = JSON.parse(content) as Record<string, StoreRecord>\n return Object.keys(data)\n } catch (error) {\n getDefaultLogger().warn(\n `Failed to get package list: ${error instanceof Error ? error.message : String(error)}`,\n )\n return []\n }\n }\n}\n\n// Export singleton instance using default manifest location.\nexport const dlxManifest = new DlxManifest()\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BA,gBAAoE;AACpE,kBAAiB;AAEjB,IAAAA,aAAgD;AAChD,oBAAiC;AACjC,mBAAgC;AAChC,0BAA4B;AAK5B,MAAM,qBAAqB;AA4CpB,SAAS,eACd,OACsD;AACtD,SAAO,MAAM,SAAS;AACxB;AAKO,SAAS,cACd,OACqD;AACrD,SAAO,MAAM,SAAS;AACxB;AAsBO,MAAM,YAAY;AAAA,EACN;AAAA,EACA;AAAA,EAEjB,YAAY,UAA8B,CAAC,GAAG;AAC5C,SAAK,eACH,QAAQ,gBAAgB,YAAAC,QAAK,SAAK,8BAAgB,GAAG,kBAAkB;AACzE,SAAK,WAAW,GAAG,KAAK,YAAY;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKQ,eAA4D;AAClE,QAAI;AACF,UAAI,KAAC,sBAAW,KAAK,YAAY,GAAG;AAClC,eAAO,uBAAO,OAAO,IAAI;AAAA,MAC3B;AAEA,YAAM,iBAAa,6BAAiB,KAAK,YAAY;AACrD,YAAM,WACJ,OAAO,eAAe,WAClB,aACA,WAAW,SAAS,MAAM,GAC9B,KAAK;AAEP,UAAI,CAAC,SAAS;AACZ,eAAO,uBAAO,OAAO,IAAI;AAAA,MAC3B;AAEA,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,SAAS,OAAO;AACd,0CAAiB,EAAE;AAAA,QACjB,4BAA4B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MACpF;AACA,aAAO,uBAAO,OAAO,IAAI;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,MAAyC;AACxD,UAAM,OAAO,KAAK,aAAa;AAC/B,UAAM,QAAQ,KAAK,IAAI;AAGvB,QAAI,SAAS,UAAU,OAAO;AAC5B,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,MAAuC;AACzC,UAAM,OAAO,KAAK,aAAa;AAC/B,UAAM,QAAQ,KAAK,IAAI;AAGvB,QAAI,SAAS,EAAE,UAAU,QAAQ;AAC/B,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBACJ,MACA,UACA,SACe;AACf,UAAM,gCAAY,SAAS,KAAK,UAAU,YAAY;AACpD,YAAM,OAAO,KAAK,aAAa;AAE/B,WAAK,IAAI,IAAI;AAAA,QACX,MAAM;AAAA,QACN,WAAW;AAAA,QACX,WAAW,KAAK,IAAI;AAAA,QACpB;AAAA,MACF;AAEA,YAAM,KAAK,cAAc,IAAI;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eACJ,MACA,UACA,SACe;AACf,UAAM,gCAAY,SAAS,KAAK,UAAU,YAAY;AACpD,YAAM,OAAO,KAAK,aAAa;AAE/B,WAAK,IAAI,IAAI;AAAA,QACX,MAAM;AAAA,QACN,WAAW;AAAA,QACX,WAAW,KAAK,IAAI;AAAA,QACpB;AAAA,MACF;AAEA,YAAM,KAAK,cAAc,IAAI;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,cACZ,MACe;AAEf,UAAM,cAAc,YAAAA,QAAK,QAAQ,KAAK,YAAY;AAClD,QAAI;AACF,oCAAc,aAAa,EAAE,WAAW,KAAK,CAAC;AAAA,IAChD,SAAS,OAAO;AACd,0CAAiB,EAAE;AAAA,QACjB,wCAAwC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MAChG;AAAA,IACF;AAGA,UAAM,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC;AAC5C,UAAM,WAAW,GAAG,KAAK,YAAY;AAErC,QAAI;AACF,mCAAc,UAAU,SAAS,MAAM;AACvC,mCAAc,KAAK,cAAc,SAAS,MAAM;AAGhD,UAAI;AACF,gBAAI,sBAAW,QAAQ,GAAG;AACxB,oCAAW,QAAQ;AAAA,QACrB;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,SAAS,OAAO;AAEd,UAAI;AACF,gBAAI,sBAAW,QAAQ,GAAG;AACxB,oCAAW,QAAQ;AAAA,QACrB;AAAA,MACF,QAAQ;AAAA,MAER;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,IAAI,MAAc,QAAoC;AAC1D,UAAM,gCAAY,SAAS,KAAK,UAAU,YAAY;AACpD,UAAI,OAAoC,uBAAO,OAAO,IAAI;AAG1D,UAAI;AACF,gBAAI,sBAAW,KAAK,YAAY,GAAG;AACjC,gBAAMC,eAAU,wBAAa,KAAK,cAAc,MAAM;AACtD,cAAIA,SAAQ,KAAK,GAAG;AAClB,mBAAO,KAAK,MAAMA,QAAO;AAAA,UAC3B;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,4CAAiB,EAAE;AAAA,UACjB,qCAAqC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QAC7F;AAAA,MACF;AAGA,WAAK,IAAI,IAAI;AAGb,YAAM,cAAc,YAAAD,QAAK,QAAQ,KAAK,YAAY;AAClD,UAAI;AACF,sCAAc,aAAa,EAAE,WAAW,KAAK,CAAC;AAAA,MAChD,SAAS,OAAO;AACd,4CAAiB,EAAE;AAAA,UACjB,wCAAwC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QAChG;AAAA,MACF;AAGA,YAAM,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC;AAC5C,YAAM,WAAW,GAAG,KAAK,YAAY;AAErC,UAAI;AACF,qCAAc,UAAU,SAAS,MAAM;AACvC,qCAAc,KAAK,cAAc,SAAS,MAAM;AAGhD,YAAI;AACF,kBAAI,sBAAW,QAAQ,GAAG;AACxB,sCAAW,QAAQ;AAAA,UACrB;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF,SAAS,OAAO;AAEd,YAAI;AACF,kBAAI,sBAAW,QAAQ,GAAG;AACxB,sCAAW,QAAQ;AAAA,UACrB;AAAA,QACF,QAAQ;AAAA,QAER;AACA,cAAM;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,MAA6B;AACvC,UAAM,gCAAY,SAAS,KAAK,UAAU,YAAY;AACpD,UAAI;AACF,YAAI,KAAC,sBAAW,KAAK,YAAY,GAAG;AAClC;AAAA,QACF;AAEA,cAAM,cAAU,wBAAa,KAAK,cAAc,MAAM;AACtD,YAAI,CAAC,QAAQ,KAAK,GAAG;AACnB;AAAA,QACF;AAEA,cAAM,OAAO,KAAK,MAAM,OAAO;AAC/B,eAAO,KAAK,IAAI;AAEhB,cAAM,iBAAiB,KAAK,UAAU,MAAM,MAAM,CAAC;AACnD,qCAAc,KAAK,cAAc,gBAAgB,MAAM;AAAA,MACzD,SAAS,OAAO;AACd,4CAAiB,EAAE;AAAA,UACjB,6BAA6B,IAAI,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QAC9F;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAA0B;AAC9B,UAAM,gCAAY,SAAS,KAAK,UAAU,YAAY;AACpD,UAAI;AACF,gBAAI,sBAAW,KAAK,YAAY,GAAG;AACjC,oCAAW,KAAK,YAAY;AAAA,QAC9B;AAAA,MACF,SAAS,OAAO;AACd,4CAAiB,EAAE;AAAA,UACjB,8BAA8B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QACtF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,QAAiC,OAAwB;AAC/D,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,IACT;AAEA,UAAM,MAAM,KAAK,IAAI,IAAI,OAAO;AAChC,WAAO,MAAM;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,iBAA2B;AACzB,QAAI;AACF,UAAI,KAAC,sBAAW,KAAK,YAAY,GAAG;AAClC,eAAO,CAAC;AAAA,MACV;AAEA,YAAM,iBAAa,6BAAiB,KAAK,YAAY;AACrD,YAAM,WACJ,OAAO,eAAe,WAClB,aACA,WAAW,SAAS,MAAM,GAC9B,KAAK;AACP,UAAI,CAAC,SAAS;AACZ,eAAO,CAAC;AAAA,MACV;AAEA,YAAM,OAAO,KAAK,MAAM,OAAO;AAC/B,aAAO,OAAO,KAAK,IAAI;AAAA,IACzB,SAAS,OAAO;AACd,0CAAiB,EAAE;AAAA,QACjB,+BAA+B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MACvF;AACA,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AACF;AAGO,MAAM,cAAc,IAAI,YAAY;",
4
+ "sourcesContent": ["/**\n * @fileoverview DLX manifest storage utilities.\n * Manages persistent caching of DLX package and binary metadata with TTL support\n * and atomic file operations.\n *\n * Key Functions:\n * - getManifestEntry: Retrieve manifest entry by spec\n * - setPackageEntry: Store npm package metadata\n * - setBinaryEntry: Store binary download metadata\n *\n * Features:\n * - TTL-based cache expiration\n * - Atomic file operations with locking\n * - JSON-based persistent storage\n * - Error-resistant implementation\n *\n * Storage Format:\n * - Stores in ~/.socket/_dlx/.dlx-manifest.json\n * - Per-spec manifest entries with timestamps\n * - Thread-safe operations using process lock utility\n *\n * Usage:\n * - Update check caching\n * - Binary metadata tracking\n * - Rate limiting registry requests\n */\n\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'fs'\nimport path from 'path'\n\nimport { readFileUtf8Sync, safeMkdirSync } from './fs'\nimport { getDefaultLogger } from './logger'\nimport { getSocketDlxDir } from './paths'\nimport { processLock } from './process-lock'\n\nconst logger = getDefaultLogger()\n\n/**\n * Manifest file name.\n */\nconst MANIFEST_FILE_NAME = '.dlx-manifest.json'\n\n/**\n * Details for npm package entries.\n */\nexport interface PackageDetails {\n installed_version: string\n size?: number\n update_check?: {\n last_check: number\n last_notification: number\n latest_known: string\n }\n}\n\n/**\n * Details for binary download entries.\n */\nexport interface BinaryDetails {\n checksum: string\n checksum_algorithm: 'sha256' | 'sha512'\n platform: string\n arch: string\n size: number\n source: {\n type: 'download'\n url: string\n }\n}\n\n/**\n * Unified manifest entry for all cached items (packages and binaries).\n * Shared fields at root, type-specific fields in details.\n */\nexport interface ManifestEntry {\n type: 'package' | 'binary'\n cache_key: string\n timestamp: number\n details: PackageDetails | BinaryDetails\n}\n\n/**\n * Type guard for package entries.\n */\nexport function isPackageEntry(\n entry: ManifestEntry,\n): entry is ManifestEntry & { details: PackageDetails } {\n return entry.type === 'package'\n}\n\n/**\n * Type guard for binary entries.\n */\nexport function isBinaryEntry(\n entry: ManifestEntry,\n): entry is ManifestEntry & { details: BinaryDetails } {\n return entry.type === 'binary'\n}\n\n/**\n * Legacy store record format (deprecated, for migration).\n */\nexport interface StoreRecord {\n timestampFetch: number\n timestampNotification: number\n version: string\n}\n\nexport interface DlxManifestOptions {\n /**\n * Custom manifest file path (defaults to ~/.socket/_dlx/.dlx-manifest.json).\n */\n manifestPath?: string\n}\n\n/**\n * DLX manifest storage manager with atomic operations.\n * Supports both legacy format (package name keys) and new unified manifest format (spec keys).\n */\nexport class DlxManifest {\n private readonly manifestPath: string\n private readonly lockPath: string\n\n constructor(options: DlxManifestOptions = {}) {\n this.manifestPath =\n options.manifestPath ?? path.join(getSocketDlxDir(), MANIFEST_FILE_NAME)\n this.lockPath = `${this.manifestPath}.lock`\n }\n\n /**\n * Read the entire manifest file.\n */\n private readManifest(): Record<string, ManifestEntry | StoreRecord> {\n try {\n if (!existsSync(this.manifestPath)) {\n return Object.create(null)\n }\n\n const rawContent = readFileUtf8Sync(this.manifestPath)\n const content = (\n typeof rawContent === 'string'\n ? rawContent\n : rawContent.toString('utf8')\n ).trim()\n\n if (!content) {\n return Object.create(null)\n }\n\n return JSON.parse(content) as Record<string, ManifestEntry | StoreRecord>\n } catch (error) {\n logger.warn(\n `Failed to read manifest: ${error instanceof Error ? error.message : String(error)}`,\n )\n return Object.create(null)\n }\n }\n\n /**\n * Get a manifest entry by spec (e.g., \"@socketsecurity/cli@^2.0.11\").\n */\n getManifestEntry(spec: string): ManifestEntry | undefined {\n const data = this.readManifest()\n const entry = data[spec]\n\n // Check if it's a new-format entry (has 'type' field).\n if (entry && 'type' in entry) {\n return entry as ManifestEntry\n }\n\n return undefined\n }\n\n /**\n * Get cached update information for a package (legacy format).\n * @deprecated Use getManifestEntry() for new code.\n */\n get(name: string): StoreRecord | undefined {\n const data = this.readManifest()\n const entry = data[name]\n\n // Return legacy format entries only.\n if (entry && !('type' in entry)) {\n return entry as StoreRecord\n }\n\n return undefined\n }\n\n /**\n * Set a package manifest entry.\n */\n async setPackageEntry(\n spec: string,\n cacheKey: string,\n details: PackageDetails,\n ): Promise<void> {\n await processLock.withLock(this.lockPath, async () => {\n const data = this.readManifest()\n\n data[spec] = {\n type: 'package',\n cache_key: cacheKey,\n timestamp: Date.now(),\n details,\n }\n\n await this.writeManifest(data)\n })\n }\n\n /**\n * Set a binary manifest entry.\n */\n async setBinaryEntry(\n spec: string,\n cacheKey: string,\n details: BinaryDetails,\n ): Promise<void> {\n await processLock.withLock(this.lockPath, async () => {\n const data = this.readManifest()\n\n data[spec] = {\n type: 'binary',\n cache_key: cacheKey,\n timestamp: Date.now(),\n details,\n }\n\n await this.writeManifest(data)\n })\n }\n\n /**\n * Write the manifest file atomically.\n */\n private async writeManifest(\n data: Record<string, ManifestEntry | StoreRecord>,\n ): Promise<void> {\n // Ensure directory exists.\n const manifestDir = path.dirname(this.manifestPath)\n try {\n safeMkdirSync(manifestDir, { recursive: true })\n } catch (error) {\n logger.warn(\n `Failed to create manifest directory: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n\n // Write atomically.\n const content = JSON.stringify(data, null, 2)\n const tempPath = `${this.manifestPath}.tmp`\n\n try {\n writeFileSync(tempPath, content, 'utf8')\n writeFileSync(this.manifestPath, content, 'utf8')\n\n // Clean up temp file.\n try {\n if (existsSync(tempPath)) {\n unlinkSync(tempPath)\n }\n } catch {\n // Cleanup failed, not critical.\n }\n } catch (error) {\n // Clean up temp file on error.\n try {\n if (existsSync(tempPath)) {\n unlinkSync(tempPath)\n }\n } catch {\n // Best effort cleanup.\n }\n throw error\n }\n }\n\n /**\n * Store update information for a package (legacy format).\n * @deprecated Use setPackageEntry() for new code.\n */\n async set(name: string, record: StoreRecord): Promise<void> {\n await processLock.withLock(this.lockPath, async () => {\n let data: Record<string, StoreRecord> = Object.create(null)\n\n // Read existing data.\n try {\n if (existsSync(this.manifestPath)) {\n const content = readFileSync(this.manifestPath, 'utf8')\n if (content.trim()) {\n data = JSON.parse(content) as Record<string, StoreRecord>\n }\n }\n } catch (error) {\n logger.warn(\n `Failed to read existing manifest: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n\n // Update record.\n data[name] = record\n\n // Ensure directory exists.\n const manifestDir = path.dirname(this.manifestPath)\n try {\n safeMkdirSync(manifestDir, { recursive: true })\n } catch (error) {\n logger.warn(\n `Failed to create manifest directory: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n\n // Write atomically.\n const content = JSON.stringify(data, null, 2)\n const tempPath = `${this.manifestPath}.tmp`\n\n try {\n writeFileSync(tempPath, content, 'utf8')\n writeFileSync(this.manifestPath, content, 'utf8')\n\n // Clean up temp file.\n try {\n if (existsSync(tempPath)) {\n unlinkSync(tempPath)\n }\n } catch {\n // Cleanup failed, not critical.\n }\n } catch (error) {\n // Clean up temp file on error.\n try {\n if (existsSync(tempPath)) {\n unlinkSync(tempPath)\n }\n } catch {\n // Best effort cleanup.\n }\n throw error\n }\n })\n }\n\n /**\n * Clear cached data for a specific entry.\n */\n async clear(name: string): Promise<void> {\n await processLock.withLock(this.lockPath, async () => {\n try {\n if (!existsSync(this.manifestPath)) {\n return\n }\n\n const content = readFileSync(this.manifestPath, 'utf8')\n if (!content.trim()) {\n return\n }\n\n const data = JSON.parse(content) as Record<string, StoreRecord>\n delete data[name]\n\n const updatedContent = JSON.stringify(data, null, 2)\n writeFileSync(this.manifestPath, updatedContent, 'utf8')\n } catch (error) {\n logger.warn(\n `Failed to clear cache for ${name}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n })\n }\n\n /**\n * Clear all cached data.\n */\n async clearAll(): Promise<void> {\n await processLock.withLock(this.lockPath, async () => {\n try {\n if (existsSync(this.manifestPath)) {\n unlinkSync(this.manifestPath)\n }\n } catch (error) {\n logger.warn(\n `Failed to clear all cache: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n })\n }\n\n /**\n * Check if cached data is fresh based on TTL.\n */\n isFresh(record: StoreRecord | undefined, ttlMs: number): boolean {\n if (!record) {\n return false\n }\n\n const age = Date.now() - record.timestampFetch\n return age < ttlMs\n }\n\n /**\n * Get all cached package names.\n */\n getAllPackages(): string[] {\n try {\n if (!existsSync(this.manifestPath)) {\n return []\n }\n\n const rawContent = readFileUtf8Sync(this.manifestPath)\n const content = (\n typeof rawContent === 'string'\n ? rawContent\n : rawContent.toString('utf8')\n ).trim()\n if (!content) {\n return []\n }\n\n const data = JSON.parse(content) as Record<string, StoreRecord>\n return Object.keys(data)\n } catch (error) {\n logger.warn(\n `Failed to get package list: ${error instanceof Error ? error.message : String(error)}`,\n )\n return []\n }\n }\n}\n\n// Export singleton instance using default manifest location.\nexport const dlxManifest = new DlxManifest()\n"],
5
+ "mappings": "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",
6
6
  "names": ["import_fs", "path", "content"]
7
7
  }
package/dist/ipc.js CHANGED
@@ -129,7 +129,7 @@ async function cleanupIpcStubs(appName) {
129
129
  const files = await import_fs.promises.readdir(stubDir);
130
130
  const now = Date.now();
131
131
  const maxAgeMs = 5 * 60 * 1e3;
132
- await Promise.all(
132
+ await Promise.allSettled(
133
133
  files.map(async (file) => {
134
134
  if (file.startsWith("stub-") && file.endsWith(".json")) {
135
135
  const filePath = import_path.default.join(stubDir, file);
package/dist/ipc.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/ipc.ts"],
4
- "sourcesContent": ["/**\n * IPC (Inter-Process Communication) Module\n * ==========================================\n *\n * This module provides secure inter-process communication utilities for Socket CLI\n * and related tools. It replaces environment variable passing with more secure and\n * scalable alternatives.\n *\n * ## Key Features:\n * - File-based stub communication for initial data handoff\n * - Node.js IPC channel support for real-time bidirectional messaging\n * - Automatic cleanup of temporary files\n * - Type-safe message validation with Zod schemas\n * - Timeout handling for reliability\n *\n * ## Use Cases:\n * 1. Passing API tokens between processes without exposing them in env vars\n * 2. Transferring large configuration objects that exceed env var size limits\n * 3. Bidirectional communication between parent and child processes\n * 4. Secure handshake protocols between Socket CLI components\n *\n * ## Security Considerations:\n * - Stub files are created with restricted permissions in OS temp directory\n * - Messages include timestamps for freshness validation\n * - Automatic cleanup prevents sensitive data persistence\n * - Unique IDs prevent message replay attacks\n *\n * @module ipc\n */\n\nimport crypto from 'crypto'\n\nimport { promises as fs } from 'fs'\n\nimport path from 'path'\n\nimport { safeDeleteSync } from './fs'\nimport { getOsTmpDir } from './paths'\nimport { z } from './zod'\n\n// Define BufferEncoding type for TypeScript compatibility.\ntype BufferEncoding = globalThis.BufferEncoding\n\n/**\n * Zod Schemas for Runtime Validation\n * ====================================\n * These schemas provide runtime type safety for IPC messages,\n * ensuring data integrity across process boundaries.\n */\n\n/**\n * Base IPC message schema - validates the core message structure.\n * All IPC messages must conform to this schema.\n */\nconst IpcMessageSchema = z.object({\n /** Unique identifier for message tracking and response correlation. */\n id: z.string().min(1),\n /** Unix timestamp for freshness validation and replay prevention. */\n timestamp: z.number().positive(),\n /** Message type identifier for routing and handling. */\n type: z.string().min(1),\n /** Payload data - can be any JSON-serializable value. */\n data: z.unknown(),\n})\n\n/**\n * IPC handshake schema - used for initial connection establishment.\n * The handshake includes version info and authentication tokens.\n * @internal Exported for testing purposes.\n */\nexport const IpcHandshakeSchema = IpcMessageSchema.extend({\n type: z.literal('handshake'),\n data: z.object({\n /** Protocol version for compatibility checking. */\n version: z.string(),\n /** Process ID for identification. */\n pid: z.number().int().positive(),\n /** Optional API token for authentication. */\n apiToken: z.string().optional(),\n /** Application name for multi-app support. */\n appName: z.string(),\n }),\n})\n\n/**\n * IPC stub file schema - validates the structure of stub files.\n * Stub files are used for passing data between processes via filesystem.\n */\nconst IpcStubSchema = z.object({\n /** Process ID that created the stub. */\n pid: z.number().int().positive(),\n /** Creation timestamp for age validation. */\n timestamp: z.number().positive(),\n /** The actual data payload. */\n data: z.unknown(),\n})\n\n/**\n * TypeScript interfaces for IPC communication.\n * These types ensure type consistency across the IPC module.\n */\n\n/**\n * Base IPC message interface.\n * All IPC messages must conform to this structure.\n */\nexport interface IpcMessage<T = unknown> {\n /** Unique identifier for message tracking and response correlation. */\n id: string\n /** Unix timestamp for freshness validation and replay prevention. */\n timestamp: number\n /** Message type identifier for routing and handling. */\n type: string\n /** Payload data - can be any JSON-serializable value. */\n data: T\n}\n\n/**\n * IPC handshake message interface.\n * Used for initial connection establishment.\n */\nexport interface IpcHandshake\n extends IpcMessage<{\n /** Protocol version for compatibility checking. */\n version: string\n /** Process ID for identification. */\n pid: number\n /** Optional API token for authentication. */\n apiToken?: string\n /** Application name for multi-app support. */\n appName: string\n }> {\n type: 'handshake'\n}\n\n/**\n * IPC stub file interface.\n * Represents the structure of stub files used for filesystem-based IPC.\n */\nexport interface IpcStub {\n /** Process ID that created the stub. */\n pid: number\n /** Creation timestamp for age validation. */\n timestamp: number\n /** The actual data payload. */\n data: unknown\n}\n\n/**\n * Options for IPC communication\n */\nexport interface IpcOptions {\n /** Timeout in milliseconds for async operations. */\n timeout?: number\n /** Text encoding for message serialization. */\n encoding?: BufferEncoding\n}\n\n/**\n * Create a unique IPC channel identifier for message correlation.\n *\n * Generates a unique identifier that combines:\n * - A prefix for namespacing (defaults to 'socket')\n * - The current process ID for process identification\n * - A random hex string for uniqueness\n *\n * @param prefix - Optional prefix to namespace the channel ID\n * @returns A unique channel identifier string\n *\n * @example\n * ```typescript\n * const channelId = createIpcChannelId('socket-cli')\n * // Returns: 'socket-cli-12345-a1b2c3d4e5f6g7h8'\n * ```\n */\nexport function createIpcChannelId(prefix = 'socket'): string {\n return `${prefix}-${process.pid}-${crypto.randomBytes(8).toString('hex')}`\n}\n\n/**\n * Get the IPC stub path for a given application.\n *\n * This function generates a unique file path for IPC stub files that are used\n * to pass data between processes. The stub files are stored in a hidden directory\n * within the system's temporary folder.\n *\n * ## Path Structure:\n * - Base: System temp directory (e.g., /tmp on Unix, %TEMP% on Windows)\n * - Directory: `.socket-ipc/{appName}/`\n * - Filename: `stub-{pid}.json`\n *\n * ## Security Features:\n * - Files are isolated per application via appName parameter\n * - Process ID in filename prevents collisions between concurrent processes\n * - Temporary directory location ensures automatic cleanup on system restart\n *\n * @param appName - The application identifier (e.g., 'socket-cli', 'socket-dlx')\n * @returns Full path to the IPC stub file\n *\n * @example\n * ```typescript\n * const stubPath = getIpcStubPath('socket-cli')\n * // Returns: '/tmp/.socket-ipc/socket-cli/stub-12345.json' (Unix)\n * // Returns: 'C:\\\\Users\\\\Name\\\\AppData\\\\Local\\\\Temp\\\\.socket-ipc\\\\socket-cli\\\\stub-12345.json' (Windows)\n * ```\n *\n * @used Currently used by socket-cli for self-update and inter-process communication\n */\nexport function getIpcStubPath(appName: string): string {\n // Get the system's temporary directory - this is platform-specific.\n const tempDir = getOsTmpDir()\n\n // Create a hidden directory structure for Socket IPC files.\n // The dot prefix makes it hidden on Unix-like systems.\n const stubDir = path.join(tempDir, '.socket-ipc', appName)\n\n // Generate filename with process ID to ensure uniqueness.\n // The PID prevents conflicts when multiple processes run simultaneously.\n return path.join(stubDir, `stub-${process.pid}.json`)\n}\n\n/**\n * Ensure IPC directory exists for stub file creation.\n *\n * This helper function creates the directory structure needed for IPC stub files.\n * It's called before writing stub files to ensure the parent directories exist.\n *\n * @param filePath - Full path to the file that needs its directory created\n * @returns Promise that resolves when directory is created\n *\n * @internal Helper function used by writeIpcStub\n */\nasync function ensureIpcDirectory(filePath: string): Promise<void> {\n const dir = path.dirname(filePath)\n // Create directory recursively if it doesn't exist.\n await fs.mkdir(dir, { recursive: true })\n}\n\n/**\n * Write IPC data to a stub file for inter-process data transfer.\n *\n * This function creates a stub file containing data that needs to be passed\n * between processes. The stub file includes metadata like process ID and\n * timestamp for validation.\n *\n * ## File Structure:\n * ```json\n * {\n * \"pid\": 12345,\n * \"timestamp\": 1699564234567,\n * \"data\": { ... }\n * }\n * ```\n *\n * ## Use Cases:\n * - Passing API tokens to child processes\n * - Transferring configuration between Socket CLI components\n * - Sharing large data that exceeds environment variable limits\n *\n * @param appName - The application identifier\n * @param data - The data to write to the stub file\n * @returns Promise resolving to the stub file path\n *\n * @example\n * ```typescript\n * const stubPath = await writeIpcStub('socket-cli', {\n * apiToken: 'secret-token',\n * config: { ... }\n * })\n * // Pass stubPath to child process for reading\n * ```\n */\nexport async function writeIpcStub(\n appName: string,\n data: unknown,\n): Promise<string> {\n const stubPath = getIpcStubPath(appName)\n await ensureIpcDirectory(stubPath)\n\n // Create stub data with validation metadata.\n const ipcData: IpcStub = {\n data,\n pid: process.pid,\n timestamp: Date.now(),\n }\n\n // Validate data structure with Zod schema.\n const validated = IpcStubSchema.parse(ipcData)\n\n // Write with pretty printing for debugging.\n await fs.writeFile(stubPath, JSON.stringify(validated, null, 2), 'utf8')\n return stubPath\n}\n\n/**\n * Read IPC data from a stub file with automatic cleanup.\n *\n * This function reads data from an IPC stub file and validates its freshness.\n * Stale files (older than 5 minutes) are automatically cleaned up to prevent\n * accumulation of temporary files.\n *\n * ## Validation Steps:\n * 1. Read and parse JSON file\n * 2. Validate structure with Zod schema\n * 3. Check timestamp freshness\n * 4. Clean up if stale\n * 5. Return data if valid\n *\n * @param stubPath - Path to the stub file to read\n * @returns Promise resolving to the data or null if invalid/stale\n *\n * @example\n * ```typescript\n * const data = await readIpcStub('/tmp/.socket-ipc/socket-cli/stub-12345.json')\n * if (data) {\n * console.log('Received:', data)\n * }\n * ```\n *\n * @unused Reserved for future implementation\n */\nexport async function readIpcStub(stubPath: string): Promise<unknown> {\n try {\n const content = await fs.readFile(stubPath, 'utf8')\n const parsed = JSON.parse(content)\n // Validate structure with Zod schema.\n const validated = IpcStubSchema.parse(parsed)\n // Check age for freshness validation.\n const ageMs = Date.now() - validated.timestamp\n // 5 minutes.\n const maxAgeMs = 5 * 60 * 1000\n if (ageMs > maxAgeMs) {\n // Clean up stale file. IPC stubs are always in tmpdir, so use force: true.\n try {\n safeDeleteSync(stubPath, { force: true })\n } catch {\n // Ignore deletion errors\n }\n return null\n }\n return validated.data\n } catch {\n // Return null for any errors (file not found, invalid JSON, validation failure).\n return null\n }\n}\n\n/**\n * Clean up IPC stub files for an application.\n *\n * This maintenance function removes stale IPC stub files to prevent\n * accumulation in the temporary directory. It's designed to be called\n * periodically or on application startup.\n *\n * ## Cleanup Rules:\n * - Files older than 5 minutes are removed (checked via both filesystem mtime and JSON timestamp)\n * - Only stub files (stub-*.json) are processed\n * - Errors are silently ignored (best-effort cleanup)\n *\n * @param appName - The application identifier\n * @returns Promise that resolves when cleanup is complete\n *\n * @example\n * ```typescript\n * // Clean up on application startup\n * await cleanupIpcStubs('socket-cli')\n * ```\n *\n * @unused Reserved for future implementation\n */\nexport async function cleanupIpcStubs(appName: string): Promise<void> {\n const tempDir = getOsTmpDir()\n const stubDir = path.join(tempDir, '.socket-ipc', appName)\n try {\n const files = await fs.readdir(stubDir)\n const now = Date.now()\n // 5 minutes.\n const maxAgeMs = 5 * 60 * 1000\n // Process each file in parallel for efficiency.\n await Promise.all(\n files.map(async file => {\n if (file.startsWith('stub-') && file.endsWith('.json')) {\n const filePath = path.join(stubDir, file)\n try {\n // Check both filesystem mtime and JSON timestamp for more reliable detection\n const stats = await fs.stat(filePath)\n const mtimeAge = now - stats.mtimeMs\n let isStale = mtimeAge > maxAgeMs\n\n // Always check the timestamp inside the JSON file for accuracy\n // This is more reliable than filesystem mtime in some environments\n try {\n const content = await fs.readFile(filePath, 'utf8')\n const parsed = JSON.parse(content)\n const validated = IpcStubSchema.parse(parsed)\n const contentAge = now - validated.timestamp\n // File is stale if EITHER check indicates staleness\n isStale = isStale || contentAge > maxAgeMs\n } catch {\n // If we can't read/parse the file, rely on mtime check\n }\n\n if (isStale) {\n // IPC stubs are always in tmpdir, so we can use force: true to skip path checks\n safeDeleteSync(filePath, { force: true })\n }\n } catch {\n // Ignore errors for individual files.\n }\n }\n }),\n )\n } catch {\n // Directory might not exist, that's ok.\n }\n}\n\n/**\n * Send data through Node.js IPC channel.\n *\n * This function sends structured messages through the Node.js IPC channel\n * when available. The IPC channel must be established with stdio: ['pipe', 'pipe', 'pipe', 'ipc'].\n *\n * ## Requirements:\n * - Process must have been spawned with IPC channel enabled\n * - Message must be serializable to JSON\n * - Process.send() must be available\n *\n * @param process - The process object with IPC channel\n * @param message - The IPC message to send\n * @returns true if message was sent, false otherwise\n *\n * @example\n * ```typescript\n * const message = createIpcMessage('handshake', { version: '1.0.0' })\n * const sent = sendIpc(childProcess, message)\n * ```\n *\n * @unused Reserved for bidirectional communication implementation\n */\nexport function sendIpc(\n process: NodeJS.Process | unknown,\n message: IpcMessage,\n): boolean {\n if (\n process &&\n typeof process === 'object' &&\n 'send' in process &&\n typeof process.send === 'function'\n ) {\n try {\n // Validate message structure before sending.\n const validated = IpcMessageSchema.parse(message)\n return process.send(validated)\n } catch {\n return false\n }\n }\n return false\n}\n\n/**\n * Receive data through Node.js IPC channel.\n *\n * Sets up a listener for IPC messages with automatic validation and parsing.\n * Returns a cleanup function to remove the listener when no longer needed.\n *\n * ## Message Flow:\n * 1. Receive raw message from IPC channel\n * 2. Validate with parseIpcMessage\n * 3. Call handler if valid\n * 4. Ignore invalid messages\n *\n * @param handler - Function to call with valid IPC messages\n * @returns Cleanup function to remove the listener\n *\n * @example\n * ```typescript\n * const cleanup = onIpc((message) => {\n * console.log('Received:', message.type, message.data)\n * })\n * // Later...\n * cleanup() // Remove listener\n * ```\n *\n * @unused Reserved for bidirectional communication\n */\nexport function onIpc(handler: (message: IpcMessage) => void): () => void {\n const listener = (message: unknown) => {\n const parsed = parseIpcMessage(message)\n if (parsed) {\n handler(parsed)\n }\n }\n process.on('message', listener)\n // Return cleanup function for proper resource management.\n return () => {\n process.off('message', listener)\n }\n}\n\n/**\n * Create a promise that resolves when a specific IPC message is received.\n *\n * This utility function provides async/await support for IPC communication,\n * allowing you to wait for specific message types with timeout support.\n *\n * ## Features:\n * - Automatic timeout handling\n * - Type-safe message data\n * - Resource cleanup on completion\n * - Promise-based API\n *\n * @param messageType - The message type to wait for\n * @param options - Options including timeout configuration\n * @returns Promise resolving to the message data\n *\n * @example\n * ```typescript\n * try {\n * const response = await waitForIpc<ConfigData>('config-response', {\n * timeout: 5000 // 5 seconds\n * })\n * console.log('Config received:', response)\n * } catch (error) {\n * console.error('Timeout waiting for config')\n * }\n * ```\n *\n * @unused Reserved for request-response pattern implementation\n */\nexport function waitForIpc<T = unknown>(\n messageType: string,\n options: IpcOptions = {},\n): Promise<T> {\n const { timeout = 30_000 } = options\n return new Promise((resolve, reject) => {\n let cleanup: (() => void) | null = null\n let timeoutId: NodeJS.Timeout | null = null\n const handleTimeout = () => {\n if (cleanup) {\n cleanup()\n }\n reject(new Error(`IPC timeout waiting for message type: ${messageType}`))\n }\n const handleMessage = (message: IpcMessage) => {\n if (message.type === messageType) {\n if (timeoutId) {\n clearTimeout(timeoutId)\n }\n if (cleanup) {\n cleanup()\n }\n resolve(message.data as T)\n }\n }\n cleanup = onIpc(handleMessage)\n if (timeout > 0) {\n timeoutId = setTimeout(handleTimeout, timeout)\n }\n })\n}\n\n/**\n * Create an IPC message with proper structure and metadata.\n *\n * This factory function creates properly structured IPC messages with:\n * - Unique ID for tracking\n * - Timestamp for freshness\n * - Type for routing\n * - Data payload\n *\n * @param type - The message type identifier\n * @param data - The message payload\n * @returns A properly structured IPC message\n *\n * @example\n * ```typescript\n * const handshake = createIpcMessage('handshake', {\n * version: '1.0.0',\n * pid: process.pid,\n * appName: 'socket-cli'\n * })\n * ```\n *\n * @unused Reserved for future message creation needs\n */\nexport function createIpcMessage<T = unknown>(\n type: string,\n data: T,\n): IpcMessage<T> {\n return {\n id: crypto.randomBytes(16).toString('hex'),\n timestamp: Date.now(),\n type,\n data,\n }\n}\n\n/**\n * Check if process has IPC channel available.\n *\n * This utility checks whether a process object has the necessary\n * properties for IPC communication. Used to determine if IPC\n * messaging is possible before attempting to send.\n *\n * @param process - The process object to check\n * @returns true if IPC is available, false otherwise\n *\n * @example\n * ```typescript\n * if (hasIpcChannel(childProcess)) {\n * sendIpc(childProcess, message)\n * } else {\n * // Fall back to alternative communication method\n * }\n * ```\n *\n * @unused Reserved for IPC availability detection\n */\nexport function hasIpcChannel(process: unknown): boolean {\n return Boolean(\n process &&\n typeof process === 'object' &&\n 'send' in process &&\n typeof process.send === 'function' &&\n 'channel' in process &&\n process.channel !== undefined,\n )\n}\n\n/**\n * Safely parse and validate IPC messages.\n *\n * This function performs runtime validation of incoming messages\n * to ensure they conform to the IPC message structure. It uses\n * Zod schemas for robust validation.\n *\n * ## Validation Steps:\n * 1. Check if message is an object\n * 2. Validate required fields exist\n * 3. Validate field types\n * 4. Return typed message or null\n *\n * @param message - The raw message to parse\n * @returns Parsed IPC message or null if invalid\n *\n * @example\n * ```typescript\n * const parsed = parseIpcMessage(rawMessage)\n * if (parsed) {\n * handleMessage(parsed)\n * }\n * ```\n *\n * @unused Reserved for message validation needs\n */\nexport function parseIpcMessage(message: unknown): IpcMessage | null {\n try {\n // Use Zod schema for comprehensive validation.\n const validated = IpcMessageSchema.parse(message)\n return validated as IpcMessage\n } catch {\n // Return null for any validation failure.\n return null\n }\n}\n"],
4
+ "sourcesContent": ["/**\n * IPC (Inter-Process Communication) Module\n * ==========================================\n *\n * This module provides secure inter-process communication utilities for Socket CLI\n * and related tools. It replaces environment variable passing with more secure and\n * scalable alternatives.\n *\n * ## Key Features:\n * - File-based stub communication for initial data handoff\n * - Node.js IPC channel support for real-time bidirectional messaging\n * - Automatic cleanup of temporary files\n * - Type-safe message validation with Zod schemas\n * - Timeout handling for reliability\n *\n * ## Use Cases:\n * 1. Passing API tokens between processes without exposing them in env vars\n * 2. Transferring large configuration objects that exceed env var size limits\n * 3. Bidirectional communication between parent and child processes\n * 4. Secure handshake protocols between Socket CLI components\n *\n * ## Security Considerations:\n * - Stub files are created with restricted permissions in OS temp directory\n * - Messages include timestamps for freshness validation\n * - Automatic cleanup prevents sensitive data persistence\n * - Unique IDs prevent message replay attacks\n *\n * @module ipc\n */\n\nimport crypto from 'crypto'\n\nimport { promises as fs } from 'fs'\n\nimport path from 'path'\n\nimport { safeDeleteSync } from './fs'\nimport { getOsTmpDir } from './paths'\nimport { z } from './zod'\n\n// Define BufferEncoding type for TypeScript compatibility.\ntype BufferEncoding = globalThis.BufferEncoding\n\n/**\n * Zod Schemas for Runtime Validation\n * ====================================\n * These schemas provide runtime type safety for IPC messages,\n * ensuring data integrity across process boundaries.\n */\n\n/**\n * Base IPC message schema - validates the core message structure.\n * All IPC messages must conform to this schema.\n */\nconst IpcMessageSchema = z.object({\n /** Unique identifier for message tracking and response correlation. */\n id: z.string().min(1),\n /** Unix timestamp for freshness validation and replay prevention. */\n timestamp: z.number().positive(),\n /** Message type identifier for routing and handling. */\n type: z.string().min(1),\n /** Payload data - can be any JSON-serializable value. */\n data: z.unknown(),\n})\n\n/**\n * IPC handshake schema - used for initial connection establishment.\n * The handshake includes version info and authentication tokens.\n * @internal Exported for testing purposes.\n */\nexport const IpcHandshakeSchema = IpcMessageSchema.extend({\n type: z.literal('handshake'),\n data: z.object({\n /** Protocol version for compatibility checking. */\n version: z.string(),\n /** Process ID for identification. */\n pid: z.number().int().positive(),\n /** Optional API token for authentication. */\n apiToken: z.string().optional(),\n /** Application name for multi-app support. */\n appName: z.string(),\n }),\n})\n\n/**\n * IPC stub file schema - validates the structure of stub files.\n * Stub files are used for passing data between processes via filesystem.\n */\nconst IpcStubSchema = z.object({\n /** Process ID that created the stub. */\n pid: z.number().int().positive(),\n /** Creation timestamp for age validation. */\n timestamp: z.number().positive(),\n /** The actual data payload. */\n data: z.unknown(),\n})\n\n/**\n * TypeScript interfaces for IPC communication.\n * These types ensure type consistency across the IPC module.\n */\n\n/**\n * Base IPC message interface.\n * All IPC messages must conform to this structure.\n */\nexport interface IpcMessage<T = unknown> {\n /** Unique identifier for message tracking and response correlation. */\n id: string\n /** Unix timestamp for freshness validation and replay prevention. */\n timestamp: number\n /** Message type identifier for routing and handling. */\n type: string\n /** Payload data - can be any JSON-serializable value. */\n data: T\n}\n\n/**\n * IPC handshake message interface.\n * Used for initial connection establishment.\n */\nexport interface IpcHandshake\n extends IpcMessage<{\n /** Protocol version for compatibility checking. */\n version: string\n /** Process ID for identification. */\n pid: number\n /** Optional API token for authentication. */\n apiToken?: string\n /** Application name for multi-app support. */\n appName: string\n }> {\n type: 'handshake'\n}\n\n/**\n * IPC stub file interface.\n * Represents the structure of stub files used for filesystem-based IPC.\n */\nexport interface IpcStub {\n /** Process ID that created the stub. */\n pid: number\n /** Creation timestamp for age validation. */\n timestamp: number\n /** The actual data payload. */\n data: unknown\n}\n\n/**\n * Options for IPC communication\n */\nexport interface IpcOptions {\n /** Timeout in milliseconds for async operations. */\n timeout?: number\n /** Text encoding for message serialization. */\n encoding?: BufferEncoding\n}\n\n/**\n * Create a unique IPC channel identifier for message correlation.\n *\n * Generates a unique identifier that combines:\n * - A prefix for namespacing (defaults to 'socket')\n * - The current process ID for process identification\n * - A random hex string for uniqueness\n *\n * @param prefix - Optional prefix to namespace the channel ID\n * @returns A unique channel identifier string\n *\n * @example\n * ```typescript\n * const channelId = createIpcChannelId('socket-cli')\n * // Returns: 'socket-cli-12345-a1b2c3d4e5f6g7h8'\n * ```\n */\nexport function createIpcChannelId(prefix = 'socket'): string {\n return `${prefix}-${process.pid}-${crypto.randomBytes(8).toString('hex')}`\n}\n\n/**\n * Get the IPC stub path for a given application.\n *\n * This function generates a unique file path for IPC stub files that are used\n * to pass data between processes. The stub files are stored in a hidden directory\n * within the system's temporary folder.\n *\n * ## Path Structure:\n * - Base: System temp directory (e.g., /tmp on Unix, %TEMP% on Windows)\n * - Directory: `.socket-ipc/{appName}/`\n * - Filename: `stub-{pid}.json`\n *\n * ## Security Features:\n * - Files are isolated per application via appName parameter\n * - Process ID in filename prevents collisions between concurrent processes\n * - Temporary directory location ensures automatic cleanup on system restart\n *\n * @param appName - The application identifier (e.g., 'socket-cli', 'socket-dlx')\n * @returns Full path to the IPC stub file\n *\n * @example\n * ```typescript\n * const stubPath = getIpcStubPath('socket-cli')\n * // Returns: '/tmp/.socket-ipc/socket-cli/stub-12345.json' (Unix)\n * // Returns: 'C:\\\\Users\\\\Name\\\\AppData\\\\Local\\\\Temp\\\\.socket-ipc\\\\socket-cli\\\\stub-12345.json' (Windows)\n * ```\n *\n * @used Currently used by socket-cli for self-update and inter-process communication\n */\nexport function getIpcStubPath(appName: string): string {\n // Get the system's temporary directory - this is platform-specific.\n const tempDir = getOsTmpDir()\n\n // Create a hidden directory structure for Socket IPC files.\n // The dot prefix makes it hidden on Unix-like systems.\n const stubDir = path.join(tempDir, '.socket-ipc', appName)\n\n // Generate filename with process ID to ensure uniqueness.\n // The PID prevents conflicts when multiple processes run simultaneously.\n return path.join(stubDir, `stub-${process.pid}.json`)\n}\n\n/**\n * Ensure IPC directory exists for stub file creation.\n *\n * This helper function creates the directory structure needed for IPC stub files.\n * It's called before writing stub files to ensure the parent directories exist.\n *\n * @param filePath - Full path to the file that needs its directory created\n * @returns Promise that resolves when directory is created\n *\n * @internal Helper function used by writeIpcStub\n */\nasync function ensureIpcDirectory(filePath: string): Promise<void> {\n const dir = path.dirname(filePath)\n // Create directory recursively if it doesn't exist.\n await fs.mkdir(dir, { recursive: true })\n}\n\n/**\n * Write IPC data to a stub file for inter-process data transfer.\n *\n * This function creates a stub file containing data that needs to be passed\n * between processes. The stub file includes metadata like process ID and\n * timestamp for validation.\n *\n * ## File Structure:\n * ```json\n * {\n * \"pid\": 12345,\n * \"timestamp\": 1699564234567,\n * \"data\": { ... }\n * }\n * ```\n *\n * ## Use Cases:\n * - Passing API tokens to child processes\n * - Transferring configuration between Socket CLI components\n * - Sharing large data that exceeds environment variable limits\n *\n * @param appName - The application identifier\n * @param data - The data to write to the stub file\n * @returns Promise resolving to the stub file path\n *\n * @example\n * ```typescript\n * const stubPath = await writeIpcStub('socket-cli', {\n * apiToken: 'secret-token',\n * config: { ... }\n * })\n * // Pass stubPath to child process for reading\n * ```\n */\nexport async function writeIpcStub(\n appName: string,\n data: unknown,\n): Promise<string> {\n const stubPath = getIpcStubPath(appName)\n await ensureIpcDirectory(stubPath)\n\n // Create stub data with validation metadata.\n const ipcData: IpcStub = {\n data,\n pid: process.pid,\n timestamp: Date.now(),\n }\n\n // Validate data structure with Zod schema.\n const validated = IpcStubSchema.parse(ipcData)\n\n // Write with pretty printing for debugging.\n await fs.writeFile(stubPath, JSON.stringify(validated, null, 2), 'utf8')\n return stubPath\n}\n\n/**\n * Read IPC data from a stub file with automatic cleanup.\n *\n * This function reads data from an IPC stub file and validates its freshness.\n * Stale files (older than 5 minutes) are automatically cleaned up to prevent\n * accumulation of temporary files.\n *\n * ## Validation Steps:\n * 1. Read and parse JSON file\n * 2. Validate structure with Zod schema\n * 3. Check timestamp freshness\n * 4. Clean up if stale\n * 5. Return data if valid\n *\n * @param stubPath - Path to the stub file to read\n * @returns Promise resolving to the data or null if invalid/stale\n *\n * @example\n * ```typescript\n * const data = await readIpcStub('/tmp/.socket-ipc/socket-cli/stub-12345.json')\n * if (data) {\n * console.log('Received:', data)\n * }\n * ```\n *\n * @unused Reserved for future implementation\n */\nexport async function readIpcStub(stubPath: string): Promise<unknown> {\n try {\n const content = await fs.readFile(stubPath, 'utf8')\n const parsed = JSON.parse(content)\n // Validate structure with Zod schema.\n const validated = IpcStubSchema.parse(parsed)\n // Check age for freshness validation.\n const ageMs = Date.now() - validated.timestamp\n // 5 minutes.\n const maxAgeMs = 5 * 60 * 1000\n if (ageMs > maxAgeMs) {\n // Clean up stale file. IPC stubs are always in tmpdir, so use force: true.\n try {\n safeDeleteSync(stubPath, { force: true })\n } catch {\n // Ignore deletion errors\n }\n return null\n }\n return validated.data\n } catch {\n // Return null for any errors (file not found, invalid JSON, validation failure).\n return null\n }\n}\n\n/**\n * Clean up IPC stub files for an application.\n *\n * This maintenance function removes stale IPC stub files to prevent\n * accumulation in the temporary directory. It's designed to be called\n * periodically or on application startup.\n *\n * ## Cleanup Rules:\n * - Files older than 5 minutes are removed (checked via both filesystem mtime and JSON timestamp)\n * - Only stub files (stub-*.json) are processed\n * - Errors are silently ignored (best-effort cleanup)\n *\n * @param appName - The application identifier\n * @returns Promise that resolves when cleanup is complete\n *\n * @example\n * ```typescript\n * // Clean up on application startup\n * await cleanupIpcStubs('socket-cli')\n * ```\n *\n * @unused Reserved for future implementation\n */\nexport async function cleanupIpcStubs(appName: string): Promise<void> {\n const tempDir = getOsTmpDir()\n const stubDir = path.join(tempDir, '.socket-ipc', appName)\n try {\n const files = await fs.readdir(stubDir)\n const now = Date.now()\n // 5 minutes.\n const maxAgeMs = 5 * 60 * 1000\n // Process each file in parallel for efficiency.\n await Promise.allSettled(\n files.map(async file => {\n if (file.startsWith('stub-') && file.endsWith('.json')) {\n const filePath = path.join(stubDir, file)\n try {\n // Check both filesystem mtime and JSON timestamp for more reliable detection\n const stats = await fs.stat(filePath)\n const mtimeAge = now - stats.mtimeMs\n let isStale = mtimeAge > maxAgeMs\n\n // Always check the timestamp inside the JSON file for accuracy\n // This is more reliable than filesystem mtime in some environments\n try {\n const content = await fs.readFile(filePath, 'utf8')\n const parsed = JSON.parse(content)\n const validated = IpcStubSchema.parse(parsed)\n const contentAge = now - validated.timestamp\n // File is stale if EITHER check indicates staleness\n isStale = isStale || contentAge > maxAgeMs\n } catch {\n // If we can't read/parse the file, rely on mtime check\n }\n\n if (isStale) {\n // IPC stubs are always in tmpdir, so we can use force: true to skip path checks\n safeDeleteSync(filePath, { force: true })\n }\n } catch {\n // Ignore errors for individual files.\n }\n }\n }),\n )\n } catch {\n // Directory might not exist, that's ok.\n }\n}\n\n/**\n * Send data through Node.js IPC channel.\n *\n * This function sends structured messages through the Node.js IPC channel\n * when available. The IPC channel must be established with stdio: ['pipe', 'pipe', 'pipe', 'ipc'].\n *\n * ## Requirements:\n * - Process must have been spawned with IPC channel enabled\n * - Message must be serializable to JSON\n * - Process.send() must be available\n *\n * @param process - The process object with IPC channel\n * @param message - The IPC message to send\n * @returns true if message was sent, false otherwise\n *\n * @example\n * ```typescript\n * const message = createIpcMessage('handshake', { version: '1.0.0' })\n * const sent = sendIpc(childProcess, message)\n * ```\n *\n * @unused Reserved for bidirectional communication implementation\n */\nexport function sendIpc(\n process: NodeJS.Process | unknown,\n message: IpcMessage,\n): boolean {\n if (\n process &&\n typeof process === 'object' &&\n 'send' in process &&\n typeof process.send === 'function'\n ) {\n try {\n // Validate message structure before sending.\n const validated = IpcMessageSchema.parse(message)\n return process.send(validated)\n } catch {\n return false\n }\n }\n return false\n}\n\n/**\n * Receive data through Node.js IPC channel.\n *\n * Sets up a listener for IPC messages with automatic validation and parsing.\n * Returns a cleanup function to remove the listener when no longer needed.\n *\n * ## Message Flow:\n * 1. Receive raw message from IPC channel\n * 2. Validate with parseIpcMessage\n * 3. Call handler if valid\n * 4. Ignore invalid messages\n *\n * @param handler - Function to call with valid IPC messages\n * @returns Cleanup function to remove the listener\n *\n * @example\n * ```typescript\n * const cleanup = onIpc((message) => {\n * console.log('Received:', message.type, message.data)\n * })\n * // Later...\n * cleanup() // Remove listener\n * ```\n *\n * @unused Reserved for bidirectional communication\n */\nexport function onIpc(handler: (message: IpcMessage) => void): () => void {\n const listener = (message: unknown) => {\n const parsed = parseIpcMessage(message)\n if (parsed) {\n handler(parsed)\n }\n }\n process.on('message', listener)\n // Return cleanup function for proper resource management.\n return () => {\n process.off('message', listener)\n }\n}\n\n/**\n * Create a promise that resolves when a specific IPC message is received.\n *\n * This utility function provides async/await support for IPC communication,\n * allowing you to wait for specific message types with timeout support.\n *\n * ## Features:\n * - Automatic timeout handling\n * - Type-safe message data\n * - Resource cleanup on completion\n * - Promise-based API\n *\n * @param messageType - The message type to wait for\n * @param options - Options including timeout configuration\n * @returns Promise resolving to the message data\n *\n * @example\n * ```typescript\n * try {\n * const response = await waitForIpc<ConfigData>('config-response', {\n * timeout: 5000 // 5 seconds\n * })\n * console.log('Config received:', response)\n * } catch (error) {\n * console.error('Timeout waiting for config')\n * }\n * ```\n *\n * @unused Reserved for request-response pattern implementation\n */\nexport function waitForIpc<T = unknown>(\n messageType: string,\n options: IpcOptions = {},\n): Promise<T> {\n const { timeout = 30_000 } = options\n return new Promise((resolve, reject) => {\n let cleanup: (() => void) | null = null\n let timeoutId: NodeJS.Timeout | null = null\n const handleTimeout = () => {\n if (cleanup) {\n cleanup()\n }\n reject(new Error(`IPC timeout waiting for message type: ${messageType}`))\n }\n const handleMessage = (message: IpcMessage) => {\n if (message.type === messageType) {\n if (timeoutId) {\n clearTimeout(timeoutId)\n }\n if (cleanup) {\n cleanup()\n }\n resolve(message.data as T)\n }\n }\n cleanup = onIpc(handleMessage)\n if (timeout > 0) {\n timeoutId = setTimeout(handleTimeout, timeout)\n }\n })\n}\n\n/**\n * Create an IPC message with proper structure and metadata.\n *\n * This factory function creates properly structured IPC messages with:\n * - Unique ID for tracking\n * - Timestamp for freshness\n * - Type for routing\n * - Data payload\n *\n * @param type - The message type identifier\n * @param data - The message payload\n * @returns A properly structured IPC message\n *\n * @example\n * ```typescript\n * const handshake = createIpcMessage('handshake', {\n * version: '1.0.0',\n * pid: process.pid,\n * appName: 'socket-cli'\n * })\n * ```\n *\n * @unused Reserved for future message creation needs\n */\nexport function createIpcMessage<T = unknown>(\n type: string,\n data: T,\n): IpcMessage<T> {\n return {\n id: crypto.randomBytes(16).toString('hex'),\n timestamp: Date.now(),\n type,\n data,\n }\n}\n\n/**\n * Check if process has IPC channel available.\n *\n * This utility checks whether a process object has the necessary\n * properties for IPC communication. Used to determine if IPC\n * messaging is possible before attempting to send.\n *\n * @param process - The process object to check\n * @returns true if IPC is available, false otherwise\n *\n * @example\n * ```typescript\n * if (hasIpcChannel(childProcess)) {\n * sendIpc(childProcess, message)\n * } else {\n * // Fall back to alternative communication method\n * }\n * ```\n *\n * @unused Reserved for IPC availability detection\n */\nexport function hasIpcChannel(process: unknown): boolean {\n return Boolean(\n process &&\n typeof process === 'object' &&\n 'send' in process &&\n typeof process.send === 'function' &&\n 'channel' in process &&\n process.channel !== undefined,\n )\n}\n\n/**\n * Safely parse and validate IPC messages.\n *\n * This function performs runtime validation of incoming messages\n * to ensure they conform to the IPC message structure. It uses\n * Zod schemas for robust validation.\n *\n * ## Validation Steps:\n * 1. Check if message is an object\n * 2. Validate required fields exist\n * 3. Validate field types\n * 4. Return typed message or null\n *\n * @param message - The raw message to parse\n * @returns Parsed IPC message or null if invalid\n *\n * @example\n * ```typescript\n * const parsed = parseIpcMessage(rawMessage)\n * if (parsed) {\n * handleMessage(parsed)\n * }\n * ```\n *\n * @unused Reserved for message validation needs\n */\nexport function parseIpcMessage(message: unknown): IpcMessage | null {\n try {\n // Use Zod schema for comprehensive validation.\n const validated = IpcMessageSchema.parse(message)\n return validated as IpcMessage\n } catch {\n // Return null for any validation failure.\n return null\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["import_fs", "crypto", "path", "fs", "process"]
7
7
  }
package/dist/promises.js CHANGED
@@ -122,7 +122,7 @@ async function pEach(array, callbackFn, options) {
122
122
  if (signal?.aborted) {
123
123
  return;
124
124
  }
125
- await Promise.all(
125
+ await Promise.allSettled(
126
126
  chunk.map(
127
127
  (item) => /* @__PURE__ */ pRetry((...args) => callbackFn(args[0]), {
128
128
  ...retries,
@@ -169,7 +169,7 @@ async function pFilterChunk(chunks, callbackFn, options) {
169
169
  filteredChunks[i] = [];
170
170
  } else {
171
171
  const chunk = chunks[i];
172
- const predicateResults = await Promise.all(
172
+ const settled = await Promise.allSettled(
173
173
  chunk.map(
174
174
  (value) => /* @__PURE__ */ pRetry((...args) => callbackFn(args[0]), {
175
175
  ...retryOpts,
@@ -177,6 +177,9 @@ async function pFilterChunk(chunks, callbackFn, options) {
177
177
  })
178
178
  )
179
179
  );
180
+ const predicateResults = settled.map(
181
+ (r) => r.status === "fulfilled" ? r.value : false
182
+ );
180
183
  filteredChunks[i] = chunk.filter((_v, i2) => predicateResults[i2]);
181
184
  }
182
185
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/promises.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview Promise utilities including chunked iteration and timers.\n * Provides async control flow helpers and promise-based timing functions.\n */\n\nimport { UNDEFINED_TOKEN } from '#constants/core'\nimport { getAbortSignal } from '#constants/process'\n\nimport { arrayChunk } from './arrays'\n\nconst abortSignal = getAbortSignal()\n\n/**\n * Configuration options for retry behavior with exponential backoff.\n *\n * Controls how failed operations are retried, including timing, backoff strategy,\n * and callback hooks for observing or modifying retry behavior.\n */\nexport interface RetryOptions {\n /**\n * Arguments to pass to the callback function on each attempt.\n *\n * @default []\n */\n args?: unknown[] | undefined\n\n /**\n * Multiplier for exponential backoff (e.g., 2 doubles delay each retry).\n * Each retry waits `baseDelayMs * (backoffFactor ** attemptNumber)`.\n *\n * @default 2\n * @example\n * // With backoffFactor: 2, baseDelayMs: 100\n * // Retry 1: 100ms\n * // Retry 2: 200ms\n * // Retry 3: 400ms\n */\n backoffFactor?: number | undefined\n\n /**\n * Initial delay before the first retry (in milliseconds).\n * This is the base value for exponential backoff calculations.\n *\n * @default 200\n */\n baseDelayMs?: number | undefined\n\n // REMOVED: Deprecated `factor` option\n // Migration: Use `backoffFactor` instead\n\n /**\n * Whether to apply randomness to spread out retries and avoid thundering herd.\n * When `true`, adds random delay between 0 and current delay value.\n *\n * @default true\n * @example\n * // With jitter: true, delay: 100ms\n * // Actual wait: 100ms + random(0-100ms) = 100-200ms\n */\n jitter?: boolean | undefined\n\n /**\n * Upper limit for any backoff delay (in milliseconds).\n * Prevents exponential backoff from growing unbounded.\n *\n * @default 10000\n */\n maxDelayMs?: number | undefined\n\n // REMOVED: Deprecated `maxTimeout` option\n // Migration: Use `maxDelayMs` instead\n\n // REMOVED: Deprecated `minTimeout` option\n // Migration: Use `baseDelayMs` instead\n\n /**\n * Callback invoked on each retry attempt.\n * Can observe errors, customize delays, or cancel retries.\n *\n * @param attempt - The current attempt number (1-based: 1, 2, 3, ...)\n * @param error - The error that triggered this retry\n * @param delay - The calculated delay in milliseconds before next retry\n * @returns `false` to cancel retries (if `onRetryCancelOnFalse` is `true`),\n * a number to override the delay, or `undefined` to use calculated delay\n *\n * @example\n * // Log each retry\n * onRetry: (attempt, error, delay) => {\n * console.log(`Retry ${attempt} after ${delay}ms: ${error}`)\n * }\n *\n * @example\n * // Cancel retries for specific errors\n * onRetry: (attempt, error) => {\n * if (error instanceof ValidationError) return false\n * }\n *\n * @example\n * // Use custom delay\n * onRetry: (attempt) => attempt * 1000 // 1s, 2s, 3s, ...\n */\n onRetry?:\n | ((\n attempt: number,\n error: unknown,\n delay: number,\n ) => boolean | number | undefined)\n | undefined\n\n /**\n * Whether `onRetry` can cancel retries by returning `false`.\n * When `true`, returning `false` from `onRetry` stops retry attempts.\n *\n * @default false\n */\n onRetryCancelOnFalse?: boolean | undefined\n\n /**\n * Whether errors thrown by `onRetry` should propagate.\n * When `true`, exceptions in `onRetry` terminate the retry loop.\n * When `false`, exceptions in `onRetry` are silently caught.\n *\n * @default false\n */\n onRetryRethrow?: boolean | undefined\n\n /**\n * Number of retry attempts (0 = no retries, only initial attempt).\n * The callback is executed `retries + 1` times total (initial + retries).\n *\n * @default 0\n * @example\n * // retries: 0 -> 1 total attempt (no retries)\n * // retries: 3 -> 4 total attempts (1 initial + 3 retries)\n */\n retries?: number | undefined\n\n /**\n * AbortSignal to support cancellation of retry operations.\n * When aborted, immediately stops retrying and returns `undefined`.\n *\n * @default process abort signal\n * @example\n * const controller = new AbortController()\n * pRetry(fn, { signal: controller.signal })\n * // Later: controller.abort() to cancel\n */\n signal?: AbortSignal | undefined\n}\n\n/**\n * Configuration options for iteration functions with concurrency control.\n *\n * Controls how array operations are parallelized and retried.\n */\nexport interface IterationOptions {\n /**\n * The number of concurrent executions performed at one time.\n * Higher values increase parallelism but may overwhelm resources.\n *\n * @default 1\n * @example\n * // Process 5 items at a time\n * await pEach(items, processItem, { concurrency: 5 })\n */\n concurrency?: number | undefined\n\n /**\n * Retry configuration as a number (retry count) or full options object.\n * Applied to each individual item's callback execution.\n *\n * @default 0 (no retries)\n * @example\n * // Simple: retry each item up to 3 times\n * await pEach(items, fetchItem, { retries: 3 })\n *\n * @example\n * // Advanced: custom backoff for each item\n * await pEach(items, fetchItem, {\n * retries: {\n * retries: 3,\n * baseDelayMs: 1000,\n * backoffFactor: 2\n * }\n * })\n */\n retries?: number | RetryOptions | undefined\n\n /**\n * AbortSignal to support cancellation of the entire iteration.\n * When aborted, stops processing remaining items.\n *\n * @default process abort signal\n */\n signal?: AbortSignal | undefined\n}\n\nlet _timers: typeof import('node:timers/promises') | undefined\n/**\n * Get the timers/promises module.\n * Uses lazy loading to avoid Webpack bundling issues.\n *\n * @private\n * @returns The Node.js timers/promises module\n */\n/*@__NO_SIDE_EFFECTS__*/\nfunction getTimers() {\n if (_timers === undefined) {\n // Use non-'node:' prefixed require to avoid Webpack errors.\n\n _timers = /*@__PURE__*/ require('node:timers/promises')\n }\n return _timers as typeof import('node:timers/promises')\n}\n\n/**\n * Normalize options for iteration functions.\n *\n * Converts various option formats into a consistent structure with defaults applied.\n * Handles number shorthand for concurrency and ensures minimum values.\n *\n * @param options - Concurrency as number, or full options object, or undefined\n * @returns Normalized options with concurrency, retries, and signal\n *\n * @example\n * // Number shorthand for concurrency\n * normalizeIterationOptions(5)\n * // => { concurrency: 5, retries: {...}, signal: AbortSignal }\n *\n * @example\n * // Full options\n * normalizeIterationOptions({ concurrency: 3, retries: 2 })\n * // => { concurrency: 3, retries: {...}, signal: AbortSignal }\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function normalizeIterationOptions(\n options?: number | IterationOptions | undefined,\n): { concurrency: number; retries: RetryOptions; signal: AbortSignal } {\n // Handle number as concurrency shorthand\n const opts = typeof options === 'number' ? { concurrency: options } : options\n\n const {\n // The number of concurrent executions performed at one time.\n concurrency = 1,\n // Retries as a number or options object.\n retries,\n // AbortSignal used to support cancellation.\n signal = abortSignal,\n } = { __proto__: null, ...opts } as IterationOptions\n\n // Ensure concurrency is at least 1\n const normalizedConcurrency = Math.max(1, concurrency)\n const retryOpts = resolveRetryOptions(retries)\n return {\n __proto__: null,\n concurrency: normalizedConcurrency,\n retries: normalizeRetryOptions({ signal, ...retryOpts }),\n signal,\n } as { concurrency: number; retries: RetryOptions; signal: AbortSignal }\n}\n\n/**\n * Normalize options for retry functionality.\n *\n * Converts various retry option formats into a complete configuration with all defaults.\n * Handles legacy property names (`factor`, `minTimeout`, `maxTimeout`) and merges them\n * with modern equivalents.\n *\n * @param options - Retry count as number, or full options object, or undefined\n * @returns Normalized retry options with all properties set\n *\n * @example\n * // Number shorthand\n * normalizeRetryOptions(3)\n * // => { retries: 3, baseDelayMs: 200, backoffFactor: 2, ... }\n *\n * @example\n * // Full options with defaults filled in\n * normalizeRetryOptions({ retries: 5, baseDelayMs: 500 })\n * // => { retries: 5, baseDelayMs: 500, backoffFactor: 2, jitter: true, ... }\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function normalizeRetryOptions(\n options?: number | RetryOptions | undefined,\n): RetryOptions {\n const resolved = resolveRetryOptions(options)\n const {\n // Arguments to pass to the callback function.\n args = [],\n // Multiplier for exponential backoff (e.g., 2 doubles delay each retry).\n backoffFactor = 2,\n // Initial delay before the first retry (in milliseconds).\n baseDelayMs = 200,\n // Whether to apply randomness to spread out retries.\n jitter = true,\n // Upper limit for any backoff delay (in milliseconds).\n maxDelayMs = 10_000,\n // Optional callback invoked on each retry attempt:\n // (attempt: number, error: unknown, delay: number) => void\n onRetry,\n // Whether onRetry can cancel retries by returning `false`.\n onRetryCancelOnFalse = false,\n // Whether onRetry will rethrow errors.\n onRetryRethrow = false,\n // Number of retry attempts (0 = no retries, only initial attempt).\n retries = 0,\n // AbortSignal used to support cancellation.\n signal = abortSignal,\n } = resolved\n return {\n args,\n backoffFactor,\n baseDelayMs,\n jitter,\n maxDelayMs,\n onRetry,\n onRetryCancelOnFalse,\n onRetryRethrow,\n retries,\n signal,\n } as RetryOptions\n}\n\n/**\n * Resolve retry options from various input formats.\n *\n * Converts shorthand and partial options into a base configuration that can be\n * further normalized. This is an internal helper for option processing.\n *\n * @param options - Retry count as number, or partial options object, or undefined\n * @returns Resolved retry options with defaults for basic properties\n *\n * @example\n * resolveRetryOptions(3)\n * // => { retries: 3, minTimeout: 200, maxTimeout: 10000, factor: 2 }\n *\n * @example\n * resolveRetryOptions({ retries: 5, maxTimeout: 5000 })\n * // => { retries: 5, minTimeout: 200, maxTimeout: 5000, factor: 2 }\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function resolveRetryOptions(\n options?: number | RetryOptions | undefined,\n): RetryOptions {\n const defaults = {\n __proto__: null,\n retries: 0,\n baseDelayMs: 200,\n maxDelayMs: 10_000,\n backoffFactor: 2,\n }\n\n if (typeof options === 'number') {\n return { ...defaults, retries: options }\n }\n\n return options ? { ...defaults, ...options } : defaults\n}\n\n/**\n * Execute an async function for each array element with concurrency control.\n *\n * Processes array items in parallel batches (chunks) with configurable concurrency.\n * Each item's callback can be retried independently on failure. Similar to\n * `Promise.all(array.map(fn))` but with controlled parallelism.\n *\n * @template T - The type of array elements\n * @param array - The array to iterate over\n * @param callbackFn - Async function to execute for each item\n * @param options - Concurrency as number, or full iteration options, or undefined\n * @returns Promise that resolves when all items are processed\n *\n * @example\n * // Process items serially (concurrency: 1)\n * await pEach(urls, async (url) => {\n * await fetch(url)\n * })\n *\n * @example\n * // Process 5 items at a time\n * await pEach(files, async (file) => {\n * await processFile(file)\n * }, 5)\n *\n * @example\n * // With retries and cancellation\n * const controller = new AbortController()\n * await pEach(tasks, async (task) => {\n * await executeTask(task)\n * }, {\n * concurrency: 3,\n * retries: 2,\n * signal: controller.signal\n * })\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pEach<T>(\n array: T[],\n callbackFn: (item: T) => Promise<unknown>,\n options?: number | IterationOptions | undefined,\n): Promise<void> {\n const iterOpts = normalizeIterationOptions(options)\n const { concurrency, retries, signal } = iterOpts\n\n // Process items with concurrency control.\n const chunks = arrayChunk(array, concurrency)\n for (const chunk of chunks) {\n if (signal?.aborted) {\n return\n }\n // Process each item in the chunk concurrently.\n // eslint-disable-next-line no-await-in-loop\n await Promise.all(\n chunk.map((item: T) =>\n pRetry((...args: unknown[]) => callbackFn(args[0] as T), {\n ...retries,\n args: [item],\n signal,\n }),\n ),\n )\n }\n}\n\n/**\n * Filter an array asynchronously with concurrency control.\n *\n * Tests each element with an async predicate function, processing items in parallel\n * batches. Returns a new array with only items that pass the test. Similar to\n * `array.filter()` but for async predicates with controlled concurrency.\n *\n * @template T - The type of array elements\n * @param array - The array to filter\n * @param callbackFn - Async predicate function returning true to keep item\n * @param options - Concurrency as number, or full iteration options, or undefined\n * @returns Promise resolving to filtered array\n *\n * @example\n * // Filter serially\n * const activeUsers = await pFilter(users, async (user) => {\n * return await isUserActive(user.id)\n * })\n *\n * @example\n * // Filter with concurrency\n * const validFiles = await pFilter(filePaths, async (path) => {\n * try {\n * await fs.access(path)\n * return true\n * } catch {\n * return false\n * }\n * }, 10)\n *\n * @example\n * // With retries for flaky checks\n * const reachable = await pFilter(endpoints, async (url) => {\n * const response = await fetch(url)\n * return response.ok\n * }, {\n * concurrency: 5,\n * retries: 2\n * })\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pFilter<T>(\n array: T[],\n callbackFn: (item: T) => Promise<boolean>,\n options?: number | IterationOptions | undefined,\n): Promise<T[]> {\n const iterOpts = normalizeIterationOptions(options)\n return (\n await pFilterChunk(\n arrayChunk(array, iterOpts.concurrency),\n callbackFn,\n iterOpts.retries,\n )\n ).flat()\n}\n\n/**\n * Process array in chunks with an async callback.\n *\n * Divides the array into fixed-size chunks and processes each chunk sequentially\n * with the callback. Useful for batch operations like bulk database inserts or\n * API calls with payload size limits.\n *\n * @template T - The type of array elements\n * @param array - The array to process in chunks\n * @param callbackFn - Async function to execute for each chunk\n * @param options - Chunk size and retry options\n * @returns Promise that resolves when all chunks are processed\n *\n * @example\n * // Insert records in batches of 100\n * await pEachChunk(records, async (chunk) => {\n * await db.batchInsert(chunk)\n * }, { chunkSize: 100 })\n *\n * @example\n * // Upload files in batches with retries\n * await pEachChunk(files, async (batch) => {\n * await uploadBatch(batch)\n * }, {\n * chunkSize: 50,\n * retries: 3,\n * baseDelayMs: 1000\n * })\n *\n * @example\n * // Process with cancellation support\n * const controller = new AbortController()\n * await pEachChunk(items, async (chunk) => {\n * await processChunk(chunk)\n * }, {\n * chunkSize: 25,\n * signal: controller.signal\n * })\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pEachChunk<T>(\n array: T[],\n callbackFn: (chunk: T[]) => Promise<unknown>,\n options?: (RetryOptions & { chunkSize?: number | undefined }) | undefined,\n): Promise<void> {\n const { chunkSize = 100, ...retryOpts } = options || {}\n const chunks = arrayChunk(array, chunkSize)\n const normalizedRetryOpts = normalizeRetryOptions(retryOpts)\n const { signal } = normalizedRetryOpts\n for (const chunk of chunks) {\n if (signal?.aborted) {\n return\n }\n // eslint-disable-next-line no-await-in-loop\n await pRetry((...args: unknown[]) => callbackFn(args[0] as T[]), {\n ...normalizedRetryOpts,\n args: [chunk],\n })\n }\n}\n\n/**\n * Filter chunked arrays with an async predicate.\n *\n * Internal helper for `pFilter`. Processes pre-chunked arrays, applying the\n * predicate to each element within each chunk with retry support.\n *\n * @template T - The type of array elements\n * @param chunks - Pre-chunked array (array of arrays)\n * @param callbackFn - Async predicate function\n * @param options - Retry count as number, or full retry options, or undefined\n * @returns Promise resolving to array of filtered chunks\n *\n * @example\n * const chunks = [[1, 2], [3, 4], [5, 6]]\n * const filtered = await pFilterChunk(chunks, async (n) => n % 2 === 0)\n * // => [[2], [4], [6]]\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pFilterChunk<T>(\n chunks: T[][],\n callbackFn: (value: T) => Promise<boolean>,\n options?: number | RetryOptions | undefined,\n): Promise<T[][]> {\n const retryOpts = normalizeRetryOptions(options)\n const { signal } = retryOpts\n const { length } = chunks\n const filteredChunks = Array(length)\n for (let i = 0; i < length; i += 1) {\n // Process each chunk, filtering based on the callback function.\n if (signal?.aborted) {\n filteredChunks[i] = []\n } else {\n const chunk = chunks[i] as T[]\n // eslint-disable-next-line no-await-in-loop\n const predicateResults = await Promise.all(\n chunk.map(value =>\n pRetry((...args: unknown[]) => callbackFn(args[0] as T), {\n ...retryOpts,\n args: [value],\n }),\n ),\n )\n filteredChunks[i] = chunk.filter((_v, i) => predicateResults[i])\n }\n }\n return filteredChunks\n}\n\n/**\n * Retry an async function with exponential backoff.\n *\n * Attempts to execute a function multiple times with increasing delays between attempts.\n * Implements exponential backoff with optional jitter to prevent thundering herd problems.\n * Supports custom retry logic via `onRetry` callback.\n *\n * The delay calculation follows: `min(baseDelayMs * (backoffFactor ** attempt), maxDelayMs)`\n * With jitter: adds random value between 0 and calculated delay.\n *\n * @template T - The return type of the callback function\n * @param callbackFn - Async function to retry\n * @param options - Retry count as number, or full retry options, or undefined\n * @returns Promise resolving to callback result, or `undefined` if aborted\n *\n * @throws {Error} The last error if all retry attempts fail\n *\n * @example\n * // Simple retry: 3 attempts with default backoff\n * const data = await pRetry(async () => {\n * return await fetchData()\n * }, 3)\n *\n * @example\n * // Custom backoff strategy\n * const result = await pRetry(async () => {\n * return await unreliableOperation()\n * }, {\n * retries: 5,\n * baseDelayMs: 1000, // Start at 1 second\n * backoffFactor: 2, // Double each time\n * maxDelayMs: 30000, // Cap at 30 seconds\n * jitter: true // Add randomness\n * })\n * // Delays: ~1s, ~2s, ~4s, ~8s, ~16s (each \u00B1 random jitter)\n *\n * @example\n * // With custom retry logic\n * const data = await pRetry(async () => {\n * return await apiCall()\n * }, {\n * retries: 3,\n * onRetry: (attempt, error, delay) => {\n * console.log(`Attempt ${attempt} failed: ${error}`)\n * console.log(`Waiting ${delay}ms before retry...`)\n *\n * // Cancel retries for client errors (4xx)\n * if (error.statusCode >= 400 && error.statusCode < 500) {\n * return false\n * }\n *\n * // Use longer delay for rate limit errors\n * if (error.statusCode === 429) {\n * return 60000 // Wait 1 minute\n * }\n * },\n * onRetryCancelOnFalse: true\n * })\n *\n * @example\n * // With cancellation support\n * const controller = new AbortController()\n * setTimeout(() => controller.abort(), 5000) // Cancel after 5s\n *\n * const result = await pRetry(async ({ signal }) => {\n * return await longRunningTask(signal)\n * }, {\n * retries: 10,\n * signal: controller.signal\n * })\n * // Returns undefined if aborted\n *\n * @example\n * // Pass arguments to callback\n * const result = await pRetry(\n * async (url, options) => {\n * return await fetch(url, options)\n * },\n * {\n * retries: 3,\n * args: ['https://api.example.com', { method: 'POST' }]\n * }\n * )\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pRetry<T>(\n callbackFn: (...args: unknown[]) => Promise<T>,\n options?: number | RetryOptions | undefined,\n): Promise<T | undefined> {\n const {\n args,\n backoffFactor,\n baseDelayMs,\n jitter,\n maxDelayMs,\n onRetry,\n onRetryCancelOnFalse,\n onRetryRethrow,\n retries,\n signal,\n } = normalizeRetryOptions(options)\n if (signal?.aborted) {\n return undefined\n }\n if (retries === 0) {\n return await callbackFn(...(args || []), { signal })\n }\n\n const timers = getTimers()\n\n let attempts = retries as number\n let delay = baseDelayMs as number\n let error: unknown = UNDEFINED_TOKEN\n\n while (attempts-- >= 0) {\n // Check abort before attempt.\n if (signal?.aborted) {\n return undefined\n }\n\n try {\n // eslint-disable-next-line no-await-in-loop\n return await callbackFn(...(args || []), { signal })\n } catch (e) {\n if (error === UNDEFINED_TOKEN) {\n error = e\n }\n if (attempts < 0) {\n break\n }\n let waitTime = delay\n if (jitter) {\n // Add randomness: Pick a value between 0 and `delay`.\n waitTime += Math.floor(Math.random() * delay)\n }\n // Clamp wait time to max delay.\n waitTime = Math.min(waitTime, maxDelayMs as number)\n if (typeof onRetry === 'function') {\n try {\n const result = onRetry((retries as number) - attempts, e, waitTime)\n if (result === false && onRetryCancelOnFalse) {\n break\n }\n // If onRetry returns a number, use it as the custom delay.\n if (typeof result === 'number' && result >= 0) {\n waitTime = Math.min(result, maxDelayMs as number)\n }\n } catch (e) {\n if (onRetryRethrow) {\n throw e\n }\n }\n }\n\n try {\n // eslint-disable-next-line no-await-in-loop\n await timers.setTimeout(waitTime, undefined, { signal })\n } catch {\n // setTimeout was aborted.\n return undefined\n }\n\n // Check abort again after delay.\n if (signal?.aborted) {\n return undefined\n }\n\n // Exponentially increase the delay for the next attempt, capping at maxDelayMs.\n delay = Math.min(delay * (backoffFactor as number), maxDelayMs as number)\n }\n }\n if (error !== UNDEFINED_TOKEN) {\n throw error\n }\n return undefined\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @fileoverview Promise utilities including chunked iteration and timers.\n * Provides async control flow helpers and promise-based timing functions.\n */\n\nimport { UNDEFINED_TOKEN } from '#constants/core'\nimport { getAbortSignal } from '#constants/process'\n\nimport { arrayChunk } from './arrays'\n\nconst abortSignal = getAbortSignal()\n\n/**\n * Configuration options for retry behavior with exponential backoff.\n *\n * Controls how failed operations are retried, including timing, backoff strategy,\n * and callback hooks for observing or modifying retry behavior.\n */\nexport interface RetryOptions {\n /**\n * Arguments to pass to the callback function on each attempt.\n *\n * @default []\n */\n args?: unknown[] | undefined\n\n /**\n * Multiplier for exponential backoff (e.g., 2 doubles delay each retry).\n * Each retry waits `baseDelayMs * (backoffFactor ** attemptNumber)`.\n *\n * @default 2\n * @example\n * // With backoffFactor: 2, baseDelayMs: 100\n * // Retry 1: 100ms\n * // Retry 2: 200ms\n * // Retry 3: 400ms\n */\n backoffFactor?: number | undefined\n\n /**\n * Initial delay before the first retry (in milliseconds).\n * This is the base value for exponential backoff calculations.\n *\n * @default 200\n */\n baseDelayMs?: number | undefined\n\n // REMOVED: Deprecated `factor` option\n // Migration: Use `backoffFactor` instead\n\n /**\n * Whether to apply randomness to spread out retries and avoid thundering herd.\n * When `true`, adds random delay between 0 and current delay value.\n *\n * @default true\n * @example\n * // With jitter: true, delay: 100ms\n * // Actual wait: 100ms + random(0-100ms) = 100-200ms\n */\n jitter?: boolean | undefined\n\n /**\n * Upper limit for any backoff delay (in milliseconds).\n * Prevents exponential backoff from growing unbounded.\n *\n * @default 10000\n */\n maxDelayMs?: number | undefined\n\n // REMOVED: Deprecated `maxTimeout` option\n // Migration: Use `maxDelayMs` instead\n\n // REMOVED: Deprecated `minTimeout` option\n // Migration: Use `baseDelayMs` instead\n\n /**\n * Callback invoked on each retry attempt.\n * Can observe errors, customize delays, or cancel retries.\n *\n * @param attempt - The current attempt number (1-based: 1, 2, 3, ...)\n * @param error - The error that triggered this retry\n * @param delay - The calculated delay in milliseconds before next retry\n * @returns `false` to cancel retries (if `onRetryCancelOnFalse` is `true`),\n * a number to override the delay, or `undefined` to use calculated delay\n *\n * @example\n * // Log each retry\n * onRetry: (attempt, error, delay) => {\n * console.log(`Retry ${attempt} after ${delay}ms: ${error}`)\n * }\n *\n * @example\n * // Cancel retries for specific errors\n * onRetry: (attempt, error) => {\n * if (error instanceof ValidationError) return false\n * }\n *\n * @example\n * // Use custom delay\n * onRetry: (attempt) => attempt * 1000 // 1s, 2s, 3s, ...\n */\n onRetry?:\n | ((\n attempt: number,\n error: unknown,\n delay: number,\n ) => boolean | number | undefined)\n | undefined\n\n /**\n * Whether `onRetry` can cancel retries by returning `false`.\n * When `true`, returning `false` from `onRetry` stops retry attempts.\n *\n * @default false\n */\n onRetryCancelOnFalse?: boolean | undefined\n\n /**\n * Whether errors thrown by `onRetry` should propagate.\n * When `true`, exceptions in `onRetry` terminate the retry loop.\n * When `false`, exceptions in `onRetry` are silently caught.\n *\n * @default false\n */\n onRetryRethrow?: boolean | undefined\n\n /**\n * Number of retry attempts (0 = no retries, only initial attempt).\n * The callback is executed `retries + 1` times total (initial + retries).\n *\n * @default 0\n * @example\n * // retries: 0 -> 1 total attempt (no retries)\n * // retries: 3 -> 4 total attempts (1 initial + 3 retries)\n */\n retries?: number | undefined\n\n /**\n * AbortSignal to support cancellation of retry operations.\n * When aborted, immediately stops retrying and returns `undefined`.\n *\n * @default process abort signal\n * @example\n * const controller = new AbortController()\n * pRetry(fn, { signal: controller.signal })\n * // Later: controller.abort() to cancel\n */\n signal?: AbortSignal | undefined\n}\n\n/**\n * Configuration options for iteration functions with concurrency control.\n *\n * Controls how array operations are parallelized and retried.\n */\nexport interface IterationOptions {\n /**\n * The number of concurrent executions performed at one time.\n * Higher values increase parallelism but may overwhelm resources.\n *\n * @default 1\n * @example\n * // Process 5 items at a time\n * await pEach(items, processItem, { concurrency: 5 })\n */\n concurrency?: number | undefined\n\n /**\n * Retry configuration as a number (retry count) or full options object.\n * Applied to each individual item's callback execution.\n *\n * @default 0 (no retries)\n * @example\n * // Simple: retry each item up to 3 times\n * await pEach(items, fetchItem, { retries: 3 })\n *\n * @example\n * // Advanced: custom backoff for each item\n * await pEach(items, fetchItem, {\n * retries: {\n * retries: 3,\n * baseDelayMs: 1000,\n * backoffFactor: 2\n * }\n * })\n */\n retries?: number | RetryOptions | undefined\n\n /**\n * AbortSignal to support cancellation of the entire iteration.\n * When aborted, stops processing remaining items.\n *\n * @default process abort signal\n */\n signal?: AbortSignal | undefined\n}\n\nlet _timers: typeof import('node:timers/promises') | undefined\n/**\n * Get the timers/promises module.\n * Uses lazy loading to avoid Webpack bundling issues.\n *\n * @private\n * @returns The Node.js timers/promises module\n */\n/*@__NO_SIDE_EFFECTS__*/\nfunction getTimers() {\n if (_timers === undefined) {\n // Use non-'node:' prefixed require to avoid Webpack errors.\n\n _timers = /*@__PURE__*/ require('node:timers/promises')\n }\n return _timers as typeof import('node:timers/promises')\n}\n\n/**\n * Normalize options for iteration functions.\n *\n * Converts various option formats into a consistent structure with defaults applied.\n * Handles number shorthand for concurrency and ensures minimum values.\n *\n * @param options - Concurrency as number, or full options object, or undefined\n * @returns Normalized options with concurrency, retries, and signal\n *\n * @example\n * // Number shorthand for concurrency\n * normalizeIterationOptions(5)\n * // => { concurrency: 5, retries: {...}, signal: AbortSignal }\n *\n * @example\n * // Full options\n * normalizeIterationOptions({ concurrency: 3, retries: 2 })\n * // => { concurrency: 3, retries: {...}, signal: AbortSignal }\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function normalizeIterationOptions(\n options?: number | IterationOptions | undefined,\n): { concurrency: number; retries: RetryOptions; signal: AbortSignal } {\n // Handle number as concurrency shorthand\n const opts = typeof options === 'number' ? { concurrency: options } : options\n\n const {\n // The number of concurrent executions performed at one time.\n concurrency = 1,\n // Retries as a number or options object.\n retries,\n // AbortSignal used to support cancellation.\n signal = abortSignal,\n } = { __proto__: null, ...opts } as IterationOptions\n\n // Ensure concurrency is at least 1\n const normalizedConcurrency = Math.max(1, concurrency)\n const retryOpts = resolveRetryOptions(retries)\n return {\n __proto__: null,\n concurrency: normalizedConcurrency,\n retries: normalizeRetryOptions({ signal, ...retryOpts }),\n signal,\n } as { concurrency: number; retries: RetryOptions; signal: AbortSignal }\n}\n\n/**\n * Normalize options for retry functionality.\n *\n * Converts various retry option formats into a complete configuration with all defaults.\n * Handles legacy property names (`factor`, `minTimeout`, `maxTimeout`) and merges them\n * with modern equivalents.\n *\n * @param options - Retry count as number, or full options object, or undefined\n * @returns Normalized retry options with all properties set\n *\n * @example\n * // Number shorthand\n * normalizeRetryOptions(3)\n * // => { retries: 3, baseDelayMs: 200, backoffFactor: 2, ... }\n *\n * @example\n * // Full options with defaults filled in\n * normalizeRetryOptions({ retries: 5, baseDelayMs: 500 })\n * // => { retries: 5, baseDelayMs: 500, backoffFactor: 2, jitter: true, ... }\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function normalizeRetryOptions(\n options?: number | RetryOptions | undefined,\n): RetryOptions {\n const resolved = resolveRetryOptions(options)\n const {\n // Arguments to pass to the callback function.\n args = [],\n // Multiplier for exponential backoff (e.g., 2 doubles delay each retry).\n backoffFactor = 2,\n // Initial delay before the first retry (in milliseconds).\n baseDelayMs = 200,\n // Whether to apply randomness to spread out retries.\n jitter = true,\n // Upper limit for any backoff delay (in milliseconds).\n maxDelayMs = 10_000,\n // Optional callback invoked on each retry attempt:\n // (attempt: number, error: unknown, delay: number) => void\n onRetry,\n // Whether onRetry can cancel retries by returning `false`.\n onRetryCancelOnFalse = false,\n // Whether onRetry will rethrow errors.\n onRetryRethrow = false,\n // Number of retry attempts (0 = no retries, only initial attempt).\n retries = 0,\n // AbortSignal used to support cancellation.\n signal = abortSignal,\n } = resolved\n return {\n args,\n backoffFactor,\n baseDelayMs,\n jitter,\n maxDelayMs,\n onRetry,\n onRetryCancelOnFalse,\n onRetryRethrow,\n retries,\n signal,\n } as RetryOptions\n}\n\n/**\n * Resolve retry options from various input formats.\n *\n * Converts shorthand and partial options into a base configuration that can be\n * further normalized. This is an internal helper for option processing.\n *\n * @param options - Retry count as number, or partial options object, or undefined\n * @returns Resolved retry options with defaults for basic properties\n *\n * @example\n * resolveRetryOptions(3)\n * // => { retries: 3, minTimeout: 200, maxTimeout: 10000, factor: 2 }\n *\n * @example\n * resolveRetryOptions({ retries: 5, maxTimeout: 5000 })\n * // => { retries: 5, minTimeout: 200, maxTimeout: 5000, factor: 2 }\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function resolveRetryOptions(\n options?: number | RetryOptions | undefined,\n): RetryOptions {\n const defaults = {\n __proto__: null,\n retries: 0,\n baseDelayMs: 200,\n maxDelayMs: 10_000,\n backoffFactor: 2,\n }\n\n if (typeof options === 'number') {\n return { ...defaults, retries: options }\n }\n\n return options ? { ...defaults, ...options } : defaults\n}\n\n/**\n * Execute an async function for each array element with concurrency control.\n *\n * Processes array items in parallel batches (chunks) with configurable concurrency.\n * Each item's callback can be retried independently on failure. Similar to\n * `Promise.all(array.map(fn))` but with controlled parallelism.\n *\n * @template T - The type of array elements\n * @param array - The array to iterate over\n * @param callbackFn - Async function to execute for each item\n * @param options - Concurrency as number, or full iteration options, or undefined\n * @returns Promise that resolves when all items are processed\n *\n * @example\n * // Process items serially (concurrency: 1)\n * await pEach(urls, async (url) => {\n * await fetch(url)\n * })\n *\n * @example\n * // Process 5 items at a time\n * await pEach(files, async (file) => {\n * await processFile(file)\n * }, 5)\n *\n * @example\n * // With retries and cancellation\n * const controller = new AbortController()\n * await pEach(tasks, async (task) => {\n * await executeTask(task)\n * }, {\n * concurrency: 3,\n * retries: 2,\n * signal: controller.signal\n * })\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pEach<T>(\n array: T[],\n callbackFn: (item: T) => Promise<unknown>,\n options?: number | IterationOptions | undefined,\n): Promise<void> {\n const iterOpts = normalizeIterationOptions(options)\n const { concurrency, retries, signal } = iterOpts\n\n // Process items with concurrency control.\n const chunks = arrayChunk(array, concurrency)\n for (const chunk of chunks) {\n if (signal?.aborted) {\n return\n }\n // Process each item in the chunk concurrently.\n // eslint-disable-next-line no-await-in-loop\n await Promise.allSettled(\n chunk.map((item: T) =>\n pRetry((...args: unknown[]) => callbackFn(args[0] as T), {\n ...retries,\n args: [item],\n signal,\n }),\n ),\n )\n }\n}\n\n/**\n * Filter an array asynchronously with concurrency control.\n *\n * Tests each element with an async predicate function, processing items in parallel\n * batches. Returns a new array with only items that pass the test. Similar to\n * `array.filter()` but for async predicates with controlled concurrency.\n *\n * @template T - The type of array elements\n * @param array - The array to filter\n * @param callbackFn - Async predicate function returning true to keep item\n * @param options - Concurrency as number, or full iteration options, or undefined\n * @returns Promise resolving to filtered array\n *\n * @example\n * // Filter serially\n * const activeUsers = await pFilter(users, async (user) => {\n * return await isUserActive(user.id)\n * })\n *\n * @example\n * // Filter with concurrency\n * const validFiles = await pFilter(filePaths, async (path) => {\n * try {\n * await fs.access(path)\n * return true\n * } catch {\n * return false\n * }\n * }, 10)\n *\n * @example\n * // With retries for flaky checks\n * const reachable = await pFilter(endpoints, async (url) => {\n * const response = await fetch(url)\n * return response.ok\n * }, {\n * concurrency: 5,\n * retries: 2\n * })\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pFilter<T>(\n array: T[],\n callbackFn: (item: T) => Promise<boolean>,\n options?: number | IterationOptions | undefined,\n): Promise<T[]> {\n const iterOpts = normalizeIterationOptions(options)\n return (\n await pFilterChunk(\n arrayChunk(array, iterOpts.concurrency),\n callbackFn,\n iterOpts.retries,\n )\n ).flat()\n}\n\n/**\n * Process array in chunks with an async callback.\n *\n * Divides the array into fixed-size chunks and processes each chunk sequentially\n * with the callback. Useful for batch operations like bulk database inserts or\n * API calls with payload size limits.\n *\n * @template T - The type of array elements\n * @param array - The array to process in chunks\n * @param callbackFn - Async function to execute for each chunk\n * @param options - Chunk size and retry options\n * @returns Promise that resolves when all chunks are processed\n *\n * @example\n * // Insert records in batches of 100\n * await pEachChunk(records, async (chunk) => {\n * await db.batchInsert(chunk)\n * }, { chunkSize: 100 })\n *\n * @example\n * // Upload files in batches with retries\n * await pEachChunk(files, async (batch) => {\n * await uploadBatch(batch)\n * }, {\n * chunkSize: 50,\n * retries: 3,\n * baseDelayMs: 1000\n * })\n *\n * @example\n * // Process with cancellation support\n * const controller = new AbortController()\n * await pEachChunk(items, async (chunk) => {\n * await processChunk(chunk)\n * }, {\n * chunkSize: 25,\n * signal: controller.signal\n * })\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pEachChunk<T>(\n array: T[],\n callbackFn: (chunk: T[]) => Promise<unknown>,\n options?: (RetryOptions & { chunkSize?: number | undefined }) | undefined,\n): Promise<void> {\n const { chunkSize = 100, ...retryOpts } = options || {}\n const chunks = arrayChunk(array, chunkSize)\n const normalizedRetryOpts = normalizeRetryOptions(retryOpts)\n const { signal } = normalizedRetryOpts\n for (const chunk of chunks) {\n if (signal?.aborted) {\n return\n }\n // eslint-disable-next-line no-await-in-loop\n await pRetry((...args: unknown[]) => callbackFn(args[0] as T[]), {\n ...normalizedRetryOpts,\n args: [chunk],\n })\n }\n}\n\n/**\n * Filter chunked arrays with an async predicate.\n *\n * Internal helper for `pFilter`. Processes pre-chunked arrays, applying the\n * predicate to each element within each chunk with retry support.\n *\n * @template T - The type of array elements\n * @param chunks - Pre-chunked array (array of arrays)\n * @param callbackFn - Async predicate function\n * @param options - Retry count as number, or full retry options, or undefined\n * @returns Promise resolving to array of filtered chunks\n *\n * @example\n * const chunks = [[1, 2], [3, 4], [5, 6]]\n * const filtered = await pFilterChunk(chunks, async (n) => n % 2 === 0)\n * // => [[2], [4], [6]]\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pFilterChunk<T>(\n chunks: T[][],\n callbackFn: (value: T) => Promise<boolean>,\n options?: number | RetryOptions | undefined,\n): Promise<T[][]> {\n const retryOpts = normalizeRetryOptions(options)\n const { signal } = retryOpts\n const { length } = chunks\n const filteredChunks = Array(length)\n for (let i = 0; i < length; i += 1) {\n // Process each chunk, filtering based on the callback function.\n if (signal?.aborted) {\n filteredChunks[i] = []\n } else {\n const chunk = chunks[i] as T[]\n // eslint-disable-next-line no-await-in-loop\n const settled = await Promise.allSettled(\n chunk.map(value =>\n pRetry((...args: unknown[]) => callbackFn(args[0] as T), {\n ...retryOpts,\n args: [value],\n }),\n ),\n )\n const predicateResults = settled.map(r =>\n r.status === 'fulfilled' ? r.value : false,\n )\n filteredChunks[i] = chunk.filter((_v, i) => predicateResults[i])\n }\n }\n return filteredChunks\n}\n\n/**\n * Retry an async function with exponential backoff.\n *\n * Attempts to execute a function multiple times with increasing delays between attempts.\n * Implements exponential backoff with optional jitter to prevent thundering herd problems.\n * Supports custom retry logic via `onRetry` callback.\n *\n * The delay calculation follows: `min(baseDelayMs * (backoffFactor ** attempt), maxDelayMs)`\n * With jitter: adds random value between 0 and calculated delay.\n *\n * @template T - The return type of the callback function\n * @param callbackFn - Async function to retry\n * @param options - Retry count as number, or full retry options, or undefined\n * @returns Promise resolving to callback result, or `undefined` if aborted\n *\n * @throws {Error} The last error if all retry attempts fail\n *\n * @example\n * // Simple retry: 3 attempts with default backoff\n * const data = await pRetry(async () => {\n * return await fetchData()\n * }, 3)\n *\n * @example\n * // Custom backoff strategy\n * const result = await pRetry(async () => {\n * return await unreliableOperation()\n * }, {\n * retries: 5,\n * baseDelayMs: 1000, // Start at 1 second\n * backoffFactor: 2, // Double each time\n * maxDelayMs: 30000, // Cap at 30 seconds\n * jitter: true // Add randomness\n * })\n * // Delays: ~1s, ~2s, ~4s, ~8s, ~16s (each \u00B1 random jitter)\n *\n * @example\n * // With custom retry logic\n * const data = await pRetry(async () => {\n * return await apiCall()\n * }, {\n * retries: 3,\n * onRetry: (attempt, error, delay) => {\n * console.log(`Attempt ${attempt} failed: ${error}`)\n * console.log(`Waiting ${delay}ms before retry...`)\n *\n * // Cancel retries for client errors (4xx)\n * if (error.statusCode >= 400 && error.statusCode < 500) {\n * return false\n * }\n *\n * // Use longer delay for rate limit errors\n * if (error.statusCode === 429) {\n * return 60000 // Wait 1 minute\n * }\n * },\n * onRetryCancelOnFalse: true\n * })\n *\n * @example\n * // With cancellation support\n * const controller = new AbortController()\n * setTimeout(() => controller.abort(), 5000) // Cancel after 5s\n *\n * const result = await pRetry(async ({ signal }) => {\n * return await longRunningTask(signal)\n * }, {\n * retries: 10,\n * signal: controller.signal\n * })\n * // Returns undefined if aborted\n *\n * @example\n * // Pass arguments to callback\n * const result = await pRetry(\n * async (url, options) => {\n * return await fetch(url, options)\n * },\n * {\n * retries: 3,\n * args: ['https://api.example.com', { method: 'POST' }]\n * }\n * )\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pRetry<T>(\n callbackFn: (...args: unknown[]) => Promise<T>,\n options?: number | RetryOptions | undefined,\n): Promise<T | undefined> {\n const {\n args,\n backoffFactor,\n baseDelayMs,\n jitter,\n maxDelayMs,\n onRetry,\n onRetryCancelOnFalse,\n onRetryRethrow,\n retries,\n signal,\n } = normalizeRetryOptions(options)\n if (signal?.aborted) {\n return undefined\n }\n if (retries === 0) {\n return await callbackFn(...(args || []), { signal })\n }\n\n const timers = getTimers()\n\n let attempts = retries as number\n let delay = baseDelayMs as number\n let error: unknown = UNDEFINED_TOKEN\n\n while (attempts-- >= 0) {\n // Check abort before attempt.\n if (signal?.aborted) {\n return undefined\n }\n\n try {\n // eslint-disable-next-line no-await-in-loop\n return await callbackFn(...(args || []), { signal })\n } catch (e) {\n if (error === UNDEFINED_TOKEN) {\n error = e\n }\n if (attempts < 0) {\n break\n }\n let waitTime = delay\n if (jitter) {\n // Add randomness: Pick a value between 0 and `delay`.\n waitTime += Math.floor(Math.random() * delay)\n }\n // Clamp wait time to max delay.\n waitTime = Math.min(waitTime, maxDelayMs as number)\n if (typeof onRetry === 'function') {\n try {\n const result = onRetry((retries as number) - attempts, e, waitTime)\n if (result === false && onRetryCancelOnFalse) {\n break\n }\n // If onRetry returns a number, use it as the custom delay.\n if (typeof result === 'number' && result >= 0) {\n waitTime = Math.min(result, maxDelayMs as number)\n }\n } catch (e) {\n if (onRetryRethrow) {\n throw e\n }\n }\n }\n\n try {\n // eslint-disable-next-line no-await-in-loop\n await timers.setTimeout(waitTime, undefined, { signal })\n } catch {\n // setTimeout was aborted.\n return undefined\n }\n\n // Check abort again after delay.\n if (signal?.aborted) {\n return undefined\n }\n\n // Exponentially increase the delay for the next attempt, capping at maxDelayMs.\n delay = Math.min(delay * (backoffFactor as number), maxDelayMs as number)\n }\n }\n if (error !== UNDEFINED_TOKEN) {\n throw error\n }\n return undefined\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["i", "e"]
7
7
  }
package/dist/spinner.js CHANGED
@@ -132,6 +132,7 @@ function Spinner(options) {
132
132
  const YoctoCtor = import_yocto_spinner.default;
133
133
  const tempInstance = YoctoCtor({});
134
134
  const YoctoSpinnerClass = tempInstance.constructor;
135
+ const logger = (0, import_logger.getDefaultLogger)();
135
136
  _Spinner = class SpinnerClass extends YoctoSpinnerClass {
136
137
  #baseText = "";
137
138
  #indentation = "";
@@ -260,7 +261,6 @@ function Spinner(options) {
260
261
  } else {
261
262
  super[methodName](normalized);
262
263
  }
263
- const logger = (0, import_logger.getDefaultLogger)();
264
264
  if (methodName === "stop") {
265
265
  if (wasSpinning && normalized) {
266
266
  logger[import_logger.lastWasBlankSymbol]((0, import_strings.isBlankString)(normalized));
@@ -319,7 +319,6 @@ function Spinner(options) {
319
319
  extras = args;
320
320
  text = "";
321
321
  }
322
- const logger = (0, import_logger.getDefaultLogger)();
323
322
  logger.error(`${import_logger.LOG_SYMBOLS[symbolType]} ${text}`, ...extras);
324
323
  return this;
325
324
  }
@@ -491,7 +490,6 @@ function Spinner(options) {
491
490
  * @returns This spinner for chaining
492
491
  */
493
492
  log(...args) {
494
- const logger = (0, import_logger.getDefaultLogger)();
495
493
  logger.log(...args);
496
494
  return this;
497
495
  }
@@ -606,7 +604,6 @@ function Spinner(options) {
606
604
  */
607
605
  step(text, ...extras) {
608
606
  if (typeof text === "string") {
609
- const logger = (0, import_logger.getDefaultLogger)();
610
607
  logger.error("");
611
608
  logger.error(text, ...extras);
612
609
  }
@@ -630,7 +627,6 @@ function Spinner(options) {
630
627
  */
631
628
  substep(text, ...extras) {
632
629
  if (typeof text === "string") {
633
- const logger = (0, import_logger.getDefaultLogger)();
634
630
  logger.error(` ${text}`, ...extras);
635
631
  }
636
632
  return this;
@@ -653,6 +649,9 @@ function Spinner(options) {
653
649
  * ```
654
650
  */
655
651
  stop(...args) {
652
+ if (!args.length || !args[0]) {
653
+ super.text = "";
654
+ }
656
655
  this.#baseText = "";
657
656
  this.#progress = void 0;
658
657
  if (this.#shimmer) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/spinner.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview CLI spinner utilities for long-running operations.\n * Provides animated progress indicators with CI environment detection.\n */\n\nimport type { Writable } from 'stream'\n\nimport { getCI } from '#env/ci'\nimport { isDebug } from './debug'\nimport { generateSocketSpinnerFrames } from './effects/pulse-frames'\nimport type {\n ShimmerColorGradient,\n ShimmerConfig,\n ShimmerDirection,\n ShimmerState,\n} from './effects/text-shimmer'\nimport { applyShimmer, COLOR_INHERIT, DIR_LTR } from './effects/text-shimmer'\nimport yoctoSpinner from './external/@socketregistry/yocto-spinner'\nimport {\n LOG_SYMBOLS,\n getDefaultLogger,\n incLogCallCountSymbol,\n lastWasBlankSymbol,\n} from './logger'\nimport { hasOwn } from './objects'\nimport { isBlankString, stringWidth } from './strings'\nimport { getTheme } from './themes/context'\nimport { THEMES } from './themes/themes'\nimport { resolveColor } from './themes/utils'\n\n/**\n * Named color values supported by the spinner.\n * Maps to standard terminal colors with bright variants.\n */\nexport type ColorName =\n | 'black'\n | 'blue'\n | 'blueBright'\n | 'cyan'\n | 'cyanBright'\n | 'gray'\n | 'green'\n | 'greenBright'\n | 'magenta'\n | 'magentaBright'\n | 'red'\n | 'redBright'\n | 'white'\n | 'whiteBright'\n | 'yellow'\n | 'yellowBright'\n\n/**\n * Special 'inherit' color value that uses the spinner's current color.\n * Used with shimmer effects to dynamically inherit the spinner color.\n */\nexport type ColorInherit = 'inherit'\n\n/**\n * RGB color tuple with values 0-255 for red, green, and blue channels.\n * @example [140, 82, 255] // Socket purple\n * @example [255, 0, 0] // Red\n */\nexport type ColorRgb = readonly [number, number, number]\n\n/**\n * Union of all supported color types: named colors or RGB tuples.\n */\nexport type ColorValue = ColorName | ColorRgb\n\n/**\n * Symbol types for status messages.\n * Maps to log symbols: success (\u2713), fail (\u2717), info (\u2139), warn (\u26A0).\n */\nexport type SymbolType = 'fail' | 'info' | 'success' | 'warn'\n\n// Map color names to RGB values.\nconst colorToRgb: Record<ColorName, ColorRgb> = {\n __proto__: null,\n black: [0, 0, 0],\n blue: [0, 0, 255],\n blueBright: [100, 149, 237],\n cyan: [0, 255, 255],\n cyanBright: [0, 255, 255],\n gray: [128, 128, 128],\n green: [0, 128, 0],\n greenBright: [0, 255, 0],\n magenta: [255, 0, 255],\n magentaBright: [255, 105, 180],\n red: [255, 0, 0],\n redBright: [255, 69, 0],\n white: [255, 255, 255],\n whiteBright: [255, 255, 255],\n yellow: [255, 255, 0],\n yellowBright: [255, 255, 153],\n} as Record<ColorName, ColorRgb>\n\n/**\n * Type guard to check if a color value is an RGB tuple.\n * @param value - Color value to check\n * @returns `true` if value is an RGB tuple, `false` if it's a color name\n */\nfunction isRgbTuple(value: ColorValue): value is ColorRgb {\n return Array.isArray(value)\n}\n\n/**\n * Convert a color value to RGB tuple format.\n * Named colors are looked up in the `colorToRgb` map, RGB tuples are returned as-is.\n * @param color - Color name or RGB tuple\n * @returns RGB tuple with values 0-255\n */\nexport function toRgb(color: ColorValue): ColorRgb {\n if (isRgbTuple(color)) {\n return color\n }\n return colorToRgb[color]\n}\n\n/**\n * Progress tracking information for display in spinner.\n * Used by `progress()` and `progressStep()` methods to show animated progress bars.\n */\nexport type ProgressInfo = {\n /** Current progress value */\n current: number\n /** Total/maximum progress value */\n total: number\n /** Optional unit label displayed after the progress count (e.g., 'files', 'items') */\n unit?: string | undefined\n}\n\n/**\n * Internal shimmer state with color configuration.\n * Extends `ShimmerState` with additional color property that can be inherited from spinner.\n */\nexport type ShimmerInfo = ShimmerState & {\n /** Color for shimmer effect - can inherit from spinner, use explicit color, or gradient */\n color: ColorInherit | ColorValue | ShimmerColorGradient\n}\n\n/**\n * Spinner instance for displaying animated loading indicators.\n * Provides methods for status updates, progress tracking, and text shimmer effects.\n *\n * KEY BEHAVIORS:\n * - Methods WITHOUT \"AndStop\" keep the spinner running (e.g., `success()`, `fail()`)\n * - Methods WITH \"AndStop\" auto-clear the spinner line (e.g., `successAndStop()`, `failAndStop()`)\n * - Status messages (done, success, fail, info, warn, step, substep) go to stderr\n * - Data messages (`log()`) go to stdout\n *\n * @example\n * ```ts\n * import { Spinner } from '@socketsecurity/lib/spinner'\n *\n * const spinner = Spinner({ text: 'Loading\u2026' })\n * spinner.start()\n *\n * // Show success while continuing to spin\n * spinner.success('Step 1 complete')\n *\n * // Stop the spinner with success message\n * spinner.successAndStop('All done!')\n * ```\n */\nexport type Spinner = {\n /** Current spinner color as RGB tuple */\n color: ColorRgb\n /** Current spinner animation style */\n spinner: SpinnerStyle\n\n /** Whether spinner is currently animating */\n get isSpinning(): boolean\n\n /** Get current shimmer state (enabled/disabled and configuration) */\n get shimmerState(): ShimmerInfo | undefined\n\n /** Clear the current line without stopping the spinner */\n clear(): Spinner\n\n /** Show debug message without stopping (only if debug mode enabled) */\n debug(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Show debug message and stop the spinner (only if debug mode enabled) */\n debugAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Alias for `fail()` - show error without stopping */\n error(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Alias for `failAndStop()` - show error and stop */\n errorAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Show failure (\u2717) without stopping the spinner */\n fail(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Show failure (\u2717) and stop the spinner, auto-clearing the line */\n failAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Get current spinner text (getter) or set new text (setter) */\n text(value: string): Spinner\n text(): string\n\n /** Increase indentation by specified spaces (default: 2) */\n indent(spaces?: number | undefined): Spinner\n /** Decrease indentation by specified spaces (default: 2) */\n dedent(spaces?: number | undefined): Spinner\n\n /** Show info (\u2139) message without stopping the spinner */\n info(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Show info (\u2139) message and stop the spinner, auto-clearing the line */\n infoAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Log to stdout without stopping the spinner */\n log(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Log and stop the spinner, auto-clearing the line */\n logAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Start spinning with optional text */\n start(text?: string | undefined): Spinner\n /** Stop spinning and clear internal state, auto-clearing the line */\n stop(text?: string | undefined): Spinner\n /** Stop and show final text without clearing the line */\n stopAndPersist(text?: string | undefined): Spinner\n\n /** Show main step message to stderr without stopping */\n step(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Show indented substep message to stderr without stopping */\n substep(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Show success (\u2713) without stopping the spinner */\n success(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Show success (\u2713) and stop the spinner, auto-clearing the line */\n successAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Alias for `success()` - show success without stopping */\n done(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Alias for `successAndStop()` - show success and stop */\n doneAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Update progress bar with current/total values and optional unit */\n progress(current: number, total: number, unit?: string | undefined): Spinner\n /** Increment progress by specified amount (default: 1) */\n progressStep(amount?: number): Spinner\n\n /** Enable shimmer effect (restores saved config or uses defaults) */\n enableShimmer(): Spinner\n /** Disable shimmer effect (preserves config for later re-enable) */\n disableShimmer(): Spinner\n /** Set complete shimmer configuration */\n setShimmer(config: ShimmerConfig): Spinner\n /** Update partial shimmer configuration */\n updateShimmer(config: Partial<ShimmerConfig>): Spinner\n\n /** Show warning (\u26A0) without stopping the spinner */\n warn(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Show warning (\u26A0) and stop the spinner, auto-clearing the line */\n warnAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n}\n\n/**\n * Configuration options for creating a spinner instance.\n */\nexport type SpinnerOptions = {\n /**\n * Spinner color as RGB tuple or color name.\n * @default [140, 82, 255] Socket purple\n */\n readonly color?: ColorValue | undefined\n /**\n * Shimmer effect configuration or direction string.\n * When enabled, text will have an animated shimmer effect.\n * @default undefined No shimmer effect\n */\n readonly shimmer?: ShimmerConfig | ShimmerDirection | undefined\n /**\n * Animation style with frames and timing.\n * @default 'socket' Custom Socket animation in CLI, minimal in CI\n */\n readonly spinner?: SpinnerStyle | undefined\n /**\n * Abort signal for cancelling the spinner.\n * @default getAbortSignal() from process constants\n */\n readonly signal?: AbortSignal | undefined\n /**\n * Output stream for spinner rendering.\n * @default process.stderr\n */\n readonly stream?: Writable | undefined\n /**\n * Initial text to display with the spinner.\n * @default undefined No initial text\n */\n readonly text?: string | undefined\n /**\n * Theme to use for spinner colors.\n * Accepts theme name ('socket', 'sunset', etc.) or Theme object.\n * @default Current theme from getTheme()\n */\n readonly theme?:\n | import('./themes/types').Theme\n | import('./themes/themes').ThemeName\n | undefined\n}\n\n/**\n * Animation style definition for spinner frames.\n * Defines the visual appearance and timing of the spinner animation.\n */\nexport type SpinnerStyle = {\n /** Array of animation frames (strings to display sequentially) */\n readonly frames: string[]\n /**\n * Milliseconds between frame changes.\n * @default 80 Standard frame rate\n */\n readonly interval?: number | undefined\n}\n\n/**\n * Minimal spinner style for CI environments.\n * Uses empty frame and max interval to effectively disable animation in CI.\n */\nexport const ciSpinner: SpinnerStyle = {\n frames: [''],\n interval: 2_147_483_647,\n}\n\n/**\n * Create a property descriptor for defining non-enumerable properties.\n * Used for adding aliased methods to the Spinner prototype.\n * @param value - Value for the property\n * @returns Property descriptor object\n * @private\n */\nfunction desc(value: unknown) {\n return {\n __proto__: null,\n configurable: true,\n value,\n writable: true,\n }\n}\n\n/**\n * Normalize text input by trimming leading whitespace.\n * Non-string values are converted to empty string.\n * @param value - Text to normalize\n * @returns Normalized string with leading whitespace removed\n * @private\n */\nfunction normalizeText(value: unknown) {\n return typeof value === 'string' ? value.trimStart() : ''\n}\n\n/**\n * Format progress information as a visual progress bar with percentage and count.\n * @param progress - Progress tracking information\n * @returns Formatted string with colored progress bar, percentage, and count\n * @private\n * @example \"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 35% (7/20 files)\"\n */\nfunction formatProgress(progress: ProgressInfo): string {\n const { current, total, unit } = progress\n const percentage = Math.round((current / total) * 100)\n const bar = renderProgressBar(percentage)\n const count = unit ? `${current}/${total} ${unit}` : `${current}/${total}`\n return `${bar} ${percentage}% (${count})`\n}\n\n/**\n * Render a progress bar using block characters (\u2588 for filled, \u2591 for empty).\n * @param percentage - Progress percentage (0-100)\n * @param width - Total width of progress bar in characters\n * @returns Colored progress bar string\n * @default width=20\n * @private\n */\nfunction renderProgressBar(percentage: number, width: number = 20): string {\n const filled = Math.round((percentage / 100) * width)\n const empty = width - filled\n const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(empty)\n // Use cyan color for the progress bar\n const colors =\n /*@__PURE__*/ require('./external/yoctocolors-cjs') as typeof import('yoctocolors-cjs')\n return colors.cyan(bar)\n}\n\nlet _cliSpinners: Record<string, SpinnerStyle> | undefined\n\n/**\n * Get available CLI spinner styles or a specific style by name.\n * Extends the standard cli-spinners collection with Socket custom spinners.\n *\n * Custom spinners:\n * - `socket` (default): Socket pulse animation with sparkles and lightning\n *\n * @param styleName - Optional name of specific spinner style to retrieve\n * @returns Specific spinner style if name provided, all styles if omitted, `undefined` if style not found\n * @see https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json\n *\n * @example\n * ```ts\n * // Get all available spinner styles\n * const allSpinners = getCliSpinners()\n *\n * // Get specific style\n * const socketStyle = getCliSpinners('socket')\n * const dotsStyle = getCliSpinners('dots')\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function getCliSpinners(\n styleName?: string | undefined,\n): SpinnerStyle | Record<string, SpinnerStyle> | undefined {\n if (_cliSpinners === undefined) {\n const YoctoCtor: any = yoctoSpinner as any\n // Get the YoctoSpinner class to access static properties.\n const tempInstance: any = YoctoCtor({})\n const YoctoSpinnerClass: any = tempInstance.constructor as any\n // Extend the standard cli-spinners collection with Socket custom spinners.\n _cliSpinners = {\n __proto__: null,\n ...YoctoSpinnerClass.spinners,\n socket: generateSocketSpinnerFrames(),\n }\n }\n if (typeof styleName === 'string' && _cliSpinners) {\n return hasOwn(_cliSpinners, styleName) ? _cliSpinners[styleName] : undefined\n }\n return _cliSpinners\n}\n\nlet _Spinner: {\n new (options?: SpinnerOptions | undefined): Spinner\n}\nlet _defaultSpinner: SpinnerStyle | undefined\n\n/**\n * Create a spinner instance for displaying loading indicators.\n * Provides an animated CLI spinner with status messages, progress tracking, and shimmer effects.\n *\n * AUTO-CLEAR BEHAVIOR:\n * - All *AndStop() methods AUTO-CLEAR the spinner line via yocto-spinner.stop()\n * Examples: `doneAndStop()`, `successAndStop()`, `failAndStop()`, etc.\n *\n * - Methods WITHOUT \"AndStop\" do NOT clear (spinner keeps spinning)\n * Examples: `done()`, `success()`, `fail()`, etc.\n *\n * STREAM USAGE:\n * - Spinner animation: stderr (yocto-spinner default)\n * - Status methods (done, success, fail, info, warn, step, substep): stderr\n * - Data methods (`log()`): stdout\n *\n * COMPARISON WITH LOGGER:\n * - `logger.done()` does NOT auto-clear (requires manual `logger.clearLine()`)\n * - `spinner.doneAndStop()` DOES auto-clear (built into yocto-spinner.stop())\n * - Pattern: `logger.clearLine().done()` vs `spinner.doneAndStop()`\n *\n * @param options - Configuration options for the spinner\n * @returns New spinner instance\n *\n * @example\n * ```ts\n * import { Spinner } from '@socketsecurity/lib/spinner'\n *\n * // Basic usage\n * const spinner = Spinner({ text: 'Loading data\u2026' })\n * spinner.start()\n * await fetchData()\n * spinner.successAndStop('Data loaded!')\n *\n * // With custom color\n * const spinner = Spinner({\n * text: 'Processing\u2026',\n * color: [255, 0, 0] // Red\n * })\n *\n * // With shimmer effect\n * const spinner = Spinner({\n * text: 'Building\u2026',\n * shimmer: { dir: 'ltr', speed: 0.5 }\n * })\n *\n * // Show progress\n * spinner.progress(5, 10, 'files')\n * spinner.progressStep() // Increment by 1\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function Spinner(options?: SpinnerOptions | undefined): Spinner {\n if (_Spinner === undefined) {\n const YoctoCtor = yoctoSpinner as any\n // Get the actual YoctoSpinner class from an instance\n const tempInstance = YoctoCtor({})\n const YoctoSpinnerClass = tempInstance.constructor\n\n /*@__PURE__*/\n _Spinner = class SpinnerClass extends (YoctoSpinnerClass as any) {\n declare isSpinning: boolean\n #baseText: string = ''\n #indentation: string = ''\n #progress?: ProgressInfo | undefined\n #shimmer?: ShimmerInfo | undefined\n #shimmerSavedConfig?: ShimmerInfo | undefined\n\n constructor(options?: SpinnerOptions | undefined) {\n const opts = { __proto__: null, ...options } as SpinnerOptions\n\n // Get theme from options or current theme\n let theme = getTheme()\n if (opts.theme) {\n // Resolve theme name or use Theme object directly\n if (typeof opts.theme === 'string') {\n theme = THEMES[opts.theme]\n } else {\n theme = opts.theme\n }\n }\n\n // Get default color from theme if not specified\n let defaultColor: ColorValue = theme.colors.primary\n if (theme.effects?.spinner?.color) {\n const resolved = resolveColor(\n theme.effects.spinner.color,\n theme.colors,\n )\n // resolveColor can return 'inherit' or gradients which aren't valid for spinner\n // Fall back to primary for these cases\n if (resolved === 'inherit' || Array.isArray(resolved[0])) {\n defaultColor = theme.colors.primary\n } else {\n defaultColor = resolved as ColorValue\n }\n }\n\n // Convert color option to RGB (default from theme).\n const spinnerColor = opts.color ?? defaultColor\n\n // Validate RGB tuple if provided.\n if (\n isRgbTuple(spinnerColor) &&\n (spinnerColor.length !== 3 ||\n !spinnerColor.every(\n n => typeof n === 'number' && n >= 0 && n <= 255,\n ))\n ) {\n throw new TypeError(\n 'RGB color must be an array of 3 numbers between 0 and 255',\n )\n }\n\n const spinnerColorRgb = toRgb(spinnerColor)\n\n // Parse shimmer config - can be object or direction string.\n let shimmerInfo: ShimmerInfo | undefined\n if (opts.shimmer) {\n let shimmerDir: ShimmerDirection\n let shimmerColor:\n | ColorInherit\n | ColorValue\n | ShimmerColorGradient\n | undefined\n // Default: 0.33 steps per frame (~150ms per step).\n let shimmerSpeed: number = 1 / 3\n\n if (typeof opts.shimmer === 'string') {\n shimmerDir = opts.shimmer\n } else {\n const shimmerConfig = {\n __proto__: null,\n ...opts.shimmer,\n } as ShimmerConfig\n shimmerDir = shimmerConfig.dir ?? DIR_LTR\n shimmerColor = shimmerConfig.color ?? COLOR_INHERIT\n shimmerSpeed = shimmerConfig.speed ?? 1 / 3\n }\n\n // Create shimmer info with initial animation state:\n // - COLOR_INHERIT means use spinner color dynamically\n // - ColorValue (name or RGB tuple) is an explicit override color\n // - undefined color defaults to COLOR_INHERIT\n // - speed controls steps per frame (lower = slower, e.g., 0.33 = ~150ms per step)\n shimmerInfo = {\n __proto__: null,\n color: shimmerColor === undefined ? COLOR_INHERIT : shimmerColor,\n currentDir: DIR_LTR,\n mode: shimmerDir,\n speed: shimmerSpeed,\n step: 0,\n } as ShimmerInfo\n }\n\n // eslint-disable-next-line constructor-super\n super({\n signal: require('#constants/process').getAbortSignal(),\n ...opts,\n // Pass RGB color directly to yocto-spinner (it now supports RGB).\n color: spinnerColorRgb,\n // onRenderFrame callback provides full control over frame + text layout.\n // Calculates spacing based on frame width to prevent text jumping.\n onRenderFrame: (\n frame: string,\n text: string,\n applyColor: (text: string) => string,\n ) => {\n const width = stringWidth(frame)\n // Narrow frames (width 1) get 2 spaces, wide frames (width 2) get 1 space.\n // Total width is consistent: 3 characters (frame + spacing) before text.\n const spacing = width === 1 ? ' ' : ' '\n return frame ? `${applyColor(frame)}${spacing}${text}` : text\n },\n // onFrameUpdate callback is called by yocto-spinner whenever a frame advances.\n // This ensures shimmer updates are perfectly synchronized with animation beats.\n onFrameUpdate: shimmerInfo\n ? () => {\n // Update parent's text without triggering render.\n // Parent's #skipRender flag prevents nested render calls.\n // Only update if we have base text to avoid blank frames.\n if (this.#baseText) {\n super.text = this.#buildDisplayText()\n }\n }\n : undefined,\n })\n\n this.#shimmer = shimmerInfo\n this.#shimmerSavedConfig = shimmerInfo\n }\n\n // Override color getter to ensure it's always RGB.\n get color(): ColorRgb {\n const value = super.color\n return isRgbTuple(value) ? value : toRgb(value)\n }\n\n // Override color setter to always convert to RGB before passing to yocto-spinner.\n set color(value: ColorValue | ColorRgb) {\n super.color = isRgbTuple(value) ? value : toRgb(value)\n }\n\n // Getter to expose current shimmer state.\n get shimmerState(): ShimmerInfo | undefined {\n if (!this.#shimmer) {\n return undefined\n }\n return {\n color: this.#shimmer.color,\n currentDir: this.#shimmer.currentDir,\n mode: this.#shimmer.mode,\n speed: this.#shimmer.speed,\n step: this.#shimmer.step,\n } as ShimmerInfo\n }\n\n /**\n * Apply a yocto-spinner method and update logger state.\n * Handles text normalization, extra arguments, and logger tracking.\n * @private\n */\n #apply(methodName: string, args: unknown[]) {\n let extras: unknown[]\n let text = args.at(0)\n if (typeof text === 'string') {\n extras = args.slice(1)\n } else {\n extras = args\n text = ''\n }\n const wasSpinning = this.isSpinning\n const normalized = normalizeText(text)\n if (methodName === 'stop' && !normalized) {\n super[methodName]()\n } else {\n super[methodName](normalized)\n }\n const logger = getDefaultLogger()\n if (methodName === 'stop') {\n if (wasSpinning && normalized) {\n logger[lastWasBlankSymbol](isBlankString(normalized))\n logger[incLogCallCountSymbol]()\n }\n } else {\n logger[lastWasBlankSymbol](false)\n logger[incLogCallCountSymbol]()\n }\n if (extras.length) {\n logger.log(...extras)\n logger[lastWasBlankSymbol](false)\n }\n return this\n }\n\n /**\n * Build the complete display text with progress, shimmer, and indentation.\n * Combines base text, progress bar, shimmer effects, and indentation.\n * @private\n */\n #buildDisplayText() {\n let displayText = this.#baseText\n\n if (this.#progress) {\n const progressText = formatProgress(this.#progress)\n displayText = displayText\n ? `${displayText} ${progressText}`\n : progressText\n }\n\n // Apply shimmer effect if enabled.\n if (displayText && this.#shimmer) {\n // If shimmer color is 'inherit', use current spinner color (getter ensures RGB).\n // Otherwise, check if it's a gradient (array of arrays) or single color.\n let shimmerColor: ColorRgb | ShimmerColorGradient\n if (this.#shimmer.color === COLOR_INHERIT) {\n shimmerColor = this.color\n } else if (Array.isArray(this.#shimmer.color[0])) {\n // It's a gradient - use as is.\n shimmerColor = this.#shimmer.color as ShimmerColorGradient\n } else {\n // It's a single color - convert to RGB.\n shimmerColor = toRgb(this.#shimmer.color as ColorValue)\n }\n\n displayText = applyShimmer(displayText, this.#shimmer, {\n color: shimmerColor,\n direction: this.#shimmer.mode,\n })\n }\n\n // Apply indentation\n if (this.#indentation && displayText) {\n displayText = this.#indentation + displayText\n }\n\n return displayText\n }\n\n /**\n * Show a status message without stopping the spinner.\n * Outputs the symbol and message to stderr, then continues spinning.\n */\n #showStatusAndKeepSpinning(symbolType: SymbolType, args: unknown[]) {\n let text = args.at(0)\n let extras: unknown[]\n if (typeof text === 'string') {\n extras = args.slice(1)\n } else {\n extras = args\n text = ''\n }\n\n // Note: Status messages always go to stderr.\n const logger = getDefaultLogger()\n logger.error(`${LOG_SYMBOLS[symbolType]} ${text}`, ...extras)\n return this\n }\n\n /**\n * Update the spinner's displayed text.\n * Rebuilds display text and triggers render.\n * @private\n */\n #updateSpinnerText() {\n // Call the parent class's text setter, which triggers render.\n super.text = this.#buildDisplayText()\n }\n\n /**\n * Show a debug message (\u2139) without stopping the spinner.\n * Only displays if debug mode is enabled via environment variable.\n * Outputs to stderr and continues spinning.\n *\n * @param text - Debug message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n debug(text?: string | undefined, ...extras: unknown[]) {\n if (isDebug()) {\n return this.#showStatusAndKeepSpinning('info', [text, ...extras])\n }\n return this\n }\n\n /**\n * Show a debug message (\u2139) and stop the spinner.\n * Only displays if debug mode is enabled via environment variable.\n * Auto-clears the spinner line before displaying the message.\n *\n * @param text - Debug message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n debugAndStop(text?: string | undefined, ...extras: unknown[]) {\n if (isDebug()) {\n return this.#apply('info', [text, ...extras])\n }\n return this\n }\n\n /**\n * Decrease indentation level by removing spaces from the left.\n * Pass 0 to reset indentation to zero completely.\n *\n * @param spaces - Number of spaces to remove\n * @returns This spinner for chaining\n * @default spaces=2\n *\n * @example\n * ```ts\n * spinner.dedent() // Remove 2 spaces\n * spinner.dedent(4) // Remove 4 spaces\n * spinner.dedent(0) // Reset to zero indentation\n * ```\n */\n dedent(spaces?: number | undefined) {\n // Pass 0 to reset indentation\n if (spaces === 0) {\n this.#indentation = ''\n } else {\n const amount = spaces ?? 2\n const newLength = Math.max(0, this.#indentation.length - amount)\n this.#indentation = this.#indentation.slice(0, newLength)\n }\n this.#updateSpinnerText()\n return this\n }\n\n /**\n * Show a done/success message (\u2713) without stopping the spinner.\n * Alias for `success()` with a shorter name.\n *\n * DESIGN DECISION: Unlike yocto-spinner, our `done()` does NOT stop the spinner.\n * Use `doneAndStop()` if you want to stop the spinner.\n *\n * @param text - Message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n done(text?: string | undefined, ...extras: unknown[]) {\n return this.#showStatusAndKeepSpinning('success', [text, ...extras])\n }\n\n /**\n * Show a done/success message (\u2713) and stop the spinner.\n * Auto-clears the spinner line before displaying the success message.\n *\n * @param text - Message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n doneAndStop(text?: string | undefined, ...extras: unknown[]) {\n return this.#apply('success', [text, ...extras])\n }\n\n /**\n * Show a failure message (\u2717) without stopping the spinner.\n * DESIGN DECISION: Unlike yocto-spinner, our `fail()` does NOT stop the spinner.\n * This allows displaying errors while continuing to spin.\n * Use `failAndStop()` if you want to stop the spinner.\n *\n * @param text - Error message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n fail(text?: string | undefined, ...extras: unknown[]) {\n return this.#showStatusAndKeepSpinning('fail', [text, ...extras])\n }\n\n /**\n * Show a failure message (\u2717) and stop the spinner.\n * Auto-clears the spinner line before displaying the error message.\n *\n * @param text - Error message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n failAndStop(text?: string | undefined, ...extras: unknown[]) {\n return this.#apply('error', [text, ...extras])\n }\n\n /**\n * Increase indentation level by adding spaces to the left.\n * Pass 0 to reset indentation to zero completely.\n *\n * @param spaces - Number of spaces to add\n * @returns This spinner for chaining\n * @default spaces=2\n *\n * @example\n * ```ts\n * spinner.indent() // Add 2 spaces\n * spinner.indent(4) // Add 4 spaces\n * spinner.indent(0) // Reset to zero indentation\n * ```\n */\n indent(spaces?: number | undefined) {\n // Pass 0 to reset indentation\n if (spaces === 0) {\n this.#indentation = ''\n } else {\n const amount = spaces ?? 2\n this.#indentation += ' '.repeat(amount)\n }\n this.#updateSpinnerText()\n return this\n }\n\n /**\n * Show an info message (\u2139) without stopping the spinner.\n * Outputs to stderr and continues spinning.\n *\n * @param text - Info message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n info(text?: string | undefined, ...extras: unknown[]) {\n return this.#showStatusAndKeepSpinning('info', [text, ...extras])\n }\n\n /**\n * Show an info message (\u2139) and stop the spinner.\n * Auto-clears the spinner line before displaying the message.\n *\n * @param text - Info message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n infoAndStop(text?: string | undefined, ...extras: unknown[]) {\n return this.#apply('info', [text, ...extras])\n }\n\n /**\n * Log a message to stdout without stopping the spinner.\n * Unlike other status methods, this outputs to stdout for data logging.\n *\n * @param args - Values to log to stdout\n * @returns This spinner for chaining\n */\n log(...args: unknown[]) {\n const logger = getDefaultLogger()\n logger.log(...args)\n return this\n }\n\n /**\n * Log a message to stdout and stop the spinner.\n * Auto-clears the spinner line before displaying the message.\n *\n * @param text - Message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n logAndStop(text?: string | undefined, ...extras: unknown[]) {\n return this.#apply('stop', [text, ...extras])\n }\n\n /**\n * Update progress information displayed with the spinner.\n * Shows a progress bar with percentage and optional unit label.\n *\n * @param current - Current progress value\n * @param total - Total/maximum progress value\n * @param unit - Optional unit label (e.g., 'files', 'items')\n * @returns This spinner for chaining\n *\n * @example\n * ```ts\n * spinner.progress(5, 10) // \"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 50% (5/10)\"\n * spinner.progress(7, 20, 'files') // \"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 35% (7/20 files)\"\n * ```\n */\n progress = (\n current: number,\n total: number,\n unit?: string | undefined,\n ) => {\n this.#progress = {\n __proto__: null,\n current,\n total,\n ...(unit ? { unit } : {}),\n } as ProgressInfo\n this.#updateSpinnerText()\n return this\n }\n\n /**\n * Increment progress by a specified amount.\n * Updates the progress bar displayed with the spinner.\n * Clamps the result between 0 and the total value.\n *\n * @param amount - Amount to increment by\n * @returns This spinner for chaining\n * @default amount=1\n *\n * @example\n * ```ts\n * spinner.progress(0, 10, 'files')\n * spinner.progressStep() // Progress: 1/10\n * spinner.progressStep(3) // Progress: 4/10\n * ```\n */\n progressStep(amount: number = 1) {\n if (this.#progress) {\n const newCurrent = this.#progress.current + amount\n this.#progress = {\n __proto__: null,\n current: Math.max(0, Math.min(newCurrent, this.#progress.total)),\n total: this.#progress.total,\n ...(this.#progress.unit ? { unit: this.#progress.unit } : {}),\n } as ProgressInfo\n this.#updateSpinnerText()\n }\n return this\n }\n\n /**\n * Start the spinner animation with optional text.\n * Begins displaying the animated spinner on stderr.\n *\n * @param text - Optional text to display with the spinner\n * @returns This spinner for chaining\n *\n * @example\n * ```ts\n * spinner.start('Loading\u2026')\n * // Later:\n * spinner.successAndStop('Done!')\n * ```\n */\n start(...args: unknown[]) {\n if (args.length) {\n const text = args.at(0)\n const normalized = normalizeText(text)\n // We clear this.text on start when `text` is falsy because yocto-spinner\n // will not clear it otherwise.\n if (!normalized) {\n this.#baseText = ''\n super.text = ''\n } else {\n this.#baseText = normalized\n }\n }\n\n this.#updateSpinnerText()\n // Don't pass text to yocto-spinner.start() since we already set it via #updateSpinnerText().\n // Passing args would cause duplicate message output.\n return this.#apply('start', [])\n }\n\n /**\n * Log a main step message to stderr without stopping the spinner.\n * Adds a blank line before the message for visual separation.\n * Aligns with `logger.step()` to use stderr for status messages.\n *\n * @param text - Step message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n *\n * @example\n * ```ts\n * spinner.step('Building application')\n * spinner.substep('Compiling TypeScript')\n * spinner.substep('Bundling assets')\n * ```\n */\n step(text?: string | undefined, ...extras: unknown[]) {\n if (typeof text === 'string') {\n const logger = getDefaultLogger()\n // Add blank line before step for visual separation.\n logger.error('')\n // Use error (stderr) to align with logger.step() default stream.\n logger.error(text, ...extras)\n }\n return this\n }\n\n /**\n * Log an indented substep message to stderr without stopping the spinner.\n * Adds 2-space indentation to the message.\n * Aligns with `logger.substep()` to use stderr for status messages.\n *\n * @param text - Substep message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n *\n * @example\n * ```ts\n * spinner.step('Building application')\n * spinner.substep('Compiling TypeScript')\n * spinner.substep('Bundling assets')\n * ```\n */\n substep(text?: string | undefined, ...extras: unknown[]) {\n if (typeof text === 'string') {\n // Add 2-space indent for substep.\n const logger = getDefaultLogger()\n // Use error (stderr) to align with logger.substep() default stream.\n logger.error(` ${text}`, ...extras)\n }\n return this\n }\n\n /**\n * Stop the spinner animation and clear internal state.\n * Auto-clears the spinner line via yocto-spinner.stop().\n * Resets progress, shimmer, and text state.\n *\n * @param text - Optional final text to display after stopping\n * @returns This spinner for chaining\n *\n * @example\n * ```ts\n * spinner.start('Processing\u2026')\n * // Do work\n * spinner.stop() // Just stop, no message\n * // or\n * spinner.stop('Finished processing')\n * ```\n */\n stop(...args: unknown[]) {\n // Clear internal state.\n this.#baseText = ''\n this.#progress = undefined\n // Reset shimmer animation state if shimmer is enabled.\n if (this.#shimmer) {\n this.#shimmer.currentDir = DIR_LTR\n this.#shimmer.step = 0\n }\n // Call parent stop first (clears screen, sets isSpinning = false).\n const result = this.#apply('stop', args)\n // Then clear text to avoid blank frame render.\n // This is safe now because isSpinning is false.\n super.text = ''\n return result\n }\n\n /**\n * Show a success message (\u2713) without stopping the spinner.\n * DESIGN DECISION: Unlike yocto-spinner, our `success()` does NOT stop the spinner.\n * This allows displaying success messages while continuing to spin for multi-step operations.\n * Use `successAndStop()` if you want to stop the spinner.\n *\n * @param text - Success message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n success(text?: string | undefined, ...extras: unknown[]) {\n return this.#showStatusAndKeepSpinning('success', [text, ...extras])\n }\n\n /**\n * Show a success message (\u2713) and stop the spinner.\n * Auto-clears the spinner line before displaying the success message.\n *\n * @param text - Success message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n successAndStop(text?: string | undefined, ...extras: unknown[]) {\n return this.#apply('success', [text, ...extras])\n }\n\n /**\n * Get or set the spinner text.\n * When called with no arguments, returns the current base text.\n * When called with text, updates the display and returns the spinner for chaining.\n *\n * @param value - Text to display (omit to get current text)\n * @returns Current text (getter) or this spinner (setter)\n *\n * @example\n * ```ts\n * // Setter\n * spinner.text('Loading data\u2026')\n * spinner.text('Processing\u2026')\n *\n * // Getter\n * const current = spinner.text()\n * console.log(current) // \"Processing\u2026\"\n * ```\n */\n text(): string\n text(value: string): Spinner\n text(value?: string): string | Spinner {\n // biome-ignore lint/complexity/noArguments: Function overload for getter/setter pattern.\n if (arguments.length === 0) {\n // Getter: return current base text\n return this.#baseText\n }\n // Setter: update base text and refresh display\n this.#baseText = value ?? ''\n this.#updateSpinnerText()\n return this as unknown as Spinner\n }\n\n /**\n * Show a warning message (\u26A0) without stopping the spinner.\n * Outputs to stderr and continues spinning.\n *\n * @param text - Warning message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n warn(text?: string | undefined, ...extras: unknown[]) {\n return this.#showStatusAndKeepSpinning('warn', [text, ...extras])\n }\n\n /**\n * Show a warning message (\u26A0) and stop the spinner.\n * Auto-clears the spinner line before displaying the warning message.\n *\n * @param text - Warning message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n warnAndStop(text?: string | undefined, ...extras: unknown[]) {\n return this.#apply('warning', [text, ...extras])\n }\n\n /**\n * Enable shimmer effect.\n * Restores saved config or uses defaults if no saved config exists.\n *\n * @returns This spinner for chaining\n *\n * @example\n * spinner.enableShimmer()\n */\n enableShimmer(): Spinner {\n if (this.#shimmerSavedConfig) {\n // Restore saved config.\n this.#shimmer = { ...this.#shimmerSavedConfig }\n } else {\n // Create default config.\n this.#shimmer = {\n color: COLOR_INHERIT,\n currentDir: DIR_LTR,\n mode: DIR_LTR,\n speed: 1 / 3,\n step: 0,\n } as ShimmerInfo\n this.#shimmerSavedConfig = this.#shimmer\n }\n\n this.#updateSpinnerText()\n return this as unknown as Spinner\n }\n\n /**\n * Disable shimmer effect.\n * Preserves config for later re-enable via enableShimmer().\n *\n * @returns This spinner for chaining\n *\n * @example\n * spinner.disableShimmer()\n */\n disableShimmer(): Spinner {\n // Disable shimmer but preserve config.\n this.#shimmer = undefined\n this.#updateSpinnerText()\n return this as unknown as Spinner\n }\n\n /**\n * Set complete shimmer configuration.\n * Replaces any existing shimmer config with the provided values.\n * Undefined properties will use default values.\n *\n * @param config - Complete shimmer configuration\n * @returns This spinner for chaining\n *\n * @example\n * spinner.setShimmer({\n * color: [255, 0, 0],\n * dir: 'rtl',\n * speed: 0.5\n * })\n */\n setShimmer(config: ShimmerConfig): Spinner {\n this.#shimmer = {\n color: config.color ?? COLOR_INHERIT,\n currentDir: DIR_LTR,\n mode: config.dir ?? DIR_LTR,\n speed: config.speed ?? 1 / 3,\n step: 0,\n } as ShimmerInfo\n this.#shimmerSavedConfig = this.#shimmer\n this.#updateSpinnerText()\n return this as unknown as Spinner\n }\n\n /**\n * Update partial shimmer configuration.\n * Merges with existing config, enabling shimmer if currently disabled.\n *\n * @param config - Partial shimmer configuration to merge\n * @returns This spinner for chaining\n *\n * @example\n * // Update just the speed\n * spinner.updateShimmer({ speed: 0.5 })\n *\n * // Update direction\n * spinner.updateShimmer({ dir: 'rtl' })\n *\n * // Update multiple properties\n * spinner.updateShimmer({ color: [255, 0, 0], speed: 0.8 })\n */\n updateShimmer(config: Partial<ShimmerConfig>): Spinner {\n const partialConfig = {\n __proto__: null,\n ...config,\n } as Partial<ShimmerConfig>\n\n if (this.#shimmer) {\n // Update existing shimmer.\n this.#shimmer = {\n ...this.#shimmer,\n ...(partialConfig.color !== undefined\n ? { color: partialConfig.color }\n : {}),\n ...(partialConfig.dir !== undefined\n ? { mode: partialConfig.dir }\n : {}),\n ...(partialConfig.speed !== undefined\n ? { speed: partialConfig.speed }\n : {}),\n } as ShimmerInfo\n this.#shimmerSavedConfig = this.#shimmer\n } else if (this.#shimmerSavedConfig) {\n // Restore and update.\n this.#shimmer = {\n ...this.#shimmerSavedConfig,\n ...(partialConfig.color !== undefined\n ? { color: partialConfig.color }\n : {}),\n ...(partialConfig.dir !== undefined\n ? { mode: partialConfig.dir }\n : {}),\n ...(partialConfig.speed !== undefined\n ? { speed: partialConfig.speed }\n : {}),\n } as ShimmerInfo\n this.#shimmerSavedConfig = this.#shimmer\n } else {\n // Create new with partial config.\n this.#shimmer = {\n color: partialConfig.color ?? COLOR_INHERIT,\n currentDir: DIR_LTR,\n mode: partialConfig.dir ?? DIR_LTR,\n speed: partialConfig.speed ?? 1 / 3,\n step: 0,\n } as ShimmerInfo\n this.#shimmerSavedConfig = this.#shimmer\n }\n\n this.#updateSpinnerText()\n return this as unknown as Spinner\n }\n } as unknown as {\n new (options?: SpinnerOptions | undefined): Spinner\n }\n // Add aliases.\n Object.defineProperties(_Spinner.prototype, {\n error: desc(_Spinner.prototype.fail),\n errorAndStop: desc(_Spinner.prototype.failAndStop),\n warning: desc(_Spinner.prototype.warn),\n warningAndStop: desc(_Spinner.prototype.warnAndStop),\n })\n _defaultSpinner = getCI()\n ? ciSpinner\n : (getCliSpinners('socket') as SpinnerStyle)\n }\n return new _Spinner({\n spinner: _defaultSpinner,\n ...options,\n })\n}\n\nlet _spinner: ReturnType<typeof Spinner> | undefined\n\n/**\n * Get the default spinner instance.\n * Lazily creates the spinner to avoid circular dependencies during module initialization.\n * Reuses the same instance across calls.\n *\n * @returns Shared default spinner instance\n *\n * @example\n * ```ts\n * import { getDefaultSpinner } from '@socketsecurity/lib/spinner'\n *\n * const spinner = getDefaultSpinner()\n * spinner.start('Loading\u2026')\n * ```\n */\nexport function getDefaultSpinner(): ReturnType<typeof Spinner> {\n if (_spinner === undefined) {\n _spinner = Spinner()\n }\n return _spinner\n}\n\n// REMOVED: Deprecated `spinner` export\n// Migration: Use getDefaultSpinner() instead\n// See: getDefaultSpinner() function above\n\n/**\n * Configuration options for `withSpinner()` helper.\n * @template T - Return type of the async operation\n */\nexport type WithSpinnerOptions<T> = {\n /** Message to display while the spinner is running */\n message: string\n /** Async function to execute while spinner is active */\n operation: () => Promise<T>\n /**\n * Optional spinner instance to use.\n * If not provided, operation runs without spinner.\n */\n spinner?: Spinner | undefined\n /**\n * Optional spinner options to apply during the operation.\n * These options will be pushed when the operation starts and popped when it completes.\n * Supports color and shimmer configuration.\n */\n withOptions?: Partial<Pick<SpinnerOptions, 'color' | 'shimmer'>> | undefined\n}\n\n/**\n * Execute an async operation with spinner lifecycle management.\n * Ensures `spinner.stop()` is always called via try/finally, even if the operation throws.\n * Provides safe cleanup and consistent spinner behavior.\n *\n * @template T - Return type of the operation\n * @param options - Configuration object\n * @param options.message - Message to display while spinner is running\n * @param options.operation - Async function to execute\n * @param options.spinner - Optional spinner instance (if not provided, no spinner is used)\n * @returns Result of the operation\n * @throws Re-throws any error from operation after stopping spinner\n *\n * @example\n * ```ts\n * import { Spinner, withSpinner } from '@socketsecurity/lib/spinner'\n *\n * const spinner = Spinner()\n *\n * // With spinner instance\n * const result = await withSpinner({\n * message: 'Processing\u2026',\n * operation: async () => {\n * return await processData()\n * },\n * spinner\n * })\n *\n * // Without spinner instance (no-op, just runs operation)\n * const result = await withSpinner({\n * message: 'Processing\u2026',\n * operation: async () => {\n * return await processData()\n * }\n * })\n * ```\n */\nexport async function withSpinner<T>(\n options: WithSpinnerOptions<T>,\n): Promise<T> {\n const { message, operation, spinner, withOptions } = {\n __proto__: null,\n ...options,\n } as WithSpinnerOptions<T>\n\n if (!spinner) {\n return await operation()\n }\n\n // Save current options if we're going to change them\n const savedColor =\n withOptions?.color !== undefined ? spinner.color : undefined\n const savedShimmerState =\n withOptions?.shimmer !== undefined ? spinner.shimmerState : undefined\n\n // Apply temporary options\n if (withOptions?.color !== undefined) {\n spinner.color = toRgb(withOptions.color)\n }\n if (withOptions?.shimmer !== undefined) {\n if (typeof withOptions.shimmer === 'string') {\n spinner.updateShimmer({ dir: withOptions.shimmer })\n } else {\n spinner.setShimmer(withOptions.shimmer)\n }\n }\n\n spinner.start(message)\n try {\n return await operation()\n } finally {\n spinner.stop()\n // Restore previous options\n if (savedColor !== undefined) {\n spinner.color = savedColor\n }\n if (withOptions?.shimmer !== undefined) {\n if (savedShimmerState) {\n spinner.setShimmer({\n color: savedShimmerState.color as any,\n dir: savedShimmerState.mode,\n speed: savedShimmerState.speed,\n })\n } else {\n spinner.disableShimmer()\n }\n }\n }\n}\n\n/**\n * Configuration options for `withSpinnerRestore()` helper.\n * @template T - Return type of the async operation\n */\nexport type WithSpinnerRestoreOptions<T> = {\n /** Async function to execute while spinner is stopped */\n operation: () => Promise<T>\n /** Optional spinner instance to restore after operation */\n spinner?: Spinner | undefined\n /** Whether spinner was spinning before the operation (used to conditionally restart) */\n wasSpinning: boolean\n}\n\n/**\n * Execute an async operation with conditional spinner restart.\n * Useful when you need to temporarily stop a spinner for an operation,\n * then restore it to its previous state (if it was spinning).\n *\n * @template T - Return type of the operation\n * @param options - Configuration object\n * @param options.operation - Async function to execute\n * @param options.spinner - Optional spinner instance to manage\n * @param options.wasSpinning - Whether spinner was spinning before the operation\n * @returns Result of the operation\n * @throws Re-throws any error from operation after restoring spinner state\n *\n * @example\n * ```ts\n * import { getDefaultSpinner, withSpinnerRestore } from '@socketsecurity/lib/spinner'\n *\n * const spinner = getDefaultSpinner()\n * const wasSpinning = spinner.isSpinning\n * spinner.stop()\n *\n * const result = await withSpinnerRestore({\n * operation: async () => {\n * // Do work without spinner\n * return await someOperation()\n * },\n * spinner,\n * wasSpinning\n * })\n * // Spinner is automatically restarted if wasSpinning was true\n * ```\n */\nexport async function withSpinnerRestore<T>(\n options: WithSpinnerRestoreOptions<T>,\n): Promise<T> {\n const { operation, spinner, wasSpinning } = {\n __proto__: null,\n ...options,\n } as WithSpinnerRestoreOptions<T>\n\n try {\n return await operation()\n } finally {\n if (spinner && wasSpinning) {\n spinner.start()\n }\n }\n}\n\n/**\n * Configuration options for `withSpinnerSync()` helper.\n * @template T - Return type of the sync operation\n */\nexport type WithSpinnerSyncOptions<T> = {\n /** Message to display while the spinner is running */\n message: string\n /** Synchronous function to execute while spinner is active */\n operation: () => T\n /**\n * Optional spinner instance to use.\n * If not provided, operation runs without spinner.\n */\n spinner?: Spinner | undefined\n /**\n * Optional spinner options to apply during the operation.\n * These options will be pushed when the operation starts and popped when it completes.\n * Supports color and shimmer configuration.\n */\n withOptions?: Partial<Pick<SpinnerOptions, 'color' | 'shimmer'>> | undefined\n}\n\n/**\n * Execute a synchronous operation with spinner lifecycle management.\n * Ensures `spinner.stop()` is always called via try/finally, even if the operation throws.\n * Provides safe cleanup and consistent spinner behavior for sync operations.\n *\n * @template T - Return type of the operation\n * @param options - Configuration object\n * @param options.message - Message to display while spinner is running\n * @param options.operation - Synchronous function to execute\n * @param options.spinner - Optional spinner instance (if not provided, no spinner is used)\n * @returns Result of the operation\n * @throws Re-throws any error from operation after stopping spinner\n *\n * @example\n * ```ts\n * import { Spinner, withSpinnerSync } from '@socketsecurity/lib/spinner'\n *\n * const spinner = Spinner()\n *\n * const result = withSpinnerSync({\n * message: 'Processing\u2026',\n * operation: () => {\n * return processDataSync()\n * },\n * spinner\n * })\n * ```\n */\nexport function withSpinnerSync<T>(options: WithSpinnerSyncOptions<T>): T {\n const { message, operation, spinner, withOptions } = {\n __proto__: null,\n ...options,\n } as WithSpinnerSyncOptions<T>\n\n if (!spinner) {\n return operation()\n }\n\n // Save current options if we're going to change them\n const savedColor =\n withOptions?.color !== undefined ? spinner.color : undefined\n const savedShimmerState =\n withOptions?.shimmer !== undefined ? spinner.shimmerState : undefined\n\n // Apply temporary options\n if (withOptions?.color !== undefined) {\n spinner.color = toRgb(withOptions.color)\n }\n if (withOptions?.shimmer !== undefined) {\n if (typeof withOptions.shimmer === 'string') {\n spinner.updateShimmer({ dir: withOptions.shimmer })\n } else {\n spinner.setShimmer(withOptions.shimmer)\n }\n }\n\n spinner.start(message)\n try {\n return operation()\n } finally {\n spinner.stop()\n // Restore previous options\n if (savedColor !== undefined) {\n spinner.color = savedColor\n }\n if (withOptions?.shimmer !== undefined) {\n if (savedShimmerState) {\n spinner.setShimmer({\n color: savedShimmerState.color as any,\n dir: savedShimmerState.mode,\n speed: savedShimmerState.speed,\n })\n } else {\n spinner.disableShimmer()\n }\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @fileoverview CLI spinner utilities for long-running operations.\n * Provides animated progress indicators with CI environment detection.\n */\n\nimport type { Writable } from 'stream'\n\nimport { getCI } from '#env/ci'\nimport { isDebug } from './debug'\nimport { generateSocketSpinnerFrames } from './effects/pulse-frames'\nimport type {\n ShimmerColorGradient,\n ShimmerConfig,\n ShimmerDirection,\n ShimmerState,\n} from './effects/text-shimmer'\nimport { applyShimmer, COLOR_INHERIT, DIR_LTR } from './effects/text-shimmer'\nimport yoctoSpinner from './external/@socketregistry/yocto-spinner'\nimport {\n LOG_SYMBOLS,\n getDefaultLogger,\n incLogCallCountSymbol,\n lastWasBlankSymbol,\n} from './logger'\nimport { hasOwn } from './objects'\nimport { isBlankString, stringWidth } from './strings'\nimport { getTheme } from './themes/context'\nimport { THEMES } from './themes/themes'\nimport { resolveColor } from './themes/utils'\n\n/**\n * Named color values supported by the spinner.\n * Maps to standard terminal colors with bright variants.\n */\nexport type ColorName =\n | 'black'\n | 'blue'\n | 'blueBright'\n | 'cyan'\n | 'cyanBright'\n | 'gray'\n | 'green'\n | 'greenBright'\n | 'magenta'\n | 'magentaBright'\n | 'red'\n | 'redBright'\n | 'white'\n | 'whiteBright'\n | 'yellow'\n | 'yellowBright'\n\n/**\n * Special 'inherit' color value that uses the spinner's current color.\n * Used with shimmer effects to dynamically inherit the spinner color.\n */\nexport type ColorInherit = 'inherit'\n\n/**\n * RGB color tuple with values 0-255 for red, green, and blue channels.\n * @example [140, 82, 255] // Socket purple\n * @example [255, 0, 0] // Red\n */\nexport type ColorRgb = readonly [number, number, number]\n\n/**\n * Union of all supported color types: named colors or RGB tuples.\n */\nexport type ColorValue = ColorName | ColorRgb\n\n/**\n * Symbol types for status messages.\n * Maps to log symbols: success (\u2713), fail (\u2717), info (\u2139), warn (\u26A0).\n */\nexport type SymbolType = 'fail' | 'info' | 'success' | 'warn'\n\n// Map color names to RGB values.\nconst colorToRgb: Record<ColorName, ColorRgb> = {\n __proto__: null,\n black: [0, 0, 0],\n blue: [0, 0, 255],\n blueBright: [100, 149, 237],\n cyan: [0, 255, 255],\n cyanBright: [0, 255, 255],\n gray: [128, 128, 128],\n green: [0, 128, 0],\n greenBright: [0, 255, 0],\n magenta: [255, 0, 255],\n magentaBright: [255, 105, 180],\n red: [255, 0, 0],\n redBright: [255, 69, 0],\n white: [255, 255, 255],\n whiteBright: [255, 255, 255],\n yellow: [255, 255, 0],\n yellowBright: [255, 255, 153],\n} as Record<ColorName, ColorRgb>\n\n/**\n * Type guard to check if a color value is an RGB tuple.\n * @param value - Color value to check\n * @returns `true` if value is an RGB tuple, `false` if it's a color name\n */\nfunction isRgbTuple(value: ColorValue): value is ColorRgb {\n return Array.isArray(value)\n}\n\n/**\n * Convert a color value to RGB tuple format.\n * Named colors are looked up in the `colorToRgb` map, RGB tuples are returned as-is.\n * @param color - Color name or RGB tuple\n * @returns RGB tuple with values 0-255\n */\nexport function toRgb(color: ColorValue): ColorRgb {\n if (isRgbTuple(color)) {\n return color\n }\n return colorToRgb[color]\n}\n\n/**\n * Progress tracking information for display in spinner.\n * Used by `progress()` and `progressStep()` methods to show animated progress bars.\n */\nexport type ProgressInfo = {\n /** Current progress value */\n current: number\n /** Total/maximum progress value */\n total: number\n /** Optional unit label displayed after the progress count (e.g., 'files', 'items') */\n unit?: string | undefined\n}\n\n/**\n * Internal shimmer state with color configuration.\n * Extends `ShimmerState` with additional color property that can be inherited from spinner.\n */\nexport type ShimmerInfo = ShimmerState & {\n /** Color for shimmer effect - can inherit from spinner, use explicit color, or gradient */\n color: ColorInherit | ColorValue | ShimmerColorGradient\n}\n\n/**\n * Spinner instance for displaying animated loading indicators.\n * Provides methods for status updates, progress tracking, and text shimmer effects.\n *\n * KEY BEHAVIORS:\n * - Methods WITHOUT \"AndStop\" keep the spinner running (e.g., `success()`, `fail()`)\n * - Methods WITH \"AndStop\" auto-clear the spinner line (e.g., `successAndStop()`, `failAndStop()`)\n * - Status messages (done, success, fail, info, warn, step, substep) go to stderr\n * - Data messages (`log()`) go to stdout\n *\n * @example\n * ```ts\n * import { Spinner } from '@socketsecurity/lib/spinner'\n *\n * const spinner = Spinner({ text: 'Loading\u2026' })\n * spinner.start()\n *\n * // Show success while continuing to spin\n * spinner.success('Step 1 complete')\n *\n * // Stop the spinner with success message\n * spinner.successAndStop('All done!')\n * ```\n */\nexport type Spinner = {\n /** Current spinner color as RGB tuple */\n color: ColorRgb\n /** Current spinner animation style */\n spinner: SpinnerStyle\n\n /** Whether spinner is currently animating */\n get isSpinning(): boolean\n\n /** Get current shimmer state (enabled/disabled and configuration) */\n get shimmerState(): ShimmerInfo | undefined\n\n /** Clear the current line without stopping the spinner */\n clear(): Spinner\n\n /** Show debug message without stopping (only if debug mode enabled) */\n debug(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Show debug message and stop the spinner (only if debug mode enabled) */\n debugAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Alias for `fail()` - show error without stopping */\n error(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Alias for `failAndStop()` - show error and stop */\n errorAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Show failure (\u2717) without stopping the spinner */\n fail(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Show failure (\u2717) and stop the spinner, auto-clearing the line */\n failAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Get current spinner text (getter) or set new text (setter) */\n text(value: string): Spinner\n text(): string\n\n /** Increase indentation by specified spaces (default: 2) */\n indent(spaces?: number | undefined): Spinner\n /** Decrease indentation by specified spaces (default: 2) */\n dedent(spaces?: number | undefined): Spinner\n\n /** Show info (\u2139) message without stopping the spinner */\n info(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Show info (\u2139) message and stop the spinner, auto-clearing the line */\n infoAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Log to stdout without stopping the spinner */\n log(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Log and stop the spinner, auto-clearing the line */\n logAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Start spinning with optional text */\n start(text?: string | undefined): Spinner\n /** Stop spinning and clear internal state, auto-clearing the line */\n stop(text?: string | undefined): Spinner\n /** Stop and show final text without clearing the line */\n stopAndPersist(text?: string | undefined): Spinner\n\n /** Show main step message to stderr without stopping */\n step(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Show indented substep message to stderr without stopping */\n substep(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Show success (\u2713) without stopping the spinner */\n success(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Show success (\u2713) and stop the spinner, auto-clearing the line */\n successAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Alias for `success()` - show success without stopping */\n done(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Alias for `successAndStop()` - show success and stop */\n doneAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n\n /** Update progress bar with current/total values and optional unit */\n progress(current: number, total: number, unit?: string | undefined): Spinner\n /** Increment progress by specified amount (default: 1) */\n progressStep(amount?: number): Spinner\n\n /** Enable shimmer effect (restores saved config or uses defaults) */\n enableShimmer(): Spinner\n /** Disable shimmer effect (preserves config for later re-enable) */\n disableShimmer(): Spinner\n /** Set complete shimmer configuration */\n setShimmer(config: ShimmerConfig): Spinner\n /** Update partial shimmer configuration */\n updateShimmer(config: Partial<ShimmerConfig>): Spinner\n\n /** Show warning (\u26A0) without stopping the spinner */\n warn(text?: string | undefined, ...extras: unknown[]): Spinner\n /** Show warning (\u26A0) and stop the spinner, auto-clearing the line */\n warnAndStop(text?: string | undefined, ...extras: unknown[]): Spinner\n}\n\n/**\n * Configuration options for creating a spinner instance.\n */\nexport type SpinnerOptions = {\n /**\n * Spinner color as RGB tuple or color name.\n * @default [140, 82, 255] Socket purple\n */\n readonly color?: ColorValue | undefined\n /**\n * Shimmer effect configuration or direction string.\n * When enabled, text will have an animated shimmer effect.\n * @default undefined No shimmer effect\n */\n readonly shimmer?: ShimmerConfig | ShimmerDirection | undefined\n /**\n * Animation style with frames and timing.\n * @default 'socket' Custom Socket animation in CLI, minimal in CI\n */\n readonly spinner?: SpinnerStyle | undefined\n /**\n * Abort signal for cancelling the spinner.\n * @default getAbortSignal() from process constants\n */\n readonly signal?: AbortSignal | undefined\n /**\n * Output stream for spinner rendering.\n * @default process.stderr\n */\n readonly stream?: Writable | undefined\n /**\n * Initial text to display with the spinner.\n * @default undefined No initial text\n */\n readonly text?: string | undefined\n /**\n * Theme to use for spinner colors.\n * Accepts theme name ('socket', 'sunset', etc.) or Theme object.\n * @default Current theme from getTheme()\n */\n readonly theme?:\n | import('./themes/types').Theme\n | import('./themes/themes').ThemeName\n | undefined\n}\n\n/**\n * Animation style definition for spinner frames.\n * Defines the visual appearance and timing of the spinner animation.\n */\nexport type SpinnerStyle = {\n /** Array of animation frames (strings to display sequentially) */\n readonly frames: string[]\n /**\n * Milliseconds between frame changes.\n * @default 80 Standard frame rate\n */\n readonly interval?: number | undefined\n}\n\n/**\n * Minimal spinner style for CI environments.\n * Uses empty frame and max interval to effectively disable animation in CI.\n */\nexport const ciSpinner: SpinnerStyle = {\n frames: [''],\n interval: 2_147_483_647,\n}\n\n/**\n * Create a property descriptor for defining non-enumerable properties.\n * Used for adding aliased methods to the Spinner prototype.\n * @param value - Value for the property\n * @returns Property descriptor object\n * @private\n */\nfunction desc(value: unknown) {\n return {\n __proto__: null,\n configurable: true,\n value,\n writable: true,\n }\n}\n\n/**\n * Normalize text input by trimming leading whitespace.\n * Non-string values are converted to empty string.\n * @param value - Text to normalize\n * @returns Normalized string with leading whitespace removed\n * @private\n */\nfunction normalizeText(value: unknown) {\n return typeof value === 'string' ? value.trimStart() : ''\n}\n\n/**\n * Format progress information as a visual progress bar with percentage and count.\n * @param progress - Progress tracking information\n * @returns Formatted string with colored progress bar, percentage, and count\n * @private\n * @example \"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 35% (7/20 files)\"\n */\nfunction formatProgress(progress: ProgressInfo): string {\n const { current, total, unit } = progress\n const percentage = Math.round((current / total) * 100)\n const bar = renderProgressBar(percentage)\n const count = unit ? `${current}/${total} ${unit}` : `${current}/${total}`\n return `${bar} ${percentage}% (${count})`\n}\n\n/**\n * Render a progress bar using block characters (\u2588 for filled, \u2591 for empty).\n * @param percentage - Progress percentage (0-100)\n * @param width - Total width of progress bar in characters\n * @returns Colored progress bar string\n * @default width=20\n * @private\n */\nfunction renderProgressBar(percentage: number, width: number = 20): string {\n const filled = Math.round((percentage / 100) * width)\n const empty = width - filled\n const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(empty)\n // Use cyan color for the progress bar\n const colors =\n /*@__PURE__*/ require('./external/yoctocolors-cjs') as typeof import('yoctocolors-cjs')\n return colors.cyan(bar)\n}\n\nlet _cliSpinners: Record<string, SpinnerStyle> | undefined\n\n/**\n * Get available CLI spinner styles or a specific style by name.\n * Extends the standard cli-spinners collection with Socket custom spinners.\n *\n * Custom spinners:\n * - `socket` (default): Socket pulse animation with sparkles and lightning\n *\n * @param styleName - Optional name of specific spinner style to retrieve\n * @returns Specific spinner style if name provided, all styles if omitted, `undefined` if style not found\n * @see https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json\n *\n * @example\n * ```ts\n * // Get all available spinner styles\n * const allSpinners = getCliSpinners()\n *\n * // Get specific style\n * const socketStyle = getCliSpinners('socket')\n * const dotsStyle = getCliSpinners('dots')\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function getCliSpinners(\n styleName?: string | undefined,\n): SpinnerStyle | Record<string, SpinnerStyle> | undefined {\n if (_cliSpinners === undefined) {\n const YoctoCtor: any = yoctoSpinner as any\n // Get the YoctoSpinner class to access static properties.\n const tempInstance: any = YoctoCtor({})\n const YoctoSpinnerClass: any = tempInstance.constructor as any\n // Extend the standard cli-spinners collection with Socket custom spinners.\n _cliSpinners = {\n __proto__: null,\n ...YoctoSpinnerClass.spinners,\n socket: generateSocketSpinnerFrames(),\n }\n }\n if (typeof styleName === 'string' && _cliSpinners) {\n return hasOwn(_cliSpinners, styleName) ? _cliSpinners[styleName] : undefined\n }\n return _cliSpinners\n}\n\nlet _Spinner: {\n new (options?: SpinnerOptions | undefined): Spinner\n}\nlet _defaultSpinner: SpinnerStyle | undefined\n\n/**\n * Create a spinner instance for displaying loading indicators.\n * Provides an animated CLI spinner with status messages, progress tracking, and shimmer effects.\n *\n * AUTO-CLEAR BEHAVIOR:\n * - All *AndStop() methods AUTO-CLEAR the spinner line via yocto-spinner.stop()\n * Examples: `doneAndStop()`, `successAndStop()`, `failAndStop()`, etc.\n *\n * - Methods WITHOUT \"AndStop\" do NOT clear (spinner keeps spinning)\n * Examples: `done()`, `success()`, `fail()`, etc.\n *\n * STREAM USAGE:\n * - Spinner animation: stderr (yocto-spinner default)\n * - Status methods (done, success, fail, info, warn, step, substep): stderr\n * - Data methods (`log()`): stdout\n *\n * COMPARISON WITH LOGGER:\n * - `logger.done()` does NOT auto-clear (requires manual `logger.clearLine()`)\n * - `spinner.doneAndStop()` DOES auto-clear (built into yocto-spinner.stop())\n * - Pattern: `logger.clearLine().done()` vs `spinner.doneAndStop()`\n *\n * @param options - Configuration options for the spinner\n * @returns New spinner instance\n *\n * @example\n * ```ts\n * import { Spinner } from '@socketsecurity/lib/spinner'\n *\n * // Basic usage\n * const spinner = Spinner({ text: 'Loading data\u2026' })\n * spinner.start()\n * await fetchData()\n * spinner.successAndStop('Data loaded!')\n *\n * // With custom color\n * const spinner = Spinner({\n * text: 'Processing\u2026',\n * color: [255, 0, 0] // Red\n * })\n *\n * // With shimmer effect\n * const spinner = Spinner({\n * text: 'Building\u2026',\n * shimmer: { dir: 'ltr', speed: 0.5 }\n * })\n *\n * // Show progress\n * spinner.progress(5, 10, 'files')\n * spinner.progressStep() // Increment by 1\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function Spinner(options?: SpinnerOptions | undefined): Spinner {\n if (_Spinner === undefined) {\n const YoctoCtor = yoctoSpinner as any\n // Get the actual YoctoSpinner class from an instance\n const tempInstance = YoctoCtor({})\n const YoctoSpinnerClass = tempInstance.constructor\n const logger = getDefaultLogger()\n\n /*@__PURE__*/\n _Spinner = class SpinnerClass extends (YoctoSpinnerClass as any) {\n declare isSpinning: boolean\n #baseText: string = ''\n #indentation: string = ''\n #progress?: ProgressInfo | undefined\n #shimmer?: ShimmerInfo | undefined\n #shimmerSavedConfig?: ShimmerInfo | undefined\n\n constructor(options?: SpinnerOptions | undefined) {\n const opts = { __proto__: null, ...options } as SpinnerOptions\n\n // Get theme from options or current theme\n let theme = getTheme()\n if (opts.theme) {\n // Resolve theme name or use Theme object directly\n if (typeof opts.theme === 'string') {\n theme = THEMES[opts.theme]\n } else {\n theme = opts.theme\n }\n }\n\n // Get default color from theme if not specified\n let defaultColor: ColorValue = theme.colors.primary\n if (theme.effects?.spinner?.color) {\n const resolved = resolveColor(\n theme.effects.spinner.color,\n theme.colors,\n )\n // resolveColor can return 'inherit' or gradients which aren't valid for spinner\n // Fall back to primary for these cases\n if (resolved === 'inherit' || Array.isArray(resolved[0])) {\n defaultColor = theme.colors.primary\n } else {\n defaultColor = resolved as ColorValue\n }\n }\n\n // Convert color option to RGB (default from theme).\n const spinnerColor = opts.color ?? defaultColor\n\n // Validate RGB tuple if provided.\n if (\n isRgbTuple(spinnerColor) &&\n (spinnerColor.length !== 3 ||\n !spinnerColor.every(\n n => typeof n === 'number' && n >= 0 && n <= 255,\n ))\n ) {\n throw new TypeError(\n 'RGB color must be an array of 3 numbers between 0 and 255',\n )\n }\n\n const spinnerColorRgb = toRgb(spinnerColor)\n\n // Parse shimmer config - can be object or direction string.\n let shimmerInfo: ShimmerInfo | undefined\n if (opts.shimmer) {\n let shimmerDir: ShimmerDirection\n let shimmerColor:\n | ColorInherit\n | ColorValue\n | ShimmerColorGradient\n | undefined\n // Default: 0.33 steps per frame (~150ms per step).\n let shimmerSpeed: number = 1 / 3\n\n if (typeof opts.shimmer === 'string') {\n shimmerDir = opts.shimmer\n } else {\n const shimmerConfig = {\n __proto__: null,\n ...opts.shimmer,\n } as ShimmerConfig\n shimmerDir = shimmerConfig.dir ?? DIR_LTR\n shimmerColor = shimmerConfig.color ?? COLOR_INHERIT\n shimmerSpeed = shimmerConfig.speed ?? 1 / 3\n }\n\n // Create shimmer info with initial animation state:\n // - COLOR_INHERIT means use spinner color dynamically\n // - ColorValue (name or RGB tuple) is an explicit override color\n // - undefined color defaults to COLOR_INHERIT\n // - speed controls steps per frame (lower = slower, e.g., 0.33 = ~150ms per step)\n shimmerInfo = {\n __proto__: null,\n color: shimmerColor === undefined ? COLOR_INHERIT : shimmerColor,\n currentDir: DIR_LTR,\n mode: shimmerDir,\n speed: shimmerSpeed,\n step: 0,\n } as ShimmerInfo\n }\n\n // eslint-disable-next-line constructor-super\n super({\n signal: require('#constants/process').getAbortSignal(),\n ...opts,\n // Pass RGB color directly to yocto-spinner (it now supports RGB).\n color: spinnerColorRgb,\n // onRenderFrame callback provides full control over frame + text layout.\n // Calculates spacing based on frame width to prevent text jumping.\n onRenderFrame: (\n frame: string,\n text: string,\n applyColor: (text: string) => string,\n ) => {\n const width = stringWidth(frame)\n // Narrow frames (width 1) get 2 spaces, wide frames (width 2) get 1 space.\n // Total width is consistent: 3 characters (frame + spacing) before text.\n const spacing = width === 1 ? ' ' : ' '\n return frame ? `${applyColor(frame)}${spacing}${text}` : text\n },\n // onFrameUpdate callback is called by yocto-spinner whenever a frame advances.\n // This ensures shimmer updates are perfectly synchronized with animation beats.\n onFrameUpdate: shimmerInfo\n ? () => {\n // Update parent's text without triggering render.\n // Parent's #skipRender flag prevents nested render calls.\n // Only update if we have base text to avoid blank frames.\n if (this.#baseText) {\n super.text = this.#buildDisplayText()\n }\n }\n : undefined,\n })\n\n this.#shimmer = shimmerInfo\n this.#shimmerSavedConfig = shimmerInfo\n }\n\n // Override color getter to ensure it's always RGB.\n get color(): ColorRgb {\n const value = super.color\n return isRgbTuple(value) ? value : toRgb(value)\n }\n\n // Override color setter to always convert to RGB before passing to yocto-spinner.\n set color(value: ColorValue | ColorRgb) {\n super.color = isRgbTuple(value) ? value : toRgb(value)\n }\n\n // Getter to expose current shimmer state.\n get shimmerState(): ShimmerInfo | undefined {\n if (!this.#shimmer) {\n return undefined\n }\n return {\n color: this.#shimmer.color,\n currentDir: this.#shimmer.currentDir,\n mode: this.#shimmer.mode,\n speed: this.#shimmer.speed,\n step: this.#shimmer.step,\n } as ShimmerInfo\n }\n\n /**\n * Apply a yocto-spinner method and update logger state.\n * Handles text normalization, extra arguments, and logger tracking.\n * @private\n */\n #apply(methodName: string, args: unknown[]) {\n let extras: unknown[]\n let text = args.at(0)\n if (typeof text === 'string') {\n extras = args.slice(1)\n } else {\n extras = args\n text = ''\n }\n const wasSpinning = this.isSpinning\n const normalized = normalizeText(text)\n if (methodName === 'stop' && !normalized) {\n super[methodName]()\n } else {\n super[methodName](normalized)\n }\n if (methodName === 'stop') {\n if (wasSpinning && normalized) {\n logger[lastWasBlankSymbol](isBlankString(normalized))\n logger[incLogCallCountSymbol]()\n }\n } else {\n logger[lastWasBlankSymbol](false)\n logger[incLogCallCountSymbol]()\n }\n if (extras.length) {\n logger.log(...extras)\n logger[lastWasBlankSymbol](false)\n }\n return this\n }\n\n /**\n * Build the complete display text with progress, shimmer, and indentation.\n * Combines base text, progress bar, shimmer effects, and indentation.\n * @private\n */\n #buildDisplayText() {\n let displayText = this.#baseText\n\n if (this.#progress) {\n const progressText = formatProgress(this.#progress)\n displayText = displayText\n ? `${displayText} ${progressText}`\n : progressText\n }\n\n // Apply shimmer effect if enabled.\n if (displayText && this.#shimmer) {\n // If shimmer color is 'inherit', use current spinner color (getter ensures RGB).\n // Otherwise, check if it's a gradient (array of arrays) or single color.\n let shimmerColor: ColorRgb | ShimmerColorGradient\n if (this.#shimmer.color === COLOR_INHERIT) {\n shimmerColor = this.color\n } else if (Array.isArray(this.#shimmer.color[0])) {\n // It's a gradient - use as is.\n shimmerColor = this.#shimmer.color as ShimmerColorGradient\n } else {\n // It's a single color - convert to RGB.\n shimmerColor = toRgb(this.#shimmer.color as ColorValue)\n }\n\n displayText = applyShimmer(displayText, this.#shimmer, {\n color: shimmerColor,\n direction: this.#shimmer.mode,\n })\n }\n\n // Apply indentation\n if (this.#indentation && displayText) {\n displayText = this.#indentation + displayText\n }\n\n return displayText\n }\n\n /**\n * Show a status message without stopping the spinner.\n * Outputs the symbol and message to stderr, then continues spinning.\n */\n #showStatusAndKeepSpinning(symbolType: SymbolType, args: unknown[]) {\n let text = args.at(0)\n let extras: unknown[]\n if (typeof text === 'string') {\n extras = args.slice(1)\n } else {\n extras = args\n text = ''\n }\n\n // Note: Status messages always go to stderr.\n logger.error(`${LOG_SYMBOLS[symbolType]} ${text}`, ...extras)\n return this\n }\n\n /**\n * Update the spinner's displayed text.\n * Rebuilds display text and triggers render.\n * @private\n */\n #updateSpinnerText() {\n // Call the parent class's text setter, which triggers render.\n super.text = this.#buildDisplayText()\n }\n\n /**\n * Show a debug message (\u2139) without stopping the spinner.\n * Only displays if debug mode is enabled via environment variable.\n * Outputs to stderr and continues spinning.\n *\n * @param text - Debug message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n debug(text?: string | undefined, ...extras: unknown[]) {\n if (isDebug()) {\n return this.#showStatusAndKeepSpinning('info', [text, ...extras])\n }\n return this\n }\n\n /**\n * Show a debug message (\u2139) and stop the spinner.\n * Only displays if debug mode is enabled via environment variable.\n * Auto-clears the spinner line before displaying the message.\n *\n * @param text - Debug message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n debugAndStop(text?: string | undefined, ...extras: unknown[]) {\n if (isDebug()) {\n return this.#apply('info', [text, ...extras])\n }\n return this\n }\n\n /**\n * Decrease indentation level by removing spaces from the left.\n * Pass 0 to reset indentation to zero completely.\n *\n * @param spaces - Number of spaces to remove\n * @returns This spinner for chaining\n * @default spaces=2\n *\n * @example\n * ```ts\n * spinner.dedent() // Remove 2 spaces\n * spinner.dedent(4) // Remove 4 spaces\n * spinner.dedent(0) // Reset to zero indentation\n * ```\n */\n dedent(spaces?: number | undefined) {\n // Pass 0 to reset indentation\n if (spaces === 0) {\n this.#indentation = ''\n } else {\n const amount = spaces ?? 2\n const newLength = Math.max(0, this.#indentation.length - amount)\n this.#indentation = this.#indentation.slice(0, newLength)\n }\n this.#updateSpinnerText()\n return this\n }\n\n /**\n * Show a done/success message (\u2713) without stopping the spinner.\n * Alias for `success()` with a shorter name.\n *\n * DESIGN DECISION: Unlike yocto-spinner, our `done()` does NOT stop the spinner.\n * Use `doneAndStop()` if you want to stop the spinner.\n *\n * @param text - Message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n done(text?: string | undefined, ...extras: unknown[]) {\n return this.#showStatusAndKeepSpinning('success', [text, ...extras])\n }\n\n /**\n * Show a done/success message (\u2713) and stop the spinner.\n * Auto-clears the spinner line before displaying the success message.\n *\n * @param text - Message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n doneAndStop(text?: string | undefined, ...extras: unknown[]) {\n return this.#apply('success', [text, ...extras])\n }\n\n /**\n * Show a failure message (\u2717) without stopping the spinner.\n * DESIGN DECISION: Unlike yocto-spinner, our `fail()` does NOT stop the spinner.\n * This allows displaying errors while continuing to spin.\n * Use `failAndStop()` if you want to stop the spinner.\n *\n * @param text - Error message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n fail(text?: string | undefined, ...extras: unknown[]) {\n return this.#showStatusAndKeepSpinning('fail', [text, ...extras])\n }\n\n /**\n * Show a failure message (\u2717) and stop the spinner.\n * Auto-clears the spinner line before displaying the error message.\n *\n * @param text - Error message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n failAndStop(text?: string | undefined, ...extras: unknown[]) {\n return this.#apply('error', [text, ...extras])\n }\n\n /**\n * Increase indentation level by adding spaces to the left.\n * Pass 0 to reset indentation to zero completely.\n *\n * @param spaces - Number of spaces to add\n * @returns This spinner for chaining\n * @default spaces=2\n *\n * @example\n * ```ts\n * spinner.indent() // Add 2 spaces\n * spinner.indent(4) // Add 4 spaces\n * spinner.indent(0) // Reset to zero indentation\n * ```\n */\n indent(spaces?: number | undefined) {\n // Pass 0 to reset indentation\n if (spaces === 0) {\n this.#indentation = ''\n } else {\n const amount = spaces ?? 2\n this.#indentation += ' '.repeat(amount)\n }\n this.#updateSpinnerText()\n return this\n }\n\n /**\n * Show an info message (\u2139) without stopping the spinner.\n * Outputs to stderr and continues spinning.\n *\n * @param text - Info message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n info(text?: string | undefined, ...extras: unknown[]) {\n return this.#showStatusAndKeepSpinning('info', [text, ...extras])\n }\n\n /**\n * Show an info message (\u2139) and stop the spinner.\n * Auto-clears the spinner line before displaying the message.\n *\n * @param text - Info message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n infoAndStop(text?: string | undefined, ...extras: unknown[]) {\n return this.#apply('info', [text, ...extras])\n }\n\n /**\n * Log a message to stdout without stopping the spinner.\n * Unlike other status methods, this outputs to stdout for data logging.\n *\n * @param args - Values to log to stdout\n * @returns This spinner for chaining\n */\n log(...args: unknown[]) {\n logger.log(...args)\n return this\n }\n\n /**\n * Log a message to stdout and stop the spinner.\n * Auto-clears the spinner line before displaying the message.\n *\n * @param text - Message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n logAndStop(text?: string | undefined, ...extras: unknown[]) {\n return this.#apply('stop', [text, ...extras])\n }\n\n /**\n * Update progress information displayed with the spinner.\n * Shows a progress bar with percentage and optional unit label.\n *\n * @param current - Current progress value\n * @param total - Total/maximum progress value\n * @param unit - Optional unit label (e.g., 'files', 'items')\n * @returns This spinner for chaining\n *\n * @example\n * ```ts\n * spinner.progress(5, 10) // \"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 50% (5/10)\"\n * spinner.progress(7, 20, 'files') // \"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 35% (7/20 files)\"\n * ```\n */\n progress = (\n current: number,\n total: number,\n unit?: string | undefined,\n ) => {\n this.#progress = {\n __proto__: null,\n current,\n total,\n ...(unit ? { unit } : {}),\n } as ProgressInfo\n this.#updateSpinnerText()\n return this\n }\n\n /**\n * Increment progress by a specified amount.\n * Updates the progress bar displayed with the spinner.\n * Clamps the result between 0 and the total value.\n *\n * @param amount - Amount to increment by\n * @returns This spinner for chaining\n * @default amount=1\n *\n * @example\n * ```ts\n * spinner.progress(0, 10, 'files')\n * spinner.progressStep() // Progress: 1/10\n * spinner.progressStep(3) // Progress: 4/10\n * ```\n */\n progressStep(amount: number = 1) {\n if (this.#progress) {\n const newCurrent = this.#progress.current + amount\n this.#progress = {\n __proto__: null,\n current: Math.max(0, Math.min(newCurrent, this.#progress.total)),\n total: this.#progress.total,\n ...(this.#progress.unit ? { unit: this.#progress.unit } : {}),\n } as ProgressInfo\n this.#updateSpinnerText()\n }\n return this\n }\n\n /**\n * Start the spinner animation with optional text.\n * Begins displaying the animated spinner on stderr.\n *\n * @param text - Optional text to display with the spinner\n * @returns This spinner for chaining\n *\n * @example\n * ```ts\n * spinner.start('Loading\u2026')\n * // Later:\n * spinner.successAndStop('Done!')\n * ```\n */\n start(...args: unknown[]) {\n if (args.length) {\n const text = args.at(0)\n const normalized = normalizeText(text)\n // We clear this.text on start when `text` is falsy because yocto-spinner\n // will not clear it otherwise.\n if (!normalized) {\n this.#baseText = ''\n super.text = ''\n } else {\n this.#baseText = normalized\n }\n }\n\n this.#updateSpinnerText()\n // Don't pass text to yocto-spinner.start() since we already set it via #updateSpinnerText().\n // Passing args would cause duplicate message output.\n return this.#apply('start', [])\n }\n\n /**\n * Log a main step message to stderr without stopping the spinner.\n * Adds a blank line before the message for visual separation.\n * Aligns with `logger.step()` to use stderr for status messages.\n *\n * @param text - Step message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n *\n * @example\n * ```ts\n * spinner.step('Building application')\n * spinner.substep('Compiling TypeScript')\n * spinner.substep('Bundling assets')\n * ```\n */\n step(text?: string | undefined, ...extras: unknown[]) {\n if (typeof text === 'string') {\n // Add blank line before step for visual separation.\n logger.error('')\n // Use error (stderr) to align with logger.step() default stream.\n logger.error(text, ...extras)\n }\n return this\n }\n\n /**\n * Log an indented substep message to stderr without stopping the spinner.\n * Adds 2-space indentation to the message.\n * Aligns with `logger.substep()` to use stderr for status messages.\n *\n * @param text - Substep message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n *\n * @example\n * ```ts\n * spinner.step('Building application')\n * spinner.substep('Compiling TypeScript')\n * spinner.substep('Bundling assets')\n * ```\n */\n substep(text?: string | undefined, ...extras: unknown[]) {\n if (typeof text === 'string') {\n // Add 2-space indent for substep.\n // Use error (stderr) to align with logger.substep() default stream.\n logger.error(` ${text}`, ...extras)\n }\n return this\n }\n\n /**\n * Stop the spinner animation and clear internal state.\n * Auto-clears the spinner line via yocto-spinner.stop().\n * Resets progress, shimmer, and text state.\n *\n * @param text - Optional final text to display after stopping\n * @returns This spinner for chaining\n *\n * @example\n * ```ts\n * spinner.start('Processing\u2026')\n * // Do work\n * spinner.stop() // Just stop, no message\n * // or\n * spinner.stop('Finished processing')\n * ```\n */\n stop(...args: unknown[]) {\n // Clear the spinner text BEFORE stopping to prevent ghost frames.\n // This ensures the terminal line is fully cleared before we stop the animation.\n if (!args.length || !args[0]) {\n super.text = ''\n }\n\n // Clear internal state.\n this.#baseText = ''\n this.#progress = undefined\n // Reset shimmer animation state if shimmer is enabled.\n if (this.#shimmer) {\n this.#shimmer.currentDir = DIR_LTR\n this.#shimmer.step = 0\n }\n // Call parent stop (clears screen, sets isSpinning = false).\n const result = this.#apply('stop', args)\n // Ensure text is cleared after stop completes.\n super.text = ''\n return result\n }\n\n /**\n * Show a success message (\u2713) without stopping the spinner.\n * DESIGN DECISION: Unlike yocto-spinner, our `success()` does NOT stop the spinner.\n * This allows displaying success messages while continuing to spin for multi-step operations.\n * Use `successAndStop()` if you want to stop the spinner.\n *\n * @param text - Success message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n success(text?: string | undefined, ...extras: unknown[]) {\n return this.#showStatusAndKeepSpinning('success', [text, ...extras])\n }\n\n /**\n * Show a success message (\u2713) and stop the spinner.\n * Auto-clears the spinner line before displaying the success message.\n *\n * @param text - Success message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n successAndStop(text?: string | undefined, ...extras: unknown[]) {\n return this.#apply('success', [text, ...extras])\n }\n\n /**\n * Get or set the spinner text.\n * When called with no arguments, returns the current base text.\n * When called with text, updates the display and returns the spinner for chaining.\n *\n * @param value - Text to display (omit to get current text)\n * @returns Current text (getter) or this spinner (setter)\n *\n * @example\n * ```ts\n * // Setter\n * spinner.text('Loading data\u2026')\n * spinner.text('Processing\u2026')\n *\n * // Getter\n * const current = spinner.text()\n * console.log(current) // \"Processing\u2026\"\n * ```\n */\n text(): string\n text(value: string): Spinner\n text(value?: string): string | Spinner {\n // biome-ignore lint/complexity/noArguments: Function overload for getter/setter pattern.\n if (arguments.length === 0) {\n // Getter: return current base text\n return this.#baseText\n }\n // Setter: update base text and refresh display\n this.#baseText = value ?? ''\n this.#updateSpinnerText()\n return this as unknown as Spinner\n }\n\n /**\n * Show a warning message (\u26A0) without stopping the spinner.\n * Outputs to stderr and continues spinning.\n *\n * @param text - Warning message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n warn(text?: string | undefined, ...extras: unknown[]) {\n return this.#showStatusAndKeepSpinning('warn', [text, ...extras])\n }\n\n /**\n * Show a warning message (\u26A0) and stop the spinner.\n * Auto-clears the spinner line before displaying the warning message.\n *\n * @param text - Warning message to display\n * @param extras - Additional values to log\n * @returns This spinner for chaining\n */\n warnAndStop(text?: string | undefined, ...extras: unknown[]) {\n return this.#apply('warning', [text, ...extras])\n }\n\n /**\n * Enable shimmer effect.\n * Restores saved config or uses defaults if no saved config exists.\n *\n * @returns This spinner for chaining\n *\n * @example\n * spinner.enableShimmer()\n */\n enableShimmer(): Spinner {\n if (this.#shimmerSavedConfig) {\n // Restore saved config.\n this.#shimmer = { ...this.#shimmerSavedConfig }\n } else {\n // Create default config.\n this.#shimmer = {\n color: COLOR_INHERIT,\n currentDir: DIR_LTR,\n mode: DIR_LTR,\n speed: 1 / 3,\n step: 0,\n } as ShimmerInfo\n this.#shimmerSavedConfig = this.#shimmer\n }\n\n this.#updateSpinnerText()\n return this as unknown as Spinner\n }\n\n /**\n * Disable shimmer effect.\n * Preserves config for later re-enable via enableShimmer().\n *\n * @returns This spinner for chaining\n *\n * @example\n * spinner.disableShimmer()\n */\n disableShimmer(): Spinner {\n // Disable shimmer but preserve config.\n this.#shimmer = undefined\n this.#updateSpinnerText()\n return this as unknown as Spinner\n }\n\n /**\n * Set complete shimmer configuration.\n * Replaces any existing shimmer config with the provided values.\n * Undefined properties will use default values.\n *\n * @param config - Complete shimmer configuration\n * @returns This spinner for chaining\n *\n * @example\n * spinner.setShimmer({\n * color: [255, 0, 0],\n * dir: 'rtl',\n * speed: 0.5\n * })\n */\n setShimmer(config: ShimmerConfig): Spinner {\n this.#shimmer = {\n color: config.color ?? COLOR_INHERIT,\n currentDir: DIR_LTR,\n mode: config.dir ?? DIR_LTR,\n speed: config.speed ?? 1 / 3,\n step: 0,\n } as ShimmerInfo\n this.#shimmerSavedConfig = this.#shimmer\n this.#updateSpinnerText()\n return this as unknown as Spinner\n }\n\n /**\n * Update partial shimmer configuration.\n * Merges with existing config, enabling shimmer if currently disabled.\n *\n * @param config - Partial shimmer configuration to merge\n * @returns This spinner for chaining\n *\n * @example\n * // Update just the speed\n * spinner.updateShimmer({ speed: 0.5 })\n *\n * // Update direction\n * spinner.updateShimmer({ dir: 'rtl' })\n *\n * // Update multiple properties\n * spinner.updateShimmer({ color: [255, 0, 0], speed: 0.8 })\n */\n updateShimmer(config: Partial<ShimmerConfig>): Spinner {\n const partialConfig = {\n __proto__: null,\n ...config,\n } as Partial<ShimmerConfig>\n\n if (this.#shimmer) {\n // Update existing shimmer.\n this.#shimmer = {\n ...this.#shimmer,\n ...(partialConfig.color !== undefined\n ? { color: partialConfig.color }\n : {}),\n ...(partialConfig.dir !== undefined\n ? { mode: partialConfig.dir }\n : {}),\n ...(partialConfig.speed !== undefined\n ? { speed: partialConfig.speed }\n : {}),\n } as ShimmerInfo\n this.#shimmerSavedConfig = this.#shimmer\n } else if (this.#shimmerSavedConfig) {\n // Restore and update.\n this.#shimmer = {\n ...this.#shimmerSavedConfig,\n ...(partialConfig.color !== undefined\n ? { color: partialConfig.color }\n : {}),\n ...(partialConfig.dir !== undefined\n ? { mode: partialConfig.dir }\n : {}),\n ...(partialConfig.speed !== undefined\n ? { speed: partialConfig.speed }\n : {}),\n } as ShimmerInfo\n this.#shimmerSavedConfig = this.#shimmer\n } else {\n // Create new with partial config.\n this.#shimmer = {\n color: partialConfig.color ?? COLOR_INHERIT,\n currentDir: DIR_LTR,\n mode: partialConfig.dir ?? DIR_LTR,\n speed: partialConfig.speed ?? 1 / 3,\n step: 0,\n } as ShimmerInfo\n this.#shimmerSavedConfig = this.#shimmer\n }\n\n this.#updateSpinnerText()\n return this as unknown as Spinner\n }\n } as unknown as {\n new (options?: SpinnerOptions | undefined): Spinner\n }\n // Add aliases.\n Object.defineProperties(_Spinner.prototype, {\n error: desc(_Spinner.prototype.fail),\n errorAndStop: desc(_Spinner.prototype.failAndStop),\n warning: desc(_Spinner.prototype.warn),\n warningAndStop: desc(_Spinner.prototype.warnAndStop),\n })\n _defaultSpinner = getCI()\n ? ciSpinner\n : (getCliSpinners('socket') as SpinnerStyle)\n }\n return new _Spinner({\n spinner: _defaultSpinner,\n ...options,\n })\n}\n\nlet _spinner: ReturnType<typeof Spinner> | undefined\n\n/**\n * Get the default spinner instance.\n * Lazily creates the spinner to avoid circular dependencies during module initialization.\n * Reuses the same instance across calls.\n *\n * @returns Shared default spinner instance\n *\n * @example\n * ```ts\n * import { getDefaultSpinner } from '@socketsecurity/lib/spinner'\n *\n * const spinner = getDefaultSpinner()\n * spinner.start('Loading\u2026')\n * ```\n */\nexport function getDefaultSpinner(): ReturnType<typeof Spinner> {\n if (_spinner === undefined) {\n _spinner = Spinner()\n }\n return _spinner\n}\n\n// REMOVED: Deprecated `spinner` export\n// Migration: Use getDefaultSpinner() instead\n// See: getDefaultSpinner() function above\n\n/**\n * Configuration options for `withSpinner()` helper.\n * @template T - Return type of the async operation\n */\nexport type WithSpinnerOptions<T> = {\n /** Message to display while the spinner is running */\n message: string\n /** Async function to execute while spinner is active */\n operation: () => Promise<T>\n /**\n * Optional spinner instance to use.\n * If not provided, operation runs without spinner.\n */\n spinner?: Spinner | undefined\n /**\n * Optional spinner options to apply during the operation.\n * These options will be pushed when the operation starts and popped when it completes.\n * Supports color and shimmer configuration.\n */\n withOptions?: Partial<Pick<SpinnerOptions, 'color' | 'shimmer'>> | undefined\n}\n\n/**\n * Execute an async operation with spinner lifecycle management.\n * Ensures `spinner.stop()` is always called via try/finally, even if the operation throws.\n * Provides safe cleanup and consistent spinner behavior.\n *\n * @template T - Return type of the operation\n * @param options - Configuration object\n * @param options.message - Message to display while spinner is running\n * @param options.operation - Async function to execute\n * @param options.spinner - Optional spinner instance (if not provided, no spinner is used)\n * @returns Result of the operation\n * @throws Re-throws any error from operation after stopping spinner\n *\n * @example\n * ```ts\n * import { Spinner, withSpinner } from '@socketsecurity/lib/spinner'\n *\n * const spinner = Spinner()\n *\n * // With spinner instance\n * const result = await withSpinner({\n * message: 'Processing\u2026',\n * operation: async () => {\n * return await processData()\n * },\n * spinner\n * })\n *\n * // Without spinner instance (no-op, just runs operation)\n * const result = await withSpinner({\n * message: 'Processing\u2026',\n * operation: async () => {\n * return await processData()\n * }\n * })\n * ```\n */\nexport async function withSpinner<T>(\n options: WithSpinnerOptions<T>,\n): Promise<T> {\n const { message, operation, spinner, withOptions } = {\n __proto__: null,\n ...options,\n } as WithSpinnerOptions<T>\n\n if (!spinner) {\n return await operation()\n }\n\n // Save current options if we're going to change them\n const savedColor =\n withOptions?.color !== undefined ? spinner.color : undefined\n const savedShimmerState =\n withOptions?.shimmer !== undefined ? spinner.shimmerState : undefined\n\n // Apply temporary options\n if (withOptions?.color !== undefined) {\n spinner.color = toRgb(withOptions.color)\n }\n if (withOptions?.shimmer !== undefined) {\n if (typeof withOptions.shimmer === 'string') {\n spinner.updateShimmer({ dir: withOptions.shimmer })\n } else {\n spinner.setShimmer(withOptions.shimmer)\n }\n }\n\n spinner.start(message)\n try {\n return await operation()\n } finally {\n spinner.stop()\n // Restore previous options\n if (savedColor !== undefined) {\n spinner.color = savedColor\n }\n if (withOptions?.shimmer !== undefined) {\n if (savedShimmerState) {\n spinner.setShimmer({\n color: savedShimmerState.color as any,\n dir: savedShimmerState.mode,\n speed: savedShimmerState.speed,\n })\n } else {\n spinner.disableShimmer()\n }\n }\n }\n}\n\n/**\n * Configuration options for `withSpinnerRestore()` helper.\n * @template T - Return type of the async operation\n */\nexport type WithSpinnerRestoreOptions<T> = {\n /** Async function to execute while spinner is stopped */\n operation: () => Promise<T>\n /** Optional spinner instance to restore after operation */\n spinner?: Spinner | undefined\n /** Whether spinner was spinning before the operation (used to conditionally restart) */\n wasSpinning: boolean\n}\n\n/**\n * Execute an async operation with conditional spinner restart.\n * Useful when you need to temporarily stop a spinner for an operation,\n * then restore it to its previous state (if it was spinning).\n *\n * @template T - Return type of the operation\n * @param options - Configuration object\n * @param options.operation - Async function to execute\n * @param options.spinner - Optional spinner instance to manage\n * @param options.wasSpinning - Whether spinner was spinning before the operation\n * @returns Result of the operation\n * @throws Re-throws any error from operation after restoring spinner state\n *\n * @example\n * ```ts\n * import { getDefaultSpinner, withSpinnerRestore } from '@socketsecurity/lib/spinner'\n *\n * const spinner = getDefaultSpinner()\n * const wasSpinning = spinner.isSpinning\n * spinner.stop()\n *\n * const result = await withSpinnerRestore({\n * operation: async () => {\n * // Do work without spinner\n * return await someOperation()\n * },\n * spinner,\n * wasSpinning\n * })\n * // Spinner is automatically restarted if wasSpinning was true\n * ```\n */\nexport async function withSpinnerRestore<T>(\n options: WithSpinnerRestoreOptions<T>,\n): Promise<T> {\n const { operation, spinner, wasSpinning } = {\n __proto__: null,\n ...options,\n } as WithSpinnerRestoreOptions<T>\n\n try {\n return await operation()\n } finally {\n if (spinner && wasSpinning) {\n spinner.start()\n }\n }\n}\n\n/**\n * Configuration options for `withSpinnerSync()` helper.\n * @template T - Return type of the sync operation\n */\nexport type WithSpinnerSyncOptions<T> = {\n /** Message to display while the spinner is running */\n message: string\n /** Synchronous function to execute while spinner is active */\n operation: () => T\n /**\n * Optional spinner instance to use.\n * If not provided, operation runs without spinner.\n */\n spinner?: Spinner | undefined\n /**\n * Optional spinner options to apply during the operation.\n * These options will be pushed when the operation starts and popped when it completes.\n * Supports color and shimmer configuration.\n */\n withOptions?: Partial<Pick<SpinnerOptions, 'color' | 'shimmer'>> | undefined\n}\n\n/**\n * Execute a synchronous operation with spinner lifecycle management.\n * Ensures `spinner.stop()` is always called via try/finally, even if the operation throws.\n * Provides safe cleanup and consistent spinner behavior for sync operations.\n *\n * @template T - Return type of the operation\n * @param options - Configuration object\n * @param options.message - Message to display while spinner is running\n * @param options.operation - Synchronous function to execute\n * @param options.spinner - Optional spinner instance (if not provided, no spinner is used)\n * @returns Result of the operation\n * @throws Re-throws any error from operation after stopping spinner\n *\n * @example\n * ```ts\n * import { Spinner, withSpinnerSync } from '@socketsecurity/lib/spinner'\n *\n * const spinner = Spinner()\n *\n * const result = withSpinnerSync({\n * message: 'Processing\u2026',\n * operation: () => {\n * return processDataSync()\n * },\n * spinner\n * })\n * ```\n */\nexport function withSpinnerSync<T>(options: WithSpinnerSyncOptions<T>): T {\n const { message, operation, spinner, withOptions } = {\n __proto__: null,\n ...options,\n } as WithSpinnerSyncOptions<T>\n\n if (!spinner) {\n return operation()\n }\n\n // Save current options if we're going to change them\n const savedColor =\n withOptions?.color !== undefined ? spinner.color : undefined\n const savedShimmerState =\n withOptions?.shimmer !== undefined ? spinner.shimmerState : undefined\n\n // Apply temporary options\n if (withOptions?.color !== undefined) {\n spinner.color = toRgb(withOptions.color)\n }\n if (withOptions?.shimmer !== undefined) {\n if (typeof withOptions.shimmer === 'string') {\n spinner.updateShimmer({ dir: withOptions.shimmer })\n } else {\n spinner.setShimmer(withOptions.shimmer)\n }\n }\n\n spinner.start(message)\n try {\n return operation()\n } finally {\n spinner.stop()\n // Restore previous options\n if (savedColor !== undefined) {\n spinner.color = savedColor\n }\n if (withOptions?.shimmer !== undefined) {\n if (savedShimmerState) {\n spinner.setShimmer({\n color: savedShimmerState.color as any,\n dir: savedShimmerState.mode,\n speed: savedShimmerState.speed,\n })\n } else {\n spinner.disableShimmer()\n }\n }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["yoctoSpinner", "options"]
7
7
  }
@@ -39,6 +39,7 @@ var import_readline = __toESM(require("readline"));
39
39
  var import_spinner = require("../spinner.js");
40
40
  var import_clear = require("./clear.js");
41
41
  var import_stdout = require("./stdout.js");
42
+ const spinner = (0, import_spinner.getDefaultSpinner)();
42
43
  function createOutputMask(options = {}) {
43
44
  const { showOutput = false } = options;
44
45
  return {
@@ -56,7 +57,7 @@ function createKeyboardHandler(mask, child, options = {}) {
56
57
  mask.verbose = !mask.verbose;
57
58
  if (mask.verbose) {
58
59
  if (mask.isSpinning) {
59
- (0, import_spinner.getDefaultSpinner)().stop();
60
+ spinner.stop();
60
61
  mask.isSpinning = false;
61
62
  }
62
63
  (0, import_clear.clearLine)();
@@ -76,7 +77,7 @@ function createKeyboardHandler(mask, child, options = {}) {
76
77
  (0, import_clear.clearLine)();
77
78
  mask.outputBuffer = [];
78
79
  if (!mask.isSpinning) {
79
- (0, import_spinner.getDefaultSpinner)().start(`${message} (ctrl+o ${toggleText})`);
80
+ spinner.start(`${message} (ctrl+o ${toggleText})`);
80
81
  mask.isSpinning = true;
81
82
  }
82
83
  }
@@ -94,7 +95,7 @@ function attachOutputMask(child, options = {}) {
94
95
  const { message = "Running\u2026" } = options;
95
96
  const mask = createOutputMask(options);
96
97
  if (mask.isSpinning && process.stdout.isTTY) {
97
- (0, import_spinner.getDefaultSpinner)().start(
98
+ spinner.start(
98
99
  `${message} (ctrl+o ${options.toggleText || "to see full output"})`
99
100
  );
100
101
  }
@@ -160,7 +161,6 @@ function attachOutputMask(child, options = {}) {
160
161
  }
161
162
  }
162
163
  if (mask.isSpinning) {
163
- const spinner = (0, import_spinner.getDefaultSpinner)();
164
164
  if (finalCode === 0) {
165
165
  spinner.successAndStop(`${message} completed`);
166
166
  } else {
@@ -180,7 +180,7 @@ function attachOutputMask(child, options = {}) {
180
180
  process.stdin.setRawMode(false);
181
181
  }
182
182
  if (mask.isSpinning) {
183
- (0, import_spinner.getDefaultSpinner)().failAndStop(`${message} error`);
183
+ spinner.failAndStop(`${message} error`);
184
184
  }
185
185
  reject(error);
186
186
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/stdio/mask.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview Interactive output masking utilities for CLI tools.\n * Provides output control with keyboard toggling (ctrl+o).\n *\n * ANSI Escape Sequences Used:\n * - '\\r': Carriage return - moves cursor to beginning of current line.\n * - '\\x1b[K' or '\\x1b[0K': CSI K (erase line) - clear from cursor to end of line.\n * - '\\x1b[2K': CSI 2K - erase entire line.\n * - '\\x1b[1A': CSI A - move cursor up 1 line.\n *\n * Terminal Control:\n * - Raw mode (setRawMode(true)): Captures keypresses immediately without buffering.\n * - TTY detection: Ensures terminal manipulation only occurs in interactive terminals.\n *\n * Key Features:\n * - Output buffering: Stores up to 1000 lines when masked to prevent memory issues.\n * - Graceful cleanup: Always restores terminal to normal mode on exit/error.\n * - Visual feedback: Uses spinner to indicate process is running when output is masked.\n */\n\nimport type { ChildProcess, SpawnOptions } from 'child_process'\nimport { spawn } from 'child_process'\nimport readline from 'readline'\nimport { getDefaultSpinner } from '../spinner.js'\nimport { clearLine } from './clear.js'\nimport { write } from './stdout.js'\n\nexport interface OutputMaskOptions {\n /**\n * Current working directory for spawned process.\n * @default process.cwd()\n */\n cwd?: string | undefined\n /**\n * Environment variables for spawned process.\n * @default process.env\n */\n env?: NodeJS.ProcessEnv | undefined\n /**\n * Filter output before displaying or buffering.\n * Return `false` to skip the line, `true` to include it.\n *\n * Useful for filtering non-fatal warnings or noise from test runners.\n * The filter runs on every chunk of output before display/buffering.\n *\n * @param text - The output text chunk (may include ANSI codes)\n * @param stream - Whether this came from 'stdout' or 'stderr'\n * @returns `true` to include this output, `false` to skip it\n *\n * @example\n * ```ts\n * filterOutput: (text, stream) => {\n * // Skip vitest worker termination errors\n * if (text.includes('Terminating worker thread')) return false\n * return true\n * }\n * ```\n */\n filterOutput?:\n | ((text: string, stream: 'stdout' | 'stderr') => boolean)\n | undefined\n /**\n * Progress message to display in spinner.\n * @default 'Running\u2026'\n */\n message?: string | undefined\n /**\n * Override the exit code based on captured output.\n *\n * Useful for handling non-fatal errors that shouldn't fail the build.\n * Called after the process exits with the original code and all captured output.\n * Return a number to override the exit code, or `undefined` to keep original.\n *\n * @param code - Original exit code from the process\n * @param stdout - All captured stdout (even filtered lines are captured)\n * @param stderr - All captured stderr (even filtered lines are captured)\n * @returns New exit code, or `undefined` to keep original\n *\n * @example\n * ```ts\n * overrideExitCode: (code, stdout, stderr) => {\n * // If only worker termination errors, treat as success\n * const output = stdout + stderr\n * const hasWorkerError = output.includes('Terminating worker thread')\n * const hasRealFailure = output.includes('FAIL')\n * if (code !== 0 && hasWorkerError && !hasRealFailure) {\n * return 0 // Override to success\n * }\n * return undefined // Keep original\n * }\n * ```\n */\n overrideExitCode?:\n | ((code: number, stdout: string, stderr: string) => number | undefined)\n | undefined\n /**\n * Start with output visible instead of masked.\n * When `true`, output shows immediately without needing ctrl+o.\n * @default false\n */\n showOutput?: boolean | undefined\n /**\n * Text to show after \"ctrl+o\" in spinner message.\n * @default 'to see full output'\n */\n toggleText?: string | undefined\n}\n\nexport interface OutputMask {\n /** Whether spinner is currently active */\n isSpinning: boolean\n /** Buffered output lines */\n outputBuffer: string[]\n /** All stderr captured (for exit code override) */\n stderrCapture: string\n /** All stdout captured (for exit code override) */\n stdoutCapture: string\n /** Whether output is currently visible */\n verbose: boolean\n}\n\n/**\n * Create an output mask for controlling command output visibility.\n * The mask tracks whether output should be shown or hidden (buffered).\n * When hidden, output is buffered and a spinner is shown instead.\n */\nexport function createOutputMask(options: OutputMaskOptions = {}): OutputMask {\n const { showOutput = false } = options\n\n return {\n isSpinning: !showOutput,\n outputBuffer: [],\n stderrCapture: '',\n stdoutCapture: '',\n verbose: showOutput,\n }\n}\n\n/**\n * Create a keyboard handler for toggling output visibility.\n * Handles two key combinations:\n * - ctrl+o: Toggle between showing and hiding output.\n * - ctrl+c: Cancel the running process.\n * The handler manipulates terminal state using ANSI escape sequences.\n */\nexport function createKeyboardHandler(\n mask: OutputMask,\n child: ChildProcess,\n options: OutputMaskOptions = {},\n): (_str: string, key: readline.Key) => void {\n const { message = 'Running\u2026', toggleText = 'to see full output' } = options\n\n return (_str, key) => {\n // ctrl+o toggles verbose mode.\n if (key?.ctrl && key.name === 'o') {\n mask.verbose = !mask.verbose\n\n if (mask.verbose) {\n // Stop spinner and show buffered output.\n if (mask.isSpinning) {\n getDefaultSpinner().stop()\n mask.isSpinning = false\n }\n\n // Clear the current line (removes spinner remnants).\n clearLine()\n\n // Show buffered output.\n if (mask.outputBuffer.length > 0) {\n console.log('--- Output (ctrl+o to hide) ---')\n mask.outputBuffer.forEach(line => {\n write(line)\n })\n }\n } else {\n // Hide output and show spinner.\n // Clear all the output lines that were shown.\n if (mask.outputBuffer.length > 0) {\n // Calculate number of lines to clear (output + header line).\n const lineCount = mask.outputBuffer.join('').split('\\n').length + 1\n // Move up and clear each line using ANSI escape sequences:\n // - '\\x1b[1A' (CSI A): Move cursor up 1 line.\n // - '\\x1b[2K' (CSI K with param 2): Erase entire line.\n // This combination effectively \"rewinds\" the terminal output.\n for (let i = 0; i < lineCount; i += 1) {\n process.stdout.write('\\x1b[1A\\x1b[2K')\n }\n }\n clearLine()\n\n // Clear the buffer and restart spinner.\n mask.outputBuffer = []\n if (!mask.isSpinning) {\n getDefaultSpinner().start(`${message} (ctrl+o ${toggleText})`)\n mask.isSpinning = true\n }\n }\n }\n // ctrl+c to cancel.\n else if (key?.ctrl && key.name === 'c') {\n // Gracefully terminate child process.\n child.kill('SIGTERM')\n // Restore terminal to normal mode before exiting.\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false)\n }\n throw new Error('Process cancelled by user')\n }\n }\n}\n\n/**\n * Attach output masking to a child process.\n * Returns a promise that resolves with the exit code.\n * This function:\n * - Sets up keyboard input handling in raw mode for immediate key capture.\n * - Buffers stdout/stderr when not in verbose mode.\n * - Shows a spinner when output is masked.\n * - Allows toggling between masked and unmasked output with ctrl+o.\n */\nexport function attachOutputMask(\n child: ChildProcess,\n options: OutputMaskOptions = {},\n): Promise<number> {\n return new Promise((resolve, reject) => {\n const { message = 'Running\u2026' } = options\n const mask = createOutputMask(options)\n\n // Start spinner if not verbose.\n if (mask.isSpinning && process.stdout.isTTY) {\n getDefaultSpinner().start(\n `${message} (ctrl+o ${options.toggleText || 'to see full output'})`,\n )\n }\n\n // Setup keyboard input handling.\n // Raw mode is required to capture ctrl+o without waiting for Enter.\n if (process.stdin.isTTY) {\n readline.emitKeypressEvents(process.stdin)\n process.stdin.setRawMode(true)\n\n const keypressHandler = createKeyboardHandler(mask, child, options)\n process.stdin.on('keypress', keypressHandler)\n\n // Cleanup on exit: restore terminal to normal mode.\n child.on('exit', () => {\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false)\n process.stdin.removeListener('keypress', keypressHandler)\n }\n })\n }\n\n // Handle stdout: either show immediately or buffer for later.\n if (child.stdout) {\n child.stdout.on('data', data => {\n const text = data.toString()\n\n // Always capture for exit code override.\n mask.stdoutCapture += text\n\n // Apply filter if provided.\n if (options.filterOutput && !options.filterOutput(text, 'stdout')) {\n // Skip this output.\n return undefined\n }\n\n if (mask.verbose) {\n write(text)\n } else {\n // Buffer the output for later.\n mask.outputBuffer.push(text)\n\n // Keep buffer size reasonable (last 1000 lines).\n // This prevents unbounded memory growth for long-running processes.\n const lines = mask.outputBuffer.join('').split('\\n')\n if (lines.length > 1000) {\n mask.outputBuffer = [lines.slice(-1000).join('\\n')]\n }\n }\n return undefined\n })\n }\n\n // Handle stderr: same as stdout, but write to stderr stream when verbose.\n if (child.stderr) {\n child.stderr.on('data', data => {\n const text = data.toString()\n\n // Always capture for exit code override.\n mask.stderrCapture += text\n\n // Apply filter if provided.\n if (options.filterOutput && !options.filterOutput(text, 'stderr')) {\n // Skip this output.\n return undefined\n }\n\n if (mask.verbose) {\n process.stderr.write(text)\n } else {\n mask.outputBuffer.push(text)\n }\n return undefined\n })\n }\n\n child.on('exit', code => {\n // Cleanup keyboard if needed.\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false)\n }\n\n // Allow caller to override exit code based on output.\n let finalCode = code || 0\n if (options.overrideExitCode) {\n const overridden = options.overrideExitCode(\n finalCode,\n mask.stdoutCapture,\n mask.stderrCapture,\n )\n if (overridden !== undefined) {\n finalCode = overridden\n }\n }\n\n if (mask.isSpinning) {\n const spinner = getDefaultSpinner()\n if (finalCode === 0) {\n spinner.successAndStop(`${message} completed`)\n } else {\n spinner.failAndStop(`${message} failed`)\n // Show buffered output on failure so user can see what went wrong.\n if (mask.outputBuffer.length > 0 && !mask.verbose) {\n console.log('\\n--- Output ---')\n mask.outputBuffer.forEach(line => {\n write(line)\n })\n }\n }\n }\n\n resolve(finalCode)\n })\n\n child.on('error', error => {\n // Ensure terminal is restored to normal mode on error.\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false)\n }\n\n if (mask.isSpinning) {\n getDefaultSpinner().failAndStop(`${message} error`)\n }\n reject(error)\n })\n })\n}\n\n/**\n * Run a command with interactive output masking.\n * Convenience wrapper around spawn + attachOutputMask.\n * Spawns a child process and attaches the output masking system to it.\n * stdin is inherited, stdout and stderr are piped for masking control.\n */\nexport async function runWithMask(\n command: string,\n args: string[] = [],\n options: OutputMaskOptions & SpawnOptions = {},\n): Promise<number> {\n const {\n message = 'Running\u2026',\n showOutput = false,\n toggleText = 'to see output',\n ...spawnOptions\n } = options\n\n const child = spawn(command, args, {\n stdio: ['inherit', 'pipe', 'pipe'],\n ...spawnOptions,\n })\n\n return await attachOutputMask(child, { message, showOutput, toggleText })\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @fileoverview Interactive output masking utilities for CLI tools.\n * Provides output control with keyboard toggling (ctrl+o).\n *\n * ANSI Escape Sequences Used:\n * - '\\r': Carriage return - moves cursor to beginning of current line.\n * - '\\x1b[K' or '\\x1b[0K': CSI K (erase line) - clear from cursor to end of line.\n * - '\\x1b[2K': CSI 2K - erase entire line.\n * - '\\x1b[1A': CSI A - move cursor up 1 line.\n *\n * Terminal Control:\n * - Raw mode (setRawMode(true)): Captures keypresses immediately without buffering.\n * - TTY detection: Ensures terminal manipulation only occurs in interactive terminals.\n *\n * Key Features:\n * - Output buffering: Stores up to 1000 lines when masked to prevent memory issues.\n * - Graceful cleanup: Always restores terminal to normal mode on exit/error.\n * - Visual feedback: Uses spinner to indicate process is running when output is masked.\n */\n\nimport type { ChildProcess, SpawnOptions } from 'child_process'\nimport { spawn } from 'child_process'\nimport readline from 'readline'\nimport { getDefaultSpinner } from '../spinner.js'\nimport { clearLine } from './clear.js'\nimport { write } from './stdout.js'\n\nconst spinner = getDefaultSpinner()\n\nexport interface OutputMaskOptions {\n /**\n * Current working directory for spawned process.\n * @default process.cwd()\n */\n cwd?: string | undefined\n /**\n * Environment variables for spawned process.\n * @default process.env\n */\n env?: NodeJS.ProcessEnv | undefined\n /**\n * Filter output before displaying or buffering.\n * Return `false` to skip the line, `true` to include it.\n *\n * Useful for filtering non-fatal warnings or noise from test runners.\n * The filter runs on every chunk of output before display/buffering.\n *\n * @param text - The output text chunk (may include ANSI codes)\n * @param stream - Whether this came from 'stdout' or 'stderr'\n * @returns `true` to include this output, `false` to skip it\n *\n * @example\n * ```ts\n * filterOutput: (text, stream) => {\n * // Skip vitest worker termination errors\n * if (text.includes('Terminating worker thread')) return false\n * return true\n * }\n * ```\n */\n filterOutput?:\n | ((text: string, stream: 'stdout' | 'stderr') => boolean)\n | undefined\n /**\n * Progress message to display in spinner.\n * @default 'Running\u2026'\n */\n message?: string | undefined\n /**\n * Override the exit code based on captured output.\n *\n * Useful for handling non-fatal errors that shouldn't fail the build.\n * Called after the process exits with the original code and all captured output.\n * Return a number to override the exit code, or `undefined` to keep original.\n *\n * @param code - Original exit code from the process\n * @param stdout - All captured stdout (even filtered lines are captured)\n * @param stderr - All captured stderr (even filtered lines are captured)\n * @returns New exit code, or `undefined` to keep original\n *\n * @example\n * ```ts\n * overrideExitCode: (code, stdout, stderr) => {\n * // If only worker termination errors, treat as success\n * const output = stdout + stderr\n * const hasWorkerError = output.includes('Terminating worker thread')\n * const hasRealFailure = output.includes('FAIL')\n * if (code !== 0 && hasWorkerError && !hasRealFailure) {\n * return 0 // Override to success\n * }\n * return undefined // Keep original\n * }\n * ```\n */\n overrideExitCode?:\n | ((code: number, stdout: string, stderr: string) => number | undefined)\n | undefined\n /**\n * Start with output visible instead of masked.\n * When `true`, output shows immediately without needing ctrl+o.\n * @default false\n */\n showOutput?: boolean | undefined\n /**\n * Text to show after \"ctrl+o\" in spinner message.\n * @default 'to see full output'\n */\n toggleText?: string | undefined\n}\n\nexport interface OutputMask {\n /** Whether spinner is currently active */\n isSpinning: boolean\n /** Buffered output lines */\n outputBuffer: string[]\n /** All stderr captured (for exit code override) */\n stderrCapture: string\n /** All stdout captured (for exit code override) */\n stdoutCapture: string\n /** Whether output is currently visible */\n verbose: boolean\n}\n\n/**\n * Create an output mask for controlling command output visibility.\n * The mask tracks whether output should be shown or hidden (buffered).\n * When hidden, output is buffered and a spinner is shown instead.\n */\nexport function createOutputMask(options: OutputMaskOptions = {}): OutputMask {\n const { showOutput = false } = options\n\n return {\n isSpinning: !showOutput,\n outputBuffer: [],\n stderrCapture: '',\n stdoutCapture: '',\n verbose: showOutput,\n }\n}\n\n/**\n * Create a keyboard handler for toggling output visibility.\n * Handles two key combinations:\n * - ctrl+o: Toggle between showing and hiding output.\n * - ctrl+c: Cancel the running process.\n * The handler manipulates terminal state using ANSI escape sequences.\n */\nexport function createKeyboardHandler(\n mask: OutputMask,\n child: ChildProcess,\n options: OutputMaskOptions = {},\n): (_str: string, key: readline.Key) => void {\n const { message = 'Running\u2026', toggleText = 'to see full output' } = options\n\n return (_str, key) => {\n // ctrl+o toggles verbose mode.\n if (key?.ctrl && key.name === 'o') {\n mask.verbose = !mask.verbose\n\n if (mask.verbose) {\n // Stop spinner and show buffered output.\n if (mask.isSpinning) {\n spinner.stop()\n mask.isSpinning = false\n }\n\n // Clear the current line (removes spinner remnants).\n clearLine()\n\n // Show buffered output.\n if (mask.outputBuffer.length > 0) {\n console.log('--- Output (ctrl+o to hide) ---')\n mask.outputBuffer.forEach(line => {\n write(line)\n })\n }\n } else {\n // Hide output and show spinner.\n // Clear all the output lines that were shown.\n if (mask.outputBuffer.length > 0) {\n // Calculate number of lines to clear (output + header line).\n const lineCount = mask.outputBuffer.join('').split('\\n').length + 1\n // Move up and clear each line using ANSI escape sequences:\n // - '\\x1b[1A' (CSI A): Move cursor up 1 line.\n // - '\\x1b[2K' (CSI K with param 2): Erase entire line.\n // This combination effectively \"rewinds\" the terminal output.\n for (let i = 0; i < lineCount; i += 1) {\n process.stdout.write('\\x1b[1A\\x1b[2K')\n }\n }\n clearLine()\n\n // Clear the buffer and restart spinner.\n mask.outputBuffer = []\n if (!mask.isSpinning) {\n spinner.start(`${message} (ctrl+o ${toggleText})`)\n mask.isSpinning = true\n }\n }\n }\n // ctrl+c to cancel.\n else if (key?.ctrl && key.name === 'c') {\n // Gracefully terminate child process.\n child.kill('SIGTERM')\n // Restore terminal to normal mode before exiting.\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false)\n }\n throw new Error('Process cancelled by user')\n }\n }\n}\n\n/**\n * Attach output masking to a child process.\n * Returns a promise that resolves with the exit code.\n * This function:\n * - Sets up keyboard input handling in raw mode for immediate key capture.\n * - Buffers stdout/stderr when not in verbose mode.\n * - Shows a spinner when output is masked.\n * - Allows toggling between masked and unmasked output with ctrl+o.\n */\nexport function attachOutputMask(\n child: ChildProcess,\n options: OutputMaskOptions = {},\n): Promise<number> {\n return new Promise((resolve, reject) => {\n const { message = 'Running\u2026' } = options\n const mask = createOutputMask(options)\n\n // Start spinner if not verbose.\n if (mask.isSpinning && process.stdout.isTTY) {\n spinner.start(\n `${message} (ctrl+o ${options.toggleText || 'to see full output'})`,\n )\n }\n\n // Setup keyboard input handling.\n // Raw mode is required to capture ctrl+o without waiting for Enter.\n if (process.stdin.isTTY) {\n readline.emitKeypressEvents(process.stdin)\n process.stdin.setRawMode(true)\n\n const keypressHandler = createKeyboardHandler(mask, child, options)\n process.stdin.on('keypress', keypressHandler)\n\n // Cleanup on exit: restore terminal to normal mode.\n child.on('exit', () => {\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false)\n process.stdin.removeListener('keypress', keypressHandler)\n }\n })\n }\n\n // Handle stdout: either show immediately or buffer for later.\n if (child.stdout) {\n child.stdout.on('data', data => {\n const text = data.toString()\n\n // Always capture for exit code override.\n mask.stdoutCapture += text\n\n // Apply filter if provided.\n if (options.filterOutput && !options.filterOutput(text, 'stdout')) {\n // Skip this output.\n return undefined\n }\n\n if (mask.verbose) {\n write(text)\n } else {\n // Buffer the output for later.\n mask.outputBuffer.push(text)\n\n // Keep buffer size reasonable (last 1000 lines).\n // This prevents unbounded memory growth for long-running processes.\n const lines = mask.outputBuffer.join('').split('\\n')\n if (lines.length > 1000) {\n mask.outputBuffer = [lines.slice(-1000).join('\\n')]\n }\n }\n return undefined\n })\n }\n\n // Handle stderr: same as stdout, but write to stderr stream when verbose.\n if (child.stderr) {\n child.stderr.on('data', data => {\n const text = data.toString()\n\n // Always capture for exit code override.\n mask.stderrCapture += text\n\n // Apply filter if provided.\n if (options.filterOutput && !options.filterOutput(text, 'stderr')) {\n // Skip this output.\n return undefined\n }\n\n if (mask.verbose) {\n process.stderr.write(text)\n } else {\n mask.outputBuffer.push(text)\n }\n return undefined\n })\n }\n\n child.on('exit', code => {\n // Cleanup keyboard if needed.\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false)\n }\n\n // Allow caller to override exit code based on output.\n let finalCode = code || 0\n if (options.overrideExitCode) {\n const overridden = options.overrideExitCode(\n finalCode,\n mask.stdoutCapture,\n mask.stderrCapture,\n )\n if (overridden !== undefined) {\n finalCode = overridden\n }\n }\n\n if (mask.isSpinning) {\n if (finalCode === 0) {\n spinner.successAndStop(`${message} completed`)\n } else {\n spinner.failAndStop(`${message} failed`)\n // Show buffered output on failure so user can see what went wrong.\n if (mask.outputBuffer.length > 0 && !mask.verbose) {\n console.log('\\n--- Output ---')\n mask.outputBuffer.forEach(line => {\n write(line)\n })\n }\n }\n }\n\n resolve(finalCode)\n })\n\n child.on('error', error => {\n // Ensure terminal is restored to normal mode on error.\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false)\n }\n\n if (mask.isSpinning) {\n spinner.failAndStop(`${message} error`)\n }\n reject(error)\n })\n })\n}\n\n/**\n * Run a command with interactive output masking.\n * Convenience wrapper around spawn + attachOutputMask.\n * Spawns a child process and attaches the output masking system to it.\n * stdin is inherited, stdout and stderr are piped for masking control.\n */\nexport async function runWithMask(\n command: string,\n args: string[] = [],\n options: OutputMaskOptions & SpawnOptions = {},\n): Promise<number> {\n const {\n message = 'Running\u2026',\n showOutput = false,\n toggleText = 'to see output',\n ...spawnOptions\n } = options\n\n const child = spawn(command, args, {\n stdio: ['inherit', 'pipe', 'pipe'],\n ...spawnOptions,\n })\n\n return await attachOutputMask(child, { message, showOutput, toggleText })\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["readline"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socketsecurity/lib",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "license": "MIT",
5
5
  "description": "Core utilities and infrastructure for Socket.dev security tools",
6
6
  "keywords": [
@@ -548,6 +548,7 @@
548
548
  "#env/*": "./dist/env/*.js",
549
549
  "#lib/*": "./dist/*.js",
550
550
  "#packages/*": "./dist/packages/*.js",
551
+ "#socketsecurity/lib/*": "@socketsecurity/lib-stable/*",
551
552
  "#types": "./dist/types.js",
552
553
  "#utils/*": "./dist/utils/*.js"
553
554
  },
@@ -582,7 +583,6 @@
582
583
  "@babel/traverse": "7.28.4",
583
584
  "@babel/types": "7.28.4",
584
585
  "@biomejs/biome": "2.2.4",
585
- "@dotenvx/dotenvx": "1.49.0",
586
586
  "@eslint/compat": "1.4.0",
587
587
  "@eslint/js": "9.38.0",
588
588
  "@inquirer/confirm": "5.1.16",
@@ -595,6 +595,7 @@
595
595
  "@socketregistry/is-unicode-supported": "1.0.5",
596
596
  "@socketregistry/packageurl-js": "1.3.5",
597
597
  "@socketregistry/yocto-spinner": "1.0.19",
598
+ "@socketsecurity/lib-stable": "https://registry.npmjs.org/@socketsecurity/lib/-/lib-3.1.3.tgz",
598
599
  "@types/node": "24.9.2",
599
600
  "@typescript/native-preview": "7.0.0-dev.20250920.1",
600
601
  "@vitest/coverage-v8": "4.0.3",
@@ -622,7 +623,6 @@
622
623
  "make-fetch-happen": "15.0.2",
623
624
  "normalize-package-data": "8.0.0",
624
625
  "npm-package-arg": "13.0.0",
625
- "npm-run-all2": "8.0.4",
626
626
  "pacote": "21.0.1",
627
627
  "picomatch": "2.3.1",
628
628
  "semver": "7.7.2",
@@ -632,7 +632,7 @@
632
632
  "taze": "19.6.0",
633
633
  "trash": "10.0.0",
634
634
  "type-coverage": "2.29.7",
635
- "typescript": "5.7.3",
635
+ "typescript": "5.9.2",
636
636
  "typescript-eslint": "8.44.1",
637
637
  "validate-npm-package-name": "6.0.2",
638
638
  "vite-tsconfig-paths": "5.1.4",