@socketsecurity/lib 1.1.1 → 1.2.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/dist/git.js CHANGED
@@ -44,6 +44,7 @@ __export(git_exports, {
44
44
  });
45
45
  module.exports = __toCommonJS(git_exports);
46
46
  var import_node_path = __toESM(require("node:path"));
47
+ var import_platform = require("#constants/platform");
47
48
  var import_globs = require("./globs");
48
49
  var import_path = require("./path");
49
50
  var import_spawn = require("./spawn");
@@ -79,7 +80,7 @@ function getGitDiffSpawnArgs(cwd) {
79
80
  ["status", "--porcelain"],
80
81
  {
81
82
  cwd: resolvedCwd,
82
- shell: process.platform === "win32"
83
+ shell: import_platform.WIN32
83
84
  }
84
85
  ],
85
86
  unstaged: [
@@ -94,7 +95,7 @@ function getGitDiffSpawnArgs(cwd) {
94
95
  ["diff", "--cached", "--name-only"],
95
96
  {
96
97
  cwd: resolvedCwd,
97
- shell: process.platform === "win32"
98
+ shell: import_platform.WIN32
98
99
  }
99
100
  ]
100
101
  };
package/dist/git.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/git.ts"],
4
- "sourcesContent": ["import path from 'node:path'\n\nimport { getGlobMatcher } from './globs'\nimport { normalizePath } from './path'\nimport { spawn, spawnSync } from './spawn'\nimport { stripAnsi } from './strings'\n\n/**\n * Options for git diff operations.\n *\n * Controls how git diff results are processed and returned.\n *\n * @example\n * ```typescript\n * // Get absolute file paths\n * const files = await getChangedFiles({ absolute: true })\n * // => ['/path/to/repo/src/file.ts']\n *\n * // Get relative paths with caching disabled\n * const files = await getChangedFiles({ cache: false })\n * // => ['src/file.ts']\n *\n * // Get files from specific directory\n * const files = await getChangedFiles({ cwd: '/path/to/repo/src' })\n * ```\n */\nexport interface GitDiffOptions {\n /**\n * Return absolute file paths instead of relative paths.\n *\n * @default false\n */\n absolute?: boolean | undefined\n /**\n * Cache git diff results to avoid repeated git subprocess calls.\n *\n * Caching is keyed by the git command and options used, so different\n * option combinations maintain separate cache entries.\n *\n * @default true\n */\n cache?: boolean | undefined\n /**\n * Working directory for git operations.\n *\n * Git operations will be run from this directory, and returned paths\n * will be relative to the git repository root. Symlinks are resolved\n * using `fs.realpathSync()`.\n *\n * @default process.cwd()\n */\n cwd?: string | undefined\n /**\n * Parse git porcelain format output (status codes like `M`, `A`, `??`).\n *\n * When `true`, strips the two-character status code and space from the\n * beginning of each line. Automatically enabled for `getChangedFiles()`.\n *\n * @default false\n */\n porcelain?: boolean | undefined\n /**\n * Return results as a `Set` instead of an array.\n *\n * @default false\n */\n asSet?: boolean | undefined\n /**\n * Additional options passed to glob matcher.\n *\n * Supports options like `dot`, `ignore`, `nocase` for filtering results.\n */\n [key: string]: unknown\n}\n\n/**\n * Options for filtering packages by git changes.\n *\n * Used to determine which packages in a monorepo have changed files.\n *\n * @example\n * ```typescript\n * // Filter packages with changes\n * const changed = filterPackagesByChanges(packages)\n *\n * // Force include all packages\n * const all = filterPackagesByChanges(packages, { force: true })\n *\n * // Use custom package key\n * const changed = filterPackagesByChanges(\n * packages,\n * { packageKey: 'directory' }\n * )\n * ```\n */\nexport interface FilterPackagesByChangesOptions {\n /**\n * Force include all packages regardless of changes.\n *\n * @default false\n */\n force?: boolean | undefined\n /**\n * Key to access package path in package objects.\n *\n * @default 'path'\n */\n packageKey?: string | undefined\n /**\n * Additional options for filtering.\n */\n [key: string]: unknown\n}\n\ntype SpawnArgs = [string, string[], Record<string, unknown>]\n\ninterface GitDiffSpawnArgs {\n all: SpawnArgs\n unstaged: SpawnArgs\n staged: SpawnArgs\n}\n\nconst gitDiffCache = new Map<string, string[]>()\n\nlet _fs: typeof import('fs') | undefined\n/**\n * Lazily load the `fs` module to avoid Webpack errors.\n *\n * Uses non-`node:` prefixed require internally to prevent Webpack from\n * attempting to bundle Node.js built-in modules.\n *\n * @returns The Node.js `fs` module.\n *\n * @example\n * ```typescript\n * const fs = getFs()\n * const exists = fs.existsSync('/path/to/file')\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nfunction getFs() {\n if (_fs === undefined) {\n // Use non-'node:' prefixed require to avoid Webpack errors.\n\n _fs = /*@__PURE__*/ require('node:fs')\n }\n return _fs as typeof import('fs')\n}\n\nlet _path: typeof import('path') | undefined\n/**\n * Lazily load the `path` module to avoid Webpack errors.\n *\n * Uses non-`node:` prefixed require internally to prevent Webpack from\n * attempting to bundle Node.js built-in modules.\n *\n * @returns The Node.js `path` module.\n *\n * @example\n * ```typescript\n * const path = getPath()\n * const joined = path.join('/foo', 'bar')\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nfunction getPath() {\n if (_path === undefined) {\n _path = /*@__PURE__*/ require('node:path')\n }\n return _path as typeof import('path')\n}\n\n/**\n * Get the git executable path.\n *\n * Currently always returns `'git'`, relying on the system PATH to resolve\n * the git binary location. This may be extended in the future to support\n * custom git paths.\n *\n * @returns The git executable name or path.\n *\n * @example\n * ```typescript\n * const git = getGitPath()\n * // => 'git'\n * ```\n */\nfunction getGitPath(): string {\n return 'git'\n}\n\n/**\n * Get the current working directory for git operations.\n *\n * Returns the real path to handle symlinks correctly. This is important\n * because symlinked directories like `/tmp -> /private/tmp` can cause\n * path mismatches when comparing git output.\n *\n * @returns The resolved real path of `process.cwd()`.\n *\n * @example\n * ```typescript\n * const cwd = getCwd()\n * // In /tmp (symlink to /private/tmp):\n * // => '/private/tmp'\n * ```\n */\nfunction getCwd(): string {\n return getFs().realpathSync(process.cwd())\n}\n\n/**\n * Get spawn arguments for different git diff operations.\n *\n * Prepares argument arrays for `spawn()`/`spawnSync()` calls that retrieve:\n * - `all`: All changed files (staged, unstaged, untracked) via `git status --porcelain`\n * - `unstaged`: Unstaged modifications via `git diff --name-only`\n * - `staged`: Staged changes via `git diff --cached --name-only`\n *\n * Automatically resolves symlinks in the provided `cwd` and enables shell\n * mode on Windows for proper command execution.\n *\n * @param cwd - Working directory for git operations, defaults to `process.cwd()`.\n * @returns Object containing spawn arguments for all, unstaged, and staged operations.\n */\nfunction getGitDiffSpawnArgs(cwd?: string | undefined): GitDiffSpawnArgs {\n const resolvedCwd = cwd ? getFs().realpathSync(cwd) : getCwd()\n return {\n all: [\n getGitPath(),\n ['status', '--porcelain'],\n {\n cwd: resolvedCwd,\n shell: process.platform === 'win32',\n },\n ],\n unstaged: [\n getGitPath(),\n ['diff', '--name-only'],\n {\n cwd: resolvedCwd,\n },\n ],\n staged: [\n getGitPath(),\n ['diff', '--cached', '--name-only'],\n {\n cwd: resolvedCwd,\n shell: process.platform === 'win32',\n },\n ],\n }\n}\n\n/**\n * Execute git diff command asynchronously and parse results.\n *\n * Internal helper for async git operations. Handles caching, command execution,\n * and result parsing. Returns empty array on git command failure.\n *\n * @param args - Spawn arguments tuple `[command, args, options]`.\n * @param options - Git diff options for caching and parsing.\n * @returns Promise resolving to array of file paths.\n */\nasync function innerDiff(\n args: SpawnArgs,\n options?: GitDiffOptions | undefined,\n): Promise<string[]> {\n const { cache = true, ...parseOptions } = { __proto__: null, ...options }\n const cacheKey = cache ? JSON.stringify({ args, parseOptions }) : undefined\n if (cache && cacheKey) {\n const result = gitDiffCache.get(cacheKey)\n if (result) {\n return result\n }\n }\n let result: string[]\n try {\n // Use stdioString: false to get raw Buffer, then convert ourselves to preserve exact output.\n const spawnResult = await spawn(args[0], args[1], {\n ...args[2],\n stdioString: false,\n })\n const stdout = Buffer.isBuffer(spawnResult.stdout)\n ? spawnResult.stdout.toString('utf8')\n : String(spawnResult.stdout)\n // Extract spawn cwd from args to pass to parser\n const spawnCwd =\n typeof args[2]['cwd'] === 'string' ? args[2]['cwd'] : undefined\n result = parseGitDiffStdout(stdout, parseOptions, spawnCwd)\n } catch {\n return []\n }\n if (cache && cacheKey) {\n gitDiffCache.set(cacheKey, result)\n }\n return result\n}\n\n/**\n * Execute git diff command synchronously and parse results.\n *\n * Internal helper for sync git operations. Handles caching, command execution,\n * and result parsing. Returns empty array on git command failure.\n *\n * @param args - Spawn arguments tuple `[command, args, options]`.\n * @param options - Git diff options for caching and parsing.\n * @returns Array of file paths.\n */\nfunction innerDiffSync(\n args: SpawnArgs,\n options?: GitDiffOptions | undefined,\n): string[] {\n const { cache = true, ...parseOptions } = { __proto__: null, ...options }\n const cacheKey = cache ? JSON.stringify({ args, parseOptions }) : undefined\n if (cache && cacheKey) {\n const result = gitDiffCache.get(cacheKey)\n if (result) {\n return result\n }\n }\n let result: string[]\n try {\n // Use stdioString: false to get raw Buffer, then convert ourselves to preserve exact output.\n const spawnResult = spawnSync(args[0], args[1], {\n ...args[2],\n stdioString: false,\n })\n const stdout = Buffer.isBuffer(spawnResult.stdout)\n ? spawnResult.stdout.toString('utf8')\n : String(spawnResult.stdout)\n // Extract spawn cwd from args to pass to parser\n const spawnCwd =\n typeof args[2]['cwd'] === 'string' ? args[2]['cwd'] : undefined\n result = parseGitDiffStdout(stdout, parseOptions, spawnCwd)\n } catch {\n return []\n }\n if (cache && cacheKey) {\n gitDiffCache.set(cacheKey, result)\n }\n return result\n}\n\n/**\n * Find git repository root by walking up from the given directory.\n *\n * Searches for a `.git` directory or file by traversing parent directories\n * upward until found or filesystem root is reached. Returns the original path\n * if no git repository is found.\n *\n * This function is exported primarily for testing purposes.\n *\n * @param startPath - Directory path to start searching from.\n * @returns Git repository root path, or `startPath` if not found.\n *\n * @example\n * ```typescript\n * const root = findGitRoot('/path/to/repo/src/subdir')\n * // => '/path/to/repo'\n *\n * const notFound = findGitRoot('/not/a/repo')\n * // => '/not/a/repo'\n * ```\n */\nexport function findGitRoot(startPath: string): string {\n const fs = getFs()\n const path = getPath()\n let currentPath = startPath\n // Walk up the directory tree looking for .git\n while (true) {\n try {\n const gitPath = path.join(currentPath, '.git')\n if (fs.existsSync(gitPath)) {\n return currentPath\n }\n } catch {\n // Ignore errors and continue walking up\n }\n const parentPath = path.dirname(currentPath)\n // Stop if we've reached the root or can't go up anymore\n if (parentPath === currentPath) {\n // Return original path if no .git found\n return startPath\n }\n currentPath = parentPath\n }\n}\n\n/**\n * Parse git diff stdout output into file path array.\n *\n * Internal helper that processes raw git command output by:\n * 1. Finding git repository root from spawn cwd\n * 2. Stripping ANSI codes and splitting into lines\n * 3. Parsing porcelain format status codes if requested\n * 4. Normalizing and optionally making paths absolute\n * 5. Filtering paths based on cwd and glob options\n *\n * Git always returns paths relative to the repository root, regardless of\n * where the command was executed. This function handles the path resolution\n * correctly by finding the repo root and adjusting paths accordingly.\n *\n * @param stdout - Raw stdout from git command.\n * @param options - Git diff options for path processing.\n * @param spawnCwd - Working directory where git command was executed.\n * @returns Array of processed file paths.\n */\nfunction parseGitDiffStdout(\n stdout: string,\n options?: GitDiffOptions | undefined,\n spawnCwd?: string | undefined,\n): string[] {\n // Find git repo root from spawnCwd. Git always returns paths relative to the repo root,\n // not the cwd where it was run. So we need to find the repo root to correctly parse paths.\n const defaultRoot = spawnCwd ? findGitRoot(spawnCwd) : getCwd()\n const {\n absolute = false,\n cwd: cwdOption = defaultRoot,\n porcelain = false,\n ...matcherOptions\n } = { __proto__: null, ...options }\n // Resolve cwd to handle symlinks.\n const cwd =\n cwdOption === defaultRoot ? defaultRoot : getFs().realpathSync(cwdOption)\n const rootPath = defaultRoot\n // Split into lines without trimming to preserve leading spaces in porcelain format.\n let rawFiles = stdout\n ? stripAnsi(stdout)\n .split('\\n')\n .map(line => line.trimEnd())\n .filter(line => line)\n : []\n // Parse porcelain format: strip status codes.\n // Git status --porcelain format is: XY filename\n // where X and Y are single characters and there's a space before the filename.\n if (porcelain) {\n rawFiles = rawFiles.map(line => {\n // Status is first 2 chars, then space, then filename.\n return line.length > 3 ? line.substring(3) : line\n })\n }\n const files = absolute\n ? rawFiles.map(relPath => normalizePath(path.join(rootPath, relPath)))\n : rawFiles.map(relPath => normalizePath(relPath))\n if (cwd === rootPath) {\n return files\n }\n const relPath = normalizePath(path.relative(rootPath, cwd))\n const matcher = getGlobMatcher([`${relPath}/**`], {\n ...(matcherOptions as {\n dot?: boolean\n ignore?: string[]\n nocase?: boolean\n }),\n absolute,\n cwd: rootPath,\n } as {\n absolute?: boolean\n cwd?: string\n dot?: boolean\n ignore?: string[]\n nocase?: boolean\n })\n const filtered: string[] = []\n for (const filepath of files) {\n if (matcher(filepath)) {\n filtered.push(filepath)\n }\n }\n return filtered\n}\n\n/**\n * Get all changed files including staged, unstaged, and untracked files.\n *\n * Uses `git status --porcelain` which returns the full working tree status\n * with status codes:\n * - `M` - Modified\n * - `A` - Added\n * - `D` - Deleted\n * - `??` - Untracked\n * - `R` - Renamed\n * - `C` - Copied\n *\n * This is the most comprehensive check - captures everything that differs\n * from the last commit, including:\n * - Files modified and staged with `git add`\n * - Files modified but not staged\n * - New files not yet tracked by git\n *\n * Status codes are automatically stripped from the output.\n *\n * @param options - Options controlling path format and filtering.\n * @returns Promise resolving to array of changed file paths.\n *\n * @example\n * ```typescript\n * // Get all changed files as relative paths\n * const files = await getChangedFiles()\n * // => ['src/foo.ts', 'src/bar.ts', 'newfile.ts']\n *\n * // Get absolute paths\n * const files = await getChangedFiles({ absolute: true })\n * // => ['/path/to/repo/src/foo.ts', ...]\n *\n * // Get changed files in specific directory\n * const files = await getChangedFiles({ cwd: '/path/to/repo/src' })\n * // => ['foo.ts', 'bar.ts']\n * ```\n */\nexport async function getChangedFiles(\n options?: GitDiffOptions | undefined,\n): Promise<string[]> {\n const args = getGitDiffSpawnArgs(options?.cwd).all\n return await innerDiff(args, {\n __proto__: null,\n ...options,\n porcelain: true,\n })\n}\n\n/**\n * Get all changed files including staged, unstaged, and untracked files.\n *\n * Synchronous version of `getChangedFiles()`. Uses `git status --porcelain`\n * which returns the full working tree status with status codes:\n * - `M` - Modified\n * - `A` - Added\n * - `D` - Deleted\n * - `??` - Untracked\n * - `R` - Renamed\n * - `C` - Copied\n *\n * This is the most comprehensive check - captures everything that differs\n * from the last commit, including:\n * - Files modified and staged with `git add`\n * - Files modified but not staged\n * - New files not yet tracked by git\n *\n * Status codes are automatically stripped from the output.\n *\n * @param options - Options controlling path format and filtering.\n * @returns Array of changed file paths.\n *\n * @example\n * ```typescript\n * // Get all changed files as relative paths\n * const files = getChangedFilesSync()\n * // => ['src/foo.ts', 'src/bar.ts', 'newfile.ts']\n *\n * // Get absolute paths\n * const files = getChangedFilesSync({ absolute: true })\n * // => ['/path/to/repo/src/foo.ts', ...]\n *\n * // Get changed files in specific directory\n * const files = getChangedFilesSync({ cwd: '/path/to/repo/src' })\n * // => ['foo.ts', 'bar.ts']\n * ```\n */\nexport function getChangedFilesSync(\n options?: GitDiffOptions | undefined,\n): string[] {\n const args = getGitDiffSpawnArgs(options?.cwd).all\n return innerDiffSync(args, {\n __proto__: null,\n ...options,\n porcelain: true,\n })\n}\n\n/**\n * Get unstaged modified files (changes not yet staged for commit).\n *\n * Uses `git diff --name-only` which returns only unstaged modifications\n * to tracked files. Does NOT include:\n * - Untracked files (new files not added to git)\n * - Staged changes (files added with `git add`)\n *\n * This is a focused check for uncommitted changes to existing tracked files.\n * Useful for detecting work-in-progress modifications before staging.\n *\n * @param options - Options controlling path format and filtering.\n * @returns Promise resolving to array of unstaged file paths.\n *\n * @example\n * ```typescript\n * // Get unstaged files\n * const files = await getUnstagedFiles()\n * // => ['src/foo.ts', 'src/bar.ts']\n *\n * // After staging some files\n * await spawn('git', ['add', 'src/foo.ts'])\n * const files = await getUnstagedFiles()\n * // => ['src/bar.ts'] (foo.ts no longer included)\n *\n * // Get absolute paths\n * const files = await getUnstagedFiles({ absolute: true })\n * // => ['/path/to/repo/src/bar.ts']\n * ```\n */\nexport async function getUnstagedFiles(\n options?: GitDiffOptions | undefined,\n): Promise<string[]> {\n const args = getGitDiffSpawnArgs(options?.cwd).unstaged\n return await innerDiff(args, options)\n}\n\n/**\n * Get unstaged modified files (changes not yet staged for commit).\n *\n * Synchronous version of `getUnstagedFiles()`. Uses `git diff --name-only`\n * which returns only unstaged modifications to tracked files. Does NOT include:\n * - Untracked files (new files not added to git)\n * - Staged changes (files added with `git add`)\n *\n * This is a focused check for uncommitted changes to existing tracked files.\n * Useful for detecting work-in-progress modifications before staging.\n *\n * @param options - Options controlling path format and filtering.\n * @returns Array of unstaged file paths.\n *\n * @example\n * ```typescript\n * // Get unstaged files\n * const files = getUnstagedFilesSync()\n * // => ['src/foo.ts', 'src/bar.ts']\n *\n * // After staging some files\n * spawnSync('git', ['add', 'src/foo.ts'])\n * const files = getUnstagedFilesSync()\n * // => ['src/bar.ts'] (foo.ts no longer included)\n *\n * // Get absolute paths\n * const files = getUnstagedFilesSync({ absolute: true })\n * // => ['/path/to/repo/src/bar.ts']\n * ```\n */\nexport function getUnstagedFilesSync(\n options?: GitDiffOptions | undefined,\n): string[] {\n const args = getGitDiffSpawnArgs(options?.cwd).unstaged\n return innerDiffSync(args, options)\n}\n\n/**\n * Get staged files ready for commit (changes added with `git add`).\n *\n * Uses `git diff --cached --name-only` which returns only staged changes.\n * Does NOT include:\n * - Unstaged modifications (changes not added with `git add`)\n * - Untracked files (new files not added to git)\n *\n * This is a focused check for what will be included in the next commit.\n * Useful for validating changes before committing or running pre-commit hooks.\n *\n * @param options - Options controlling path format and filtering.\n * @returns Promise resolving to array of staged file paths.\n *\n * @example\n * ```typescript\n * // Get currently staged files\n * const files = await getStagedFiles()\n * // => ['src/foo.ts']\n *\n * // Stage more files\n * await spawn('git', ['add', 'src/bar.ts'])\n * const files = await getStagedFiles()\n * // => ['src/foo.ts', 'src/bar.ts']\n *\n * // Get absolute paths\n * const files = await getStagedFiles({ absolute: true })\n * // => ['/path/to/repo/src/foo.ts', ...]\n * ```\n */\nexport async function getStagedFiles(\n options?: GitDiffOptions | undefined,\n): Promise<string[]> {\n const args = getGitDiffSpawnArgs(options?.cwd).staged\n return await innerDiff(args, options)\n}\n\n/**\n * Get staged files ready for commit (changes added with `git add`).\n *\n * Synchronous version of `getStagedFiles()`. Uses `git diff --cached --name-only`\n * which returns only staged changes. Does NOT include:\n * - Unstaged modifications (changes not added with `git add`)\n * - Untracked files (new files not added to git)\n *\n * This is a focused check for what will be included in the next commit.\n * Useful for validating changes before committing or running pre-commit hooks.\n *\n * @param options - Options controlling path format and filtering.\n * @returns Array of staged file paths.\n *\n * @example\n * ```typescript\n * // Get currently staged files\n * const files = getStagedFilesSync()\n * // => ['src/foo.ts']\n *\n * // Stage more files\n * spawnSync('git', ['add', 'src/bar.ts'])\n * const files = getStagedFilesSync()\n * // => ['src/foo.ts', 'src/bar.ts']\n *\n * // Get absolute paths\n * const files = getStagedFilesSync({ absolute: true })\n * // => ['/path/to/repo/src/foo.ts', ...]\n * ```\n */\nexport function getStagedFilesSync(\n options?: GitDiffOptions | undefined,\n): string[] {\n const args = getGitDiffSpawnArgs(options?.cwd).staged\n return innerDiffSync(args, options)\n}\n\n/**\n * Check if a file or directory has any git changes.\n *\n * Checks if the given pathname has any changes including:\n * - Staged modifications (added with `git add`)\n * - Unstaged modifications (not yet staged)\n * - Untracked status (new file/directory not in git)\n *\n * For directories, returns `true` if ANY file within the directory has changes.\n *\n * Symlinks in the pathname and cwd are automatically resolved using\n * `fs.realpathSync()` before comparison.\n *\n * @param pathname - File or directory path to check.\n * @param options - Options for the git status check.\n * @returns Promise resolving to `true` if path has any changes, `false` otherwise.\n *\n * @example\n * ```typescript\n * // Check if file is changed\n * const changed = await isChanged('src/foo.ts')\n * // => true\n *\n * // Check if directory has any changes\n * const changed = await isChanged('src/')\n * // => true (if any file in src/ is changed)\n *\n * // Check from different cwd\n * const changed = await isChanged(\n * '/path/to/repo/src/foo.ts',\n * { cwd: '/path/to/repo' }\n * )\n * ```\n */\nexport async function isChanged(\n pathname: string,\n options?: GitDiffOptions | undefined,\n): Promise<boolean> {\n const files = await getChangedFiles({\n __proto__: null,\n ...options,\n absolute: false,\n })\n // Resolve pathname to handle symlinks before computing relative path.\n const resolvedPathname = getFs().realpathSync(pathname)\n const baseCwd = options?.cwd ? getFs().realpathSync(options['cwd']) : getCwd()\n const relativePath = normalizePath(path.relative(baseCwd, resolvedPathname))\n return files.includes(relativePath)\n}\n\n/**\n * Check if a file or directory has any git changes.\n *\n * Synchronous version of `isChanged()`. Checks if the given pathname has\n * any changes including:\n * - Staged modifications (added with `git add`)\n * - Unstaged modifications (not yet staged)\n * - Untracked status (new file/directory not in git)\n *\n * For directories, returns `true` if ANY file within the directory has changes.\n *\n * Symlinks in the pathname and cwd are automatically resolved using\n * `fs.realpathSync()` before comparison.\n *\n * @param pathname - File or directory path to check.\n * @param options - Options for the git status check.\n * @returns `true` if path has any changes, `false` otherwise.\n *\n * @example\n * ```typescript\n * // Check if file is changed\n * const changed = isChangedSync('src/foo.ts')\n * // => true\n *\n * // Check if directory has any changes\n * const changed = isChangedSync('src/')\n * // => true (if any file in src/ is changed)\n *\n * // Check from different cwd\n * const changed = isChangedSync(\n * '/path/to/repo/src/foo.ts',\n * { cwd: '/path/to/repo' }\n * )\n * ```\n */\nexport function isChangedSync(\n pathname: string,\n options?: GitDiffOptions | undefined,\n): boolean {\n const files = getChangedFilesSync({\n __proto__: null,\n ...options,\n absolute: false,\n })\n // Resolve pathname to handle symlinks before computing relative path.\n const resolvedPathname = getFs().realpathSync(pathname)\n const baseCwd = options?.cwd ? getFs().realpathSync(options['cwd']) : getCwd()\n const relativePath = normalizePath(path.relative(baseCwd, resolvedPathname))\n return files.includes(relativePath)\n}\n\n/**\n * Check if a file or directory has unstaged changes.\n *\n * Checks if the given pathname has modifications that are not yet staged\n * for commit (changes not added with `git add`). Does NOT include:\n * - Staged changes (already added with `git add`)\n * - Untracked files (new files not in git)\n *\n * For directories, returns `true` if ANY file within the directory has\n * unstaged changes.\n *\n * Symlinks in the pathname and cwd are automatically resolved using\n * `fs.realpathSync()` before comparison.\n *\n * @param pathname - File or directory path to check.\n * @param options - Options for the git diff check.\n * @returns Promise resolving to `true` if path has unstaged changes, `false` otherwise.\n *\n * @example\n * ```typescript\n * // Check if file has unstaged changes\n * const unstaged = await isUnstaged('src/foo.ts')\n * // => true\n *\n * // After staging the file\n * await spawn('git', ['add', 'src/foo.ts'])\n * const unstaged = await isUnstaged('src/foo.ts')\n * // => false\n *\n * // Check directory\n * const unstaged = await isUnstaged('src/')\n * // => true (if any file in src/ has unstaged changes)\n * ```\n */\nexport async function isUnstaged(\n pathname: string,\n options?: GitDiffOptions | undefined,\n): Promise<boolean> {\n const files = await getUnstagedFiles({\n __proto__: null,\n ...options,\n absolute: false,\n })\n // Resolve pathname to handle symlinks before computing relative path.\n const resolvedPathname = getFs().realpathSync(pathname)\n const baseCwd = options?.cwd ? getFs().realpathSync(options['cwd']) : getCwd()\n const relativePath = normalizePath(path.relative(baseCwd, resolvedPathname))\n return files.includes(relativePath)\n}\n\n/**\n * Check if a file or directory has unstaged changes.\n *\n * Synchronous version of `isUnstaged()`. Checks if the given pathname has\n * modifications that are not yet staged for commit (changes not added with\n * `git add`). Does NOT include:\n * - Staged changes (already added with `git add`)\n * - Untracked files (new files not in git)\n *\n * For directories, returns `true` if ANY file within the directory has\n * unstaged changes.\n *\n * Symlinks in the pathname and cwd are automatically resolved using\n * `fs.realpathSync()` before comparison.\n *\n * @param pathname - File or directory path to check.\n * @param options - Options for the git diff check.\n * @returns `true` if path has unstaged changes, `false` otherwise.\n *\n * @example\n * ```typescript\n * // Check if file has unstaged changes\n * const unstaged = isUnstagedSync('src/foo.ts')\n * // => true\n *\n * // After staging the file\n * spawnSync('git', ['add', 'src/foo.ts'])\n * const unstaged = isUnstagedSync('src/foo.ts')\n * // => false\n *\n * // Check directory\n * const unstaged = isUnstagedSync('src/')\n * // => true (if any file in src/ has unstaged changes)\n * ```\n */\nexport function isUnstagedSync(\n pathname: string,\n options?: GitDiffOptions | undefined,\n): boolean {\n const files = getUnstagedFilesSync({\n __proto__: null,\n ...options,\n absolute: false,\n })\n // Resolve pathname to handle symlinks before computing relative path.\n const resolvedPathname = getFs().realpathSync(pathname)\n const baseCwd = options?.cwd ? getFs().realpathSync(options['cwd']) : getCwd()\n const relativePath = normalizePath(path.relative(baseCwd, resolvedPathname))\n return files.includes(relativePath)\n}\n\n/**\n * Check if a file or directory is staged for commit.\n *\n * Checks if the given pathname has changes staged with `git add` that will\n * be included in the next commit. Does NOT include:\n * - Unstaged modifications (changes not added with `git add`)\n * - Untracked files (new files not in git)\n *\n * For directories, returns `true` if ANY file within the directory is staged.\n *\n * Symlinks in the pathname and cwd are automatically resolved using\n * `fs.realpathSync()` before comparison.\n *\n * @param pathname - File or directory path to check.\n * @param options - Options for the git diff check.\n * @returns Promise resolving to `true` if path is staged, `false` otherwise.\n *\n * @example\n * ```typescript\n * // Check if file is staged\n * const staged = await isStaged('src/foo.ts')\n * // => false\n *\n * // Stage the file\n * await spawn('git', ['add', 'src/foo.ts'])\n * const staged = await isStaged('src/foo.ts')\n * // => true\n *\n * // Check directory\n * const staged = await isStaged('src/')\n * // => true (if any file in src/ is staged)\n * ```\n */\nexport async function isStaged(\n pathname: string,\n options?: GitDiffOptions | undefined,\n): Promise<boolean> {\n const files = await getStagedFiles({\n __proto__: null,\n ...options,\n absolute: false,\n })\n // Resolve pathname to handle symlinks before computing relative path.\n const resolvedPathname = getFs().realpathSync(pathname)\n const baseCwd = options?.cwd ? getFs().realpathSync(options['cwd']) : getCwd()\n const relativePath = normalizePath(path.relative(baseCwd, resolvedPathname))\n return files.includes(relativePath)\n}\n\n/**\n * Check if a file or directory is staged for commit.\n *\n * Synchronous version of `isStaged()`. Checks if the given pathname has\n * changes staged with `git add` that will be included in the next commit.\n * Does NOT include:\n * - Unstaged modifications (changes not added with `git add`)\n * - Untracked files (new files not in git)\n *\n * For directories, returns `true` if ANY file within the directory is staged.\n *\n * Symlinks in the pathname and cwd are automatically resolved using\n * `fs.realpathSync()` before comparison.\n *\n * @param pathname - File or directory path to check.\n * @param options - Options for the git diff check.\n * @returns `true` if path is staged, `false` otherwise.\n *\n * @example\n * ```typescript\n * // Check if file is staged\n * const staged = isStagedSync('src/foo.ts')\n * // => false\n *\n * // Stage the file\n * spawnSync('git', ['add', 'src/foo.ts'])\n * const staged = isStagedSync('src/foo.ts')\n * // => true\n *\n * // Check directory\n * const staged = isStagedSync('src/')\n * // => true (if any file in src/ is staged)\n * ```\n */\nexport function isStagedSync(\n pathname: string,\n options?: GitDiffOptions | undefined,\n): boolean {\n const files = getStagedFilesSync({\n __proto__: null,\n ...options,\n absolute: false,\n })\n // Resolve pathname to handle symlinks before computing relative path.\n const resolvedPathname = getFs().realpathSync(pathname)\n const baseCwd = options?.cwd ? getFs().realpathSync(options['cwd']) : getCwd()\n const relativePath = normalizePath(path.relative(baseCwd, resolvedPathname))\n return files.includes(relativePath)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import path from 'node:path'\n\nimport { WIN32 } from '#constants/platform'\nimport { getGlobMatcher } from './globs'\nimport { normalizePath } from './path'\nimport { spawn, spawnSync } from './spawn'\nimport { stripAnsi } from './strings'\n\n/**\n * Options for git diff operations.\n *\n * Controls how git diff results are processed and returned.\n *\n * @example\n * ```typescript\n * // Get absolute file paths\n * const files = await getChangedFiles({ absolute: true })\n * // => ['/path/to/repo/src/file.ts']\n *\n * // Get relative paths with caching disabled\n * const files = await getChangedFiles({ cache: false })\n * // => ['src/file.ts']\n *\n * // Get files from specific directory\n * const files = await getChangedFiles({ cwd: '/path/to/repo/src' })\n * ```\n */\nexport interface GitDiffOptions {\n /**\n * Return absolute file paths instead of relative paths.\n *\n * @default false\n */\n absolute?: boolean | undefined\n /**\n * Cache git diff results to avoid repeated git subprocess calls.\n *\n * Caching is keyed by the git command and options used, so different\n * option combinations maintain separate cache entries.\n *\n * @default true\n */\n cache?: boolean | undefined\n /**\n * Working directory for git operations.\n *\n * Git operations will be run from this directory, and returned paths\n * will be relative to the git repository root. Symlinks are resolved\n * using `fs.realpathSync()`.\n *\n * @default process.cwd()\n */\n cwd?: string | undefined\n /**\n * Parse git porcelain format output (status codes like `M`, `A`, `??`).\n *\n * When `true`, strips the two-character status code and space from the\n * beginning of each line. Automatically enabled for `getChangedFiles()`.\n *\n * @default false\n */\n porcelain?: boolean | undefined\n /**\n * Return results as a `Set` instead of an array.\n *\n * @default false\n */\n asSet?: boolean | undefined\n /**\n * Additional options passed to glob matcher.\n *\n * Supports options like `dot`, `ignore`, `nocase` for filtering results.\n */\n [key: string]: unknown\n}\n\n/**\n * Options for filtering packages by git changes.\n *\n * Used to determine which packages in a monorepo have changed files.\n *\n * @example\n * ```typescript\n * // Filter packages with changes\n * const changed = filterPackagesByChanges(packages)\n *\n * // Force include all packages\n * const all = filterPackagesByChanges(packages, { force: true })\n *\n * // Use custom package key\n * const changed = filterPackagesByChanges(\n * packages,\n * { packageKey: 'directory' }\n * )\n * ```\n */\nexport interface FilterPackagesByChangesOptions {\n /**\n * Force include all packages regardless of changes.\n *\n * @default false\n */\n force?: boolean | undefined\n /**\n * Key to access package path in package objects.\n *\n * @default 'path'\n */\n packageKey?: string | undefined\n /**\n * Additional options for filtering.\n */\n [key: string]: unknown\n}\n\ntype SpawnArgs = [string, string[], Record<string, unknown>]\n\ninterface GitDiffSpawnArgs {\n all: SpawnArgs\n unstaged: SpawnArgs\n staged: SpawnArgs\n}\n\nconst gitDiffCache = new Map<string, string[]>()\n\nlet _fs: typeof import('fs') | undefined\n/**\n * Lazily load the `fs` module to avoid Webpack errors.\n *\n * Uses non-`node:` prefixed require internally to prevent Webpack from\n * attempting to bundle Node.js built-in modules.\n *\n * @returns The Node.js `fs` module.\n *\n * @example\n * ```typescript\n * const fs = getFs()\n * const exists = fs.existsSync('/path/to/file')\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nfunction getFs() {\n if (_fs === undefined) {\n // Use non-'node:' prefixed require to avoid Webpack errors.\n\n _fs = /*@__PURE__*/ require('node:fs')\n }\n return _fs as typeof import('fs')\n}\n\nlet _path: typeof import('path') | undefined\n/**\n * Lazily load the `path` module to avoid Webpack errors.\n *\n * Uses non-`node:` prefixed require internally to prevent Webpack from\n * attempting to bundle Node.js built-in modules.\n *\n * @returns The Node.js `path` module.\n *\n * @example\n * ```typescript\n * const path = getPath()\n * const joined = path.join('/foo', 'bar')\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nfunction getPath() {\n if (_path === undefined) {\n _path = /*@__PURE__*/ require('node:path')\n }\n return _path as typeof import('path')\n}\n\n/**\n * Get the git executable path.\n *\n * Currently always returns `'git'`, relying on the system PATH to resolve\n * the git binary location. This may be extended in the future to support\n * custom git paths.\n *\n * @returns The git executable name or path.\n *\n * @example\n * ```typescript\n * const git = getGitPath()\n * // => 'git'\n * ```\n */\nfunction getGitPath(): string {\n return 'git'\n}\n\n/**\n * Get the current working directory for git operations.\n *\n * Returns the real path to handle symlinks correctly. This is important\n * because symlinked directories like `/tmp -> /private/tmp` can cause\n * path mismatches when comparing git output.\n *\n * @returns The resolved real path of `process.cwd()`.\n *\n * @example\n * ```typescript\n * const cwd = getCwd()\n * // In /tmp (symlink to /private/tmp):\n * // => '/private/tmp'\n * ```\n */\nfunction getCwd(): string {\n return getFs().realpathSync(process.cwd())\n}\n\n/**\n * Get spawn arguments for different git diff operations.\n *\n * Prepares argument arrays for `spawn()`/`spawnSync()` calls that retrieve:\n * - `all`: All changed files (staged, unstaged, untracked) via `git status --porcelain`\n * - `unstaged`: Unstaged modifications via `git diff --name-only`\n * - `staged`: Staged changes via `git diff --cached --name-only`\n *\n * Automatically resolves symlinks in the provided `cwd` and enables shell\n * mode on Windows for proper command execution.\n *\n * @param cwd - Working directory for git operations, defaults to `process.cwd()`.\n * @returns Object containing spawn arguments for all, unstaged, and staged operations.\n */\nfunction getGitDiffSpawnArgs(cwd?: string | undefined): GitDiffSpawnArgs {\n const resolvedCwd = cwd ? getFs().realpathSync(cwd) : getCwd()\n return {\n all: [\n getGitPath(),\n ['status', '--porcelain'],\n {\n cwd: resolvedCwd,\n shell: WIN32,\n },\n ],\n unstaged: [\n getGitPath(),\n ['diff', '--name-only'],\n {\n cwd: resolvedCwd,\n },\n ],\n staged: [\n getGitPath(),\n ['diff', '--cached', '--name-only'],\n {\n cwd: resolvedCwd,\n shell: WIN32,\n },\n ],\n }\n}\n\n/**\n * Execute git diff command asynchronously and parse results.\n *\n * Internal helper for async git operations. Handles caching, command execution,\n * and result parsing. Returns empty array on git command failure.\n *\n * @param args - Spawn arguments tuple `[command, args, options]`.\n * @param options - Git diff options for caching and parsing.\n * @returns Promise resolving to array of file paths.\n */\nasync function innerDiff(\n args: SpawnArgs,\n options?: GitDiffOptions | undefined,\n): Promise<string[]> {\n const { cache = true, ...parseOptions } = { __proto__: null, ...options }\n const cacheKey = cache ? JSON.stringify({ args, parseOptions }) : undefined\n if (cache && cacheKey) {\n const result = gitDiffCache.get(cacheKey)\n if (result) {\n return result\n }\n }\n let result: string[]\n try {\n // Use stdioString: false to get raw Buffer, then convert ourselves to preserve exact output.\n const spawnResult = await spawn(args[0], args[1], {\n ...args[2],\n stdioString: false,\n })\n const stdout = Buffer.isBuffer(spawnResult.stdout)\n ? spawnResult.stdout.toString('utf8')\n : String(spawnResult.stdout)\n // Extract spawn cwd from args to pass to parser\n const spawnCwd =\n typeof args[2]['cwd'] === 'string' ? args[2]['cwd'] : undefined\n result = parseGitDiffStdout(stdout, parseOptions, spawnCwd)\n } catch {\n return []\n }\n if (cache && cacheKey) {\n gitDiffCache.set(cacheKey, result)\n }\n return result\n}\n\n/**\n * Execute git diff command synchronously and parse results.\n *\n * Internal helper for sync git operations. Handles caching, command execution,\n * and result parsing. Returns empty array on git command failure.\n *\n * @param args - Spawn arguments tuple `[command, args, options]`.\n * @param options - Git diff options for caching and parsing.\n * @returns Array of file paths.\n */\nfunction innerDiffSync(\n args: SpawnArgs,\n options?: GitDiffOptions | undefined,\n): string[] {\n const { cache = true, ...parseOptions } = { __proto__: null, ...options }\n const cacheKey = cache ? JSON.stringify({ args, parseOptions }) : undefined\n if (cache && cacheKey) {\n const result = gitDiffCache.get(cacheKey)\n if (result) {\n return result\n }\n }\n let result: string[]\n try {\n // Use stdioString: false to get raw Buffer, then convert ourselves to preserve exact output.\n const spawnResult = spawnSync(args[0], args[1], {\n ...args[2],\n stdioString: false,\n })\n const stdout = Buffer.isBuffer(spawnResult.stdout)\n ? spawnResult.stdout.toString('utf8')\n : String(spawnResult.stdout)\n // Extract spawn cwd from args to pass to parser\n const spawnCwd =\n typeof args[2]['cwd'] === 'string' ? args[2]['cwd'] : undefined\n result = parseGitDiffStdout(stdout, parseOptions, spawnCwd)\n } catch {\n return []\n }\n if (cache && cacheKey) {\n gitDiffCache.set(cacheKey, result)\n }\n return result\n}\n\n/**\n * Find git repository root by walking up from the given directory.\n *\n * Searches for a `.git` directory or file by traversing parent directories\n * upward until found or filesystem root is reached. Returns the original path\n * if no git repository is found.\n *\n * This function is exported primarily for testing purposes.\n *\n * @param startPath - Directory path to start searching from.\n * @returns Git repository root path, or `startPath` if not found.\n *\n * @example\n * ```typescript\n * const root = findGitRoot('/path/to/repo/src/subdir')\n * // => '/path/to/repo'\n *\n * const notFound = findGitRoot('/not/a/repo')\n * // => '/not/a/repo'\n * ```\n */\nexport function findGitRoot(startPath: string): string {\n const fs = getFs()\n const path = getPath()\n let currentPath = startPath\n // Walk up the directory tree looking for .git\n while (true) {\n try {\n const gitPath = path.join(currentPath, '.git')\n if (fs.existsSync(gitPath)) {\n return currentPath\n }\n } catch {\n // Ignore errors and continue walking up\n }\n const parentPath = path.dirname(currentPath)\n // Stop if we've reached the root or can't go up anymore\n if (parentPath === currentPath) {\n // Return original path if no .git found\n return startPath\n }\n currentPath = parentPath\n }\n}\n\n/**\n * Parse git diff stdout output into file path array.\n *\n * Internal helper that processes raw git command output by:\n * 1. Finding git repository root from spawn cwd\n * 2. Stripping ANSI codes and splitting into lines\n * 3. Parsing porcelain format status codes if requested\n * 4. Normalizing and optionally making paths absolute\n * 5. Filtering paths based on cwd and glob options\n *\n * Git always returns paths relative to the repository root, regardless of\n * where the command was executed. This function handles the path resolution\n * correctly by finding the repo root and adjusting paths accordingly.\n *\n * @param stdout - Raw stdout from git command.\n * @param options - Git diff options for path processing.\n * @param spawnCwd - Working directory where git command was executed.\n * @returns Array of processed file paths.\n */\nfunction parseGitDiffStdout(\n stdout: string,\n options?: GitDiffOptions | undefined,\n spawnCwd?: string | undefined,\n): string[] {\n // Find git repo root from spawnCwd. Git always returns paths relative to the repo root,\n // not the cwd where it was run. So we need to find the repo root to correctly parse paths.\n const defaultRoot = spawnCwd ? findGitRoot(spawnCwd) : getCwd()\n const {\n absolute = false,\n cwd: cwdOption = defaultRoot,\n porcelain = false,\n ...matcherOptions\n } = { __proto__: null, ...options }\n // Resolve cwd to handle symlinks.\n const cwd =\n cwdOption === defaultRoot ? defaultRoot : getFs().realpathSync(cwdOption)\n const rootPath = defaultRoot\n // Split into lines without trimming to preserve leading spaces in porcelain format.\n let rawFiles = stdout\n ? stripAnsi(stdout)\n .split('\\n')\n .map(line => line.trimEnd())\n .filter(line => line)\n : []\n // Parse porcelain format: strip status codes.\n // Git status --porcelain format is: XY filename\n // where X and Y are single characters and there's a space before the filename.\n if (porcelain) {\n rawFiles = rawFiles.map(line => {\n // Status is first 2 chars, then space, then filename.\n return line.length > 3 ? line.substring(3) : line\n })\n }\n const files = absolute\n ? rawFiles.map(relPath => normalizePath(path.join(rootPath, relPath)))\n : rawFiles.map(relPath => normalizePath(relPath))\n if (cwd === rootPath) {\n return files\n }\n const relPath = normalizePath(path.relative(rootPath, cwd))\n const matcher = getGlobMatcher([`${relPath}/**`], {\n ...(matcherOptions as {\n dot?: boolean\n ignore?: string[]\n nocase?: boolean\n }),\n absolute,\n cwd: rootPath,\n } as {\n absolute?: boolean\n cwd?: string\n dot?: boolean\n ignore?: string[]\n nocase?: boolean\n })\n const filtered: string[] = []\n for (const filepath of files) {\n if (matcher(filepath)) {\n filtered.push(filepath)\n }\n }\n return filtered\n}\n\n/**\n * Get all changed files including staged, unstaged, and untracked files.\n *\n * Uses `git status --porcelain` which returns the full working tree status\n * with status codes:\n * - `M` - Modified\n * - `A` - Added\n * - `D` - Deleted\n * - `??` - Untracked\n * - `R` - Renamed\n * - `C` - Copied\n *\n * This is the most comprehensive check - captures everything that differs\n * from the last commit, including:\n * - Files modified and staged with `git add`\n * - Files modified but not staged\n * - New files not yet tracked by git\n *\n * Status codes are automatically stripped from the output.\n *\n * @param options - Options controlling path format and filtering.\n * @returns Promise resolving to array of changed file paths.\n *\n * @example\n * ```typescript\n * // Get all changed files as relative paths\n * const files = await getChangedFiles()\n * // => ['src/foo.ts', 'src/bar.ts', 'newfile.ts']\n *\n * // Get absolute paths\n * const files = await getChangedFiles({ absolute: true })\n * // => ['/path/to/repo/src/foo.ts', ...]\n *\n * // Get changed files in specific directory\n * const files = await getChangedFiles({ cwd: '/path/to/repo/src' })\n * // => ['foo.ts', 'bar.ts']\n * ```\n */\nexport async function getChangedFiles(\n options?: GitDiffOptions | undefined,\n): Promise<string[]> {\n const args = getGitDiffSpawnArgs(options?.cwd).all\n return await innerDiff(args, {\n __proto__: null,\n ...options,\n porcelain: true,\n })\n}\n\n/**\n * Get all changed files including staged, unstaged, and untracked files.\n *\n * Synchronous version of `getChangedFiles()`. Uses `git status --porcelain`\n * which returns the full working tree status with status codes:\n * - `M` - Modified\n * - `A` - Added\n * - `D` - Deleted\n * - `??` - Untracked\n * - `R` - Renamed\n * - `C` - Copied\n *\n * This is the most comprehensive check - captures everything that differs\n * from the last commit, including:\n * - Files modified and staged with `git add`\n * - Files modified but not staged\n * - New files not yet tracked by git\n *\n * Status codes are automatically stripped from the output.\n *\n * @param options - Options controlling path format and filtering.\n * @returns Array of changed file paths.\n *\n * @example\n * ```typescript\n * // Get all changed files as relative paths\n * const files = getChangedFilesSync()\n * // => ['src/foo.ts', 'src/bar.ts', 'newfile.ts']\n *\n * // Get absolute paths\n * const files = getChangedFilesSync({ absolute: true })\n * // => ['/path/to/repo/src/foo.ts', ...]\n *\n * // Get changed files in specific directory\n * const files = getChangedFilesSync({ cwd: '/path/to/repo/src' })\n * // => ['foo.ts', 'bar.ts']\n * ```\n */\nexport function getChangedFilesSync(\n options?: GitDiffOptions | undefined,\n): string[] {\n const args = getGitDiffSpawnArgs(options?.cwd).all\n return innerDiffSync(args, {\n __proto__: null,\n ...options,\n porcelain: true,\n })\n}\n\n/**\n * Get unstaged modified files (changes not yet staged for commit).\n *\n * Uses `git diff --name-only` which returns only unstaged modifications\n * to tracked files. Does NOT include:\n * - Untracked files (new files not added to git)\n * - Staged changes (files added with `git add`)\n *\n * This is a focused check for uncommitted changes to existing tracked files.\n * Useful for detecting work-in-progress modifications before staging.\n *\n * @param options - Options controlling path format and filtering.\n * @returns Promise resolving to array of unstaged file paths.\n *\n * @example\n * ```typescript\n * // Get unstaged files\n * const files = await getUnstagedFiles()\n * // => ['src/foo.ts', 'src/bar.ts']\n *\n * // After staging some files\n * await spawn('git', ['add', 'src/foo.ts'])\n * const files = await getUnstagedFiles()\n * // => ['src/bar.ts'] (foo.ts no longer included)\n *\n * // Get absolute paths\n * const files = await getUnstagedFiles({ absolute: true })\n * // => ['/path/to/repo/src/bar.ts']\n * ```\n */\nexport async function getUnstagedFiles(\n options?: GitDiffOptions | undefined,\n): Promise<string[]> {\n const args = getGitDiffSpawnArgs(options?.cwd).unstaged\n return await innerDiff(args, options)\n}\n\n/**\n * Get unstaged modified files (changes not yet staged for commit).\n *\n * Synchronous version of `getUnstagedFiles()`. Uses `git diff --name-only`\n * which returns only unstaged modifications to tracked files. Does NOT include:\n * - Untracked files (new files not added to git)\n * - Staged changes (files added with `git add`)\n *\n * This is a focused check for uncommitted changes to existing tracked files.\n * Useful for detecting work-in-progress modifications before staging.\n *\n * @param options - Options controlling path format and filtering.\n * @returns Array of unstaged file paths.\n *\n * @example\n * ```typescript\n * // Get unstaged files\n * const files = getUnstagedFilesSync()\n * // => ['src/foo.ts', 'src/bar.ts']\n *\n * // After staging some files\n * spawnSync('git', ['add', 'src/foo.ts'])\n * const files = getUnstagedFilesSync()\n * // => ['src/bar.ts'] (foo.ts no longer included)\n *\n * // Get absolute paths\n * const files = getUnstagedFilesSync({ absolute: true })\n * // => ['/path/to/repo/src/bar.ts']\n * ```\n */\nexport function getUnstagedFilesSync(\n options?: GitDiffOptions | undefined,\n): string[] {\n const args = getGitDiffSpawnArgs(options?.cwd).unstaged\n return innerDiffSync(args, options)\n}\n\n/**\n * Get staged files ready for commit (changes added with `git add`).\n *\n * Uses `git diff --cached --name-only` which returns only staged changes.\n * Does NOT include:\n * - Unstaged modifications (changes not added with `git add`)\n * - Untracked files (new files not added to git)\n *\n * This is a focused check for what will be included in the next commit.\n * Useful for validating changes before committing or running pre-commit hooks.\n *\n * @param options - Options controlling path format and filtering.\n * @returns Promise resolving to array of staged file paths.\n *\n * @example\n * ```typescript\n * // Get currently staged files\n * const files = await getStagedFiles()\n * // => ['src/foo.ts']\n *\n * // Stage more files\n * await spawn('git', ['add', 'src/bar.ts'])\n * const files = await getStagedFiles()\n * // => ['src/foo.ts', 'src/bar.ts']\n *\n * // Get absolute paths\n * const files = await getStagedFiles({ absolute: true })\n * // => ['/path/to/repo/src/foo.ts', ...]\n * ```\n */\nexport async function getStagedFiles(\n options?: GitDiffOptions | undefined,\n): Promise<string[]> {\n const args = getGitDiffSpawnArgs(options?.cwd).staged\n return await innerDiff(args, options)\n}\n\n/**\n * Get staged files ready for commit (changes added with `git add`).\n *\n * Synchronous version of `getStagedFiles()`. Uses `git diff --cached --name-only`\n * which returns only staged changes. Does NOT include:\n * - Unstaged modifications (changes not added with `git add`)\n * - Untracked files (new files not added to git)\n *\n * This is a focused check for what will be included in the next commit.\n * Useful for validating changes before committing or running pre-commit hooks.\n *\n * @param options - Options controlling path format and filtering.\n * @returns Array of staged file paths.\n *\n * @example\n * ```typescript\n * // Get currently staged files\n * const files = getStagedFilesSync()\n * // => ['src/foo.ts']\n *\n * // Stage more files\n * spawnSync('git', ['add', 'src/bar.ts'])\n * const files = getStagedFilesSync()\n * // => ['src/foo.ts', 'src/bar.ts']\n *\n * // Get absolute paths\n * const files = getStagedFilesSync({ absolute: true })\n * // => ['/path/to/repo/src/foo.ts', ...]\n * ```\n */\nexport function getStagedFilesSync(\n options?: GitDiffOptions | undefined,\n): string[] {\n const args = getGitDiffSpawnArgs(options?.cwd).staged\n return innerDiffSync(args, options)\n}\n\n/**\n * Check if a file or directory has any git changes.\n *\n * Checks if the given pathname has any changes including:\n * - Staged modifications (added with `git add`)\n * - Unstaged modifications (not yet staged)\n * - Untracked status (new file/directory not in git)\n *\n * For directories, returns `true` if ANY file within the directory has changes.\n *\n * Symlinks in the pathname and cwd are automatically resolved using\n * `fs.realpathSync()` before comparison.\n *\n * @param pathname - File or directory path to check.\n * @param options - Options for the git status check.\n * @returns Promise resolving to `true` if path has any changes, `false` otherwise.\n *\n * @example\n * ```typescript\n * // Check if file is changed\n * const changed = await isChanged('src/foo.ts')\n * // => true\n *\n * // Check if directory has any changes\n * const changed = await isChanged('src/')\n * // => true (if any file in src/ is changed)\n *\n * // Check from different cwd\n * const changed = await isChanged(\n * '/path/to/repo/src/foo.ts',\n * { cwd: '/path/to/repo' }\n * )\n * ```\n */\nexport async function isChanged(\n pathname: string,\n options?: GitDiffOptions | undefined,\n): Promise<boolean> {\n const files = await getChangedFiles({\n __proto__: null,\n ...options,\n absolute: false,\n })\n // Resolve pathname to handle symlinks before computing relative path.\n const resolvedPathname = getFs().realpathSync(pathname)\n const baseCwd = options?.cwd ? getFs().realpathSync(options['cwd']) : getCwd()\n const relativePath = normalizePath(path.relative(baseCwd, resolvedPathname))\n return files.includes(relativePath)\n}\n\n/**\n * Check if a file or directory has any git changes.\n *\n * Synchronous version of `isChanged()`. Checks if the given pathname has\n * any changes including:\n * - Staged modifications (added with `git add`)\n * - Unstaged modifications (not yet staged)\n * - Untracked status (new file/directory not in git)\n *\n * For directories, returns `true` if ANY file within the directory has changes.\n *\n * Symlinks in the pathname and cwd are automatically resolved using\n * `fs.realpathSync()` before comparison.\n *\n * @param pathname - File or directory path to check.\n * @param options - Options for the git status check.\n * @returns `true` if path has any changes, `false` otherwise.\n *\n * @example\n * ```typescript\n * // Check if file is changed\n * const changed = isChangedSync('src/foo.ts')\n * // => true\n *\n * // Check if directory has any changes\n * const changed = isChangedSync('src/')\n * // => true (if any file in src/ is changed)\n *\n * // Check from different cwd\n * const changed = isChangedSync(\n * '/path/to/repo/src/foo.ts',\n * { cwd: '/path/to/repo' }\n * )\n * ```\n */\nexport function isChangedSync(\n pathname: string,\n options?: GitDiffOptions | undefined,\n): boolean {\n const files = getChangedFilesSync({\n __proto__: null,\n ...options,\n absolute: false,\n })\n // Resolve pathname to handle symlinks before computing relative path.\n const resolvedPathname = getFs().realpathSync(pathname)\n const baseCwd = options?.cwd ? getFs().realpathSync(options['cwd']) : getCwd()\n const relativePath = normalizePath(path.relative(baseCwd, resolvedPathname))\n return files.includes(relativePath)\n}\n\n/**\n * Check if a file or directory has unstaged changes.\n *\n * Checks if the given pathname has modifications that are not yet staged\n * for commit (changes not added with `git add`). Does NOT include:\n * - Staged changes (already added with `git add`)\n * - Untracked files (new files not in git)\n *\n * For directories, returns `true` if ANY file within the directory has\n * unstaged changes.\n *\n * Symlinks in the pathname and cwd are automatically resolved using\n * `fs.realpathSync()` before comparison.\n *\n * @param pathname - File or directory path to check.\n * @param options - Options for the git diff check.\n * @returns Promise resolving to `true` if path has unstaged changes, `false` otherwise.\n *\n * @example\n * ```typescript\n * // Check if file has unstaged changes\n * const unstaged = await isUnstaged('src/foo.ts')\n * // => true\n *\n * // After staging the file\n * await spawn('git', ['add', 'src/foo.ts'])\n * const unstaged = await isUnstaged('src/foo.ts')\n * // => false\n *\n * // Check directory\n * const unstaged = await isUnstaged('src/')\n * // => true (if any file in src/ has unstaged changes)\n * ```\n */\nexport async function isUnstaged(\n pathname: string,\n options?: GitDiffOptions | undefined,\n): Promise<boolean> {\n const files = await getUnstagedFiles({\n __proto__: null,\n ...options,\n absolute: false,\n })\n // Resolve pathname to handle symlinks before computing relative path.\n const resolvedPathname = getFs().realpathSync(pathname)\n const baseCwd = options?.cwd ? getFs().realpathSync(options['cwd']) : getCwd()\n const relativePath = normalizePath(path.relative(baseCwd, resolvedPathname))\n return files.includes(relativePath)\n}\n\n/**\n * Check if a file or directory has unstaged changes.\n *\n * Synchronous version of `isUnstaged()`. Checks if the given pathname has\n * modifications that are not yet staged for commit (changes not added with\n * `git add`). Does NOT include:\n * - Staged changes (already added with `git add`)\n * - Untracked files (new files not in git)\n *\n * For directories, returns `true` if ANY file within the directory has\n * unstaged changes.\n *\n * Symlinks in the pathname and cwd are automatically resolved using\n * `fs.realpathSync()` before comparison.\n *\n * @param pathname - File or directory path to check.\n * @param options - Options for the git diff check.\n * @returns `true` if path has unstaged changes, `false` otherwise.\n *\n * @example\n * ```typescript\n * // Check if file has unstaged changes\n * const unstaged = isUnstagedSync('src/foo.ts')\n * // => true\n *\n * // After staging the file\n * spawnSync('git', ['add', 'src/foo.ts'])\n * const unstaged = isUnstagedSync('src/foo.ts')\n * // => false\n *\n * // Check directory\n * const unstaged = isUnstagedSync('src/')\n * // => true (if any file in src/ has unstaged changes)\n * ```\n */\nexport function isUnstagedSync(\n pathname: string,\n options?: GitDiffOptions | undefined,\n): boolean {\n const files = getUnstagedFilesSync({\n __proto__: null,\n ...options,\n absolute: false,\n })\n // Resolve pathname to handle symlinks before computing relative path.\n const resolvedPathname = getFs().realpathSync(pathname)\n const baseCwd = options?.cwd ? getFs().realpathSync(options['cwd']) : getCwd()\n const relativePath = normalizePath(path.relative(baseCwd, resolvedPathname))\n return files.includes(relativePath)\n}\n\n/**\n * Check if a file or directory is staged for commit.\n *\n * Checks if the given pathname has changes staged with `git add` that will\n * be included in the next commit. Does NOT include:\n * - Unstaged modifications (changes not added with `git add`)\n * - Untracked files (new files not in git)\n *\n * For directories, returns `true` if ANY file within the directory is staged.\n *\n * Symlinks in the pathname and cwd are automatically resolved using\n * `fs.realpathSync()` before comparison.\n *\n * @param pathname - File or directory path to check.\n * @param options - Options for the git diff check.\n * @returns Promise resolving to `true` if path is staged, `false` otherwise.\n *\n * @example\n * ```typescript\n * // Check if file is staged\n * const staged = await isStaged('src/foo.ts')\n * // => false\n *\n * // Stage the file\n * await spawn('git', ['add', 'src/foo.ts'])\n * const staged = await isStaged('src/foo.ts')\n * // => true\n *\n * // Check directory\n * const staged = await isStaged('src/')\n * // => true (if any file in src/ is staged)\n * ```\n */\nexport async function isStaged(\n pathname: string,\n options?: GitDiffOptions | undefined,\n): Promise<boolean> {\n const files = await getStagedFiles({\n __proto__: null,\n ...options,\n absolute: false,\n })\n // Resolve pathname to handle symlinks before computing relative path.\n const resolvedPathname = getFs().realpathSync(pathname)\n const baseCwd = options?.cwd ? getFs().realpathSync(options['cwd']) : getCwd()\n const relativePath = normalizePath(path.relative(baseCwd, resolvedPathname))\n return files.includes(relativePath)\n}\n\n/**\n * Check if a file or directory is staged for commit.\n *\n * Synchronous version of `isStaged()`. Checks if the given pathname has\n * changes staged with `git add` that will be included in the next commit.\n * Does NOT include:\n * - Unstaged modifications (changes not added with `git add`)\n * - Untracked files (new files not in git)\n *\n * For directories, returns `true` if ANY file within the directory is staged.\n *\n * Symlinks in the pathname and cwd are automatically resolved using\n * `fs.realpathSync()` before comparison.\n *\n * @param pathname - File or directory path to check.\n * @param options - Options for the git diff check.\n * @returns `true` if path is staged, `false` otherwise.\n *\n * @example\n * ```typescript\n * // Check if file is staged\n * const staged = isStagedSync('src/foo.ts')\n * // => false\n *\n * // Stage the file\n * spawnSync('git', ['add', 'src/foo.ts'])\n * const staged = isStagedSync('src/foo.ts')\n * // => true\n *\n * // Check directory\n * const staged = isStagedSync('src/')\n * // => true (if any file in src/ is staged)\n * ```\n */\nexport function isStagedSync(\n pathname: string,\n options?: GitDiffOptions | undefined,\n): boolean {\n const files = getStagedFilesSync({\n __proto__: null,\n ...options,\n absolute: false,\n })\n // Resolve pathname to handle symlinks before computing relative path.\n const resolvedPathname = getFs().realpathSync(pathname)\n const baseCwd = options?.cwd ? getFs().realpathSync(options['cwd']) : getCwd()\n const relativePath = normalizePath(path.relative(baseCwd, resolvedPathname))\n return files.includes(relativePath)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["result", "path", "relPath"]
7
7
  }
