@socketsecurity/lib 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,29 @@ 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
+ ## [2.7.0](https://github.com/SocketDev/socket-lib/releases/tag/v2.7.0) - 2025-10-28
9
+
10
+ ### Added
11
+
12
+ - **DLX cache locking for concurrent installation protection**: Added process-lock protection to dlx-package installation operations
13
+ - Lock file created at `~/.socket/_dlx/<hash>/.lock` (similar to npm npx's `concurrency.lock`)
14
+ - Prevents concurrent installations from corrupting the same package cache
15
+ - Uses 5-second stale timeout and 2-second periodic touching (aligned with npm npx)
16
+ - Double-check pattern verifies installation after acquiring lock to avoid redundant work
17
+ - Completes 100% alignment with npm's npx locking strategy
18
+
19
+ ## [2.6.0](https://github.com/SocketDev/socket-lib/releases/tag/v2.6.0) - 2025-10-28
20
+
21
+ ### Changed
22
+
23
+ - **Process locking aligned with npm npx**: Enhanced process-lock module to match npm's npx locking strategy
24
+ - Reduced stale timeout from 10 seconds to 5 seconds (matches npm npx)
25
+ - Added periodic lock touching (2-second interval) to prevent false stale detection during long operations
26
+ - Implemented second-level granularity for mtime comparison to avoid APFS floating-point precision issues
27
+ - Added automatic touch timer cleanup on process exit
28
+ - Timers use `unref()` to prevent keeping process alive
29
+ - Aligns with npm's npx implementation per https://github.com/npm/cli/pull/8512
30
+
8
31
  ## [2.5.0](https://github.com/SocketDev/socket-lib/releases/tag/v2.5.0) - 2025-10-28
9
32
 
10
33
  ### Added
@@ -1,3 +1,3 @@
1
1
  /* Socket Lib - Built with esbuild */
2
- var j=Object.create;var l=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var E=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var $=(n,e)=>{for(var t in e)l(n,t,{get:e[t],enumerable:!0})},m=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of _(e))!J.call(n,a)&&a!==t&&l(n,a,{get:()=>e[a],enumerable:!(r=v(e,a))||r.enumerable});return n};var k=(n,e,t)=>(t=n!=null?j(E(n)):{},m(e||!n||!n.__esModule?l(t,"default",{value:n,enumerable:!0}):t,n)),I=n=>m(l({},"__esModule",{value:!0}),n);var W={};$(W,{dlxPackage:()=>V,downloadPackage:()=>S,executePackage:()=>O});module.exports=I(W);var P=require("node:crypto"),p=require("node:fs"),s=k(require("node:path")),h=k(require("./external/pacote")),w=require("./constants/platform"),x=require("./constants/packages"),y=require("./fs"),g=require("./path"),D=require("./paths"),b=require("./spawn");const N=/[~^><=xX* ]|\|\|/;function C(n){return(0,P.createHash)("sha256").update(n).digest("hex").slice(0,16)}function A(n){if(n.startsWith("@")){const t=n.split("@");return t.length===3?{name:t[1],version:t[2]}:t.length===2?{name:`@${t[1]}`,version:void 0}:{name:`@${t[1]}`,version:t[2]}}const e=n.lastIndexOf("@");return e===-1?{name:n,version:void 0}:{name:n.slice(0,e),version:n.slice(e+1)}}async function K(n,e,t){const r=C(n),a=(0,g.normalizePath)(s.default.join((0,D.getSocketDlxDir)(),r)),c=(0,g.normalizePath)(s.default.join(a,"node_modules",e));if(!t&&(0,p.existsSync)(c)){const i=s.default.join(c,"package.json");if((0,p.existsSync)(i))return{installed:!1,packageDir:a}}await p.promises.mkdir(a,{recursive:!0});const o=(0,x.getPacoteCachePath)();return await h.default.extract(n,c,{cache:o||s.default.join(a,".cache")}),{installed:!0,packageDir:a}}function T(n,e,t){const r=(0,g.normalizePath)(s.default.join(n,"node_modules",e)),a=s.default.join(r,"package.json"),o=(0,y.readJsonSync)(a).bin;let i;if(typeof o=="string")i=o;else if(typeof o=="object"&&o!==null){const d=t||e.split("/").pop();i=o[d]}if(!i)throw new Error(`No binary found for package "${e}"`);return(0,g.normalizePath)(s.default.join(r,i))}async function V(n,e,t){const r=await S(e),a=O(r.binaryPath,n,e?.spawnOptions,t);return{...r,spawnPromise:a}}async function S(n){const{force:e,package:t}={__proto__:null,...n},{name:r,version:a}=A(t),c=a!==void 0&&N.test(a),o=e!==void 0?e:c,i=a?`${r}@${a}`:r,{installed:d,packageDir:u}=await K(i,r,o),f=T(u,r);if(!w.WIN32&&(0,p.existsSync)(f)){const{chmodSync:R}=require("node:fs");try{R(f,493)}catch{}}return{binaryPath:f,installed:d,packageDir:u}}function O(n,e,t,r){return(0,b.spawn)(n,e,t,r)}0&&(module.exports={dlxPackage,downloadPackage,executePackage});
2
+ var v=Object.create;var d=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var I=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var $=(n,e)=>{for(var t in e)d(n,t,{get:e[t],enumerable:!0})},m=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of E(e))!J.call(n,a)&&a!==t&&d(n,a,{get:()=>e[a],enumerable:!(o=_(e,a))||o.enumerable});return n};var k=(n,e,t)=>(t=n!=null?v(I(n)):{},m(e||!n||!n.__esModule?d(t,"default",{value:n,enumerable:!0}):t,n)),N=n=>m(d({},"__esModule",{value:!0}),n);var V={};$(V,{dlxPackage:()=>T,downloadPackage:()=>j,executePackage:()=>O});module.exports=N(V);var P=require("node:crypto"),p=require("node:fs"),i=k(require("node:path")),h=k(require("./external/pacote")),w=require("./constants/platform"),x=require("./constants/packages"),y=require("./fs"),g=require("./path"),D=require("./paths"),b=require("./process-lock"),S=require("./spawn");const C=/[~^><=xX* ]|\|\|/;function A(n){return(0,P.createHash)("sha256").update(n).digest("hex").slice(0,16)}function K(n){if(n.startsWith("@")){const t=n.split("@");return t.length===3?{name:t[1],version:t[2]}:t.length===2?{name:`@${t[1]}`,version:void 0}:{name:`@${t[1]}`,version:t[2]}}const e=n.lastIndexOf("@");return e===-1?{name:n,version:void 0}:{name:n.slice(0,e),version:n.slice(e+1)}}async function L(n,e,t){const o=A(n),a=(0,g.normalizePath)(i.default.join((0,D.getSocketDlxDir)(),o)),c=(0,g.normalizePath)(i.default.join(a,"node_modules",e)),r=i.default.join(a,".lock");return await b.processLock.withLock(r,async()=>{if(!t&&(0,p.existsSync)(c)){const l=i.default.join(c,"package.json");if((0,p.existsSync)(l))return{installed:!1,packageDir:a}}await p.promises.mkdir(a,{recursive:!0});const s=(0,x.getPacoteCachePath)();return await h.default.extract(n,c,{cache:s||i.default.join(a,".cache")}),{installed:!0,packageDir:a}},{staleMs:5e3,touchIntervalMs:2e3})}function M(n,e,t){const o=(0,g.normalizePath)(i.default.join(n,"node_modules",e)),a=i.default.join(o,"package.json"),r=(0,y.readJsonSync)(a).bin;let s;if(typeof r=="string")s=r;else if(typeof r=="object"&&r!==null){const l=t||e.split("/").pop();s=r[l]}if(!s)throw new Error(`No binary found for package "${e}"`);return(0,g.normalizePath)(i.default.join(o,s))}async function T(n,e,t){const o=await j(e),a=O(o.binaryPath,n,e?.spawnOptions,t);return{...o,spawnPromise:a}}async function j(n){const{force:e,package:t}={__proto__:null,...n},{name:o,version:a}=K(t),c=a!==void 0&&C.test(a),r=e!==void 0?e:c,s=a?`${o}@${a}`:o,{installed:l,packageDir:u}=await L(s,o,r),f=M(u,o);if(!w.WIN32&&(0,p.existsSync)(f)){const{chmodSync:R}=require("node:fs");try{R(f,493)}catch{}}return{binaryPath:f,installed:l,packageDir:u}}function O(n,e,t,o){return(0,S.spawn)(n,e,t,o)}0&&(module.exports={dlxPackage,downloadPackage,executePackage});
3
3
  //# sourceMappingURL=dlx-package.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/dlx-package.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview DLX package execution - Install and execute npm packages.\n *\n * This module provides functionality to install and execute npm packages\n * in the ~/.socket/_dlx directory, similar to npx but with Socket's own cache.\n *\n * Uses content-addressed storage like npm's _npx:\n * - Hash is generated from package spec (name@version)\n * - Each unique spec gets its own directory: ~/.socket/_dlx/<hash>/\n * - Allows caching multiple versions of the same package\n *\n * Version range handling:\n * - Exact versions (1.0.0) use cache if available\n * - Range versions (^1.0.0, ~1.0.0) auto-force to get latest within range\n * - User can override with explicit force: false\n *\n * Key difference from dlx-binary.ts:\n * - dlx-binary.ts: Downloads standalone binaries from URLs\n * - dlx-package.ts: Installs npm packages from registries\n *\n * Implementation:\n * - Uses pacote for package installation (no npm CLI required)\n * - Split into downloadPackage() and executePackage() for flexibility\n * - dlxPackage() combines both for convenience\n */\n\nimport { createHash } from 'node:crypto'\nimport { existsSync, promises as fs } from 'node:fs'\nimport path from 'node:path'\n\nimport pacote from './external/pacote'\nimport { WIN32 } from './constants/platform'\nimport { getPacoteCachePath } from './constants/packages'\nimport { readJsonSync } from './fs'\nimport { normalizePath } from './path'\nimport { getSocketDlxDir } from './paths'\nimport type { SpawnExtra, SpawnOptions } from './spawn'\nimport { spawn } from './spawn'\n\n/**\n * Regex to check if a version string contains range operators.\n * Matches any version with range operators: ~, ^, >, <, =, x, X, *, spaces, or ||.\n */\nconst rangeOperatorsRegExp = /[~^><=xX* ]|\\|\\|/\n\nexport interface DownloadPackageResult {\n /** Path to the installed package directory. */\n packageDir: string\n /** Path to the binary. */\n binaryPath: string\n /** Whether the package was newly installed. */\n installed: boolean\n}\n\nexport interface DlxPackageOptions {\n /**\n * Package to install (e.g., '@cyclonedx/cdxgen@10.0.0').\n */\n package: string\n /**\n * Force reinstallation even if package exists.\n */\n force?: boolean | undefined\n /**\n * Additional spawn options for the execution.\n */\n spawnOptions?: SpawnOptions | undefined\n}\n\nexport interface DlxPackageResult {\n /** Path to the installed package directory. */\n packageDir: string\n /** Path to the binary that was executed. */\n binaryPath: string\n /** Whether the package was newly installed. */\n installed: boolean\n /** The spawn promise for the running process. */\n spawnPromise: ReturnType<typeof spawn>\n}\n\n/**\n * Generate a cache key from package spec, similar to npm's _npx.\n * Uses first 16 hex characters of SHA256 hash.\n */\nfunction generatePackageCacheKey(packageSpec: string): string {\n return createHash('sha256').update(packageSpec).digest('hex').slice(0, 16)\n}\n\n/**\n * Parse package spec into name and version.\n * Examples:\n * - 'lodash@4.17.21' \u2192 { name: 'lodash', version: '4.17.21' }\n * - '@scope/pkg@1.0.0' \u2192 { name: '@scope/pkg', version: '1.0.0' }\n * - 'lodash' \u2192 { name: 'lodash', version: undefined }\n */\nfunction parsePackageSpec(spec: string): {\n name: string\n version: string | undefined\n} {\n // Handle scoped packages (@scope/name@version).\n if (spec.startsWith('@')) {\n const parts = spec.split('@')\n if (parts.length === 3) {\n // @scope@version -> Invalid, but handle gracefully.\n return { name: parts[1], version: parts[2] }\n }\n if (parts.length === 2) {\n // @scope/name with no version.\n return { name: `@${parts[1]}`, version: undefined }\n }\n // @scope/name@version.\n const scopeAndName = `@${parts[1]}`\n return { name: scopeAndName, version: parts[2] }\n }\n\n // Handle unscoped packages (name@version).\n const atIndex = spec.lastIndexOf('@')\n if (atIndex === -1) {\n return { name: spec, version: undefined }\n }\n\n return {\n name: spec.slice(0, atIndex),\n version: spec.slice(atIndex + 1),\n }\n}\n\n/**\n * Install package to ~/.socket/_dlx/<hash>/ if not already installed.\n * Uses pacote for installation (no npm CLI required).\n */\nasync function ensurePackageInstalled(\n packageSpec: string,\n packageName: string,\n force: boolean,\n): Promise<{ installed: boolean; packageDir: string }> {\n const cacheKey = generatePackageCacheKey(packageSpec)\n const packageDir = normalizePath(path.join(getSocketDlxDir(), cacheKey))\n const installedDir = normalizePath(\n path.join(packageDir, 'node_modules', packageName),\n )\n\n // Check if already installed (unless force).\n if (!force && existsSync(installedDir)) {\n // Verify package.json exists.\n const pkgJsonPath = path.join(installedDir, 'package.json')\n if (existsSync(pkgJsonPath)) {\n return { installed: false, packageDir }\n }\n }\n\n // Ensure package directory exists.\n await fs.mkdir(packageDir, { recursive: true })\n\n // Use pacote to extract the package.\n // Pacote leverages npm cache when available but doesn't require npm CLI.\n const pacoteCachePath = getPacoteCachePath()\n await pacote.extract(packageSpec, installedDir, {\n // Use consistent pacote cache path (respects npm cache locations when available).\n cache: pacoteCachePath || path.join(packageDir, '.cache'),\n })\n\n return { installed: true, packageDir }\n}\n\n/**\n * Find the binary path for an installed package.\n */\nfunction findBinaryPath(\n packageDir: string,\n packageName: string,\n binaryName?: string,\n): string {\n const installedDir = normalizePath(\n path.join(packageDir, 'node_modules', packageName),\n )\n const pkgJsonPath = path.join(installedDir, 'package.json')\n\n // Read package.json to find bin entry.\n const pkgJson = readJsonSync(pkgJsonPath) as Record<string, unknown>\n const bin = pkgJson['bin']\n\n let binPath: string | undefined\n\n if (typeof bin === 'string') {\n // Single binary.\n binPath = bin\n } else if (typeof bin === 'object' && bin !== null) {\n // Multiple binaries - use binaryName or package name.\n const binName = binaryName || packageName.split('/').pop()\n binPath = (bin as Record<string, string>)[binName!]\n }\n\n if (!binPath) {\n throw new Error(`No binary found for package \"${packageName}\"`)\n }\n\n return normalizePath(path.join(installedDir, binPath))\n}\n\n/**\n * Execute a package via DLX - install if needed and run its binary.\n *\n * This is the Socket equivalent of npx/pnpm dlx/yarn dlx, but using\n * our own cache directory (~/.socket/_dlx) and installation logic.\n *\n * Auto-forces reinstall for version ranges to get latest within range.\n *\n * @example\n * ```typescript\n * // Download and execute cdxgen\n * const result = await dlxPackage(\n * ['--version'],\n * { package: '@cyclonedx/cdxgen@10.0.0' }\n * )\n * await result.spawnPromise\n * ```\n */\nexport async function dlxPackage(\n args: readonly string[] | string[],\n options?: DlxPackageOptions | undefined,\n spawnExtra?: SpawnExtra | undefined,\n): Promise<DlxPackageResult> {\n // Download the package.\n const downloadResult = await downloadPackage(options!)\n\n // Execute the binary.\n const spawnPromise = executePackage(\n downloadResult.binaryPath,\n args,\n options?.spawnOptions,\n spawnExtra,\n )\n\n return {\n ...downloadResult,\n spawnPromise,\n }\n}\n\n/**\n * Download and install a package without executing it.\n * This is useful for self-update or when you need the package files\n * but don't want to run the binary immediately.\n *\n * @example\n * ```typescript\n * // Install @socketsecurity/cli without running it\n * const result = await downloadPackage({\n * package: '@socketsecurity/cli@1.2.0',\n * force: true\n * })\n * console.log('Installed to:', result.packageDir)\n * console.log('Binary at:', result.binaryPath)\n * ```\n */\nexport async function downloadPackage(\n options: DlxPackageOptions,\n): Promise<DownloadPackageResult> {\n const { force: userForce, package: packageSpec } = {\n __proto__: null,\n ...options,\n } as DlxPackageOptions\n\n // Parse package spec.\n const { name: packageName, version: packageVersion } =\n parsePackageSpec(packageSpec)\n\n // Auto-force for version ranges to get latest within range.\n // User can still override with explicit force: false if they want cache.\n const isVersionRange =\n packageVersion !== undefined && rangeOperatorsRegExp.test(packageVersion)\n const force = userForce !== undefined ? userForce : isVersionRange\n\n // Build full package spec for installation.\n const fullPackageSpec = packageVersion\n ? `${packageName}@${packageVersion}`\n : packageName\n\n // Ensure package is installed.\n const { installed, packageDir } = await ensurePackageInstalled(\n fullPackageSpec,\n packageName,\n force,\n )\n\n // Find binary path.\n const binaryPath = findBinaryPath(packageDir, packageName)\n\n // Make binary executable on Unix systems.\n if (!WIN32 && existsSync(binaryPath)) {\n const { chmodSync } = require('node:fs')\n try {\n chmodSync(binaryPath, 0o755)\n } catch {\n // Ignore chmod errors.\n }\n }\n\n return {\n binaryPath,\n installed,\n packageDir,\n }\n}\n\n/**\n * Execute a package's binary.\n * The package must already be installed (use downloadPackage first).\n *\n * @example\n * ```typescript\n * // Execute an already-installed package\n * const downloaded = await downloadPackage({ package: 'cowsay@1.5.0' })\n * const result = await executePackage(\n * downloaded.binaryPath,\n * ['Hello World'],\n * { stdio: 'inherit' }\n * )\n * ```\n */\nexport function executePackage(\n binaryPath: string,\n args: readonly string[] | string[],\n spawnOptions?: SpawnOptions | undefined,\n spawnExtra?: SpawnExtra | undefined,\n): ReturnType<typeof spawn> {\n return spawn(binaryPath, args, spawnOptions, spawnExtra)\n}\n"],
