@gitkraken/core-gitlens 0.9.6 → 0.9.8

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.
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","names":[],"sources":["../../../../git-cli/src/providers/status.ts"],"sourcesContent":["import type { Cache } from '@gitlens/git/cache.js';\nimport type { GitServiceContext } from '@gitlens/git/context.js';\nimport type { GitFile } from '@gitlens/git/models/file.js';\nimport { GitFileWorkingTreeStatus } from '@gitlens/git/models/fileStatus.js';\nimport type { GitConflictFile } from '@gitlens/git/models/staging.js';\nimport { GitStatus } from '@gitlens/git/models/status.js';\nimport type { GitStatusFile } from '@gitlens/git/models/statusFile.js';\nimport type { GitStatusSubProvider, GitWorkingChangesState } from '@gitlens/git/providers/status.js';\nimport type { GitCommandPriority } from '@gitlens/git/run.types.js';\nimport { isCancellationError, raceWithTimeout } from '@gitlens/utils/cancellation.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { normalizePath, splitPath, stripFolderGlob } from '@gitlens/utils/path.js';\nimport { getSettledValue } from '@gitlens/utils/promise.js';\nimport { PromiseMap } from '@gitlens/utils/promiseCache.js';\nimport { iterateByDelimiter } from '@gitlens/utils/string.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport { fileUri, joinUriPath, toFsPath } from '@gitlens/utils/uri.js';\nimport type { CliGitProviderInternal } from '../cliGitProvider.js';\nimport type { GitResult } from '../exec/exec.types.js';\nimport type { Git } from '../exec/git.js';\nimport { gitConfigsStatus } from '../exec/git.js';\nimport { parseGitConflictFiles } from '../parsers/indexParser.js';\nimport { parseGitStatus } from '../parsers/statusParser.js';\n\n/** Backstop when `advanced.git.timeout` is disabled (0). Independent of the command timeout so disabling it can't\n * also disable deadlock recovery — mirrors `@gate`'s always-on 5-min force-clear. */\nconst disabledTimeoutBackstopMs = 1000 * 60 * 5;\n\n/**\n * Deadlock-backstop duration for a status read (see `dedupeByStatusGeneration`), exported for testing.\n * Always returns a value so a wedged read recovers even with `advanced.git.timeout` disabled (which bounds git\n * *commands*, not deadlock recovery). With a timeout set, `gitTimeout * 2` sits above the per-command timeout so\n * `git.run` reads reject at their own timeout first; the backstop's real job is bounding the timeout-less\n * `git.stream` reads (`hasUntrackedFiles`/`hasConflictingFiles`).\n */\nexport function computeDeadlockBackstopMs(gitTimeout: number | undefined): number {\n\tconst timeout = gitTimeout ?? 60000;\n\treturn timeout > 0 ? timeout * 2 : disabledTimeoutBackstopMs;\n}\n\n/**\n * A non-default `untracked`/`branch` gets its own key, so a full-status caller never joins a narrower run; the\n * default stays `'getStatus'`, which `getStatusForPathCore`'s rename path joins.\n */\nfunction getStatusReadKey(options?: { untracked?: 'no' | 'normal' | 'all'; branch?: false }): string {\n\tif (options?.untracked == null && options?.branch !== false) return 'getStatus';\n\n\tconst parts: string[] = [];\n\tif (options?.untracked != null) {\n\t\tparts.push(`untracked=${options.untracked}`);\n\t}\n\tif (options?.branch === false) {\n\t\tparts.push('branch=false');\n\t}\n\treturn `getStatus:${parts.join(':')}`;\n}\n\nexport class StatusGitSubProvider implements GitStatusSubProvider {\n\tconstructor(\n\t\tprivate readonly context: GitServiceContext,\n\t\tprivate readonly git: Git,\n\t\tprivate readonly cache: Cache,\n\t\tprivate readonly provider: CliGitProviderInternal,\n\t) {}\n\n\t/** In-flight reads, keyed by `<read>\\0<repoPath>\\0<generation>`. `PromiseMap` (not a bare Map) so joiners\n\t * get per-caller cancellation: the shared run aborts only when ALL current callers do. */\n\tprivate readonly _pendingReads = new PromiseMap<string, unknown>();\n\n\t/**\n\t * Single-flight for point-in-time reads of mutable working-tree state — replaces `@gate`, which dedups on\n\t * repoPath with no notion of *when* a run started and so joins a pre-change read to a post-change caller\n\t * (see {@link Cache.getStatusGeneration}). Same-generation callers share one run (via a `PromiseMap`, so a\n\t * joiner cancelling can't reject the others); a caller in a newer generation gets a different key and a\n\t * fresh run; a `force` read advances the clock first, so it fences every later reader too, not just itself.\n\t * The exec-layer command dedup (`Git.pendingCommands`) is fenced automatically: each run carries this entry's\n\t * aggregate signal, whose id is part of the exec cache key, so a newer-generation run never joins an older\n\t * one's process; the generation-derived `correlationKey` is a readable second guard on that key. A `raceWithTimeout` backstop (scaled to `advanced.git.timeout`, see\n\t * {@link computeDeadlockBackstopMs}) replaces `@gate`'s 5-min force-clear so a never-settling read (a hung\n\t * `git.stream`, which has no per-command timeout) can't wedge same-generation callers — including blocking\n\t * awaiters like the commit flow — indefinitely.\n\t */\n\tprivate dedupeByStatusGeneration<T>(\n\t\trepoPath: string,\n\t\tread: string,\n\t\trun: (correlationKey: string, signal: AbortSignal | undefined) => Promise<T>,\n\t\tcancellation?: AbortSignal,\n\t\tforce?: boolean,\n\t): Promise<T> {\n\t\t// A `force` (user refresh) read is the user asserting the working tree may have changed without the watcher\n\t\t// observing it — the same class of event as a watcher tick, so it ADVANCES the clock rather than carving out\n\t\t// a private key for itself. A one-shot nonce would fence only this caller: the pre-assertion run stays\n\t\t// joinable, so the very next ordinary reader still picks up pre-change content — and, stamped with a newer\n\t\t// `Wip.revision` (assigned at producer start, not read start), applies it right back over the refreshed\n\t\t// result. Advancing is also CHEAPER than a nonce: concurrent readers join the one fresh read instead of each\n\t\t// spawning a private one.\n\t\tif (force) {\n\t\t\tthis.cache.incrementStatusGeneration(repoPath);\n\t\t}\n\t\tconst generation = this.cache.getStatusGeneration(repoPath);\n\t\t// `\\0` separators: `read`/`repoPath` are free-form (paths can contain `:`), so a printable delimiter could\n\t\t// collide (`getStatusForPath:/a:x` vs `getStatusForPath:/a` + `:x`).\n\t\tconst key = `${read}\\0${repoPath}\\0${generation}`;\n\t\tconst correlationKey = `status:${generation}`;\n\t\tconst backstopMs = computeDeadlockBackstopMs(this.git.options.gitTimeout);\n\n\t\treturn this._pendingReads.getOrCreate(\n\t\t\tkey,\n\t\t\t(cacheable, signal) => {\n\t\t\t\t// Point-in-time read: never memoize a settled value. `invalidate()` at the start makes the entry\n\t\t\t\t// self-evict on settle, so a later caller in this same generation re-reads (a newer generation\n\t\t\t\t// already gets a different key). `signal` is the aggregate — the run aborts only if every caller\n\t\t\t\t// cancels, so one caller's abort can't reject the others. The backstop rejects a wedged read so\n\t\t\t\t// waiters unblock and the entry self-evicts; it ALSO aborts the underlying git op (via `backstop`\n\t\t\t\t// linked into the run's signal) so a wedged read releases its GitQueue slot/process rather than\n\t\t\t\t// running orphaned — best-effort, since a truly-stuck process may ignore the abort.\n\t\t\t\tcacheable.invalidate();\n\t\t\t\tconst backstop = new AbortController();\n\t\t\t\tconst runSignal = signal != null ? AbortSignal.any([signal, backstop.signal]) : backstop.signal;\n\t\t\t\treturn raceWithTimeout(run(correlationKey, runSignal), backstopMs, backstop);\n\t\t\t},\n\t\t\tcancellation,\n\t\t) as Promise<T>;\n\t}\n\n\t@debug()\n\tgetStatus(\n\t\trepoPath: string | undefined,\n\t\toptions?: {\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tforce?: boolean;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tif (repoPath == null) return Promise.resolve(undefined);\n\n\t\t// `-u` already means `-uall`, so an explicit `'all'` shares the default run\n\t\tconst opts = options?.untracked === 'all' ? { ...options, untracked: undefined } : options;\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\tgetStatusReadKey(opts),\n\t\t\t(correlationKey, signal) =>\n\t\t\t\tthis.getStatusCore(repoPath, { ...opts, correlationKey: correlationKey }, signal),\n\t\t\tcancellation,\n\t\t\topts?.force,\n\t\t);\n\t}\n\n\tprivate async getStatusCore(\n\t\trepoPath: string,\n\t\toptions?: {\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tcorrelationKey?: string;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tconst porcelainVersion = (await this.git.supports('git:status:porcelain-v2')) ? 2 : 1;\n\n\t\tconst result = await this.statusCore(\n\t\t\trepoPath,\n\t\t\tporcelainVersion,\n\t\t\t{\n\t\t\t\tsimilarityThreshold: this.context.config?.commits.similarityThreshold,\n\t\t\t\tpriority: options?.priority,\n\t\t\t\tcorrelationKey: options?.correlationKey,\n\t\t\t\tuntracked: options?.untracked,\n\t\t\t\tbranch: options?.branch,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t\tconst repoUri = fileUri(normalizePath(repoPath));\n\t\tconst status = parseGitStatus(result.stdout, repoPath, porcelainVersion, p =>\n\t\t\tjoinUriPath(repoUri, normalizePath(p)),\n\t\t);\n\n\t\tif (options?.branch === false) {\n\t\t\t// Without `--branch` a clean tree prints nothing at all, which the parser reads as no status\n\t\t\tif (status == null && result.completion.status === 'exited' && result.exitCode === 0) {\n\t\t\t\treturn new GitStatus(normalizePath(repoPath), '', '', []);\n\t\t\t}\n\t\t\t// No branch name either, which `GitStatus` reads as detached, so there is no paused rebase to look up\n\t\t\treturn status;\n\t\t}\n\n\t\tif (status?.detached) {\n\t\t\tconst pausedOpStatus = await this.provider.pausedOps?.getPausedOperationStatus?.(\n\t\t\t\trepoPath,\n\t\t\t\tundefined,\n\t\t\t\tcancellation,\n\t\t\t);\n\t\t\tif (pausedOpStatus?.type === 'rebase') {\n\t\t\t\treturn new GitStatus(\n\t\t\t\t\trepoPath,\n\t\t\t\t\tpausedOpStatus.incoming.name,\n\t\t\t\t\tstatus.sha,\n\t\t\t\t\tstatus.files,\n\t\t\t\t\tstatus.upstream,\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\treturn status;\n\t}\n\n\t@debug()\n\tasync getStatusForFile(\n\t\trepoPath: string,\n\t\tpathOrUri: string | Uri,\n\t\toptions?: { renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile | undefined> {\n\t\tconst files = await this.getStatusForPathCore(\n\t\t\trepoPath,\n\t\t\ttoFsPath(pathOrUri),\n\t\t\t{ ...options, exact: true },\n\t\t\tcancellation,\n\t\t);\n\t\treturn files?.[0];\n\t}\n\n\t@debug()\n\tasync getStatusForPath(\n\t\trepoPath: string,\n\t\tpathOrUri: string | Uri,\n\t\toptions?: { renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\treturn this.getStatusForPathCore(repoPath, toFsPath(pathOrUri), { ...options, exact: false }, cancellation);\n\t}\n\n\tprivate async getStatusForPathCore(\n\t\trepoPath: string,\n\t\tpathOrUri: string,\n\t\toptions: { exact: boolean; renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\tconst relativePath = stripFolderGlob(splitPath(pathOrUri, repoPath)[0]);\n\n\t\t// Rename-aware queries can't scope by pathspec (that disables Git's rename detection), so they run the\n\t\t// SAME full `git status` as `getStatus`. Delegate to it — sharing one process/dedup entry — and filter,\n\t\t// rather than spawning a second identical `git status`.\n\t\tif (options.renames !== false) {\n\t\t\tconst status = await this.getStatus(repoPath, undefined, cancellation);\n\t\t\tif (status == null) return undefined;\n\n\t\t\tif (options.exact) {\n\t\t\t\tconst file = status.files.find(f => f.path === relativePath);\n\t\t\t\treturn file ? [file] : undefined;\n\t\t\t}\n\t\t\treturn status.files.filter(f => f.path.startsWith(relativePath));\n\t\t}\n\n\t\t// Non-rename query: pathspec-scoped `git status` (a distinct command), deduped on its own key. `exact`\n\t\t// is NOT in the key — the pathspec-scoped command is identical for exact/non-exact, so both share one run.\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t// Free-form `relativePath` (may contain `:`) goes LAST so the prefix can't be shifted into it.\n\t\t\t`getStatusForPath:${relativePath}`,\n\t\t\t(correlationKey, signal) => this.getStatusForPathScoped(repoPath, relativePath, correlationKey, signal),\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprivate async getStatusForPathScoped(\n\t\trepoPath: string,\n\t\trelativePath: string,\n\t\tcorrelationKey: string,\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\tconst porcelainVersion = (await this.git.supports('git:status:porcelain-v2')) ? 2 : 1;\n\t\tconst result = await this.statusCore(\n\t\t\trepoPath,\n\t\t\tporcelainVersion,\n\t\t\t{ similarityThreshold: this.context.config?.commits.similarityThreshold, correlationKey: correlationKey },\n\t\t\tcancellation,\n\t\t\trelativePath,\n\t\t);\n\n\t\tconst repoUri = fileUri(normalizePath(repoPath));\n\t\tconst status = parseGitStatus(result.stdout, repoPath, porcelainVersion, p =>\n\t\t\tjoinUriPath(repoUri, normalizePath(p)),\n\t\t);\n\t\treturn status?.files;\n\t}\n\n\tprivate async statusCore(\n\t\trepoPath: string,\n\t\tporcelainVersion: number = 1,\n\t\toptions?: {\n\t\t\tsimilarityThreshold?: number | null;\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tcorrelationKey?: string;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t\t...pathspecs: string[]\n\t): Promise<GitResult> {\n\t\tconst params = ['status', porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain'];\n\t\tif (options?.branch !== false) {\n\t\t\tparams.push('--branch');\n\t\t}\n\t\tparams.push(options?.untracked != null ? `-u${options.untracked}` : '-u');\n\t\tif (await this.git.supports('git:status:find-renames')) {\n\t\t\tparams.push(\n\t\t\t\t`--find-renames${options?.similarityThreshold == null ? '' : `=${options.similarityThreshold}%`}`,\n\t\t\t);\n\t\t}\n\n\t\treturn this.git.run(\n\t\t\t{\n\t\t\t\tcwd: repoPath,\n\t\t\t\tcancellation: cancellation,\n\t\t\t\tconfigs: gitConfigsStatus,\n\t\t\t\tenv: { GIT_OPTIONAL_LOCKS: '0' },\n\t\t\t\tcorrelationKey: options?.correlationKey,\n\t\t\t\t...(options?.priority != null ? { priority: options.priority } : undefined),\n\t\t\t},\n\t\t\t...params,\n\t\t\t'--',\n\t\t\t...pathspecs,\n\t\t);\n\t}\n\n\t@debug()\n\thasWorkingChanges(\n\t\trepoPath: string,\n\t\toptions?: {\n\t\t\tstaged?: boolean;\n\t\t\tunstaged?: boolean;\n\t\t\tuntracked?: boolean;\n\t\t\tthrowOnError?: boolean;\n\t\t\tpriority?: GitCommandPriority;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<boolean> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst staged = options?.staged ?? true;\n\t\tconst unstaged = options?.unstaged ?? true;\n\t\tconst untracked = options?.untracked ?? true;\n\t\t// `throwOnError` is in the key because it changes the RESULT on git failure (throw vs `false`) — a\n\t\t// throwing caller must not join, and be silently satisfied by, a non-throwing caller's graceful run.\n\t\t// `priority` is in the key for a different reason: it doesn't change the answer, but a joiner INHERITS\n\t\t// the in-flight run's scheduling. `GitQueue` refuses to start background work while anything is\n\t\t// waiting at normal/interactive, so letting a foreground caller (the overview's dirty pill, the\n\t\t// Worktrees view) join a `background` probe would strand it for the whole graph load. Splitting the\n\t\t// two lanes costs at most one extra spawn; joining costs unbounded foreground latency.\n\t\tconst throwOnError = options?.throwOnError ?? false;\n\t\tconst priority = options?.priority ?? 'default';\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t`hasWorkingChanges:${staged}:${unstaged}:${untracked}:${throwOnError}:${priority}`,\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tif (staged || unstaged) {\n\t\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcwd: repoPath,\n\t\t\t\t\t\t\t\tcancellation: signal,\n\t\t\t\t\t\t\t\terrors: 'ignore',\n\t\t\t\t\t\t\t\tcorrelationKey: correlationKey,\n\t\t\t\t\t\t\t\t...(options?.priority != null ? { priority: options.priority } : undefined),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t\tstaged && unstaged ? 'HEAD' : staged ? '--staged' : undefined,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (result.exitCode === 1) {\n\t\t\t\t\t\t\tif (staged && unstaged) {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has staged and unstaged changes');\n\t\t\t\t\t\t\t} else if (staged) {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has staged changes');\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has unstaged changes');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for untracked files. NOTE: this runs through `git.stream`, which spawns directly\n\t\t\t\t\t// and never enters the queue — so `priority` above doesn't reach it. Only clean worktrees\n\t\t\t\t\t// get this far (a dirty one already returned above), and callers fanning out across many\n\t\t\t\t\t// worktrees are expected to bound their own concurrency.\n\t\t\t\t\tif (untracked) {\n\t\t\t\t\t\tconst hasUntracked = await this.hasUntrackedFiles(repoPath, signal);\n\t\t\t\t\t\tif (hasUntracked) {\n\t\t\t\t\t\t\tscope?.addExitInfo('has untracked files');\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tscope?.addExitInfo('no working changes');\n\t\t\t\t\treturn false;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return false for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error checking for changes');\n\t\t\t\t\tif (throwOnError) throw ex;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetWorkingChangesState(repoPath: string, cancellation?: AbortSignal): Promise<GitWorkingChangesState> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getWorkingChangesState',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst [stagedResult, unstagedResult, untrackedResult] = await Promise.allSettled([\n\t\t\t\t\t\t// Check for staged changes\n\t\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t\t'--staged',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// Check for unstaged changes\n\t\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// Check for untracked files\n\t\t\t\t\t\tthis.hasUntrackedFiles(repoPath, signal),\n\t\t\t\t\t]);\n\n\t\t\t\t\tconst result = {\n\t\t\t\t\t\tstaged: getSettledValue(stagedResult)?.exitCode === 1,\n\t\t\t\t\t\tunstaged: getSettledValue(unstagedResult)?.exitCode === 1,\n\t\t\t\t\t\tuntracked: getSettledValue(untrackedResult) === true,\n\t\t\t\t\t};\n\n\t\t\t\t\tscope?.addExitInfo(\n\t\t\t\t\t\tresult.staged || result.unstaged || result.untracked\n\t\t\t\t\t\t\t? `has ${result.staged ? 'staged' : ''}${result.unstaged ? (result.staged ? ', unstaged' : 'unstaged ') : ''}${\n\t\t\t\t\t\t\t\t\tresult.untracked\n\t\t\t\t\t\t\t\t\t\t? result.staged || result.unstaged\n\t\t\t\t\t\t\t\t\t\t\t? ', untracked'\n\t\t\t\t\t\t\t\t\t\t\t: 'untracked'\n\t\t\t\t\t\t\t\t\t\t: ''\n\t\t\t\t\t\t\t\t} changes`\n\t\t\t\t\t\t\t: 'no working changes',\n\t\t\t\t\t);\n\n\t\t\t\t\treturn result;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error checking for changes');\n\t\t\t\t\t// Return all false on error for graceful degradation\n\t\t\t\t\treturn { staged: false, unstaged: false, untracked: false };\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\thasConflictingFiles(repoPath: string, cancellation?: AbortSignal): Promise<boolean> {\n\t\t// Route through `dedupeByStatusGeneration` (like every other point-in-time read here) for the join-fence\n\t\t// and the `raceWithTimeout` backstop — a bare `git.stream` has no per-command timeout and could wedge.\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'hasConflictingFiles',\n\t\t\tasync (_correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst stream = this.git.stream({ cwd: repoPath, cancellation: signal }, 'ls-files', '--unmerged');\n\t\t\t\t\tusing _streamDisposer = createDisposable(() => void stream.return?.(undefined));\n\n\t\t\t\t\t// Early exit on first chunk - breaking causes SIGPIPE, killing git process\n\t\t\t\t\tfor await (const _chunk of stream) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn false;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetConflictingFiles(repoPath: string, cancellation?: AbortSignal): Promise<GitConflictFile[]> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getConflictingFiles',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t'ls-files',\n\t\t\t\t\t\t'-z',\n\t\t\t\t\t\t'--unmerged',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!result.stdout) {\n\t\t\t\t\t\tscope?.addExitInfo('no conflicting files');\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\n\t\t\t\t\tconst files = parseGitConflictFiles(result.stdout, repoPath);\n\t\t\t\t\tscope?.addExitInfo(`${String(files.length)} conflicting file(s)`);\n\t\t\t\t\treturn files;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return empty array for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error getting conflicting files');\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprivate async hasUntrackedFiles(repoPath: string, cancellation?: AbortSignal): Promise<boolean> {\n\t\ttry {\n\t\t\tconst stream = this.git.stream(\n\t\t\t\t{ cwd: repoPath, cancellation: cancellation },\n\t\t\t\t'ls-files',\n\t\t\t\t// '-z', // Unneeded since we are only looking for presence\n\t\t\t\t'--others',\n\t\t\t\t'--exclude-standard',\n\t\t\t);\n\t\t\tusing _streamDisposer = createDisposable(() => void stream.return?.(undefined));\n\n\t\t\t// Early exit on first chunk - breaking causes SIGPIPE, killing git process\n\t\t\tfor await (const _chunk of stream) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t} catch (ex) {\n\t\t\t// Re-throw cancellation errors\n\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t// Treat other errors as \"no untracked files\" for graceful degradation\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t@debug()\n\tgetUntrackedFiles(repoPath: string, cancellation?: AbortSignal): Promise<GitFile[]> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getUntrackedFiles',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t'ls-files',\n\t\t\t\t\t\t'-z',\n\t\t\t\t\t\t'--others',\n\t\t\t\t\t\t'--exclude-standard',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!result.stdout) {\n\t\t\t\t\t\tscope?.addExitInfo('no untracked files');\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\n\t\t\t\t\tconst files: GitFile[] = [];\n\n\t\t\t\t\tfor (const line of iterateByDelimiter(result.stdout, '\\0')) {\n\t\t\t\t\t\tif (!line.length) continue;\n\n\t\t\t\t\t\tfiles.push({ path: line, repoPath: repoPath, status: GitFileWorkingTreeStatus.Untracked });\n\t\t\t\t\t}\n\n\t\t\t\t\tscope?.addExitInfo(`${String(files.length)} untracked file(s)`);\n\t\t\t\t\treturn files;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return empty array for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error getting untracked files');\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"status.js","names":[],"sources":["../../../../git-cli/src/providers/status.ts"],"sourcesContent":["import type { Cache } from '@gitlens/git/cache.js';\nimport type { GitServiceContext } from '@gitlens/git/context.js';\nimport type { GitFile } from '@gitlens/git/models/file.js';\nimport { GitFileWorkingTreeStatus } from '@gitlens/git/models/fileStatus.js';\nimport type { GitConflictFile } from '@gitlens/git/models/staging.js';\nimport { GitStatus } from '@gitlens/git/models/status.js';\nimport type { GitStatusFile } from '@gitlens/git/models/statusFile.js';\nimport type { GitStatusSubProvider, GitWorkingChangesState } from '@gitlens/git/providers/status.js';\nimport type { GitCommandPriority } from '@gitlens/git/run.types.js';\nimport { isCancellationError, raceWithTimeout } from '@gitlens/utils/cancellation.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { normalizePath, splitPath, stripFolderGlob } from '@gitlens/utils/path.js';\nimport { getSettledValue } from '@gitlens/utils/promise.js';\nimport { PromiseMap } from '@gitlens/utils/promiseCache.js';\nimport { iterateByDelimiter } from '@gitlens/utils/string.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport { toFsPath } from '@gitlens/utils/uri.js';\nimport type { CliGitProviderInternal } from '../cliGitProvider.js';\nimport type { GitResult } from '../exec/exec.types.js';\nimport type { Git } from '../exec/git.js';\nimport { gitConfigsStatus } from '../exec/git.js';\nimport { parseGitConflictFiles } from '../parsers/indexParser.js';\nimport { parseGitStatus } from '../parsers/statusParser.js';\n\n/** Backstop when `advanced.git.timeout` is disabled (0). Independent of the command timeout so disabling it can't\n * also disable deadlock recovery — mirrors `@gate`'s always-on 5-min force-clear. */\nconst disabledTimeoutBackstopMs = 1000 * 60 * 5;\n\n/**\n * Deadlock-backstop duration for a status read (see `dedupeByStatusGeneration`), exported for testing.\n * Always returns a value so a wedged read recovers even with `advanced.git.timeout` disabled (which bounds git\n * *commands*, not deadlock recovery). With a timeout set, `gitTimeout * 2` sits above the per-command timeout so\n * `git.run` reads reject at their own timeout first; the backstop's real job is bounding the timeout-less\n * `git.stream` reads (`hasUntrackedFiles`/`hasConflictingFiles`).\n */\nexport function computeDeadlockBackstopMs(gitTimeout: number | undefined): number {\n\tconst timeout = gitTimeout ?? 60000;\n\treturn timeout > 0 ? timeout * 2 : disabledTimeoutBackstopMs;\n}\n\n/**\n * A non-default `untracked`/`branch` gets its own key, so a full-status caller never joins a narrower run; the\n * default stays `'getStatus'`, which `getStatusForPathCore`'s rename path joins.\n */\nfunction getStatusReadKey(options?: { untracked?: 'no' | 'normal' | 'all'; branch?: false }): string {\n\tif (options?.untracked == null && options?.branch !== false) return 'getStatus';\n\n\tconst parts: string[] = [];\n\tif (options?.untracked != null) {\n\t\tparts.push(`untracked=${options.untracked}`);\n\t}\n\tif (options?.branch === false) {\n\t\tparts.push('branch=false');\n\t}\n\treturn `getStatus:${parts.join(':')}`;\n}\n\nexport class StatusGitSubProvider implements GitStatusSubProvider {\n\tconstructor(\n\t\tprivate readonly context: GitServiceContext,\n\t\tprivate readonly git: Git,\n\t\tprivate readonly cache: Cache,\n\t\tprivate readonly provider: CliGitProviderInternal,\n\t) {}\n\n\t/** In-flight reads, keyed by `<read>\\0<repoPath>\\0<generation>`. `PromiseMap` (not a bare Map) so joiners\n\t * get per-caller cancellation: the shared run aborts only when ALL current callers do. */\n\tprivate readonly _pendingReads = new PromiseMap<string, unknown>();\n\n\t/**\n\t * Single-flight for point-in-time reads of mutable working-tree state — replaces `@gate`, which dedups on\n\t * repoPath with no notion of *when* a run started and so joins a pre-change read to a post-change caller\n\t * (see {@link Cache.getStatusGeneration}). Same-generation callers share one run (via a `PromiseMap`, so a\n\t * joiner cancelling can't reject the others); a caller in a newer generation gets a different key and a\n\t * fresh run; a `force` read advances the clock first, so it fences every later reader too, not just itself.\n\t * The exec-layer command dedup (`Git.pendingCommands`) is fenced automatically: each run carries this entry's\n\t * aggregate signal, whose id is part of the exec cache key, so a newer-generation run never joins an older\n\t * one's process; the generation-derived `correlationKey` is a readable second guard on that key. A `raceWithTimeout` backstop (scaled to `advanced.git.timeout`, see\n\t * {@link computeDeadlockBackstopMs}) replaces `@gate`'s 5-min force-clear so a never-settling read (a hung\n\t * `git.stream`, which has no per-command timeout) can't wedge same-generation callers — including blocking\n\t * awaiters like the commit flow — indefinitely.\n\t */\n\tprivate dedupeByStatusGeneration<T>(\n\t\trepoPath: string,\n\t\tread: string,\n\t\trun: (correlationKey: string, signal: AbortSignal | undefined) => Promise<T>,\n\t\tcancellation?: AbortSignal,\n\t\tforce?: boolean,\n\t): Promise<T> {\n\t\t// A `force` (user refresh) read is the user asserting the working tree may have changed without the watcher\n\t\t// observing it — the same class of event as a watcher tick, so it ADVANCES the clock rather than carving out\n\t\t// a private key for itself. A one-shot nonce would fence only this caller: the pre-assertion run stays\n\t\t// joinable, so the very next ordinary reader still picks up pre-change content — and, stamped with a newer\n\t\t// `Wip.revision` (assigned at producer start, not read start), applies it right back over the refreshed\n\t\t// result. Advancing is also CHEAPER than a nonce: concurrent readers join the one fresh read instead of each\n\t\t// spawning a private one.\n\t\tif (force) {\n\t\t\tthis.cache.incrementStatusGeneration(repoPath);\n\t\t}\n\t\tconst generation = this.cache.getStatusGeneration(repoPath);\n\t\t// `\\0` separators: `read`/`repoPath` are free-form (paths can contain `:`), so a printable delimiter could\n\t\t// collide (`getStatusForPath:/a:x` vs `getStatusForPath:/a` + `:x`).\n\t\tconst key = `${read}\\0${repoPath}\\0${generation}`;\n\t\tconst correlationKey = `status:${generation}`;\n\t\tconst backstopMs = computeDeadlockBackstopMs(this.git.options.gitTimeout);\n\n\t\treturn this._pendingReads.getOrCreate(\n\t\t\tkey,\n\t\t\t(cacheable, signal) => {\n\t\t\t\t// Point-in-time read: never memoize a settled value. `invalidate()` at the start makes the entry\n\t\t\t\t// self-evict on settle, so a later caller in this same generation re-reads (a newer generation\n\t\t\t\t// already gets a different key). `signal` is the aggregate — the run aborts only if every caller\n\t\t\t\t// cancels, so one caller's abort can't reject the others. The backstop rejects a wedged read so\n\t\t\t\t// waiters unblock and the entry self-evicts; it ALSO aborts the underlying git op (via `backstop`\n\t\t\t\t// linked into the run's signal) so a wedged read releases its GitQueue slot/process rather than\n\t\t\t\t// running orphaned — best-effort, since a truly-stuck process may ignore the abort.\n\t\t\t\tcacheable.invalidate();\n\t\t\t\tconst backstop = new AbortController();\n\t\t\t\tconst runSignal = signal != null ? AbortSignal.any([signal, backstop.signal]) : backstop.signal;\n\t\t\t\treturn raceWithTimeout(run(correlationKey, runSignal), backstopMs, backstop);\n\t\t\t},\n\t\t\tcancellation,\n\t\t) as Promise<T>;\n\t}\n\n\t@debug()\n\tgetStatus(\n\t\trepoPath: string | undefined,\n\t\toptions?: {\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tforce?: boolean;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tif (repoPath == null) return Promise.resolve(undefined);\n\n\t\t// `-u` already means `-uall`, so an explicit `'all'` shares the default run\n\t\tconst opts = options?.untracked === 'all' ? { ...options, untracked: undefined } : options;\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\tgetStatusReadKey(opts),\n\t\t\t(correlationKey, signal) =>\n\t\t\t\tthis.getStatusCore(repoPath, { ...opts, correlationKey: correlationKey }, signal),\n\t\t\tcancellation,\n\t\t\topts?.force,\n\t\t);\n\t}\n\n\tprivate async getStatusCore(\n\t\trepoPath: string,\n\t\toptions?: {\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tcorrelationKey?: string;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tconst porcelainVersion = (await this.git.supports('git:status:porcelain-v2')) ? 2 : 1;\n\n\t\tconst result = await this.statusCore(\n\t\t\trepoPath,\n\t\t\tporcelainVersion,\n\t\t\t{\n\t\t\t\tsimilarityThreshold: this.context.config?.commits.similarityThreshold,\n\t\t\t\tpriority: options?.priority,\n\t\t\t\tcorrelationKey: options?.correlationKey,\n\t\t\t\tuntracked: options?.untracked,\n\t\t\t\tbranch: options?.branch,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t\tconst status = parseGitStatus(result.stdout, repoPath, porcelainVersion);\n\n\t\tif (options?.branch === false) {\n\t\t\t// Without `--branch` a clean tree prints nothing at all, which the parser reads as no status\n\t\t\tif (status == null && result.completion.status === 'exited' && result.exitCode === 0) {\n\t\t\t\treturn new GitStatus(normalizePath(repoPath), '', '', []);\n\t\t\t}\n\t\t\t// No branch name either, which `GitStatus` reads as detached, so there is no paused rebase to look up\n\t\t\treturn status;\n\t\t}\n\n\t\tif (status?.detached) {\n\t\t\tconst pausedOpStatus = await this.provider.pausedOps?.getPausedOperationStatus?.(\n\t\t\t\trepoPath,\n\t\t\t\tundefined,\n\t\t\t\tcancellation,\n\t\t\t);\n\t\t\tif (pausedOpStatus?.type === 'rebase') {\n\t\t\t\treturn new GitStatus(\n\t\t\t\t\trepoPath,\n\t\t\t\t\tpausedOpStatus.incoming.name,\n\t\t\t\t\tstatus.sha,\n\t\t\t\t\tstatus.files,\n\t\t\t\t\tstatus.upstream,\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\treturn status;\n\t}\n\n\t@debug()\n\tasync getStatusForFile(\n\t\trepoPath: string,\n\t\tpathOrUri: string | Uri,\n\t\toptions?: { renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile | undefined> {\n\t\tconst files = await this.getStatusForPathCore(\n\t\t\trepoPath,\n\t\t\ttoFsPath(pathOrUri),\n\t\t\t{ ...options, exact: true },\n\t\t\tcancellation,\n\t\t);\n\t\treturn files?.[0];\n\t}\n\n\t@debug()\n\tasync getStatusForPath(\n\t\trepoPath: string,\n\t\tpathOrUri: string | Uri,\n\t\toptions?: { renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\treturn this.getStatusForPathCore(repoPath, toFsPath(pathOrUri), { ...options, exact: false }, cancellation);\n\t}\n\n\tprivate async getStatusForPathCore(\n\t\trepoPath: string,\n\t\tpathOrUri: string,\n\t\toptions: { exact: boolean; renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\tconst relativePath = stripFolderGlob(splitPath(pathOrUri, repoPath)[0]);\n\n\t\t// Rename-aware queries can't scope by pathspec (that disables Git's rename detection), so they run the\n\t\t// SAME full `git status` as `getStatus`. Delegate to it — sharing one process/dedup entry — and filter,\n\t\t// rather than spawning a second identical `git status`.\n\t\tif (options.renames !== false) {\n\t\t\tconst status = await this.getStatus(repoPath, undefined, cancellation);\n\t\t\tif (status == null) return undefined;\n\n\t\t\tif (options.exact) {\n\t\t\t\tconst file = status.files.find(f => f.path === relativePath);\n\t\t\t\treturn file ? [file] : undefined;\n\t\t\t}\n\t\t\treturn status.files.filter(f => f.path.startsWith(relativePath));\n\t\t}\n\n\t\t// Non-rename query: pathspec-scoped `git status` (a distinct command), deduped on its own key. `exact`\n\t\t// is NOT in the key — the pathspec-scoped command is identical for exact/non-exact, so both share one run.\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t// Free-form `relativePath` (may contain `:`) goes LAST so the prefix can't be shifted into it.\n\t\t\t`getStatusForPath:${relativePath}`,\n\t\t\t(correlationKey, signal) => this.getStatusForPathScoped(repoPath, relativePath, correlationKey, signal),\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprivate async getStatusForPathScoped(\n\t\trepoPath: string,\n\t\trelativePath: string,\n\t\tcorrelationKey: string,\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\tconst porcelainVersion = (await this.git.supports('git:status:porcelain-v2')) ? 2 : 1;\n\t\tconst result = await this.statusCore(\n\t\t\trepoPath,\n\t\t\tporcelainVersion,\n\t\t\t{ similarityThreshold: this.context.config?.commits.similarityThreshold, correlationKey: correlationKey },\n\t\t\tcancellation,\n\t\t\trelativePath,\n\t\t);\n\n\t\tconst status = parseGitStatus(result.stdout, repoPath, porcelainVersion);\n\t\treturn status?.files;\n\t}\n\n\tprivate async statusCore(\n\t\trepoPath: string,\n\t\tporcelainVersion: number = 1,\n\t\toptions?: {\n\t\t\tsimilarityThreshold?: number | null;\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tcorrelationKey?: string;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t\t...pathspecs: string[]\n\t): Promise<GitResult> {\n\t\tconst params = ['status', porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain'];\n\t\tif (options?.branch !== false) {\n\t\t\tparams.push('--branch');\n\t\t}\n\t\tparams.push(options?.untracked != null ? `-u${options.untracked}` : '-u');\n\t\tif (await this.git.supports('git:status:find-renames')) {\n\t\t\tparams.push(\n\t\t\t\t`--find-renames${options?.similarityThreshold == null ? '' : `=${options.similarityThreshold}%`}`,\n\t\t\t);\n\t\t}\n\n\t\treturn this.git.run(\n\t\t\t{\n\t\t\t\tcwd: repoPath,\n\t\t\t\tcancellation: cancellation,\n\t\t\t\tconfigs: gitConfigsStatus,\n\t\t\t\tenv: { GIT_OPTIONAL_LOCKS: '0' },\n\t\t\t\tcorrelationKey: options?.correlationKey,\n\t\t\t\t...(options?.priority != null ? { priority: options.priority } : undefined),\n\t\t\t},\n\t\t\t...params,\n\t\t\t'--',\n\t\t\t...pathspecs,\n\t\t);\n\t}\n\n\t@debug()\n\thasWorkingChanges(\n\t\trepoPath: string,\n\t\toptions?: {\n\t\t\tstaged?: boolean;\n\t\t\tunstaged?: boolean;\n\t\t\tuntracked?: boolean;\n\t\t\tthrowOnError?: boolean;\n\t\t\tpriority?: GitCommandPriority;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<boolean> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst staged = options?.staged ?? true;\n\t\tconst unstaged = options?.unstaged ?? true;\n\t\tconst untracked = options?.untracked ?? true;\n\t\t// `throwOnError` is in the key because it changes the RESULT on git failure (throw vs `false`) — a\n\t\t// throwing caller must not join, and be silently satisfied by, a non-throwing caller's graceful run.\n\t\t// `priority` is in the key for a different reason: it doesn't change the answer, but a joiner INHERITS\n\t\t// the in-flight run's scheduling. `GitQueue` refuses to start background work while anything is\n\t\t// waiting at normal/interactive, so letting a foreground caller (the overview's dirty pill, the\n\t\t// Worktrees view) join a `background` probe would strand it for the whole graph load. Splitting the\n\t\t// two lanes costs at most one extra spawn; joining costs unbounded foreground latency.\n\t\tconst throwOnError = options?.throwOnError ?? false;\n\t\tconst priority = options?.priority ?? 'default';\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t`hasWorkingChanges:${staged}:${unstaged}:${untracked}:${throwOnError}:${priority}`,\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tif (staged || unstaged) {\n\t\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcwd: repoPath,\n\t\t\t\t\t\t\t\tcancellation: signal,\n\t\t\t\t\t\t\t\terrors: 'ignore',\n\t\t\t\t\t\t\t\tcorrelationKey: correlationKey,\n\t\t\t\t\t\t\t\t...(options?.priority != null ? { priority: options.priority } : undefined),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t\tstaged && unstaged ? 'HEAD' : staged ? '--staged' : undefined,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (result.exitCode === 1) {\n\t\t\t\t\t\t\tif (staged && unstaged) {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has staged and unstaged changes');\n\t\t\t\t\t\t\t} else if (staged) {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has staged changes');\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has unstaged changes');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for untracked files. NOTE: this runs through `git.stream`, which spawns directly\n\t\t\t\t\t// and never enters the queue — so `priority` above doesn't reach it. Only clean worktrees\n\t\t\t\t\t// get this far (a dirty one already returned above), and callers fanning out across many\n\t\t\t\t\t// worktrees are expected to bound their own concurrency.\n\t\t\t\t\tif (untracked) {\n\t\t\t\t\t\tconst hasUntracked = await this.hasUntrackedFiles(repoPath, signal);\n\t\t\t\t\t\tif (hasUntracked) {\n\t\t\t\t\t\t\tscope?.addExitInfo('has untracked files');\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tscope?.addExitInfo('no working changes');\n\t\t\t\t\treturn false;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return false for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error checking for changes');\n\t\t\t\t\tif (throwOnError) throw ex;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetWorkingChangesState(repoPath: string, cancellation?: AbortSignal): Promise<GitWorkingChangesState> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getWorkingChangesState',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst [stagedResult, unstagedResult, untrackedResult] = await Promise.allSettled([\n\t\t\t\t\t\t// Check for staged changes\n\t\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t\t'--staged',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// Check for unstaged changes\n\t\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// Check for untracked files\n\t\t\t\t\t\tthis.hasUntrackedFiles(repoPath, signal),\n\t\t\t\t\t]);\n\n\t\t\t\t\tconst result = {\n\t\t\t\t\t\tstaged: getSettledValue(stagedResult)?.exitCode === 1,\n\t\t\t\t\t\tunstaged: getSettledValue(unstagedResult)?.exitCode === 1,\n\t\t\t\t\t\tuntracked: getSettledValue(untrackedResult) === true,\n\t\t\t\t\t};\n\n\t\t\t\t\tscope?.addExitInfo(\n\t\t\t\t\t\tresult.staged || result.unstaged || result.untracked\n\t\t\t\t\t\t\t? `has ${result.staged ? 'staged' : ''}${result.unstaged ? (result.staged ? ', unstaged' : 'unstaged ') : ''}${\n\t\t\t\t\t\t\t\t\tresult.untracked\n\t\t\t\t\t\t\t\t\t\t? result.staged || result.unstaged\n\t\t\t\t\t\t\t\t\t\t\t? ', untracked'\n\t\t\t\t\t\t\t\t\t\t\t: 'untracked'\n\t\t\t\t\t\t\t\t\t\t: ''\n\t\t\t\t\t\t\t\t} changes`\n\t\t\t\t\t\t\t: 'no working changes',\n\t\t\t\t\t);\n\n\t\t\t\t\treturn result;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error checking for changes');\n\t\t\t\t\t// Return all false on error for graceful degradation\n\t\t\t\t\treturn { staged: false, unstaged: false, untracked: false };\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\thasConflictingFiles(repoPath: string, cancellation?: AbortSignal): Promise<boolean> {\n\t\t// Route through `dedupeByStatusGeneration` (like every other point-in-time read here) for the join-fence\n\t\t// and the `raceWithTimeout` backstop — a bare `git.stream` has no per-command timeout and could wedge.\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'hasConflictingFiles',\n\t\t\tasync (_correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst stream = this.git.stream({ cwd: repoPath, cancellation: signal }, 'ls-files', '--unmerged');\n\t\t\t\t\tusing _streamDisposer = createDisposable(() => void stream.return?.(undefined));\n\n\t\t\t\t\t// Early exit on first chunk - breaking causes SIGPIPE, killing git process\n\t\t\t\t\tfor await (const _chunk of stream) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn false;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetConflictingFiles(repoPath: string, cancellation?: AbortSignal): Promise<GitConflictFile[]> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getConflictingFiles',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t'ls-files',\n\t\t\t\t\t\t'-z',\n\t\t\t\t\t\t'--unmerged',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!result.stdout) {\n\t\t\t\t\t\tscope?.addExitInfo('no conflicting files');\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\n\t\t\t\t\tconst files = parseGitConflictFiles(result.stdout, repoPath);\n\t\t\t\t\tscope?.addExitInfo(`${String(files.length)} conflicting file(s)`);\n\t\t\t\t\treturn files;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return empty array for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error getting conflicting files');\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprivate async hasUntrackedFiles(repoPath: string, cancellation?: AbortSignal): Promise<boolean> {\n\t\ttry {\n\t\t\tconst stream = this.git.stream(\n\t\t\t\t{ cwd: repoPath, cancellation: cancellation },\n\t\t\t\t'ls-files',\n\t\t\t\t// '-z', // Unneeded since we are only looking for presence\n\t\t\t\t'--others',\n\t\t\t\t'--exclude-standard',\n\t\t\t);\n\t\t\tusing _streamDisposer = createDisposable(() => void stream.return?.(undefined));\n\n\t\t\t// Early exit on first chunk - breaking causes SIGPIPE, killing git process\n\t\t\tfor await (const _chunk of stream) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t} catch (ex) {\n\t\t\t// Re-throw cancellation errors\n\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t// Treat other errors as \"no untracked files\" for graceful degradation\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t@debug()\n\tgetUntrackedFiles(repoPath: string, cancellation?: AbortSignal): Promise<GitFile[]> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getUntrackedFiles',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t'ls-files',\n\t\t\t\t\t\t'-z',\n\t\t\t\t\t\t'--others',\n\t\t\t\t\t\t'--exclude-standard',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!result.stdout) {\n\t\t\t\t\t\tscope?.addExitInfo('no untracked files');\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\n\t\t\t\t\tconst files: GitFile[] = [];\n\n\t\t\t\t\tfor (const line of iterateByDelimiter(result.stdout, '\\0')) {\n\t\t\t\t\t\tif (!line.length) continue;\n\n\t\t\t\t\t\tfiles.push({ path: line, repoPath: repoPath, status: GitFileWorkingTreeStatus.Untracked });\n\t\t\t\t\t}\n\n\t\t\t\t\tscope?.addExitInfo(`${String(files.length)} untracked file(s)`);\n\t\t\t\t\treturn files;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return empty array for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error getting untracked files');\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { IssueSorting } from "../../../git/models/issue.js";
2
+ import "../providerFilters.js";
2
3
  //#region ../plus/integrations/src/providers/issueSorts.d.ts
3
4
  /** GitHub and GHE: one `search` channel serves all three issue reads, so all three order the same way. */
4
5
  export declare const githubIssueSorts: IssueSorting[];
@@ -1,6 +1,7 @@
1
1
  import { PullRequest, PullRequestFilter, PullRequestStateFilter } from "../../../git/models/pullRequest.js";
2
2
  import { IntegrationIds } from "../constants.js";
3
3
  import { ProviderWarning } from "../results.js";
4
+ import "../providerFilters.js";
4
5
  import { ProviderPullRequest, ProviderReposInput, ProviderRepository } from "../providers/models.js";
5
6
  import { GitHostIntegration } from "../models/gitHostIntegration.js";
6
7
  import { IntegrationResult } from "../models/integration.js";
@@ -1,3 +1,4 @@
1
+ import { Writable } from "stream";
1
2
  //#region ../utils/src/env/node/exec.d.ts
2
3
  interface RunErrorContext {
3
4
  message: string;
@@ -76,6 +77,13 @@ type ExitCodeOnlyRunOptions<TEncoding = BufferEncoding | 'buffer'> = RunOptions<
76
77
  };
77
78
  export declare function run(command: string, args: readonly string[], encoding: BufferEncoding | string, options: ExitCodeOnlyRunOptions<BufferEncoding>): Promise<number>;
78
79
  export declare function run(command: string, args: readonly string[], encoding: BufferEncoding | string, options?: RunOptions<BufferEncoding>): Promise<string>;
80
+ /**
81
+ * Writes a child's whole input and closes it. A child can exit, or be killed on cancellation, before reading all of
82
+ * it, and the pipe then errors (EPIPE, or EOF on Windows). Unlistened, that error is thrown as an uncaught exception
83
+ * and takes down the whole host process, so it is dropped: the child's exit status and stderr already report the
84
+ * failure, and a child that chose to stop reading answered from what it read.
85
+ */
86
+ export declare function endStdin(stream: Writable | null, stdin: string | Buffer, stdinEncoding: string | undefined): void;
79
87
  export interface RunExitResult {
80
88
  /**
81
89
  * The process's exit code. ABSENT when the process never exited normally — killed by a signal, in which
@@ -182,9 +182,21 @@ function run(command, args, encoding, options) {
182
182
  resolve(await decode(Buffer.from(stdout, "binary"), encoding ? { encoding } : void 0));
183
183
  }
184
184
  });
185
- if (stdin != null) proc.stdin?.end(stdin, stdinEncoding ?? "utf8");
185
+ if (stdin != null) endStdin(proc.stdin, stdin, stdinEncoding);
186
186
  }).finally(() => scope?.[Symbol.dispose]());
187
187
  }
188
+ /**
189
+ * Writes a child's whole input and closes it. A child can exit, or be killed on cancellation, before reading all of
190
+ * it, and the pipe then errors (EPIPE, or EOF on Windows). Unlistened, that error is thrown as an uncaught exception
191
+ * and takes down the whole host process, so it is dropped: the child's exit status and stderr already report the
192
+ * failure, and a child that chose to stop reading answered from what it read.
193
+ */
194
+ function endStdin(stream, stdin, stdinEncoding) {
195
+ if (stream == null) return;
196
+ stream.on("error", () => {});
197
+ if (typeof stdin === "string") stream.end(stdin, stdinEncoding ?? "utf8");
198
+ else stream.end(stdin);
199
+ }
188
200
  function runSpawn(command, args, encoding, options) {
189
201
  const scope = getScopedLogger() ?? maybeStartScopedLogger("Exec.runSpawn");
190
202
  const { stdin, stdinEncoding, cancellation, decode, ...opts } = options;
@@ -257,16 +269,13 @@ function runSpawn(command, args, encoding, options) {
257
269
  stderr
258
270
  });
259
271
  });
260
- if (stdin != null) {
261
- if (typeof stdin === "string") proc.stdin.end(stdin, stdinEncoding ?? "utf8");
262
- else if (stdin instanceof Buffer) proc.stdin.end(stdin);
263
- }
272
+ if (stdin != null) endStdin(proc.stdin, stdin, stdinEncoding);
264
273
  }).finally(() => scope?.[Symbol.dispose]());