@@ -126,7 +126,7 @@ async function isolatePackage(packageSpec, options) {
126
126
  await install(packageTempDir);
127
127
  } else {
128
128
  const { spawn } = require("../spawn");
129
- const WIN322 = require("../../constants/platform").WIN32;
129
+ const WIN322 = require("../constants/platform").WIN32;
130
130
  const packageInstallSpec = spec.startsWith("https://") ? spec : `${packageName}@${spec}`;
131
131
  await spawn("pnpm", ["add", packageInstallSpec], {
132
132
  cwd: packageTempDir,
@@ -171,7 +171,7 @@ async function isolatePackage(packageSpec, options) {
171
171
  await install(installedPath);
172
172
  } else {
173
173
  const { spawn } = require("../spawn");
174
- const WIN322 = require("../../constants/platform").WIN32;
174
+ const WIN322 = require("../constants/platform").WIN32;
175
175
  await spawn("pnpm", ["install"], {
176
176
  cwd: installedPath,
177
177
  shell: WIN322,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/packages/isolation.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview Package isolation utilities for testing.\n * Provides tools to set up isolated test environments for packages.\n */\n\nimport { existsSync, promises as fs } from 'node:fs'\n\nimport { WIN32 } from '#constants/platform'\n\nimport type { PackageJson } from '../packages'\nimport { isAbsolute, isPath, trimLeadingDotSlash } from '../path'\nimport { readPackageJson } from './operations'\n\nlet _os: typeof import('node:os') | undefined\nlet _path: typeof import('node:path') | undefined\n\n/*@__NO_SIDE_EFFECTS__*/\nfunction getOs() {\n if (_os === undefined) {\n // Use non-'node:' prefixed require to avoid Webpack errors.\n\n _os = /*@__PURE__*/ require('node:os')\n }\n return _os as typeof import('node:os')\n}\n\n/*@__NO_SIDE_EFFECTS__*/\nfunction getPath() {\n if (_path === undefined) {\n // Use non-'node:' prefixed require to avoid Webpack errors.\n\n _path = /*@__PURE__*/ require('node:path')\n }\n return _path as typeof import('path')\n}\n\n/**\n * Copy options for fs.cp with cross-platform retry support.\n */\nconst FS_CP_OPTIONS = {\n dereference: true,\n errorOnExist: false,\n filter: (src: string) =>\n !src.includes('node_modules') && !src.endsWith('.DS_Store'),\n force: true,\n recursive: true,\n ...(WIN32 ? { maxRetries: 3, retryDelay: 100 } : {}),\n}\n\n/**\n * Resolve a path to its real location, handling symlinks.\n */\nasync function resolveRealPath(pathStr: string): Promise<string> {\n const path = getPath()\n return await fs.realpath(pathStr).catch(() => path.resolve(pathStr))\n}\n\n/**\n * Merge and write package.json with original and new values.\n */\nasync function mergePackageJson(\n pkgJsonPath: string,\n originalPkgJson: PackageJson | undefined,\n): Promise<PackageJson> {\n const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'))\n const mergedPkgJson = originalPkgJson\n ? { ...originalPkgJson, ...pkgJson }\n : pkgJson\n return mergedPkgJson\n}\n\nexport type IsolatePackageOptions = {\n imports?: Record<string, string> | undefined\n install?: ((cwd: string) => Promise<void>) | undefined\n onPackageJson?:\n | ((pkgJson: PackageJson) => PackageJson | Promise<PackageJson>)\n | undefined\n sourcePath?: string | undefined\n}\n\nexport type IsolatePackageResult = {\n exports?: Record<string, unknown> | undefined\n tmpdir: string\n}\n\n/**\n * Isolates a package in a temporary test environment.\n *\n * Supports multiple input types:\n * 1. File system path (absolute or relative)\n * 2. Package name with optional version spec\n * 3. npm package spec (parsed via npm-package-arg)\n *\n * @throws {Error} When package installation or setup fails.\n */\nexport async function isolatePackage(\n packageSpec: string,\n options?: IsolatePackageOptions | undefined,\n): Promise<IsolatePackageResult> {\n const os = getOs()\n const path = getPath()\n const opts = { __proto__: null, ...options } as IsolatePackageOptions\n const { imports, install, onPackageJson, sourcePath: optSourcePath } = opts\n\n let sourcePath = optSourcePath\n let packageName: string | undefined\n let spec: string | undefined\n\n // Determine if this is a path or package spec.\n if (isPath(packageSpec)) {\n // File system path.\n // Handle edge case on Windows where path.relative() returns an absolute path\n // when paths are on different drives, and the test prepends './' to it.\n // Example: './C:\\Users\\...' should be treated as 'C:\\Users\\...'.\n const trimmedPath = trimLeadingDotSlash(packageSpec)\n const pathToResolve = isAbsolute(trimmedPath) ? trimmedPath : packageSpec\n sourcePath = path.resolve(pathToResolve)\n\n if (!existsSync(sourcePath)) {\n throw new Error(`Source path does not exist: ${sourcePath}`)\n }\n\n // Read package.json to get the name.\n const pkgJson = await readPackageJson(sourcePath, { normalize: true })\n if (!pkgJson) {\n throw new Error(`Could not read package.json from: ${sourcePath}`)\n }\n packageName = pkgJson.name as string\n } else {\n // Parse as npm package spec.\n const npa = /*@__PURE__*/ require('../external/npm-package-arg')\n const parsed = npa(packageSpec)\n\n packageName = parsed.name\n\n if (parsed.type === 'directory' || parsed.type === 'file') {\n sourcePath = parsed.fetchSpec\n if (!sourcePath || !existsSync(sourcePath)) {\n throw new Error(`Source path does not exist: ${sourcePath}`)\n }\n // If package name not provided by parser, read from package.json.\n if (!packageName) {\n const pkgJson = await readPackageJson(sourcePath, { normalize: true })\n if (!pkgJson) {\n throw new Error(`Could not read package.json from: ${sourcePath}`)\n }\n packageName = pkgJson.name as string\n }\n } else {\n // Registry package.\n spec = parsed.fetchSpec || parsed.rawSpec\n }\n }\n\n if (!packageName) {\n throw new Error(`Could not determine package name from: ${packageSpec}`)\n }\n\n // Create temp directory for this package.\n const sanitizedName = packageName.replace(/[@/]/g, '-')\n const tempDir = await fs.mkdtemp(\n path.join(os.tmpdir(), `socket-test-${sanitizedName}-`),\n )\n const packageTempDir = path.join(tempDir, sanitizedName)\n await fs.mkdir(packageTempDir, { recursive: true })\n\n let installedPath: string\n let originalPackageJson: PackageJson | undefined\n\n if (spec) {\n // Installing from registry first, then copying source on top if provided.\n await fs.writeFile(\n path.join(packageTempDir, 'package.json'),\n JSON.stringify(\n {\n name: 'test-temp',\n private: true,\n version: '1.0.0',\n },\n null,\n 2,\n ),\n )\n\n // Use custom install function or default pnpm install.\n if (install) {\n await install(packageTempDir)\n } else {\n const { spawn } = /*@__PURE__*/ require('../spawn')\n const WIN32 = require('../../constants/platform').WIN32\n const packageInstallSpec = spec.startsWith('https://')\n ? spec\n : `${packageName}@${spec}`\n\n await spawn('pnpm', ['add', packageInstallSpec], {\n cwd: packageTempDir,\n shell: WIN32,\n stdio: 'pipe',\n })\n }\n\n installedPath = path.join(packageTempDir, 'node_modules', packageName)\n\n // Save original package.json before copying source.\n originalPackageJson = await readPackageJson(installedPath, {\n normalize: true,\n })\n\n // Copy source files on top if provided.\n if (sourcePath) {\n // Check if source and destination are the same (symlinked).\n const realInstalledPath = await resolveRealPath(installedPath)\n const realSourcePath = await resolveRealPath(sourcePath)\n\n if (realSourcePath !== realInstalledPath) {\n await fs.cp(sourcePath, installedPath, FS_CP_OPTIONS)\n }\n }\n } else {\n // Just copying local package, no registry install.\n if (!sourcePath) {\n throw new Error('sourcePath is required when no version spec provided')\n }\n\n const scopedPath = packageName.startsWith('@')\n ? path.join(\n packageTempDir,\n 'node_modules',\n packageName.split('/')[0] ?? '',\n )\n : path.join(packageTempDir, 'node_modules')\n\n await fs.mkdir(scopedPath, { recursive: true })\n installedPath = path.join(packageTempDir, 'node_modules', packageName)\n\n await fs.cp(sourcePath, installedPath, FS_CP_OPTIONS)\n }\n\n // Prepare package.json if callback provided or if we need to merge with original.\n if (onPackageJson || originalPackageJson) {\n const pkgJsonPath = path.join(installedPath, 'package.json')\n const mergedPkgJson = await mergePackageJson(\n pkgJsonPath,\n originalPackageJson,\n )\n\n const finalPkgJson = onPackageJson\n ? await onPackageJson(mergedPkgJson)\n : mergedPkgJson\n\n await fs.writeFile(pkgJsonPath, JSON.stringify(finalPkgJson, null, 2))\n }\n\n // Install dependencies.\n if (install) {\n await install(installedPath)\n } else {\n const { spawn } = /*@__PURE__*/ require('../spawn')\n const WIN32 = require('../../constants/platform').WIN32\n await spawn('pnpm', ['install'], {\n cwd: installedPath,\n shell: WIN32,\n stdio: 'pipe',\n })\n }\n\n // Load module exports if imports provided.\n const exports: Record<string, unknown> = imports\n ? { __proto__: null }\n : (undefined as unknown as Record<string, unknown>)\n\n if (imports) {\n for (const { 0: key, 1: specifier } of Object.entries(imports)) {\n const fullPath = path.join(installedPath, specifier)\n exports[key] = require(fullPath)\n }\n }\n\n return {\n exports,\n tmpdir: installedPath,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @fileoverview Package isolation utilities for testing.\n * Provides tools to set up isolated test environments for packages.\n */\n\nimport { existsSync, promises as fs } from 'node:fs'\n\nimport { WIN32 } from '#constants/platform'\n\nimport type { PackageJson } from '../packages'\nimport { isAbsolute, isPath, trimLeadingDotSlash } from '../path'\nimport { readPackageJson } from './operations'\n\nlet _os: typeof import('node:os') | undefined\nlet _path: typeof import('node:path') | undefined\n\n/*@__NO_SIDE_EFFECTS__*/\nfunction getOs() {\n if (_os === undefined) {\n // Use non-'node:' prefixed require to avoid Webpack errors.\n\n _os = /*@__PURE__*/ require('node:os')\n }\n return _os as typeof import('node:os')\n}\n\n/*@__NO_SIDE_EFFECTS__*/\nfunction getPath() {\n if (_path === undefined) {\n // Use non-'node:' prefixed require to avoid Webpack errors.\n\n _path = /*@__PURE__*/ require('node:path')\n }\n return _path as typeof import('path')\n}\n\n/**\n * Copy options for fs.cp with cross-platform retry support.\n */\nconst FS_CP_OPTIONS = {\n dereference: true,\n errorOnExist: false,\n filter: (src: string) =>\n !src.includes('node_modules') && !src.endsWith('.DS_Store'),\n force: true,\n recursive: true,\n ...(WIN32 ? { maxRetries: 3, retryDelay: 100 } : {}),\n}\n\n/**\n * Resolve a path to its real location, handling symlinks.\n */\nasync function resolveRealPath(pathStr: string): Promise<string> {\n const path = getPath()\n return await fs.realpath(pathStr).catch(() => path.resolve(pathStr))\n}\n\n/**\n * Merge and write package.json with original and new values.\n */\nasync function mergePackageJson(\n pkgJsonPath: string,\n originalPkgJson: PackageJson | undefined,\n): Promise<PackageJson> {\n const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'))\n const mergedPkgJson = originalPkgJson\n ? { ...originalPkgJson, ...pkgJson }\n : pkgJson\n return mergedPkgJson\n}\n\nexport type IsolatePackageOptions = {\n imports?: Record<string, string> | undefined\n install?: ((cwd: string) => Promise<void>) | undefined\n onPackageJson?:\n | ((pkgJson: PackageJson) => PackageJson | Promise<PackageJson>)\n | undefined\n sourcePath?: string | undefined\n}\n\nexport type IsolatePackageResult = {\n exports?: Record<string, unknown> | undefined\n tmpdir: string\n}\n\n/**\n * Isolates a package in a temporary test environment.\n *\n * Supports multiple input types:\n * 1. File system path (absolute or relative)\n * 2. Package name with optional version spec\n * 3. npm package spec (parsed via npm-package-arg)\n *\n * @throws {Error} When package installation or setup fails.\n */\nexport async function isolatePackage(\n packageSpec: string,\n options?: IsolatePackageOptions | undefined,\n): Promise<IsolatePackageResult> {\n const os = getOs()\n const path = getPath()\n const opts = { __proto__: null, ...options } as IsolatePackageOptions\n const { imports, install, onPackageJson, sourcePath: optSourcePath } = opts\n\n let sourcePath = optSourcePath\n let packageName: string | undefined\n let spec: string | undefined\n\n // Determine if this is a path or package spec.\n if (isPath(packageSpec)) {\n // File system path.\n // Handle edge case on Windows where path.relative() returns an absolute path\n // when paths are on different drives, and the test prepends './' to it.\n // Example: './C:\\Users\\...' should be treated as 'C:\\Users\\...'.\n const trimmedPath = trimLeadingDotSlash(packageSpec)\n const pathToResolve = isAbsolute(trimmedPath) ? trimmedPath : packageSpec\n sourcePath = path.resolve(pathToResolve)\n\n if (!existsSync(sourcePath)) {\n throw new Error(`Source path does not exist: ${sourcePath}`)\n }\n\n // Read package.json to get the name.\n const pkgJson = await readPackageJson(sourcePath, { normalize: true })\n if (!pkgJson) {\n throw new Error(`Could not read package.json from: ${sourcePath}`)\n }\n packageName = pkgJson.name as string\n } else {\n // Parse as npm package spec.\n const npa = /*@__PURE__*/ require('../external/npm-package-arg')\n const parsed = npa(packageSpec)\n\n packageName = parsed.name\n\n if (parsed.type === 'directory' || parsed.type === 'file') {\n sourcePath = parsed.fetchSpec\n if (!sourcePath || !existsSync(sourcePath)) {\n throw new Error(`Source path does not exist: ${sourcePath}`)\n }\n // If package name not provided by parser, read from package.json.\n if (!packageName) {\n const pkgJson = await readPackageJson(sourcePath, { normalize: true })\n if (!pkgJson) {\n throw new Error(`Could not read package.json from: ${sourcePath}`)\n }\n packageName = pkgJson.name as string\n }\n } else {\n // Registry package.\n spec = parsed.fetchSpec || parsed.rawSpec\n }\n }\n\n if (!packageName) {\n throw new Error(`Could not determine package name from: ${packageSpec}`)\n }\n\n // Create temp directory for this package.\n const sanitizedName = packageName.replace(/[@/]/g, '-')\n const tempDir = await fs.mkdtemp(\n path.join(os.tmpdir(), `socket-test-${sanitizedName}-`),\n )\n const packageTempDir = path.join(tempDir, sanitizedName)\n await fs.mkdir(packageTempDir, { recursive: true })\n\n let installedPath: string\n let originalPackageJson: PackageJson | undefined\n\n if (spec) {\n // Installing from registry first, then copying source on top if provided.\n await fs.writeFile(\n path.join(packageTempDir, 'package.json'),\n JSON.stringify(\n {\n name: 'test-temp',\n private: true,\n version: '1.0.0',\n },\n null,\n 2,\n ),\n )\n\n // Use custom install function or default pnpm install.\n if (install) {\n await install(packageTempDir)\n } else {\n const { spawn } = /*@__PURE__*/ require('../spawn')\n const WIN32 = require('../constants/platform').WIN32\n const packageInstallSpec = spec.startsWith('https://')\n ? spec\n : `${packageName}@${spec}`\n\n await spawn('pnpm', ['add', packageInstallSpec], {\n cwd: packageTempDir,\n shell: WIN32,\n stdio: 'pipe',\n })\n }\n\n installedPath = path.join(packageTempDir, 'node_modules', packageName)\n\n // Save original package.json before copying source.\n originalPackageJson = await readPackageJson(installedPath, {\n normalize: true,\n })\n\n // Copy source files on top if provided.\n if (sourcePath) {\n // Check if source and destination are the same (symlinked).\n const realInstalledPath = await resolveRealPath(installedPath)\n const realSourcePath = await resolveRealPath(sourcePath)\n\n if (realSourcePath !== realInstalledPath) {\n await fs.cp(sourcePath, installedPath, FS_CP_OPTIONS)\n }\n }\n } else {\n // Just copying local package, no registry install.\n if (!sourcePath) {\n throw new Error('sourcePath is required when no version spec provided')\n }\n\n const scopedPath = packageName.startsWith('@')\n ? path.join(\n packageTempDir,\n 'node_modules',\n packageName.split('/')[0] ?? '',\n )\n : path.join(packageTempDir, 'node_modules')\n\n await fs.mkdir(scopedPath, { recursive: true })\n installedPath = path.join(packageTempDir, 'node_modules', packageName)\n\n await fs.cp(sourcePath, installedPath, FS_CP_OPTIONS)\n }\n\n // Prepare package.json if callback provided or if we need to merge with original.\n if (onPackageJson || originalPackageJson) {\n const pkgJsonPath = path.join(installedPath, 'package.json')\n const mergedPkgJson = await mergePackageJson(\n pkgJsonPath,\n originalPackageJson,\n )\n\n const finalPkgJson = onPackageJson\n ? await onPackageJson(mergedPkgJson)\n : mergedPkgJson\n\n await fs.writeFile(pkgJsonPath, JSON.stringify(finalPkgJson, null, 2))\n }\n\n // Install dependencies.\n if (install) {\n await install(installedPath)\n } else {\n const { spawn } = /*@__PURE__*/ require('../spawn')\n const WIN32 = require('../constants/platform').WIN32\n await spawn('pnpm', ['install'], {\n cwd: installedPath,\n shell: WIN32,\n stdio: 'pipe',\n })\n }\n\n // Load module exports if imports provided.\n const exports: Record<string, unknown> = imports\n ? { __proto__: null }\n : (undefined as unknown as Record<string, unknown>)\n\n if (imports) {\n for (const { 0: key, 1: specifier } of Object.entries(imports)) {\n const fullPath = path.join(installedPath, specifier)\n exports[key] = require(fullPath)\n }\n }\n\n return {\n exports,\n tmpdir: installedPath,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["fs", "WIN32", "exports"]
7
7
  }
@@ -33,7 +33,7 @@ let _fetcher;
33
33
  function getFetcher() {
34
34
  if (_fetcher === void 0) {
35
35
  const makeFetchHappen = require("../external/make-fetch-happen");
36
- const { getPacoteCachePath } = require("../../constants/packages");
36
+ const { getPacoteCachePath } = require("../constants/packages");
37
37
  _fetcher = makeFetchHappen.defaults({
38
38
  cachePath: getPacoteCachePath(),
39
39
  // Prefer-offline: Staleness checks for cached data will be bypassed, but
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/packages/provenance.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview Package provenance and attestation verification utilities.\n */\n\nimport { NPM_REGISTRY_URL } from '#constants/agents'\n\nimport { createCompositeAbortSignal, createTimeoutSignal } from '../abort'\nimport type { ProvenanceOptions } from '../packages'\nimport { parseUrl } from '../url'\n\n// IMPORTANT: Do not use destructuring here - use direct assignment instead.\n// tsgo has a bug that incorrectly transpiles destructured exports, resulting in\n// `exports.SomeName = void 0;` which causes runtime errors.\n// See: https://github.com/SocketDev/socket-packageurl-js/issues/3\nconst ArrayIsArray = Array.isArray\n\nconst SLSA_PROVENANCE_V0_2 = 'https://slsa.dev/provenance/v0.2'\nconst SLSA_PROVENANCE_V1_0 = 'https://slsa.dev/provenance/v1'\n\nlet _fetcher: typeof import('make-fetch-happen') | undefined\n/*@__NO_SIDE_EFFECTS__*/\nfunction getFetcher() {\n if (_fetcher === undefined) {\n const makeFetchHappen =\n /*@__PURE__*/ require('../external/make-fetch-happen')\n // Lazy load constants to avoid circular dependencies.\n const { getPacoteCachePath } =\n /*@__PURE__*/ require('../../constants/packages')\n _fetcher = makeFetchHappen.defaults({\n cachePath: getPacoteCachePath(),\n // Prefer-offline: Staleness checks for cached data will be bypassed, but\n // missing data will be requested from the server.\n // https://github.com/npm/make-fetch-happen?tab=readme-ov-file#--optscache\n cache: 'force-cache',\n })\n }\n return _fetcher as typeof import('make-fetch-happen')\n}\n\n/**\n * Extract and filter SLSA provenance attestations from attestation data.\n */\nfunction getAttestations(attestationData: unknown): unknown[] {\n const data = attestationData as { attestations?: unknown[] }\n if (!data.attestations || !ArrayIsArray(data.attestations)) {\n return []\n }\n\n return data.attestations.filter((attestation: unknown) => {\n const att = attestation as { predicateType?: string }\n return (\n att.predicateType === SLSA_PROVENANCE_V0_2 ||\n att.predicateType === SLSA_PROVENANCE_V1_0\n )\n })\n}\n\n/**\n * Find the first attestation with valid provenance data.\n */\nfunction findProvenance(attestations: unknown[]): unknown {\n for (const attestation of attestations) {\n const att = attestation as {\n bundle?: { dsseEnvelope?: { payload?: string } }\n predicate?: unknown\n }\n try {\n let predicate = att.predicate\n\n // If predicate is not directly available, try to decode from DSSE envelope\n if (!predicate && att.bundle?.dsseEnvelope?.payload) {\n try {\n const decodedPayload = Buffer.from(\n att.bundle.dsseEnvelope.payload,\n 'base64',\n ).toString('utf8')\n const statement = JSON.parse(decodedPayload)\n predicate = statement.predicate\n } catch {\n // Failed to decode, continue to next attestation\n continue\n }\n }\n\n const predicateData = predicate as {\n buildDefinition?: { externalParameters?: unknown }\n }\n if (predicateData?.buildDefinition?.externalParameters) {\n return {\n predicate,\n externalParameters: predicateData.buildDefinition.externalParameters,\n }\n }\n // c8 ignore start - Error handling for malformed attestation data should continue processing other attestations.\n } catch {\n // Continue checking other attestations if one fails to parse\n }\n // c8 ignore stop\n }\n return undefined\n}\n\n/**\n * Check if a value indicates a trusted publisher (GitHub or GitLab).\n */\nfunction isTrustedPublisher(value: unknown): boolean {\n if (typeof value !== 'string' || !value) {\n return false\n }\n\n let url = parseUrl(value)\n let hostname = url?.hostname\n\n // Handle GitHub workflow refs with @ syntax by trying the first part.\n // Example: \"https://github.com/owner/repo/.github/workflows/ci.yml@refs/heads/main\"\n if (!url && value.includes('@')) {\n const firstPart = value.split('@')[0]\n if (firstPart) {\n url = parseUrl(firstPart)\n }\n if (url) {\n hostname = url.hostname\n }\n }\n\n // Try common URL prefixes if not already a complete URL.\n if (!url) {\n const httpsUrl = parseUrl(`https://${value}`)\n if (httpsUrl) {\n hostname = httpsUrl.hostname\n }\n }\n\n if (hostname) {\n return (\n hostname === 'github.com' ||\n hostname.endsWith('.github.com') ||\n hostname === 'gitlab.com' ||\n hostname.endsWith('.gitlab.com')\n )\n }\n\n // Fallback: check for provider keywords in non-URL strings.\n return value.includes('github') || value.includes('gitlab')\n}\n\n/**\n * Convert raw attestation data to user-friendly provenance details.\n */\nexport function getProvenanceDetails(attestationData: unknown): unknown {\n const attestations = getAttestations(attestationData)\n if (!attestations.length) {\n return undefined\n }\n // Find the first attestation with valid provenance data.\n const provenance = findProvenance(attestations)\n if (!provenance) {\n return { level: 'attested' }\n }\n\n const provenanceData = provenance as {\n externalParameters?: {\n context?: string\n ref?: string\n repository?: string\n run_id?: string\n sha?: string\n workflow?: {\n ref?: string\n repository?: string\n }\n workflow_ref?: string\n }\n predicate?: {\n buildDefinition?: { buildType?: string }\n }\n }\n const { externalParameters, predicate } = provenanceData\n const def = predicate?.buildDefinition\n\n // Handle both SLSA v0.2 (direct properties) and v1 (nested workflow object)\n const workflow = externalParameters?.workflow\n const workflowRef = workflow?.ref || externalParameters?.workflow_ref\n const workflowUrl = externalParameters?.context\n const workflowPlatform = def?.buildType\n const repository = workflow?.repository || externalParameters?.repository\n const gitRef = externalParameters?.ref || workflow?.ref\n const commitSha = externalParameters?.sha\n const workflowRunId = externalParameters?.run_id\n\n // Check for trusted publishers (GitHub Actions, GitLab CI/CD).\n const trusted =\n isTrustedPublisher(workflowRef) ||\n isTrustedPublisher(workflowUrl) ||\n isTrustedPublisher(workflowPlatform) ||\n isTrustedPublisher(repository)\n\n return {\n commitSha,\n gitRef,\n level: trusted ? 'trusted' : 'attested',\n repository,\n workflowRef,\n workflowUrl,\n workflowPlatform,\n workflowRunId,\n }\n}\n\n/**\n * Fetch package provenance information from npm registry.\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function fetchPackageProvenance(\n pkgName: string,\n pkgVersion: string,\n options?: ProvenanceOptions,\n): Promise<unknown> {\n const { signal, timeout = 10_000 } = {\n __proto__: null,\n ...options,\n } as ProvenanceOptions\n\n if (signal?.aborted) {\n return undefined\n }\n\n // Create composite signal combining external signal with timeout\n const timeoutSignal = createTimeoutSignal(timeout)\n const compositeSignal = createCompositeAbortSignal(signal, timeoutSignal)\n const fetcher = getFetcher()\n\n try {\n const response = await fetcher(\n // The npm registry attestations API endpoint.\n `${NPM_REGISTRY_URL}/-/npm/v1/attestations/${encodeURIComponent(pkgName)}@${encodeURIComponent(pkgVersion)}`,\n {\n method: 'GET',\n signal: compositeSignal,\n headers: {\n 'User-Agent': 'socket-registry',\n },\n } as {\n method: string\n signal: AbortSignal\n headers: Record<string, string>\n },\n )\n if (response.ok) {\n return getProvenanceDetails(await response.json())\n }\n } catch {}\n return undefined\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @fileoverview Package provenance and attestation verification utilities.\n */\n\nimport { NPM_REGISTRY_URL } from '#constants/agents'\n\nimport { createCompositeAbortSignal, createTimeoutSignal } from '../abort'\nimport type { ProvenanceOptions } from '../packages'\nimport { parseUrl } from '../url'\n\n// IMPORTANT: Do not use destructuring here - use direct assignment instead.\n// tsgo has a bug that incorrectly transpiles destructured exports, resulting in\n// `exports.SomeName = void 0;` which causes runtime errors.\n// See: https://github.com/SocketDev/socket-packageurl-js/issues/3\nconst ArrayIsArray = Array.isArray\n\nconst SLSA_PROVENANCE_V0_2 = 'https://slsa.dev/provenance/v0.2'\nconst SLSA_PROVENANCE_V1_0 = 'https://slsa.dev/provenance/v1'\n\nlet _fetcher: typeof import('make-fetch-happen') | undefined\n/*@__NO_SIDE_EFFECTS__*/\nfunction getFetcher() {\n if (_fetcher === undefined) {\n const makeFetchHappen =\n /*@__PURE__*/ require('../external/make-fetch-happen')\n // Lazy load constants to avoid circular dependencies.\n const { getPacoteCachePath } =\n /*@__PURE__*/ require('../constants/packages')\n _fetcher = makeFetchHappen.defaults({\n cachePath: getPacoteCachePath(),\n // Prefer-offline: Staleness checks for cached data will be bypassed, but\n // missing data will be requested from the server.\n // https://github.com/npm/make-fetch-happen?tab=readme-ov-file#--optscache\n cache: 'force-cache',\n })\n }\n return _fetcher as typeof import('make-fetch-happen')\n}\n\n/**\n * Extract and filter SLSA provenance attestations from attestation data.\n */\nfunction getAttestations(attestationData: unknown): unknown[] {\n const data = attestationData as { attestations?: unknown[] }\n if (!data.attestations || !ArrayIsArray(data.attestations)) {\n return []\n }\n\n return data.attestations.filter((attestation: unknown) => {\n const att = attestation as { predicateType?: string }\n return (\n att.predicateType === SLSA_PROVENANCE_V0_2 ||\n att.predicateType === SLSA_PROVENANCE_V1_0\n )\n })\n}\n\n/**\n * Find the first attestation with valid provenance data.\n */\nfunction findProvenance(attestations: unknown[]): unknown {\n for (const attestation of attestations) {\n const att = attestation as {\n bundle?: { dsseEnvelope?: { payload?: string } }\n predicate?: unknown\n }\n try {\n let predicate = att.predicate\n\n // If predicate is not directly available, try to decode from DSSE envelope\n if (!predicate && att.bundle?.dsseEnvelope?.payload) {\n try {\n const decodedPayload = Buffer.from(\n att.bundle.dsseEnvelope.payload,\n 'base64',\n ).toString('utf8')\n const statement = JSON.parse(decodedPayload)\n predicate = statement.predicate\n } catch {\n // Failed to decode, continue to next attestation\n continue\n }\n }\n\n const predicateData = predicate as {\n buildDefinition?: { externalParameters?: unknown }\n }\n if (predicateData?.buildDefinition?.externalParameters) {\n return {\n predicate,\n externalParameters: predicateData.buildDefinition.externalParameters,\n }\n }\n // c8 ignore start - Error handling for malformed attestation data should continue processing other attestations.\n } catch {\n // Continue checking other attestations if one fails to parse\n }\n // c8 ignore stop\n }\n return undefined\n}\n\n/**\n * Check if a value indicates a trusted publisher (GitHub or GitLab).\n */\nfunction isTrustedPublisher(value: unknown): boolean {\n if (typeof value !== 'string' || !value) {\n return false\n }\n\n let url = parseUrl(value)\n let hostname = url?.hostname\n\n // Handle GitHub workflow refs with @ syntax by trying the first part.\n // Example: \"https://github.com/owner/repo/.github/workflows/ci.yml@refs/heads/main\"\n if (!url && value.includes('@')) {\n const firstPart = value.split('@')[0]\n if (firstPart) {\n url = parseUrl(firstPart)\n }\n if (url) {\n hostname = url.hostname\n }\n }\n\n // Try common URL prefixes if not already a complete URL.\n if (!url) {\n const httpsUrl = parseUrl(`https://${value}`)\n if (httpsUrl) {\n hostname = httpsUrl.hostname\n }\n }\n\n if (hostname) {\n return (\n hostname === 'github.com' ||\n hostname.endsWith('.github.com') ||\n hostname === 'gitlab.com' ||\n hostname.endsWith('.gitlab.com')\n )\n }\n\n // Fallback: check for provider keywords in non-URL strings.\n return value.includes('github') || value.includes('gitlab')\n}\n\n/**\n * Convert raw attestation data to user-friendly provenance details.\n */\nexport function getProvenanceDetails(attestationData: unknown): unknown {\n const attestations = getAttestations(attestationData)\n if (!attestations.length) {\n return undefined\n }\n // Find the first attestation with valid provenance data.\n const provenance = findProvenance(attestations)\n if (!provenance) {\n return { level: 'attested' }\n }\n\n const provenanceData = provenance as {\n externalParameters?: {\n context?: string\n ref?: string\n repository?: string\n run_id?: string\n sha?: string\n workflow?: {\n ref?: string\n repository?: string\n }\n workflow_ref?: string\n }\n predicate?: {\n buildDefinition?: { buildType?: string }\n }\n }\n const { externalParameters, predicate } = provenanceData\n const def = predicate?.buildDefinition\n\n // Handle both SLSA v0.2 (direct properties) and v1 (nested workflow object)\n const workflow = externalParameters?.workflow\n const workflowRef = workflow?.ref || externalParameters?.workflow_ref\n const workflowUrl = externalParameters?.context\n const workflowPlatform = def?.buildType\n const repository = workflow?.repository || externalParameters?.repository\n const gitRef = externalParameters?.ref || workflow?.ref\n const commitSha = externalParameters?.sha\n const workflowRunId = externalParameters?.run_id\n\n // Check for trusted publishers (GitHub Actions, GitLab CI/CD).\n const trusted =\n isTrustedPublisher(workflowRef) ||\n isTrustedPublisher(workflowUrl) ||\n isTrustedPublisher(workflowPlatform) ||\n isTrustedPublisher(repository)\n\n return {\n commitSha,\n gitRef,\n level: trusted ? 'trusted' : 'attested',\n repository,\n workflowRef,\n workflowUrl,\n workflowPlatform,\n workflowRunId,\n }\n}\n\n/**\n * Fetch package provenance information from npm registry.\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function fetchPackageProvenance(\n pkgName: string,\n pkgVersion: string,\n options?: ProvenanceOptions,\n): Promise<unknown> {\n const { signal, timeout = 10_000 } = {\n __proto__: null,\n ...options,\n } as ProvenanceOptions\n\n if (signal?.aborted) {\n return undefined\n }\n\n // Create composite signal combining external signal with timeout\n const timeoutSignal = createTimeoutSignal(timeout)\n const compositeSignal = createCompositeAbortSignal(signal, timeoutSignal)\n const fetcher = getFetcher()\n\n try {\n const response = await fetcher(\n // The npm registry attestations API endpoint.\n `${NPM_REGISTRY_URL}/-/npm/v1/attestations/${encodeURIComponent(pkgName)}@${encodeURIComponent(pkgVersion)}`,\n {\n method: 'GET',\n signal: compositeSignal,\n headers: {\n 'User-Agent': 'socket-registry',\n },\n } as {\n method: string\n signal: AbortSignal\n headers: Record<string, string>\n },\n )\n if (response.ok) {\n return getProvenanceDetails(await response.json())\n }\n } catch {}\n return undefined\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/path.js CHANGED
@@ -383,12 +383,20 @@ function relative(from, to) {
383
383
  let lastCommonSep = -1;
384
384
  let i = 0;
385
385
  for (; i < length; i += 1) {
386
- const fromCode = actualFrom.charCodeAt(fromStart + i);
387
- const toCode = actualTo.charCodeAt(toStart + i);
386
+ let fromCode = actualFrom.charCodeAt(fromStart + i);
387
+ let toCode = actualTo.charCodeAt(toStart + i);
388
+ if (WIN322) {
389
+ if (fromCode >= CHAR_UPPERCASE_A && fromCode <= CHAR_UPPERCASE_Z) {
390
+ fromCode += 32;
391
+ }
392
+ if (toCode >= CHAR_UPPERCASE_A && toCode <= CHAR_UPPERCASE_Z) {
393
+ toCode += 32;
394
+ }
395
+ }
388
396
  if (fromCode !== toCode) {
389
397
  break;
390
398
  }
391
- if (/* @__PURE__ */ isPathSeparator(fromCode)) {
399
+ if (/* @__PURE__ */ isPathSeparator(actualFrom.charCodeAt(fromStart + i))) {
392
400
  lastCommonSep = i;
393
401
  }
394
402
  }