5
- "mappings": "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",
6
- "names": ["dlx_package_exports", "__export", "dlxPackage", "downloadPackage", "executePackage", "__toCommonJS", "import_node_crypto", "import_node_fs", "import_node_path", "import_pacote", "import_platform", "import_packages", "import_fs", "import_path", "import_paths", "import_spawn", "rangeOperatorsRegExp", "generatePackageCacheKey", "packageSpec", "parsePackageSpec", "spec", "parts", "atIndex", "ensurePackageInstalled", "packageName", "force", "cacheKey", "packageDir", "path", "installedDir", "pkgJsonPath", "fs", "pacoteCachePath", "pacote", "findBinaryPath", "binaryName", "bin", "binPath", "binName", "args", "options", "spawnExtra", "downloadResult", "spawnPromise", "userForce", "packageVersion", "isVersionRange", "fullPackageSpec", "installed", "binaryPath", "chmodSync", "spawnOptions"]
4
+ "sourcesContent": ["/**\n * @fileoverview DLX package execution - Install and execute npm packages.\n *\n * This module provides functionality to install and execute npm packages\n * in the ~/.socket/_dlx directory, similar to npx but with Socket's own cache.\n *\n * Uses content-addressed storage like npm's _npx:\n * - Hash is generated from package spec (name@version)\n * - Each unique spec gets its own directory: ~/.socket/_dlx/<hash>/\n * - Allows caching multiple versions of the same package\n *\n * Concurrency protection:\n * - Uses process-lock to prevent concurrent installation corruption\n * - Lock file created at ~/.socket/_dlx/<hash>/.lock\n * - Aligned with npm npx's concurrency.lock strategy (5s stale, 2s touching)\n * - Prevents multiple processes from corrupting the same package installation\n *\n * Version range handling:\n * - Exact versions (1.0.0) use cache if available\n * - Range versions (^1.0.0, ~1.0.0) auto-force to get latest within range\n * - User can override with explicit force: false\n *\n * Key difference from dlx-binary.ts:\n * - dlx-binary.ts: Downloads standalone binaries from URLs\n * - dlx-package.ts: Installs npm packages from registries\n *\n * Implementation:\n * - Uses pacote for package installation (no npm CLI required)\n * - Split into downloadPackage() and executePackage() for flexibility\n * - dlxPackage() combines both for convenience\n */\n\nimport { createHash } from 'node:crypto'\nimport { existsSync, promises as fs } from 'node:fs'\nimport path from 'node:path'\n\nimport pacote from './external/pacote'\nimport { WIN32 } from './constants/platform'\nimport { getPacoteCachePath } from './constants/packages'\nimport { readJsonSync } from './fs'\nimport { normalizePath } from './path'\nimport { getSocketDlxDir } from './paths'\nimport { processLock } from './process-lock'\nimport type { SpawnExtra, SpawnOptions } from './spawn'\nimport { spawn } from './spawn'\n\n/**\n * Regex to check if a version string contains range operators.\n * Matches any version with range operators: ~, ^, >, <, =, x, X, *, spaces, or ||.\n */\nconst rangeOperatorsRegExp = /[~^><=xX* ]|\\|\\|/\n\nexport interface DownloadPackageResult {\n /** Path to the installed package directory. */\n packageDir: string\n /** Path to the binary. */\n binaryPath: string\n /** Whether the package was newly installed. */\n installed: boolean\n}\n\nexport interface DlxPackageOptions {\n /**\n * Package to install (e.g., '@cyclonedx/cdxgen@10.0.0').\n */\n package: string\n /**\n * Force reinstallation even if package exists.\n */\n force?: boolean | undefined\n /**\n * Additional spawn options for the execution.\n */\n spawnOptions?: SpawnOptions | undefined\n}\n\nexport interface DlxPackageResult {\n /** Path to the installed package directory. */\n packageDir: string\n /** Path to the binary that was executed. */\n binaryPath: string\n /** Whether the package was newly installed. */\n installed: boolean\n /** The spawn promise for the running process. */\n spawnPromise: ReturnType<typeof spawn>\n}\n\n/**\n * Generate a cache key from package spec, similar to npm's _npx.\n * Uses first 16 hex characters of SHA256 hash.\n */\nfunction generatePackageCacheKey(packageSpec: string): string {\n return createHash('sha256').update(packageSpec).digest('hex').slice(0, 16)\n}\n\n/**\n * Parse package spec into name and version.\n * Examples:\n * - 'lodash@4.17.21' \u2192 { name: 'lodash', version: '4.17.21' }\n * - '@scope/pkg@1.0.0' \u2192 { name: '@scope/pkg', version: '1.0.0' }\n * - 'lodash' \u2192 { name: 'lodash', version: undefined }\n */\nfunction parsePackageSpec(spec: string): {\n name: string\n version: string | undefined\n} {\n // Handle scoped packages (@scope/name@version).\n if (spec.startsWith('@')) {\n const parts = spec.split('@')\n if (parts.length === 3) {\n // @scope@version -> Invalid, but handle gracefully.\n return { name: parts[1], version: parts[2] }\n }\n if (parts.length === 2) {\n // @scope/name with no version.\n return { name: `@${parts[1]}`, version: undefined }\n }\n // @scope/name@version.\n const scopeAndName = `@${parts[1]}`\n return { name: scopeAndName, version: parts[2] }\n }\n\n // Handle unscoped packages (name@version).\n const atIndex = spec.lastIndexOf('@')\n if (atIndex === -1) {\n return { name: spec, version: undefined }\n }\n\n return {\n name: spec.slice(0, atIndex),\n version: spec.slice(atIndex + 1),\n }\n}\n\n/**\n * Install package to ~/.socket/_dlx/<hash>/ if not already installed.\n * Uses pacote for installation (no npm CLI required).\n * Protected by process lock to prevent concurrent installation corruption.\n */\nasync function ensurePackageInstalled(\n packageSpec: string,\n packageName: string,\n force: boolean,\n): Promise<{ installed: boolean; packageDir: string }> {\n const cacheKey = generatePackageCacheKey(packageSpec)\n const packageDir = normalizePath(path.join(getSocketDlxDir(), cacheKey))\n const installedDir = normalizePath(\n path.join(packageDir, 'node_modules', packageName),\n )\n\n // Use process lock to prevent concurrent installations.\n // Similar to npm npx's concurrency.lock approach.\n const lockPath = path.join(packageDir, '.lock')\n\n return await processLock.withLock(\n lockPath,\n async () => {\n // Double-check if already installed (unless force).\n // Another process may have installed while waiting for lock.\n if (!force && existsSync(installedDir)) {\n // Verify package.json exists.\n const pkgJsonPath = path.join(installedDir, 'package.json')\n if (existsSync(pkgJsonPath)) {\n return { installed: false, packageDir }\n }\n }\n\n // Ensure package directory exists.\n await fs.mkdir(packageDir, { recursive: true })\n\n // Use pacote to extract the package.\n // Pacote leverages npm cache when available but doesn't require npm CLI.\n const pacoteCachePath = getPacoteCachePath()\n await pacote.extract(packageSpec, installedDir, {\n // Use consistent pacote cache path (respects npm cache locations when available).\n cache: pacoteCachePath || path.join(packageDir, '.cache'),\n })\n\n return { installed: true, packageDir }\n },\n {\n // Align with npm npx locking strategy.\n staleMs: 5000,\n touchIntervalMs: 2000,\n },\n )\n}\n\n/**\n * Find the binary path for an installed package.\n */\nfunction findBinaryPath(\n packageDir: string,\n packageName: string,\n binaryName?: string,\n): string {\n const installedDir = normalizePath(\n path.join(packageDir, 'node_modules', packageName),\n )\n const pkgJsonPath = path.join(installedDir, 'package.json')\n\n // Read package.json to find bin entry.\n const pkgJson = readJsonSync(pkgJsonPath) as Record<string, unknown>\n const bin = pkgJson['bin']\n\n let binPath: string | undefined\n\n if (typeof bin === 'string') {\n // Single binary.\n binPath = bin\n } else if (typeof bin === 'object' && bin !== null) {\n // Multiple binaries - use binaryName or package name.\n const binName = binaryName || packageName.split('/').pop()\n binPath = (bin as Record<string, string>)[binName!]\n }\n\n if (!binPath) {\n throw new Error(`No binary found for package \"${packageName}\"`)\n }\n\n return normalizePath(path.join(installedDir, binPath))\n}\n\n/**\n * Execute a package via DLX - install if needed and run its binary.\n *\n * This is the Socket equivalent of npx/pnpm dlx/yarn dlx, but using\n * our own cache directory (~/.socket/_dlx) and installation logic.\n *\n * Auto-forces reinstall for version ranges to get latest within range.\n *\n * @example\n * ```typescript\n * // Download and execute cdxgen\n * const result = await dlxPackage(\n * ['--version'],\n * { package: '@cyclonedx/cdxgen@10.0.0' }\n * )\n * await result.spawnPromise\n * ```\n */\nexport async function dlxPackage(\n args: readonly string[] | string[],\n options?: DlxPackageOptions | undefined,\n spawnExtra?: SpawnExtra | undefined,\n): Promise<DlxPackageResult> {\n // Download the package.\n const downloadResult = await downloadPackage(options!)\n\n // Execute the binary.\n const spawnPromise = executePackage(\n downloadResult.binaryPath,\n args,\n options?.spawnOptions,\n spawnExtra,\n )\n\n return {\n ...downloadResult,\n spawnPromise,\n }\n}\n\n/**\n * Download and install a package without executing it.\n * This is useful for self-update or when you need the package files\n * but don't want to run the binary immediately.\n *\n * @example\n * ```typescript\n * // Install @socketsecurity/cli without running it\n * const result = await downloadPackage({\n * package: '@socketsecurity/cli@1.2.0',\n * force: true\n * })\n * console.log('Installed to:', result.packageDir)\n * console.log('Binary at:', result.binaryPath)\n * ```\n */\nexport async function downloadPackage(\n options: DlxPackageOptions,\n): Promise<DownloadPackageResult> {\n const { force: userForce, package: packageSpec } = {\n __proto__: null,\n ...options,\n } as DlxPackageOptions\n\n // Parse package spec.\n const { name: packageName, version: packageVersion } =\n parsePackageSpec(packageSpec)\n\n // Auto-force for version ranges to get latest within range.\n // User can still override with explicit force: false if they want cache.\n const isVersionRange =\n packageVersion !== undefined && rangeOperatorsRegExp.test(packageVersion)\n const force = userForce !== undefined ? userForce : isVersionRange\n\n // Build full package spec for installation.\n const fullPackageSpec = packageVersion\n ? `${packageName}@${packageVersion}`\n : packageName\n\n // Ensure package is installed.\n const { installed, packageDir } = await ensurePackageInstalled(\n fullPackageSpec,\n packageName,\n force,\n )\n\n // Find binary path.\n const binaryPath = findBinaryPath(packageDir, packageName)\n\n // Make binary executable on Unix systems.\n if (!WIN32 && existsSync(binaryPath)) {\n const { chmodSync } = require('node:fs')\n try {\n chmodSync(binaryPath, 0o755)\n } catch {\n // Ignore chmod errors.\n }\n }\n\n return {\n binaryPath,\n installed,\n packageDir,\n }\n}\n\n/**\n * Execute a package's binary.\n * The package must already be installed (use downloadPackage first).\n *\n * @example\n * ```typescript\n * // Execute an already-installed package\n * const downloaded = await downloadPackage({ package: 'cowsay@1.5.0' })\n * const result = await executePackage(\n * downloaded.binaryPath,\n * ['Hello World'],\n * { stdio: 'inherit' }\n * )\n * ```\n */\nexport function executePackage(\n binaryPath: string,\n args: readonly string[] | string[],\n spawnOptions?: SpawnOptions | undefined,\n spawnExtra?: SpawnExtra | undefined,\n): ReturnType<typeof spawn> {\n return spawn(binaryPath, args, spawnOptions, spawnExtra)\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["dlx_package_exports", "__export", "dlxPackage", "downloadPackage", "executePackage", "__toCommonJS", "import_node_crypto", "import_node_fs", "import_node_path", "import_pacote", "import_platform", "import_packages", "import_fs", "import_path", "import_paths", "import_process_lock", "import_spawn", "rangeOperatorsRegExp", "generatePackageCacheKey", "packageSpec", "parsePackageSpec", "spec", "parts", "atIndex", "ensurePackageInstalled", "packageName", "force", "cacheKey", "packageDir", "path", "installedDir", "lockPath", "pkgJsonPath", "fs", "pacoteCachePath", "pacote", "findBinaryPath", "binaryName", "bin", "binPath", "binName", "args", "options", "spawnExtra", "downloadResult", "spawnPromise", "userForce", "packageVersion", "isVersionRange", "fullPackageSpec", "installed", "binaryPath", "chmodSync", "spawnOptions"]
7
7
  }