265
274
  }
266
275
  async function fsExists(path) {
267
276
  return new Promise((resolve) => access(path, constants.F_OK, (err) => resolve(err == null)));
268
277
  }
269
278
  //#endregion
270
- export { CancelledRunError, RunError, findExecutable, fsExists, getWindowsShortPath, run, runSpawn };
279
+ export { CancelledRunError, RunError, endStdin, findExecutable, fsExists, getWindowsShortPath, run, runSpawn };
271
280
 
272
281
  //# sourceMappingURL=exec.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"exec.js","names":["joinPaths"],"sources":["../../../../../utils/src/env/node/exec.ts"],"sourcesContent":["import type { ExecFileException, ExecFileOptions } from 'child_process';\nimport { exec, execFile, spawn } from 'child_process';\nimport type { Stats } from 'fs';\nimport { access, constants } from 'fs';\nimport { stat } from 'fs/promises';\nimport { join as joinPaths } from 'path';\nimport * as process from 'process';\nimport { getScopedLogger, maybeStartScopedLogger } from '../../logger.scoped.js';\nimport { normalizePath } from '../../path.js';\nimport { isWindows } from './platform.js';\n\n// -- Error types --\n\ninterface RunErrorContext {\n\tmessage: string;\n\tcmd?: string | undefined;\n\tkilled?: boolean | undefined;\n\tcode?: string | number | null | undefined;\n\tsignal?: NodeJS.Signals | undefined;\n}\n\nexport class RunError extends Error {\n\treadonly cmd?: string | undefined;\n\treadonly killed?: boolean | undefined;\n\treadonly code?: string | number | undefined;\n\treadonly signal?: NodeJS.Signals | undefined;\n\treadonly stdout: string;\n\treadonly stderr: string;\n\n\tconstructor(context: RunErrorContext, stdout: string, stderr: string) {\n\t\tsuper(context.message);\n\n\t\tthis.cmd = context.cmd;\n\t\tthis.killed = context.killed;\n\t\tthis.code = context.code ?? undefined;\n\t\tthis.signal = context.signal;\n\t\tthis.stdout = stdout?.trim() ?? '';\n\t\tthis.stderr = stderr?.trim() ?? '';\n\n\t\tthis.name = 'RunError';\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\nexport class CancelledRunError extends RunError {\n\tconstructor(cmd: string, killed: boolean, code?: number | string | undefined, signal: NodeJS.Signals = 'SIGTERM') {\n\t\tsuper(\n\t\t\t{ message: `Operation cancelled; command=${cmd}`, cmd: cmd, killed: killed, code: code, signal: signal },\n\t\t\t'',\n\t\t\t'',\n\t\t);\n\n\t\tthis.name = 'CancelledRunError';\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\n// -- Execution utilities --\n\nconst slashesRegex = /[\\\\/]/;\nconst ps1Regex = /\\.ps1$/i;\nconst batOrCmdRegex = /\\.(bat|cmd)$/i;\nconst jsRegex = /\\.(js)$/i;\n\n/**\n * Search PATH to see if a file exists in any of the path folders.\n *\n * @param {string} exe The file to search for\n * @return {string} A fully qualified path, or the original path if nothing\n * is found\n *\n * @private\n */\nasync function runDownPath(exe: string): Promise<string> {\n\t// NB: Windows won't search PATH looking for executables in spawn like\n\t// Posix does\n\n\t// Files with any directory path don't get this applied\n\tif (slashesRegex.test(exe)) return exe;\n\n\tconst target = joinPaths('.', exe);\n\ttry {\n\t\tconst stats = await stat(target);\n\t\tif (stats?.isFile() && isExecutable(stats)) return target;\n\t} catch {}\n\n\tconst path = process.env.PATH;\n\tif (path != null && path.length !== 0) {\n\t\tconst haystack = path.split(isWindows ? ';' : ':');\n\t\tfor (const p of haystack) {\n\t\t\tconst needle = joinPaths(p, exe);\n\t\t\ttry {\n\t\t\t\tconst stats = await stat(needle);\n\t\t\t\tif (stats?.isFile() && isExecutable(stats)) return needle;\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\treturn exe;\n}\n\nfunction isExecutable(stats: Stats) {\n\tif (isWindows) return true;\n\n\tconst isGroup = stats.gid ? stats.gid === process.getgid?.() : true;\n\tconst isUser = stats.uid ? stats.uid === process.getuid?.() : true;\n\n\treturn Boolean(stats.mode & 0o0001 || (stats.mode & 0o0010 && isGroup) || (stats.mode & 0o0100 && isUser));\n}\n\n/**\n * Finds the executable and parameters to run on Windows. This method\n * mimics the POSIX behavior of being able to run scripts as executables by\n * replacing the passed-in executable with the script runner, for PowerShell,\n * CMD, and node scripts.\n *\n * This method also does the work of running down PATH, which spawn on Windows\n * also doesn't do, unlike on POSIX.\n */\nexport async function findExecutable(exe: string, args: string[]): Promise<{ cmd: string; args: string[] }> {\n\t// POSIX can just execute scripts directly, no need for silly goosery\n\tif (!isWindows) return { cmd: await runDownPath(exe), args: args };\n\n\tif (!(await fsExists(exe))) {\n\t\t// NB: When you write something like `surf-client ... -- surf-build` on Windows,\n\t\t// a shell would normally convert that to surf-build.cmd, but since it's passed\n\t\t// in as an argument, it doesn't happen\n\t\tconst possibleExts = ['.exe', '.bat', '.cmd', '.ps1'];\n\t\tfor (const ext of possibleExts) {\n\t\t\tconst possibleFullPath = await runDownPath(`${exe}${ext}`);\n\n\t\t\tif (await fsExists(possibleFullPath)) return findExecutable(possibleFullPath, args);\n\t\t}\n\t}\n\n\tif (ps1Regex.test(exe)) {\n\t\tconst cmd = joinPaths(\n\t\t\tprocess.env.SYSTEMROOT ?? 'C:\\\\WINDOWS',\n\t\t\t'System32',\n\t\t\t'WindowsPowerShell',\n\t\t\t'v1.0',\n\t\t\t'PowerShell.exe',\n\t\t);\n\t\tconst psargs = ['-ExecutionPolicy', 'Unrestricted', '-NoLogo', '-NonInteractive', '-File', exe];\n\n\t\treturn { cmd: cmd, args: [...psargs, ...args] };\n\t}\n\n\tif (batOrCmdRegex.test(exe)) {\n\t\tconst cmd = joinPaths(process.env.SYSTEMROOT ?? 'C:\\\\WINDOWS', 'System32', 'cmd.exe');\n\t\tconst cmdArgs = ['/C', exe, ...args];\n\n\t\treturn { cmd: cmd, args: cmdArgs };\n\t}\n\n\tif (jsRegex.test(exe)) {\n\t\tconst cmd = process.execPath;\n\t\tconst nodeArgs = [exe];\n\n\t\treturn { cmd: cmd, args: [...nodeArgs, ...args] };\n\t}\n\n\treturn { cmd: exe, args: args };\n}\n\nexport async function getWindowsShortPath(path: string): Promise<string> {\n\treturn new Promise<string>((resolve, reject) => {\n\t\texec(`for %I in (\"${path}\") do @echo %~sI`, (error, stdout, _stderr) => {\n\t\t\tif (error != null) {\n\t\t\t\treject(error);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tresolve(normalizePath(stdout.trim()));\n\t\t});\n\t});\n}\n\nexport interface RunOptions<TEncoding = BufferEncoding | 'buffer'> {\n\tcwd?: string;\n\treadonly env?: Record<string, string | undefined>;\n\treadonly encoding?: TEncoding;\n\t/**\n\t * Decodes non-UTF-8 output. Called when `encoding` is not utf8/binary/buffer.\n\t * Provided by the host via the `GitOptions.decode` option.\n\t * If absent and a non-UTF-8 encoding is requested, throws at runtime.\n\t */\n\treadonly decode?: (data: Uint8Array, options?: { readonly encoding: string }) => string | Promise<string>;\n\t/**\n\t * The size the output buffer to allocate to the spawned process. Set this\n\t * if you are anticipating a large amount of output.\n\t *\n\t * If not specified, this will be 10MB (10485760 bytes) which should be\n\t * enough for most Git operations.\n\t */\n\treadonly maxBuffer?: number;\n\treadonly cancellation?: AbortSignal;\n\t/**\n\t * An optional string or buffer which will be written to\n\t * the child process stdin stream immediately immediately\n\t * after spawning the process.\n\t */\n\treadonly stdin?: string | Buffer;\n\t/**\n\t * The encoding to use when writing to stdin, if the stdin\n\t * parameter is a string.\n\t */\n\treadonly stdinEncoding?: string;\n\treadonly timeout?: number;\n\t/**\n\t * When true, suppresses the `[EXEC]` stderr warning emitted on debug-enabled scopes.\n\t * Use for commands whose failure is expected (e.g. paired with `errors: 'ignore'`).\n\t */\n\treadonly quiet?: boolean;\n}\n\nconst bufferExceededRegex = /stdout maxBuffer( length)? exceeded/;\n\ntype ExitCodeOnlyRunOptions<TEncoding = BufferEncoding | 'buffer'> = RunOptions<TEncoding> & { exitCodeOnly: true };\n\nexport function run(\n\tcommand: string,\n\targs: readonly string[],\n\tencoding: BufferEncoding | string,\n\toptions: ExitCodeOnlyRunOptions<BufferEncoding>,\n): Promise<number>;\nexport function run(\n\tcommand: string,\n\targs: readonly string[],\n\tencoding: BufferEncoding | string,\n\toptions?: RunOptions<BufferEncoding>,\n): Promise<string>;\nexport function run<T extends number | string>(\n\tcommand: string,\n\targs: readonly string[],\n\tencoding: BufferEncoding | string,\n\toptions?: RunOptions<BufferEncoding> & { exitCodeOnly?: boolean },\n): Promise<T> {\n\tconst scope = getScopedLogger() ?? maybeStartScopedLogger('Exec.run');\n\n\tconst { stdin, stdinEncoding, cancellation, decode, ...opts }: RunOptions<BufferEncoding> & ExecFileOptions = {\n\t\tmaxBuffer: 1000 * 1024 * 1024,\n\t\t...options,\n\t};\n\n\tconst promise = new Promise<T>((resolve, reject) => {\n\t\tconst proc = execFile(\n\t\t\tcommand,\n\t\t\targs,\n\t\t\t{ ...opts, signal: cancellation },\n\t\t\tasync (error: ExecFileException | null, stdout, stderr) => {\n\t\t\t\tif (options?.exitCodeOnly) {\n\t\t\t\t\tresolve((error?.code ?? proc.exitCode) as T);\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (error != null) {\n\t\t\t\t\tif (error.signal === 'SIGTERM') {\n\t\t\t\t\t\treject(\n\t\t\t\t\t\t\tnew CancelledRunError(\n\t\t\t\t\t\t\t\t`${command} ${args.join(' ')}`,\n\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\terror.code ?? undefined,\n\t\t\t\t\t\t\t\terror.signal,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (bufferExceededRegex.test(error.message)) {\n\t\t\t\t\t\terror.message = `Command output exceeded the allocated stdout buffer. Set 'options.maxBuffer' to a larger value than ${opts.maxBuffer} bytes`;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet stdoutDecoded: string;\n\t\t\t\t\tlet stderrDecoded: string;\n\t\t\t\t\tif (encoding === 'utf8' || encoding === 'binary' || encoding === 'buffer') {\n\t\t\t\t\t\t// stdout & stderr can be `Buffer` or `string\n\t\t\t\t\t\t// oxlint-disable-next-line typescript/no-unnecessary-type-conversion\n\t\t\t\t\t\tstdoutDecoded = stdout.toString();\n\t\t\t\t\t\t// oxlint-disable-next-line typescript/no-unnecessary-type-conversion\n\t\t\t\t\t\tstderrDecoded = stderr.toString();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (decode == null) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t`Non-UTF-8 encoding '${encoding}' requested but no decode function configured`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstdoutDecoded = await decode(\n\t\t\t\t\t\t\tBuffer.from(stdout, 'binary'),\n\t\t\t\t\t\t\tencoding ? { encoding: encoding } : undefined,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tstderrDecoded = await decode(\n\t\t\t\t\t\t\tBuffer.from(stderr, 'binary'),\n\t\t\t\t\t\t\tencoding ? { encoding: encoding } : undefined,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treject(new RunError(error, stdoutDecoded, stderrDecoded));\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (stderr && !options?.quiet && scope?.enabled('debug')) {\n\t\t\t\t\tscope?.warn(`[EXEC] '${command} ${args.join(' ')}' \\u2022 ${stderr}`);\n\t\t\t\t}\n\n\t\t\t\tif (encoding === 'utf8' || encoding === 'binary' || encoding === 'buffer') {\n\t\t\t\t\tresolve(stdout as T);\n\t\t\t\t} else {\n\t\t\t\t\tif (decode == null) {\n\t\t\t\t\t\tthrow new Error(`Non-UTF-8 encoding '${encoding}' requested but no decode function configured`);\n\t\t\t\t\t}\n\n\t\t\t\t\tresolve(\n\t\t\t\t\t\t(await decode(\n\t\t\t\t\t\t\tBuffer.from(stdout, 'binary'),\n\t\t\t\t\t\t\tencoding ? { encoding: encoding } : undefined,\n\t\t\t\t\t\t)) as T,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\n\t\tif (stdin != null) {\n\t\t\tproc.stdin?.end(stdin, (stdinEncoding ?? 'utf8') as BufferEncoding);\n\t\t}\n\t});\n\n\treturn promise.finally(() => scope?.[Symbol.dispose]());\n}\n\nexport interface RunExitResult {\n\t/**\n\t * The process's exit code. ABSENT when the process never exited normally — killed by a signal, in which\n\t * case {@link signal} is set. Deliberately optional rather than defaulted to `0`: a signalled process\n\t * reported as `exitCode: 0` is indistinguishable from a clean success, so callers would read a partial\n\t * or empty stdout as a complete answer.\n\t */\n\treadonly exitCode?: number;\n\t/** Set when the process was terminated by a signal instead of exiting; {@link exitCode} is then absent. */\n\treadonly signal?: NodeJS.Signals;\n}\n\nexport interface RunResult<T extends string | Buffer> extends RunExitResult {\n\tstdout: T;\n\tstderr: T;\n}\n\nexport function runSpawn(\n\tcommand: string,\n\targs: readonly string[],\n\tencoding: BufferEncoding | 'buffer' | string,\n\toptions: ExitCodeOnlyRunOptions,\n): Promise<RunExitResult>;\nexport function runSpawn<T extends string | Buffer>(\n\tcommand: string,\n\targs: readonly string[],\n\tencoding: BufferEncoding | 'buffer' | string,\n\toptions: RunOptions,\n): Promise<RunResult<T>>;\nexport function runSpawn<T extends string | Buffer>(\n\tcommand: string,\n\targs: readonly string[],\n\tencoding: BufferEncoding | 'buffer' | string,\n\toptions: RunOptions & { exitCodeOnly?: boolean },\n): Promise<RunExitResult | RunResult<T>> {\n\tconst scope = getScopedLogger() ?? maybeStartScopedLogger('Exec.runSpawn');\n\n\tconst { stdin, stdinEncoding, cancellation, decode, ...opts }: RunOptions = options;\n\n\tconst promise = new Promise<RunExitResult | RunResult<T>>((resolve, reject) => {\n\t\tconst proc = spawn(command, args, { ...opts, signal: cancellation });\n\t\tconst errorEncoding = encoding === 'buffer' ? 'utf8' : encoding;\n\n\t\tconst stdoutBuffers: Buffer[] = [];\n\t\tproc.stdout.on('data', (data: Buffer) => stdoutBuffers.push(data));\n\n\t\tconst stderrBuffers: Buffer[] = [];\n\t\tproc.stderr.on('data', (data: Buffer) => stderrBuffers.push(data));\n\n\t\tfunction getStdio<T>(\n\t\t\tencoding: BufferEncoding | 'buffer' | string,\n\t\t): { stdout: T; stderr: T } | Promise<{ stdout: T; stderr: T }> {\n\t\t\tconst stdout = Buffer.concat(stdoutBuffers);\n\t\t\tconst stderr = Buffer.concat(stderrBuffers);\n\t\t\tif (encoding === 'utf8' || encoding === 'binary') {\n\t\t\t\treturn { stdout: stdout.toString(encoding) as T, stderr: stderr.toString(encoding) as T };\n\t\t\t}\n\t\t\tif (encoding === 'buffer') {\n\t\t\t\treturn { stdout: stdout as T, stderr: stderr as T };\n\t\t\t}\n\n\t\t\tif (decode == null) {\n\t\t\t\tthrow new Error(`Non-UTF-8 encoding '${encoding}' requested but no decode function configured`);\n\t\t\t}\n\n\t\t\treturn Promise.all([\n\t\t\t\tdecode(stdout, encoding ? { encoding: encoding } : undefined),\n\t\t\t\tdecode(stderr, encoding ? { encoding: encoding } : undefined),\n\t\t\t]).then(([decodedStdout, decodedStderr]) => ({\n\t\t\t\tstdout: decodedStdout as T,\n\t\t\t\tstderr: decodedStderr as T,\n\t\t\t}));\n\t\t}\n\n\t\tproc.once('error', async ex => {\n\t\t\tif (ex?.name === 'AbortError') {\n\t\t\t\treject(new CancelledRunError(`${command} ${args.join(' ')}`, true));\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst stdio = getStdio<string>(errorEncoding);\n\t\t\tconst { stdout, stderr } = stdio instanceof Promise ? await stdio : stdio;\n\n\t\t\treject(new RunError(ex, stdout, stderr));\n\t\t});\n\n\t\tproc.once('close', async (code, signal) => {\n\t\t\tif (options?.exitCodeOnly) {\n\t\t\t\t// Resolves unconditionally, so this is the one path that can hand back a signalled run. `code`\n\t\t\t\t// is null exactly when the process was killed — report the signal rather than coercing to `0`,\n\t\t\t\t// which would claim a clean success for a command that never finished. Note this returns BEFORE\n\t\t\t\t// the SIGTERM-to-cancellation diversion below, so callers have to classify that themselves.\n\t\t\t\tresolve({ exitCode: code ?? undefined, signal: signal ?? undefined });\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (code !== 0 || signal) {\n\t\t\t\tconst stdio = getStdio<string>(errorEncoding);\n\t\t\t\tconst { stdout, stderr } = stdio instanceof Promise ? await stdio : stdio;\n\t\t\t\tif (stderr.length && !options?.quiet && scope?.enabled('debug')) {\n\t\t\t\t\tscope?.warn(`[EXEC] '${command} ${args.join(' ')}' \\u2022 ${stderr}`);\n\t\t\t\t}\n\n\t\t\t\tif (signal === 'SIGTERM') {\n\t\t\t\t\treject(new CancelledRunError(`${command} ${args.join(' ')}`, true, code ?? undefined, signal));\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\treject(\n\t\t\t\t\tnew RunError(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmessage: `Command failed with exit code ${code}`,\n\t\t\t\t\t\t\tcode: code,\n\t\t\t\t\t\t\tsignal: signal ?? undefined,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tstdout,\n\t\t\t\t\t\tstderr,\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst stdio = getStdio<T>(encoding);\n\t\t\tconst { stdout, stderr } = stdio instanceof Promise ? await stdio : stdio;\n\t\t\tif (stderr.length && !options?.quiet && scope?.enabled('debug')) {\n\t\t\t\tscope?.warn(\n\t\t\t\t\t`[EXEC] '${command} ${args.join(' ')}' \\u2022 ${typeof stderr === 'string' ? stderr : stderr.toString()}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// The guard above already rejected every non-zero and every signalled close, so this is a clean\n\t\t\t// zero exit — `code` cannot be null here. (The optional typing exists for the `exitCodeOnly`\n\t\t\t// branch, which is where a codeless result can genuinely surface.)\n\t\t\tresolve({ exitCode: code ?? undefined, signal: signal ?? undefined, stdout: stdout, stderr: stderr });\n\t\t});\n\n\t\tif (stdin != null) {\n\t\t\tif (typeof stdin === 'string') {\n\t\t\t\tproc.stdin.end(stdin, (stdinEncoding ?? 'utf8') as BufferEncoding);\n\t\t\t} else if (stdin instanceof Buffer) {\n\t\t\t\tproc.stdin.end(stdin);\n\t\t\t}\n\t\t}\n\t});\n\n\treturn promise.finally(() => scope?.[Symbol.dispose]());\n}\n\nexport async function fsExists(path: string): Promise<boolean> {\n\treturn new Promise<boolean>(resolve => access(path, constants.F_OK, err => resolve(err == null)));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"exec.js","names":["joinPaths"],"sources":["../../../../../utils/src/env/node/exec.ts"],"sourcesContent":["import type { ExecFileException, ExecFileOptions } from 'child_process';\nimport { exec, execFile, spawn } from 'child_process';\nimport type { Stats } from 'fs';\nimport { access, constants } from 'fs';\nimport { stat } from 'fs/promises';\nimport { join as joinPaths } from 'path';\nimport * as process from 'process';\nimport type { Writable } from 'stream';\nimport { getScopedLogger, maybeStartScopedLogger } from '../../logger.scoped.js';\nimport { normalizePath } from '../../path.js';\nimport { isWindows } from './platform.js';\n\n// -- Error types --\n\ninterface RunErrorContext {\n\tmessage: string;\n\tcmd?: string | undefined;\n\tkilled?: boolean | undefined;\n\tcode?: string | number | null | undefined;\n\tsignal?: NodeJS.Signals | undefined;\n}\n\nexport class RunError extends Error {\n\treadonly cmd?: string | undefined;\n\treadonly killed?: boolean | undefined;\n\treadonly code?: string | number | undefined;\n\treadonly signal?: NodeJS.Signals | undefined;\n\treadonly stdout: string;\n\treadonly stderr: string;\n\n\tconstructor(context: RunErrorContext, stdout: string, stderr: string) {\n\t\tsuper(context.message);\n\n\t\tthis.cmd = context.cmd;\n\t\tthis.killed = context.killed;\n\t\tthis.code = context.code ?? undefined;\n\t\tthis.signal = context.signal;\n\t\tthis.stdout = stdout?.trim() ?? '';\n\t\tthis.stderr = stderr?.trim() ?? '';\n\n\t\tthis.name = 'RunError';\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\nexport class CancelledRunError extends RunError {\n\tconstructor(cmd: string, killed: boolean, code?: number | string | undefined, signal: NodeJS.Signals = 'SIGTERM') {\n\t\tsuper(\n\t\t\t{ message: `Operation cancelled; command=${cmd}`, cmd: cmd, killed: killed, code: code, signal: signal },\n\t\t\t'',\n\t\t\t'',\n\t\t);\n\n\t\tthis.name = 'CancelledRunError';\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\n// -- Execution utilities --\n\nconst slashesRegex = /[\\\\/]/;\nconst ps1Regex = /\\.ps1$/i;\nconst batOrCmdRegex = /\\.(bat|cmd)$/i;\nconst jsRegex = /\\.(js)$/i;\n\n/**\n * Search PATH to see if a file exists in any of the path folders.\n *\n * @param {string} exe The file to search for\n * @return {string} A fully qualified path, or the original path if nothing\n * is found\n *\n * @private\n */\nasync function runDownPath(exe: string): Promise<string> {\n\t// NB: Windows won't search PATH looking for executables in spawn like\n\t// Posix does\n\n\t// Files with any directory path don't get this applied\n\tif (slashesRegex.test(exe)) return exe;\n\n\tconst target = joinPaths('.', exe);\n\ttry {\n\t\tconst stats = await stat(target);\n\t\tif (stats?.isFile() && isExecutable(stats)) return target;\n\t} catch {}\n\n\tconst path = process.env.PATH;\n\tif (path != null && path.length !== 0) {\n\t\tconst haystack = path.split(isWindows ? ';' : ':');\n\t\tfor (const p of haystack) {\n\t\t\tconst needle = joinPaths(p, exe);\n\t\t\ttry {\n\t\t\t\tconst stats = await stat(needle);\n\t\t\t\tif (stats?.isFile() && isExecutable(stats)) return needle;\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\treturn exe;\n}\n\nfunction isExecutable(stats: Stats) {\n\tif (isWindows) return true;\n\n\tconst isGroup = stats.gid ? stats.gid === process.getgid?.() : true;\n\tconst isUser = stats.uid ? stats.uid === process.getuid?.() : true;\n\n\treturn Boolean(stats.mode & 0o0001 || (stats.mode & 0o0010 && isGroup) || (stats.mode & 0o0100 && isUser));\n}\n\n/**\n * Finds the executable and parameters to run on Windows. This method\n * mimics the POSIX behavior of being able to run scripts as executables by\n * replacing the passed-in executable with the script runner, for PowerShell,\n * CMD, and node scripts.\n *\n * This method also does the work of running down PATH, which spawn on Windows\n * also doesn't do, unlike on POSIX.\n */\nexport async function findExecutable(exe: string, args: string[]): Promise<{ cmd: string; args: string[] }> {\n\t// POSIX can just execute scripts directly, no need for silly goosery\n\tif (!isWindows) return { cmd: await runDownPath(exe), args: args };\n\n\tif (!(await fsExists(exe))) {\n\t\t// NB: When you write something like `surf-client ... -- surf-build` on Windows,\n\t\t// a shell would normally convert that to surf-build.cmd, but since it's passed\n\t\t// in as an argument, it doesn't happen\n\t\tconst possibleExts = ['.exe', '.bat', '.cmd', '.ps1'];\n\t\tfor (const ext of possibleExts) {\n\t\t\tconst possibleFullPath = await runDownPath(`${exe}${ext}`);\n\n\t\t\tif (await fsExists(possibleFullPath)) return findExecutable(possibleFullPath, args);\n\t\t}\n\t}\n\n\tif (ps1Regex.test(exe)) {\n\t\tconst cmd = joinPaths(\n\t\t\tprocess.env.SYSTEMROOT ?? 'C:\\\\WINDOWS',\n\t\t\t'System32',\n\t\t\t'WindowsPowerShell',\n\t\t\t'v1.0',\n\t\t\t'PowerShell.exe',\n\t\t);\n\t\tconst psargs = ['-ExecutionPolicy', 'Unrestricted', '-NoLogo', '-NonInteractive', '-File', exe];\n\n\t\treturn { cmd: cmd, args: [...psargs, ...args] };\n\t}\n\n\tif (batOrCmdRegex.test(exe)) {\n\t\tconst cmd = joinPaths(process.env.SYSTEMROOT ?? 'C:\\\\WINDOWS', 'System32', 'cmd.exe');\n\t\tconst cmdArgs = ['/C', exe, ...args];\n\n\t\treturn { cmd: cmd, args: cmdArgs };\n\t}\n\n\tif (jsRegex.test(exe)) {\n\t\tconst cmd = process.execPath;\n\t\tconst nodeArgs = [exe];\n\n\t\treturn { cmd: cmd, args: [...nodeArgs, ...args] };\n\t}\n\n\treturn { cmd: exe, args: args };\n}\n\nexport async function getWindowsShortPath(path: string): Promise<string> {\n\treturn new Promise<string>((resolve, reject) => {\n\t\texec(`for %I in (\"${path}\") do @echo %~sI`, (error, stdout, _stderr) => {\n\t\t\tif (error != null) {\n\t\t\t\treject(error);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tresolve(normalizePath(stdout.trim()));\n\t\t});\n\t});\n}\n\nexport interface RunOptions<TEncoding = BufferEncoding | 'buffer'> {\n\tcwd?: string;\n\treadonly env?: Record<string, string | undefined>;\n\treadonly encoding?: TEncoding;\n\t/**\n\t * Decodes non-UTF-8 output. Called when `encoding` is not utf8/binary/buffer.\n\t * Provided by the host via the `GitOptions.decode` option.\n\t * If absent and a non-UTF-8 encoding is requested, throws at runtime.\n\t */\n\treadonly decode?: (data: Uint8Array, options?: { readonly encoding: string }) => string | Promise<string>;\n\t/**\n\t * The size the output buffer to allocate to the spawned process. Set this\n\t * if you are anticipating a large amount of output.\n\t *\n\t * If not specified, this will be 10MB (10485760 bytes) which should be\n\t * enough for most Git operations.\n\t */\n\treadonly maxBuffer?: number;\n\treadonly cancellation?: AbortSignal;\n\t/**\n\t * An optional string or buffer which will be written to\n\t * the child process stdin stream immediately immediately\n\t * after spawning the process.\n\t */\n\treadonly stdin?: string | Buffer;\n\t/**\n\t * The encoding to use when writing to stdin, if the stdin\n\t * parameter is a string.\n\t */\n\treadonly stdinEncoding?: string;\n\treadonly timeout?: number;\n\t/**\n\t * When true, suppresses the `[EXEC]` stderr warning emitted on debug-enabled scopes.\n\t * Use for commands whose failure is expected (e.g. paired with `errors: 'ignore'`).\n\t */\n\treadonly quiet?: boolean;\n}\n\nconst bufferExceededRegex = /stdout maxBuffer( length)? exceeded/;\n\ntype ExitCodeOnlyRunOptions<TEncoding = BufferEncoding | 'buffer'> = RunOptions<TEncoding> & { exitCodeOnly: true };\n\nexport function run(\n\tcommand: string,\n\targs: readonly string[],\n\tencoding: BufferEncoding | string,\n\toptions: ExitCodeOnlyRunOptions<BufferEncoding>,\n): Promise<number>;\nexport function run(\n\tcommand: string,\n\targs: readonly string[],\n\tencoding: BufferEncoding | string,\n\toptions?: RunOptions<BufferEncoding>,\n): Promise<string>;\nexport function run<T extends number | string>(\n\tcommand: string,\n\targs: readonly string[],\n\tencoding: BufferEncoding | string,\n\toptions?: RunOptions<BufferEncoding> & { exitCodeOnly?: boolean },\n): Promise<T> {\n\tconst scope = getScopedLogger() ?? maybeStartScopedLogger('Exec.run');\n\n\tconst { stdin, stdinEncoding, cancellation, decode, ...opts }: RunOptions<BufferEncoding> & ExecFileOptions = {\n\t\tmaxBuffer: 1000 * 1024 * 1024,\n\t\t...options,\n\t};\n\n\tconst promise = new Promise<T>((resolve, reject) => {\n\t\tconst proc = execFile(\n\t\t\tcommand,\n\t\t\targs,\n\t\t\t{ ...opts, signal: cancellation },\n\t\t\tasync (error: ExecFileException | null, stdout, stderr) => {\n\t\t\t\tif (options?.exitCodeOnly) {\n\t\t\t\t\tresolve((error?.code ?? proc.exitCode) as T);\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (error != null) {\n\t\t\t\t\tif (error.signal === 'SIGTERM') {\n\t\t\t\t\t\treject(\n\t\t\t\t\t\t\tnew CancelledRunError(\n\t\t\t\t\t\t\t\t`${command} ${args.join(' ')}`,\n\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\terror.code ?? undefined,\n\t\t\t\t\t\t\t\terror.signal,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (bufferExceededRegex.test(error.message)) {\n\t\t\t\t\t\terror.message = `Command output exceeded the allocated stdout buffer. Set 'options.maxBuffer' to a larger value than ${opts.maxBuffer} bytes`;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet stdoutDecoded: string;\n\t\t\t\t\tlet stderrDecoded: string;\n\t\t\t\t\tif (encoding === 'utf8' || encoding === 'binary' || encoding === 'buffer') {\n\t\t\t\t\t\t// stdout & stderr can be `Buffer` or `string\n\t\t\t\t\t\t// oxlint-disable-next-line typescript/no-unnecessary-type-conversion\n\t\t\t\t\t\tstdoutDecoded = stdout.toString();\n\t\t\t\t\t\t// oxlint-disable-next-line typescript/no-unnecessary-type-conversion\n\t\t\t\t\t\tstderrDecoded = stderr.toString();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (decode == null) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t`Non-UTF-8 encoding '${encoding}' requested but no decode function configured`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstdoutDecoded = await decode(\n\t\t\t\t\t\t\tBuffer.from(stdout, 'binary'),\n\t\t\t\t\t\t\tencoding ? { encoding: encoding } : undefined,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tstderrDecoded = await decode(\n\t\t\t\t\t\t\tBuffer.from(stderr, 'binary'),\n\t\t\t\t\t\t\tencoding ? { encoding: encoding } : undefined,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treject(new RunError(error, stdoutDecoded, stderrDecoded));\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (stderr && !options?.quiet && scope?.enabled('debug')) {\n\t\t\t\t\tscope?.warn(`[EXEC] '${command} ${args.join(' ')}' \\u2022 ${stderr}`);\n\t\t\t\t}\n\n\t\t\t\tif (encoding === 'utf8' || encoding === 'binary' || encoding === 'buffer') {\n\t\t\t\t\tresolve(stdout as T);\n\t\t\t\t} else {\n\t\t\t\t\tif (decode == null) {\n\t\t\t\t\t\tthrow new Error(`Non-UTF-8 encoding '${encoding}' requested but no decode function configured`);\n\t\t\t\t\t}\n\n\t\t\t\t\tresolve(\n\t\t\t\t\t\t(await decode(\n\t\t\t\t\t\t\tBuffer.from(stdout, 'binary'),\n\t\t\t\t\t\t\tencoding ? { encoding: encoding } : undefined,\n\t\t\t\t\t\t)) as T,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\n\t\tif (stdin != null) {\n\t\t\tendStdin(proc.stdin, stdin, stdinEncoding);\n\t\t}\n\t});\n\n\treturn promise.finally(() => scope?.[Symbol.dispose]());\n}\n\n/**\n * Writes a child's whole input and closes it. A child can exit, or be killed on cancellation, before reading all of\n * it, and the pipe then errors (EPIPE, or EOF on Windows). Unlistened, that error is thrown as an uncaught exception\n * and takes down the whole host process, so it is dropped: the child's exit status and stderr already report the\n * failure, and a child that chose to stop reading answered from what it read.\n */\nexport function endStdin(stream: Writable | null, stdin: string | Buffer, stdinEncoding: string | undefined): void {\n\tif (stream == null) return;\n\n\tstream.on('error', () => {});\n\tif (typeof stdin === 'string') {\n\t\tstream.end(stdin, (stdinEncoding ?? 'utf8') as BufferEncoding);\n\t} else {\n\t\tstream.end(stdin);\n\t}\n}\n\nexport interface RunExitResult {\n\t/**\n\t * The process's exit code. ABSENT when the process never exited normally — killed by a signal, in which\n\t * case {@link signal} is set. Deliberately optional rather than defaulted to `0`: a signalled process\n\t * reported as `exitCode: 0` is indistinguishable from a clean success, so callers would read a partial\n\t * or empty stdout as a complete answer.\n\t */\n\treadonly exitCode?: number;\n\t/** Set when the process was terminated by a signal instead of exiting; {@link exitCode} is then absent. */\n\treadonly signal?: NodeJS.Signals;\n}\n\nexport interface RunResult<T extends string | Buffer> extends RunExitResult {\n\tstdout: T;\n\tstderr: T;\n}\n\nexport function runSpawn(\n\tcommand: string,\n\targs: readonly string[],\n\tencoding: BufferEncoding | 'buffer' | string,\n\toptions: ExitCodeOnlyRunOptions,\n): Promise<RunExitResult>;\nexport function runSpawn<T extends string | Buffer>(\n\tcommand: string,\n\targs: readonly string[],\n\tencoding: BufferEncoding | 'buffer' | string,\n\toptions: RunOptions,\n): Promise<RunResult<T>>;\nexport function runSpawn<T extends string | Buffer>(\n\tcommand: string,\n\targs: readonly string[],\n\tencoding: BufferEncoding | 'buffer' | string,\n\toptions: RunOptions & { exitCodeOnly?: boolean },\n): Promise<RunExitResult | RunResult<T>> {\n\tconst scope = getScopedLogger() ?? maybeStartScopedLogger('Exec.runSpawn');\n\n\tconst { stdin, stdinEncoding, cancellation, decode, ...opts }: RunOptions = options;\n\n\tconst promise = new Promise<RunExitResult | RunResult<T>>((resolve, reject) => {\n\t\tconst proc = spawn(command, args, { ...opts, signal: cancellation });\n\t\tconst errorEncoding = encoding === 'buffer' ? 'utf8' : encoding;\n\n\t\tconst stdoutBuffers: Buffer[] = [];\n\t\tproc.stdout.on('data', (data: Buffer) => stdoutBuffers.push(data));\n\n\t\tconst stderrBuffers: Buffer[] = [];\n\t\tproc.stderr.on('data', (data: Buffer) => stderrBuffers.push(data));\n\n\t\tfunction getStdio<T>(\n\t\t\tencoding: BufferEncoding | 'buffer' | string,\n\t\t): { stdout: T; stderr: T } | Promise<{ stdout: T; stderr: T }> {\n\t\t\tconst stdout = Buffer.concat(stdoutBuffers);\n\t\t\tconst stderr = Buffer.concat(stderrBuffers);\n\t\t\tif (encoding === 'utf8' || encoding === 'binary') {\n\t\t\t\treturn { stdout: stdout.toString(encoding) as T, stderr: stderr.toString(encoding) as T };\n\t\t\t}\n\t\t\tif (encoding === 'buffer') {\n\t\t\t\treturn { stdout: stdout as T, stderr: stderr as T };\n\t\t\t}\n\n\t\t\tif (decode == null) {\n\t\t\t\tthrow new Error(`Non-UTF-8 encoding '${encoding}' requested but no decode function configured`);\n\t\t\t}\n\n\t\t\treturn Promise.all([\n\t\t\t\tdecode(stdout, encoding ? { encoding: encoding } : undefined),\n\t\t\t\tdecode(stderr, encoding ? { encoding: encoding } : undefined),\n\t\t\t]).then(([decodedStdout, decodedStderr]) => ({\n\t\t\t\tstdout: decodedStdout as T,\n\t\t\t\tstderr: decodedStderr as T,\n\t\t\t}));\n\t\t}\n\n\t\tproc.once('error', async ex => {\n\t\t\tif (ex?.name === 'AbortError') {\n\t\t\t\treject(new CancelledRunError(`${command} ${args.join(' ')}`, true));\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst stdio = getStdio<string>(errorEncoding);\n\t\t\tconst { stdout, stderr } = stdio instanceof Promise ? await stdio : stdio;\n\n\t\t\treject(new RunError(ex, stdout, stderr));\n\t\t});\n\n\t\tproc.once('close', async (code, signal) => {\n\t\t\tif (options?.exitCodeOnly) {\n\t\t\t\t// Resolves unconditionally, so this is the one path that can hand back a signalled run. `code`\n\t\t\t\t// is null exactly when the process was killed — report the signal rather than coercing to `0`,\n\t\t\t\t// which would claim a clean success for a command that never finished. Note this returns BEFORE\n\t\t\t\t// the SIGTERM-to-cancellation diversion below, so callers have to classify that themselves.\n\t\t\t\tresolve({ exitCode: code ?? undefined, signal: signal ?? undefined });\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (code !== 0 || signal) {\n\t\t\t\tconst stdio = getStdio<string>(errorEncoding);\n\t\t\t\tconst { stdout, stderr } = stdio instanceof Promise ? await stdio : stdio;\n\t\t\t\tif (stderr.length && !options?.quiet && scope?.enabled('debug')) {\n\t\t\t\t\tscope?.warn(`[EXEC] '${command} ${args.join(' ')}' \\u2022 ${stderr}`);\n\t\t\t\t}\n\n\t\t\t\tif (signal === 'SIGTERM') {\n\t\t\t\t\treject(new CancelledRunError(`${command} ${args.join(' ')}`, true, code ?? undefined, signal));\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\treject(\n\t\t\t\t\tnew RunError(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmessage: `Command failed with exit code ${code}`,\n\t\t\t\t\t\t\tcode: code,\n\t\t\t\t\t\t\tsignal: signal ?? undefined,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tstdout,\n\t\t\t\t\t\tstderr,\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst stdio = getStdio<T>(encoding);\n\t\t\tconst { stdout, stderr } = stdio instanceof Promise ? await stdio : stdio;\n\t\t\tif (stderr.length && !options?.quiet && scope?.enabled('debug')) {\n\t\t\t\tscope?.warn(\n\t\t\t\t\t`[EXEC] '${command} ${args.join(' ')}' \\u2022 ${typeof stderr === 'string' ? stderr : stderr.toString()}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// The guard above already rejected every non-zero and every signalled close, so this is a clean\n\t\t\t// zero exit — `code` cannot be null here. (The optional typing exists for the `exitCodeOnly`\n\t\t\t// branch, which is where a codeless result can genuinely surface.)\n\t\t\tresolve({ exitCode: code ?? undefined, signal: signal ?? undefined, stdout: stdout, stderr: stderr });\n\t\t});\n\n\t\tif (stdin != null) {\n\t\t\tendStdin(proc.stdin, stdin, stdinEncoding);\n\t\t}\n\t});\n\n\treturn promise.finally(() => scope?.[Symbol.dispose]());\n}\n\nexport async function fsExists(path: string): Promise<boolean> {\n\treturn new Promise<boolean>(resolve => access(path, constants.F_OK, err => resolve(err == null)));\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gitkraken/core-gitlens",
3
3
  "description": "GitLens core — shared Git / AI / GitHub primitives for internal GitKraken consumption",
4
- "version": "0.9.6",
4
+ "version": "0.9.8",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": {
7
7
  "name": "GitKraken",