@@ -20,10 +20,16 @@ export interface ProcessLockOptions {
20
20
  /**
21
21
  * Stale lock timeout in milliseconds.
22
22
  * Locks older than this are considered abandoned and can be reclaimed.
23
- * Aligned with npm's npx locking strategy (5-10 seconds).
24
- * @default 10000 (10 seconds)
23
+ * Aligned with npm's npx locking strategy (5 seconds).
24
+ * @default 5000 (5 seconds)
25
25
  */
26
26
  staleMs?: number | undefined;
27
+ /**
28
+ * Interval for touching lock file to keep it fresh in milliseconds.
29
+ * Set to 0 to disable periodic touching.
30
+ * @default 2000 (2 seconds)
31
+ */
32
+ touchIntervalMs?: number | undefined;
27
33
  }
28
34
  /**
29
35
  * Process lock manager with stale detection and exit cleanup.
@@ -32,16 +38,38 @@ export interface ProcessLockOptions {
32
38
  */
33
39
  declare class ProcessLockManager {
34
40
  private activeLocks;
41
+ private touchTimers;
35
42
  private exitHandlerRegistered;
36
43
  /**
37
44
  * Ensure process exit handler is registered for cleanup.
38
45
  * Registers a handler that cleans up all active locks when the process exits.
39
46
  */
40
47
  private ensureExitHandler;
48
+ /**
49
+ * Touch a lock file to update its mtime.
50
+ * This prevents the lock from being detected as stale during long operations.
51
+ *
52
+ * @param lockPath - Path to the lock directory
53
+ */
54
+ private touchLock;
55
+ /**
56
+ * Start periodic touching of a lock file.
57
+ * Aligned with npm npx strategy to prevent false stale detection.
58
+ *
59
+ * @param lockPath - Path to the lock directory
60
+ * @param intervalMs - Touch interval in milliseconds
61
+ */
62
+ private startTouchTimer;
63
+ /**
64
+ * Stop periodic touching of a lock file.
65
+ *
66
+ * @param lockPath - Path to the lock directory
67
+ */
68
+ private stopTouchTimer;
41
69
  /**
42
70
  * Check if a lock is stale based on mtime.
43
- * A lock is considered stale if it's older than the specified timeout,
44
- * indicating the holding process likely died abnormally.
71
+ * Uses second-level granularity to avoid APFS floating-point precision issues.
72
+ * Aligned with npm's npx locking strategy.
45
73
  *
46
74
  * @param lockPath - Path to the lock directory
47
75
  * @param staleMs - Stale timeout in milliseconds
@@ -74,7 +102,7 @@ declare class ProcessLockManager {
74
102
  acquire(lockPath: string, options?: ProcessLockOptions): Promise<() => void>;
75
103
  /**
76
104
  * Release a lock and remove from tracking.
77
- * Removes the lock directory and stops tracking it for exit cleanup.
105
+ * Stops periodic touching and removes the lock directory.
78
106
  *
79
107
  * @param lockPath - Path to the lock directory
80
108
  *
@@ -1,3 +1,3 @@
1
1
  /* Socket Lib - Built with esbuild */
2
- var c=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var v=(i,e)=>{for(var r in e)c(i,r,{get:e[r],enumerable:!0})},w=(i,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of g(e))!p.call(i,t)&&t!==r&&c(i,t,{get:()=>e[t],enumerable:!(n=y(e,t))||n.enumerable});return i};var x=i=>w(c({},"__esModule",{value:!0}),i);var E={};v(E,{processLock:()=>S});module.exports=x(E);var s=require("node:fs"),a=require("./fs"),u=require("./logger"),f=require("./promises"),m=require("./signal-exit");class L{activeLocks=new Set;exitHandlerRegistered=!1;ensureExitHandler(){this.exitHandlerRegistered||((0,m.onExit)(()=>{for(const e of this.activeLocks)try{(0,s.existsSync)(e)&&(0,a.safeDeleteSync)(e,{recursive:!0})}catch{}}),this.exitHandlerRegistered=!0)}isStale(e,r){try{if(!(0,s.existsSync)(e))return!1;const n=(0,s.statSync)(e);return Date.now()-n.mtime.getTime()>r}catch{return!1}}async acquire(e,r={}){const{baseDelayMs:n=100,maxDelayMs:t=1e3,retries:l=3,staleMs:d=1e4}=r;return this.ensureExitHandler(),await(0,f.pRetry)(async()=>{try{if((0,s.existsSync)(e)&&this.isStale(e,d)){u.logger.log(`Removing stale lock: ${e}`);try{(0,a.safeDeleteSync)(e,{recursive:!0})}catch{}}return(0,s.mkdirSync)(e,{recursive:!1}),this.activeLocks.add(e),()=>this.release(e)}catch(o){throw o instanceof Error&&o.code==="EEXIST"?this.isStale(e,d)?new Error(`Stale lock detected: ${e}`):new Error(`Lock already exists: ${e}`):o}},{retries:l,baseDelayMs:n,maxDelayMs:t,jitter:!0})}release(e){try{(0,s.existsSync)(e)&&(0,a.safeDeleteSync)(e,{recursive:!0}),this.activeLocks.delete(e)}catch(r){u.logger.warn(`Failed to release lock ${e}: ${r instanceof Error?r.message:String(r)}`)}}async withLock(e,r,n){const t=await this.acquire(e,n);try{return await r()}finally{t()}}}const S=new L;0&&(module.exports={processLock});
2
+ var m=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var y=(n,e)=>{for(var r in e)m(n,r,{get:e[r],enumerable:!0})},T=(n,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of g(e))!p.call(n,i)&&i!==r&&m(n,i,{get:()=>e[i],enumerable:!(s=v(e,i))||s.enumerable});return n};var w=n=>T(m({},"__esModule",{value:!0}),n);var x={};y(x,{processLock:()=>L});module.exports=w(x);var t=require("node:fs"),o=require("./fs"),a=require("./logger"),f=require("./promises"),l=require("./signal-exit");class S{activeLocks=new Set;touchTimers=new Map;exitHandlerRegistered=!1;ensureExitHandler(){this.exitHandlerRegistered||((0,l.onExit)(()=>{for(const e of this.touchTimers.values())clearInterval(e);this.touchTimers.clear();for(const e of this.activeLocks)try{(0,t.existsSync)(e)&&(0,o.safeDeleteSync)(e,{recursive:!0})}catch{}}),this.exitHandlerRegistered=!0)}touchLock(e){try{if((0,t.existsSync)(e)){const r=new Date;(0,t.utimesSync)(e,r,r)}}catch(r){a.logger.warn(`Failed to touch lock ${e}: ${r instanceof Error?r.message:String(r)}`)}}startTouchTimer(e,r){if(r<=0||this.touchTimers.has(e))return;const s=setInterval(()=>{this.touchLock(e)},r);s.unref(),this.touchTimers.set(e,s)}stopTouchTimer(e){const r=this.touchTimers.get(e);r&&(clearInterval(r),this.touchTimers.delete(e))}isStale(e,r){try{if(!(0,t.existsSync)(e))return!1;const s=(0,t.statSync)(e),i=Math.floor((Date.now()-s.mtime.getTime())/1e3),c=Math.floor(r/1e3);return i>c}catch{return!1}}async acquire(e,r={}){const{baseDelayMs:s=100,maxDelayMs:i=1e3,retries:c=3,staleMs:d=5e3,touchIntervalMs:h=2e3}=r;return this.ensureExitHandler(),await(0,f.pRetry)(async()=>{try{if((0,t.existsSync)(e)&&this.isStale(e,d)){a.logger.log(`Removing stale lock: ${e}`);try{(0,o.safeDeleteSync)(e,{recursive:!0})}catch{}}return(0,t.mkdirSync)(e,{recursive:!1}),this.activeLocks.add(e),this.startTouchTimer(e,h),()=>this.release(e)}catch(u){throw u instanceof Error&&u.code==="EEXIST"?this.isStale(e,d)?new Error(`Stale lock detected: ${e}`):new Error(`Lock already exists: ${e}`):u}},{retries:c,baseDelayMs:s,maxDelayMs:i,jitter:!0})}release(e){this.stopTouchTimer(e);try{(0,t.existsSync)(e)&&(0,o.safeDeleteSync)(e,{recursive:!0}),this.activeLocks.delete(e)}catch(r){a.logger.warn(`Failed to release lock ${e}: ${r instanceof Error?r.message:String(r)}`)}}async withLock(e,r,s){const i=await this.acquire(e,s);try{return await r()}finally{i()}}}const L=new S;0&&(module.exports={processLock});
3
3
  //# sourceMappingURL=process-lock.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/process-lock.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview Process locking utilities with stale detection and exit cleanup.\n * Provides cross-platform inter-process synchronization using file-system based locks.\n */\n\nimport { existsSync, mkdirSync, statSync } from 'node:fs'\n\nimport { safeDeleteSync } from './fs'\nimport { logger } from './logger'\nimport { pRetry } from './promises'\nimport { onExit } from './signal-exit'\n\n/**\n * Lock acquisition options.\n */\nexport interface ProcessLockOptions {\n /**\n * Maximum number of retry attempts.\n * @default 3\n */\n retries?: number | undefined\n\n /**\n * Base delay between retries in milliseconds.\n * @default 100\n */\n baseDelayMs?: number | undefined\n\n /**\n * Maximum delay between retries in milliseconds.\n * @default 1000\n */\n maxDelayMs?: number | undefined\n\n /**\n * Stale lock timeout in milliseconds.\n * Locks older than this are considered abandoned and can be reclaimed.\n * Aligned with npm's npx locking strategy (5-10 seconds).\n * @default 10000 (10 seconds)\n */\n staleMs?: number | undefined\n}\n\n/**\n * Process lock manager with stale detection and exit cleanup.\n * Provides cross-platform inter-process synchronization using file-system\n * based locks.\n */\nclass ProcessLockManager {\n private activeLocks = new Set<string>()\n private exitHandlerRegistered = false\n\n /**\n * Ensure process exit handler is registered for cleanup.\n * Registers a handler that cleans up all active locks when the process exits.\n */\n private ensureExitHandler() {\n if (this.exitHandlerRegistered) {\n return\n }\n\n onExit(() => {\n for (const lockPath of this.activeLocks) {\n try {\n if (existsSync(lockPath)) {\n safeDeleteSync(lockPath, { recursive: true })\n }\n } catch {\n // Ignore cleanup errors during exit\n }\n }\n })\n\n this.exitHandlerRegistered = true\n }\n\n /**\n * Check if a lock is stale based on mtime.\n * A lock is considered stale if it's older than the specified timeout,\n * indicating the holding process likely died abnormally.\n *\n * @param lockPath - Path to the lock directory\n * @param staleMs - Stale timeout in milliseconds\n * @returns True if lock exists and is stale\n */\n private isStale(lockPath: string, staleMs: number): boolean {\n try {\n if (!existsSync(lockPath)) {\n return false\n }\n\n const stats = statSync(lockPath)\n const age = Date.now() - stats.mtime.getTime()\n return age > staleMs\n } catch {\n return false\n }\n }\n\n /**\n * Acquire a lock using mkdir for atomic operation.\n * Handles stale locks and includes exit cleanup.\n *\n * This method attempts to create a lock directory atomically. If the lock\n * already exists, it checks if it's stale and removes it before retrying.\n * Uses exponential backoff with jitter for retry attempts.\n *\n * @param lockPath - Path to the lock directory\n * @param options - Lock acquisition options\n * @returns Release function to unlock\n * @throws Error if lock cannot be acquired after all retries\n *\n * @example\n * ```typescript\n * const release = await processLock.acquire('/tmp/my-lock')\n * try {\n * // Critical section\n * } finally {\n * release()\n * }\n * ```\n */\n async acquire(\n lockPath: string,\n options: ProcessLockOptions = {},\n ): Promise<() => void> {\n const {\n baseDelayMs = 100,\n maxDelayMs = 1000,\n retries = 3,\n staleMs = 10_000,\n } = options\n\n // Ensure exit handler is registered before any lock acquisition\n this.ensureExitHandler()\n\n return await pRetry(\n async () => {\n try {\n // Check for stale lock and remove if necessary\n if (existsSync(lockPath) && this.isStale(lockPath, staleMs)) {\n logger.log(`Removing stale lock: ${lockPath}`)\n try {\n safeDeleteSync(lockPath, { recursive: true })\n } catch {\n // Ignore errors removing stale lock - will retry\n }\n }\n\n // Atomic lock acquisition via mkdir\n mkdirSync(lockPath, { recursive: false })\n\n // Track lock for cleanup\n this.activeLocks.add(lockPath)\n\n // Return release function\n return () => this.release(lockPath)\n } catch (error) {\n // Handle lock contention\n if (error instanceof Error && (error as any).code === 'EEXIST') {\n if (this.isStale(lockPath, staleMs)) {\n throw new Error(`Stale lock detected: ${lockPath}`)\n }\n throw new Error(`Lock already exists: ${lockPath}`)\n }\n throw error\n }\n },\n {\n retries,\n baseDelayMs,\n maxDelayMs,\n jitter: true,\n },\n )\n }\n\n /**\n * Release a lock and remove from tracking.\n * Removes the lock directory and stops tracking it for exit cleanup.\n *\n * @param lockPath - Path to the lock directory\n *\n * @example\n * ```typescript\n * processLock.release('/tmp/my-lock')\n * ```\n */\n release(lockPath: string): void {\n try {\n if (existsSync(lockPath)) {\n safeDeleteSync(lockPath, { recursive: true })\n }\n this.activeLocks.delete(lockPath)\n } catch (error) {\n logger.warn(\n `Failed to release lock ${lockPath}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n /**\n * Execute a function with exclusive lock protection.\n * Automatically handles lock acquisition, execution, and cleanup.\n *\n * This is the recommended way to use process locks, as it guarantees\n * cleanup even if the callback throws an error.\n *\n * @param lockPath - Path to the lock directory\n * @param fn - Function to execute while holding the lock\n * @param options - Lock acquisition options\n * @returns Result of the callback function\n * @throws Error from callback or lock acquisition failure\n *\n * @example\n * ```typescript\n * const result = await processLock.withLock('/tmp/my-lock', async () => {\n * // Critical section\n * return someValue\n * })\n * ```\n */\n async withLock<T>(\n lockPath: string,\n fn: () => Promise<T>,\n options?: ProcessLockOptions,\n ): Promise<T> {\n const release = await this.acquire(lockPath, options)\n try {\n return await fn()\n } finally {\n release()\n }\n }\n}\n\n// Export singleton instance.\nexport const processLock = new ProcessLockManager()\n"],
5
- "mappings": ";4ZAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,iBAAAE,IAAA,eAAAC,EAAAH,GAKA,IAAAI,EAAgD,mBAEhDC,EAA+B,gBAC/BC,EAAuB,oBACvBC,EAAuB,sBACvBC,EAAuB,yBAsCvB,MAAMC,CAAmB,CACf,YAAc,IAAI,IAClB,sBAAwB,GAMxB,mBAAoB,CACtB,KAAK,2BAIT,UAAO,IAAM,CACX,UAAWC,KAAY,KAAK,YAC1B,GAAI,IACE,cAAWA,CAAQ,MACrB,kBAAeA,EAAU,CAAE,UAAW,EAAK,CAAC,CAEhD,MAAQ,CAER,CAEJ,CAAC,EAED,KAAK,sBAAwB,GAC/B,CAWQ,QAAQA,EAAkBC,EAA0B,CAC1D,GAAI,CACF,GAAI,IAAC,cAAWD,CAAQ,EACtB,MAAO,GAGT,MAAME,KAAQ,YAASF,CAAQ,EAE/B,OADY,KAAK,IAAI,EAAIE,EAAM,MAAM,QAAQ,EAChCD,CACf,MAAQ,CACN,MAAO,EACT,CACF,CAyBA,MAAM,QACJD,EACAG,EAA8B,CAAC,EACV,CACrB,KAAM,CACJ,YAAAC,EAAc,IACd,WAAAC,EAAa,IACb,QAAAC,EAAU,EACV,QAAAL,EAAU,GACZ,EAAIE,EAGJ,YAAK,kBAAkB,EAEhB,QAAM,UACX,SAAY,CACV,GAAI,CAEF,MAAI,cAAWH,CAAQ,GAAK,KAAK,QAAQA,EAAUC,CAAO,EAAG,CAC3D,SAAO,IAAI,wBAAwBD,CAAQ,EAAE,EAC7C,GAAI,IACF,kBAAeA,EAAU,CAAE,UAAW,EAAK,CAAC,CAC9C,MAAQ,CAER,CACF,CAGA,sBAAUA,EAAU,CAAE,UAAW,EAAM,CAAC,EAGxC,KAAK,YAAY,IAAIA,CAAQ,EAGtB,IAAM,KAAK,QAAQA,CAAQ,CACpC,OAASO,EAAO,CAEd,MAAIA,aAAiB,OAAUA,EAAc,OAAS,SAChD,KAAK,QAAQP,EAAUC,CAAO,EAC1B,IAAI,MAAM,wBAAwBD,CAAQ,EAAE,EAE9C,IAAI,MAAM,wBAAwBA,CAAQ,EAAE,EAE9CO,CACR,CACF,EACA,CACE,QAAAD,EACA,YAAAF,EACA,WAAAC,EACA,OAAQ,EACV,CACF,CACF,CAaA,QAAQL,EAAwB,CAC9B,GAAI,IACE,cAAWA,CAAQ,MACrB,kBAAeA,EAAU,CAAE,UAAW,EAAK,CAAC,EAE9C,KAAK,YAAY,OAAOA,CAAQ,CAClC,OAASO,EAAO,CACd,SAAO,KACL,0BAA0BP,CAAQ,KAAKO,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,CAAC,EAC/F,CACF,CACF,CAuBA,MAAM,SACJP,EACAQ,EACAL,EACY,CACZ,MAAMM,EAAU,MAAM,KAAK,QAAQT,EAAUG,CAAO,EACpD,GAAI,CACF,OAAO,MAAMK,EAAG,CAClB,QAAE,CACAC,EAAQ,CACV,CACF,CACF,CAGO,MAAMjB,EAAc,IAAIO",
6
- "names": ["process_lock_exports", "__export", "processLock", "__toCommonJS", "import_node_fs", "import_fs", "import_logger", "import_promises", "import_signal_exit", "ProcessLockManager", "lockPath", "staleMs", "stats", "options", "baseDelayMs", "maxDelayMs", "retries", "error", "fn", "release"]
4
+ "sourcesContent": ["/**\n * @fileoverview Process locking utilities with stale detection and exit cleanup.\n * Provides cross-platform inter-process synchronization using file-system based locks.\n * Aligned with npm's npx locking strategy (5-second stale timeout, periodic touching).\n */\n\nimport { existsSync, mkdirSync, statSync, utimesSync } from 'node:fs'\n\nimport { safeDeleteSync } from './fs'\nimport { logger } from './logger'\nimport { pRetry } from './promises'\nimport { onExit } from './signal-exit'\n\n/**\n * Lock acquisition options.\n */\nexport interface ProcessLockOptions {\n /**\n * Maximum number of retry attempts.\n * @default 3\n */\n retries?: number | undefined\n\n /**\n * Base delay between retries in milliseconds.\n * @default 100\n */\n baseDelayMs?: number | undefined\n\n /**\n * Maximum delay between retries in milliseconds.\n * @default 1000\n */\n maxDelayMs?: number | undefined\n\n /**\n * Stale lock timeout in milliseconds.\n * Locks older than this are considered abandoned and can be reclaimed.\n * Aligned with npm's npx locking strategy (5 seconds).\n * @default 5000 (5 seconds)\n */\n staleMs?: number | undefined\n\n /**\n * Interval for touching lock file to keep it fresh in milliseconds.\n * Set to 0 to disable periodic touching.\n * @default 2000 (2 seconds)\n */\n touchIntervalMs?: number | undefined\n}\n\n/**\n * Process lock manager with stale detection and exit cleanup.\n * Provides cross-platform inter-process synchronization using file-system\n * based locks.\n */\nclass ProcessLockManager {\n private activeLocks = new Set<string>()\n private touchTimers = new Map<string, NodeJS.Timeout>()\n private exitHandlerRegistered = false\n\n /**\n * Ensure process exit handler is registered for cleanup.\n * Registers a handler that cleans up all active locks when the process exits.\n */\n private ensureExitHandler() {\n if (this.exitHandlerRegistered) {\n return\n }\n\n onExit(() => {\n // Clear all touch timers.\n for (const timer of this.touchTimers.values()) {\n clearInterval(timer)\n }\n this.touchTimers.clear()\n\n // Clean up all active locks.\n for (const lockPath of this.activeLocks) {\n try {\n if (existsSync(lockPath)) {\n safeDeleteSync(lockPath, { recursive: true })\n }\n } catch {\n // Ignore cleanup errors during exit.\n }\n }\n })\n\n this.exitHandlerRegistered = true\n }\n\n /**\n * Touch a lock file to update its mtime.\n * This prevents the lock from being detected as stale during long operations.\n *\n * @param lockPath - Path to the lock directory\n */\n private touchLock(lockPath: string): void {\n try {\n if (existsSync(lockPath)) {\n const now = new Date()\n utimesSync(lockPath, now, now)\n }\n } catch (error) {\n logger.warn(\n `Failed to touch lock ${lockPath}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n /**\n * Start periodic touching of a lock file.\n * Aligned with npm npx strategy to prevent false stale detection.\n *\n * @param lockPath - Path to the lock directory\n * @param intervalMs - Touch interval in milliseconds\n */\n private startTouchTimer(lockPath: string, intervalMs: number): void {\n if (intervalMs <= 0 || this.touchTimers.has(lockPath)) {\n return\n }\n\n const timer = setInterval(() => {\n this.touchLock(lockPath)\n }, intervalMs)\n\n // Prevent timer from keeping process alive.\n timer.unref()\n\n this.touchTimers.set(lockPath, timer)\n }\n\n /**\n * Stop periodic touching of a lock file.\n *\n * @param lockPath - Path to the lock directory\n */\n private stopTouchTimer(lockPath: string): void {\n const timer = this.touchTimers.get(lockPath)\n if (timer) {\n clearInterval(timer)\n this.touchTimers.delete(lockPath)\n }\n }\n\n /**\n * Check if a lock is stale based on mtime.\n * Uses second-level granularity to avoid APFS floating-point precision issues.\n * Aligned with npm's npx locking strategy.\n *\n * @param lockPath - Path to the lock directory\n * @param staleMs - Stale timeout in milliseconds\n * @returns True if lock exists and is stale\n */\n private isStale(lockPath: string, staleMs: number): boolean {\n try {\n if (!existsSync(lockPath)) {\n return false\n }\n\n const stats = statSync(lockPath)\n // Use second-level granularity to avoid APFS issues.\n const ageSeconds = Math.floor((Date.now() - stats.mtime.getTime()) / 1000)\n const staleSeconds = Math.floor(staleMs / 1000)\n return ageSeconds > staleSeconds\n } catch {\n return false\n }\n }\n\n /**\n * Acquire a lock using mkdir for atomic operation.\n * Handles stale locks and includes exit cleanup.\n *\n * This method attempts to create a lock directory atomically. If the lock\n * already exists, it checks if it's stale and removes it before retrying.\n * Uses exponential backoff with jitter for retry attempts.\n *\n * @param lockPath - Path to the lock directory\n * @param options - Lock acquisition options\n * @returns Release function to unlock\n * @throws Error if lock cannot be acquired after all retries\n *\n * @example\n * ```typescript\n * const release = await processLock.acquire('/tmp/my-lock')\n * try {\n * // Critical section\n * } finally {\n * release()\n * }\n * ```\n */\n async acquire(\n lockPath: string,\n options: ProcessLockOptions = {},\n ): Promise<() => void> {\n const {\n baseDelayMs = 100,\n maxDelayMs = 1000,\n retries = 3,\n staleMs = 5000,\n touchIntervalMs = 2000,\n } = options\n\n // Ensure exit handler is registered before any lock acquisition.\n this.ensureExitHandler()\n\n return await pRetry(\n async () => {\n try {\n // Check for stale lock and remove if necessary.\n if (existsSync(lockPath) && this.isStale(lockPath, staleMs)) {\n logger.log(`Removing stale lock: ${lockPath}`)\n try {\n safeDeleteSync(lockPath, { recursive: true })\n } catch {\n // Ignore errors removing stale lock - will retry.\n }\n }\n\n // Atomic lock acquisition via mkdir.\n mkdirSync(lockPath, { recursive: false })\n\n // Track lock for cleanup.\n this.activeLocks.add(lockPath)\n\n // Start periodic touching to prevent stale detection.\n this.startTouchTimer(lockPath, touchIntervalMs)\n\n // Return release function.\n return () => this.release(lockPath)\n } catch (error) {\n // Handle lock contention.\n if (error instanceof Error && (error as any).code === 'EEXIST') {\n if (this.isStale(lockPath, staleMs)) {\n throw new Error(`Stale lock detected: ${lockPath}`)\n }\n throw new Error(`Lock already exists: ${lockPath}`)\n }\n throw error\n }\n },\n {\n retries,\n baseDelayMs,\n maxDelayMs,\n jitter: true,\n },\n )\n }\n\n /**\n * Release a lock and remove from tracking.\n * Stops periodic touching and removes the lock directory.\n *\n * @param lockPath - Path to the lock directory\n *\n * @example\n * ```typescript\n * processLock.release('/tmp/my-lock')\n * ```\n */\n release(lockPath: string): void {\n // Stop periodic touching.\n this.stopTouchTimer(lockPath)\n\n try {\n if (existsSync(lockPath)) {\n safeDeleteSync(lockPath, { recursive: true })\n }\n this.activeLocks.delete(lockPath)\n } catch (error) {\n logger.warn(\n `Failed to release lock ${lockPath}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n /**\n * Execute a function with exclusive lock protection.\n * Automatically handles lock acquisition, execution, and cleanup.\n *\n * This is the recommended way to use process locks, as it guarantees\n * cleanup even if the callback throws an error.\n *\n * @param lockPath - Path to the lock directory\n * @param fn - Function to execute while holding the lock\n * @param options - Lock acquisition options\n * @returns Result of the callback function\n * @throws Error from callback or lock acquisition failure\n *\n * @example\n * ```typescript\n * const result = await processLock.withLock('/tmp/my-lock', async () => {\n * // Critical section\n * return someValue\n * })\n * ```\n */\n async withLock<T>(\n lockPath: string,\n fn: () => Promise<T>,\n options?: ProcessLockOptions,\n ): Promise<T> {\n const release = await this.acquire(lockPath, options)\n try {\n return await fn()\n } finally {\n release()\n }\n }\n}\n\n// Export singleton instance.\nexport const processLock = new ProcessLockManager()\n"],
5
+ "mappings": "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",
6
+ "names": ["process_lock_exports", "__export", "processLock", "__toCommonJS", "import_node_fs", "import_fs", "import_logger", "import_promises", "import_signal_exit", "ProcessLockManager", "timer", "lockPath", "now", "error", "intervalMs", "staleMs", "stats", "ageSeconds", "staleSeconds", "options", "baseDelayMs", "maxDelayMs", "retries", "touchIntervalMs", "fn", "release"]
7
7
  }
@@ -104,7 +104,7 @@ export interface JsonParseOptions {
104
104
  * Maximum allowed size of JSON string in bytes.
105
105
  * Prevents memory exhaustion from extremely large payloads.
106
106
  *
107
- * @default 10485760 (10 MB)
107
+ * @default 10_485_760 (10 MB)
108
108
  *
109
109
  * @example
110
110
  * ```ts
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/validation/types.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview Validation type definitions.\n * Provides core types for schema validation and JSON parsing with security features.\n */\n\n/**\n * Result of a schema validation operation.\n * Contains either successful parsed data or error information.\n *\n * @template T - The expected type of the parsed data\n *\n * @example\n * ```ts\n * const result: ParseResult<User> = schema.safeParse(data)\n * if (result.success) {\n * console.log(result.data) // User object\n * } else {\n * console.error(result.error) // Error details\n * }\n * ```\n */\nexport interface ParseResult<T> {\n /** Indicates whether parsing was successful */\n success: boolean\n /** Parsed and validated data (only present when `success` is `true`) */\n data?: T | undefined\n /** Error information (only present when `success` is `false`) */\n error?: any\n}\n\n/**\n * Base schema interface compatible with Zod and similar validation libraries.\n * Provides both safe and throwing parsing methods.\n *\n * @template T - The expected output type after validation\n *\n * @example\n * ```ts\n * import { z } from 'zod'\n *\n * const userSchema = z.object({\n * name: z.string(),\n * age: z.number()\n * })\n *\n * // Schema satisfies this interface\n * const schema: Schema<User> = userSchema\n * const result = schema.safeParse({ name: 'Alice', age: 30 })\n * ```\n */\nexport interface Schema<T = any> {\n /**\n * Safely parse data without throwing errors.\n * Returns a result object indicating success or failure.\n *\n * @param data - The data to validate\n * @returns Parse result with success flag and data or error\n */\n safeParse(data: any): ParseResult<T>\n\n /**\n * Parse data and throw an error if validation fails.\n * Use this when you want to fail fast on invalid data.\n *\n * @param data - The data to validate\n * @returns The validated and parsed data\n * @throws {Error} When validation fails\n */\n parse(data: any): T\n\n /**\n * Optional schema name for debugging and error messages.\n * Useful for identifying which schema failed in complex validation chains.\n */\n _name?: string | undefined\n}\n\n/**\n * Options for configuring JSON parsing behavior with security controls.\n *\n * @example\n * ```ts\n * const options: JsonParseOptions = {\n * maxSize: 1024 * 1024, // 1MB limit\n * allowPrototype: false // Block prototype pollution\n * }\n * ```\n */\nexport interface JsonParseOptions {\n /**\n * Allow dangerous prototype pollution keys (`__proto__`, `constructor`, `prototype`).\n * Set to `true` only if you trust the JSON source completely.\n *\n * @default false\n *\n * @example\n * ```ts\n * // Will throw error by default\n * safeJsonParse('{\"__proto__\": {\"polluted\": true}}')\n *\n * // Allows the parse (dangerous!)\n * safeJsonParse('{\"__proto__\": {\"polluted\": true}}', undefined, {\n * allowPrototype: true\n * })\n * ```\n */\n allowPrototype?: boolean | undefined\n\n /**\n * Maximum allowed size of JSON string in bytes.\n * Prevents memory exhaustion from extremely large payloads.\n *\n * @default 10485760 (10 MB)\n *\n * @example\n * ```ts\n * // Limit to 1KB\n * safeJsonParse(jsonString, undefined, { maxSize: 1024 })\n * ```\n */\n maxSize?: number | undefined\n}\n\n/**\n * Discriminated union type for JSON parsing results.\n * Enables type-safe handling of success and failure cases.\n *\n * @template T - The expected type of the parsed data\n *\n * @example\n * ```ts\n * const result: JsonParseResult<User> = parseJsonWithResult(jsonString)\n *\n * if (result.success) {\n * // TypeScript knows result.data is available\n * console.log(result.data.name)\n * } else {\n * // TypeScript knows result.error is available\n * console.error(result.error)\n * }\n * ```\n */\nexport type JsonParseResult<T> =\n | { success: true; data: T }\n | { success: false; error: string }\n"],
4
+ "sourcesContent": ["/**\n * @fileoverview Validation type definitions.\n * Provides core types for schema validation and JSON parsing with security features.\n */\n\n/**\n * Result of a schema validation operation.\n * Contains either successful parsed data or error information.\n *\n * @template T - The expected type of the parsed data\n *\n * @example\n * ```ts\n * const result: ParseResult<User> = schema.safeParse(data)\n * if (result.success) {\n * console.log(result.data) // User object\n * } else {\n * console.error(result.error) // Error details\n * }\n * ```\n */\nexport interface ParseResult<T> {\n /** Indicates whether parsing was successful */\n success: boolean\n /** Parsed and validated data (only present when `success` is `true`) */\n data?: T | undefined\n /** Error information (only present when `success` is `false`) */\n error?: any\n}\n\n/**\n * Base schema interface compatible with Zod and similar validation libraries.\n * Provides both safe and throwing parsing methods.\n *\n * @template T - The expected output type after validation\n *\n * @example\n * ```ts\n * import { z } from 'zod'\n *\n * const userSchema = z.object({\n * name: z.string(),\n * age: z.number()\n * })\n *\n * // Schema satisfies this interface\n * const schema: Schema<User> = userSchema\n * const result = schema.safeParse({ name: 'Alice', age: 30 })\n * ```\n */\nexport interface Schema<T = any> {\n /**\n * Safely parse data without throwing errors.\n * Returns a result object indicating success or failure.\n *\n * @param data - The data to validate\n * @returns Parse result with success flag and data or error\n */\n safeParse(data: any): ParseResult<T>\n\n /**\n * Parse data and throw an error if validation fails.\n * Use this when you want to fail fast on invalid data.\n *\n * @param data - The data to validate\n * @returns The validated and parsed data\n * @throws {Error} When validation fails\n */\n parse(data: any): T\n\n /**\n * Optional schema name for debugging and error messages.\n * Useful for identifying which schema failed in complex validation chains.\n */\n _name?: string | undefined\n}\n\n/**\n * Options for configuring JSON parsing behavior with security controls.\n *\n * @example\n * ```ts\n * const options: JsonParseOptions = {\n * maxSize: 1024 * 1024, // 1MB limit\n * allowPrototype: false // Block prototype pollution\n * }\n * ```\n */\nexport interface JsonParseOptions {\n /**\n * Allow dangerous prototype pollution keys (`__proto__`, `constructor`, `prototype`).\n * Set to `true` only if you trust the JSON source completely.\n *\n * @default false\n *\n * @example\n * ```ts\n * // Will throw error by default\n * safeJsonParse('{\"__proto__\": {\"polluted\": true}}')\n *\n * // Allows the parse (dangerous!)\n * safeJsonParse('{\"__proto__\": {\"polluted\": true}}', undefined, {\n * allowPrototype: true\n * })\n * ```\n */\n allowPrototype?: boolean | undefined\n\n /**\n * Maximum allowed size of JSON string in bytes.\n * Prevents memory exhaustion from extremely large payloads.\n *\n * @default 10_485_760 (10 MB)\n *\n * @example\n * ```ts\n * // Limit to 1KB\n * safeJsonParse(jsonString, undefined, { maxSize: 1024 })\n * ```\n */\n maxSize?: number | undefined\n}\n\n/**\n * Discriminated union type for JSON parsing results.\n * Enables type-safe handling of success and failure cases.\n *\n * @template T - The expected type of the parsed data\n *\n * @example\n * ```ts\n * const result: JsonParseResult<User> = parseJsonWithResult(jsonString)\n *\n * if (result.success) {\n * // TypeScript knows result.data is available\n * console.log(result.data.name)\n * } else {\n * // TypeScript knows result.error is available\n * console.error(result.error)\n * }\n * ```\n */\nexport type JsonParseResult<T> =\n | { success: true; data: T }\n | { success: false; error: string }\n"],
5
5
  "mappings": ";kWAAA,IAAAA,EAAA,kBAAAC,EAAAD",
6
6
  "names": ["types_exports", "__toCommonJS"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socketsecurity/lib",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "license": "MIT",
5
5
  "description": "Core utilities and infrastructure for Socket.dev security tools",
6
6
  "keywords": [