@gitkraken/core-gitlens 0.16.0 → 0.16.2

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/dist/git/cache.js +2 -2
  3. package/dist/git/cache.js.map +1 -1
  4. package/dist/git/run.types.d.ts +3 -0
  5. package/dist/git/watching/classifyChange.js +3 -0
  6. package/dist/git/watching/classifyChange.js.map +1 -1
  7. package/dist/git/watching/watchSession.d.ts +2 -0
  8. package/dist/git/watching/watchSession.js +9 -1
  9. package/dist/git/watching/watchSession.js.map +1 -1
  10. package/dist/git-cli/cliGitProvider.d.ts +1 -1
  11. package/dist/git-cli/cliGitProvider.js +7 -4
  12. package/dist/git-cli/cliGitProvider.js.map +1 -1
  13. package/dist/git-cli/exec/commandClassifier.d.ts +18 -3
  14. package/dist/git-cli/exec/commandClassifier.js +562 -16
  15. package/dist/git-cli/exec/commandClassifier.js.map +1 -1
  16. package/dist/git-cli/exec/exec.d.ts +2 -2
  17. package/dist/git-cli/exec/git.d.ts +31 -4
  18. package/dist/git-cli/exec/git.js +105 -43
  19. package/dist/git-cli/exec/git.js.map +1 -1
  20. package/dist/git-cli/exec/headState.d.ts +32 -0
  21. package/dist/git-cli/exec/headState.js +78 -0
  22. package/dist/git-cli/exec/headState.js.map +1 -0
  23. package/dist/git-cli/providers/branches.js +14 -4
  24. package/dist/git-cli/providers/branches.js.map +1 -1
  25. package/dist/git-cli/providers/operations.d.ts +6 -7
  26. package/dist/git-cli/providers/operations.js +46 -64
  27. package/dist/git-cli/providers/operations.js.map +1 -1
  28. package/dist/git-cli/providers/refs.d.ts +4 -1
  29. package/dist/git-cli/providers/refs.js +13 -9
  30. package/dist/git-cli/providers/refs.js.map +1 -1
  31. package/dist/plus/integrations/integrationService.d.ts +1 -0
  32. package/dist/plus/integrations/integrationService.js +18 -5
  33. package/dist/plus/integrations/integrationService.js.map +1 -1
  34. package/dist/plus/integrations/providers/apiClients.d.ts +2 -0
  35. package/dist/plus/integrations/providers/apiClients.js +12 -3
  36. package/dist/plus/integrations/providers/apiClients.js.map +1 -1
  37. package/dist/plus/integrations/providers/azure/azure.d.ts +13 -11
  38. package/dist/plus/integrations/providers/azure/azure.js +128 -47
  39. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  40. package/dist/plus/integrations/providers/azure/models.d.ts +4 -10
  41. package/dist/plus/integrations/providers/azure/models.js +25 -9
  42. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  43. package/dist/plus/integrations/providers/azure/workItemDescriptions.d.ts +10 -0
  44. package/dist/plus/integrations/providers/azure/workItemDescriptions.js +48 -0
  45. package/dist/plus/integrations/providers/azure/workItemDescriptions.js.map +1 -0
  46. package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +20 -19
  47. package/dist/plus/integrations/providers/azure/workItemStateCategories.js +65 -40
  48. package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
  49. package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
  50. package/dist/plus/integrations/providers/providerApiClient.d.ts +9 -0
  51. package/dist/plus/integrations/providers/providerApiClient.js +66 -0
  52. package/dist/plus/integrations/providers/providerApiClient.js.map +1 -0
  53. package/dist/plus/integrations/providers/providersApi.d.ts +10 -9
  54. package/dist/plus/integrations/providers/providersApi.js +163 -145
  55. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  56. package/dist/plus/integrations/reads/drains.d.ts +0 -1
  57. package/dist/utils/env/node/exec.d.ts +38 -1
  58. package/dist/utils/env/node/exec.js +14 -6
  59. package/dist/utils/env/node/exec.js.map +1 -1
  60. package/docs/integrations.md +30 -13
  61. package/package.json +2 -2
@@ -2,7 +2,6 @@ import { ResourceDescriptor } from "../../../git/models/resourceDescriptor.js";
2
2
  import { PullRequest, PullRequestFilter, PullRequestStateFilter } from "../../../git/models/pullRequest.js";
3
3
  import { IntegrationIds } from "../constants.js";
4
4
  import { ProviderWarning } from "../results.js";
5
- import "../providerFilters.js";
6
5
  import { ProviderPullRequest, ProviderReposInput, ProviderRepository } from "../providers/models.js";
7
6
  import { GitHostIntegration } from "../models/gitHostIntegration.js";
8
7
  import { IssuesIntegration } from "../models/issuesIntegration.js";
@@ -1,5 +1,5 @@
1
1
  import { CancellationReason } from "../../cancellation.js";
2
- import { Writable } from "stream";
2
+ import { Readable, Writable } from "stream";
3
3
  //#region ../utils/src/env/node/exec.d.ts
4
4
  interface RunErrorContext {
5
5
  message: string;
@@ -35,6 +35,41 @@ export declare function findExecutable(exe: string, args: string[]): Promise<{
35
35
  args: string[];
36
36
  }>;
37
37
  export declare function getWindowsShortPath(path: string): Promise<string>;
38
+ /** What core uses of a spawned process; Node's `ChildProcess` satisfies it */
39
+ export interface SpawnedProcess {
40
+ readonly stdin: Writable | null;
41
+ readonly stdout: Readable | null;
42
+ readonly stderr: Readable | null;
43
+ readonly killed: boolean;
44
+ readonly exitCode: number | null;
45
+ readonly spawnargs: string[];
46
+ once(event: 'error', listener: (error: Error) => void): this;
47
+ once(event: 'close', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
48
+ removeAllListeners(): this;
49
+ }
50
+ export interface ProcessSpawnOptions {
51
+ readonly cwd?: string;
52
+ /** The child's whole environment, not additions to the spawner's own; an `undefined` value leaves the key out */
53
+ readonly env?: Record<string, string | undefined>;
54
+ /** Milliseconds; absent means none */
55
+ readonly timeout?: number;
56
+ readonly signal?: AbortSignal;
57
+ /** A `null` entry means `'pipe'` */
58
+ readonly stdio?: ['pipe' | 'ignore', null, null];
59
+ }
60
+ /**
61
+ * Launches a process in place of Node's `spawn`, and must behave as it does:
62
+ * - pipe stdout and stderr, and stdin unless `stdio[0]` is `'ignore'`, which leaves `stdin` null
63
+ * - honour `signal`, including one already aborted: kill with SIGTERM, then emit `'error'` with `name` `AbortError`,
64
+ * unless the process has already exited
65
+ * - honour `timeout`: kill with SIGTERM and set `killed`, without emitting `'error'`
66
+ * - report a killed process's `'close'` with a null code and its signal, not an exit code such as 143
67
+ * - emit no event before returning, `'error'` at most once, and `'close'` exactly once and last: also after `'error'`
68
+ * (a failed launch, an abort), and only after `stdout` and `stderr` have ended or been destroyed, with `exitCode`
69
+ * and `killed` already set
70
+ * - close the child's stdout when `stdout` is destroyed, which is how a reader that stops early ends the process
71
+ */
72
+ export type ProcessSpawner = (command: string, args: readonly string[], options: ProcessSpawnOptions) => SpawnedProcess;
38
73
  export interface RunOptions<TEncoding = BufferEncoding | 'buffer'> {
39
74
  cwd?: string;
40
75
  readonly env?: Record<string, string | undefined>;
@@ -73,6 +108,8 @@ export interface RunOptions<TEncoding = BufferEncoding | 'buffer'> {
73
108
  * Use for commands whose failure is expected (e.g. paired with `errors: 'ignore'`).
74
109
  */
75
110
  readonly quiet?: boolean;
111
+ /** Launches the process; defaults to Node's `spawn`. Only used by `runSpawn` */
112
+ readonly spawn?: ProcessSpawner;
76
113
  }
77
114
  type ExitCodeOnlyRunOptions<TEncoding = BufferEncoding | 'buffer'> = RunOptions<TEncoding> & {
78
115
  exitCodeOnly: true;
@@ -201,17 +201,24 @@ function endStdin(stream, stdin, stdinEncoding) {
201
201
  }
202
202
  function runSpawn(command, args, encoding, options) {
203
203
  const scope = getScopedLogger() ?? maybeStartScopedLogger("Exec.runSpawn");
204
- const { stdin, stdinEncoding, cancellation, decode, ...opts } = options;
204
+ const { stdin, stdinEncoding, cancellation, decode, spawn: spawner, ...opts } = options;
205
205
  return new Promise((resolve, reject) => {
206
- const proc = spawn(command, args, {
207
- ...opts,
208
- signal: cancellation
206
+ const proc = (spawner ?? spawn)(command, args, {
207
+ cwd: opts.cwd,
208
+ env: opts.env,
209
+ timeout: opts.timeout === 0 ? void 0 : opts.timeout,
210
+ signal: cancellation,
211
+ stdio: [
212
+ "pipe",
213
+ null,
214
+ null
215
+ ]
209
216
  });
210
217
  const errorEncoding = encoding === "buffer" ? "utf8" : encoding;
211
218
  const stdoutBuffers = [];
212
- proc.stdout.on("data", (data) => stdoutBuffers.push(data));
219
+ proc.stdout?.on("data", (data) => stdoutBuffers.push(data));
213
220
  const stderrBuffers = [];
214
- proc.stderr.on("data", (data) => stderrBuffers.push(data));
221
+ proc.stderr?.on("data", (data) => stderrBuffers.push(data));
215
222
  function getStdio(encoding) {
216
223
  const stdout = Buffer.concat(stdoutBuffers);
217
224
  const stderr = Buffer.concat(stderrBuffers);
@@ -272,6 +279,7 @@ function runSpawn(command, args, encoding, options) {
272
279
  stderr
273
280
  });
274
281
  });
282
+ if (proc.stdout == null || proc.stderr == null) reject(/* @__PURE__ */ new Error(`'${command}' was spawned without stdout and stderr pipes`));
275
283
  if (stdin != null) endStdin(proc.stdin, stdin, stdinEncoding);
276
284
  }).finally(() => scope?.[Symbol.dispose]());
277
285
  }
@@ -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 type { Writable } from 'stream';\nimport type { CancellationReason } from '../../cancellation.js';\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\treadonly reason: CancellationReason;\n\n\tconstructor(\n\t\tcmd: string,\n\t\tkilled: boolean,\n\t\tcode?: number | string | undefined,\n\t\tsignal: NodeJS.Signals = 'SIGTERM',\n\t\treason: CancellationReason = 'unknown',\n\t) {\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.reason = reason;\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\tcancellation?.aborted ? 'aborted' : error.killed ? 'timeout' : 'unknown',\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\t/** Whether Node itself killed the process (its spawn `timeout`), as opposed to an external signal. */\n\treadonly killed?: boolean;\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, undefined, undefined, 'aborted'));\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, killed: proc.killed });\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\t// Only Node's spawn `timeout` kills it (a caller abort rejected above, from `'error'`); an\n\t\t\t\t\t// external SIGTERM leaves `proc.killed` false\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew CancelledRunError(\n\t\t\t\t\t\t\t`${command} ${args.join(' ')}`,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\tcode ?? undefined,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\tproc.killed ? 'timeout' : 'unknown',\n\t\t\t\t\t\t),\n\t\t\t\t\t);\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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"}
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 { Readable, Writable } from 'stream';\nimport type { CancellationReason } from '../../cancellation.js';\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\treadonly reason: CancellationReason;\n\n\tconstructor(\n\t\tcmd: string,\n\t\tkilled: boolean,\n\t\tcode?: number | string | undefined,\n\t\tsignal: NodeJS.Signals = 'SIGTERM',\n\t\treason: CancellationReason = 'unknown',\n\t) {\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.reason = reason;\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\n/** What core uses of a spawned process; Node's `ChildProcess` satisfies it */\nexport interface SpawnedProcess {\n\treadonly stdin: Writable | null;\n\treadonly stdout: Readable | null;\n\treadonly stderr: Readable | null;\n\treadonly killed: boolean;\n\treadonly exitCode: number | null;\n\treadonly spawnargs: string[];\n\tonce(event: 'error', listener: (error: Error) => void): this;\n\tonce(event: 'close', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;\n\tremoveAllListeners(): this;\n}\n\nexport interface ProcessSpawnOptions {\n\treadonly cwd?: string;\n\t/** The child's whole environment, not additions to the spawner's own; an `undefined` value leaves the key out */\n\treadonly env?: Record<string, string | undefined>;\n\t/** Milliseconds; absent means none */\n\treadonly timeout?: number;\n\treadonly signal?: AbortSignal;\n\t/** A `null` entry means `'pipe'` */\n\treadonly stdio?: ['pipe' | 'ignore', null, null];\n}\n\n/**\n * Launches a process in place of Node's `spawn`, and must behave as it does:\n * - pipe stdout and stderr, and stdin unless `stdio[0]` is `'ignore'`, which leaves `stdin` null\n * - honour `signal`, including one already aborted: kill with SIGTERM, then emit `'error'` with `name` `AbortError`,\n * unless the process has already exited\n * - honour `timeout`: kill with SIGTERM and set `killed`, without emitting `'error'`\n * - report a killed process's `'close'` with a null code and its signal, not an exit code such as 143\n * - emit no event before returning, `'error'` at most once, and `'close'` exactly once and last: also after `'error'`\n * (a failed launch, an abort), and only after `stdout` and `stderr` have ended or been destroyed, with `exitCode`\n * and `killed` already set\n * - close the child's stdout when `stdout` is destroyed, which is how a reader that stops early ends the process\n */\nexport type ProcessSpawner = (command: string, args: readonly string[], options: ProcessSpawnOptions) => SpawnedProcess;\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\t/** Launches the process; defaults to Node's `spawn`. Only used by `runSpawn` */\n\treadonly spawn?: ProcessSpawner;\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\tcancellation?.aborted ? 'aborted' : error.killed ? 'timeout' : 'unknown',\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\t/** Whether Node itself killed the process (its spawn `timeout`), as opposed to an external signal. */\n\treadonly killed?: boolean;\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, spawn: spawner, ...opts }: RunOptions = options;\n\n\tconst promise = new Promise<RunExitResult | RunResult<T>>((resolve, reject) => {\n\t\tconst proc = (spawner ?? spawn)(command, args, {\n\t\t\tcwd: opts.cwd,\n\t\t\tenv: opts.env,\n\t\t\t// 0 disables it, which the spawner contract spells as absent\n\t\t\ttimeout: opts.timeout === 0 ? undefined : opts.timeout,\n\t\t\tsignal: cancellation,\n\t\t\tstdio: ['pipe', null, null],\n\t\t});\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, undefined, undefined, 'aborted'));\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, killed: proc.killed });\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\t// Only Node's spawn `timeout` kills it (a caller abort rejected above, from `'error'`); an\n\t\t\t\t\t// external SIGTERM leaves `proc.killed` false\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew CancelledRunError(\n\t\t\t\t\t\t\t`${command} ${args.join(' ')}`,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\tcode ?? undefined,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\tproc.killed ? 'timeout' : 'unknown',\n\t\t\t\t\t\t),\n\t\t\t\t\t);\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\t// Only a host spawner can leave these out; reading no output as empty would pass for a clean answer\n\t\tif (proc.stdout == null || proc.stderr == null) {\n\t\t\treject(new Error(`'${command}' was spawned without stdout and stderr pipes`));\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":";;;;;;;;;AAuBA,IAAa,WAAb,cAA8B,MAAM;CACnC;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,SAA0B,QAAgB,QAAgB;EACrE,MAAM,QAAQ,OAAO;EAErB,KAAK,MAAM,QAAQ;EACnB,KAAK,SAAS,QAAQ;EACtB,KAAK,OAAO,QAAQ,QAAQ,KAAA;EAC5B,KAAK,SAAS,QAAQ;EACtB,KAAK,SAAS,QAAQ,KAAK,KAAK;EAChC,KAAK,SAAS,QAAQ,KAAK,KAAK;EAEhC,KAAK,OAAO;EACZ,MAAM,oBAAoB,MAAM,UAAU;CAC3C;AACD;AAEA,IAAa,oBAAb,cAAuC,SAAS;CAC/C;CAEA,YACC,KACA,QACA,MACA,SAAyB,WACzB,SAA6B,WAC5B;EACD,MACC;GAAE,SAAS,gCAAgC;GAAY;GAAa;GAAc;GAAc;EAAO,GACvG,IACA,EACD;EAEA,KAAK,SAAS;EAEd,KAAK,OAAO;EACZ,MAAM,oBAAoB,MAAM,UAAU;CAC3C;AACD;AAIA,MAAM,eAAe;AACrB,MAAM,WAAW;AACjB,MAAM,gBAAgB;AACtB,MAAM,UAAU;;;;;;;;;;AAWhB,eAAe,YAAY,KAA8B;CAKxD,IAAI,aAAa,KAAK,GAAG,GAAG,OAAO;CAEnC,MAAM,SAASA,KAAU,KAAK,GAAG;CACjC,IAAI;EACH,MAAM,QAAQ,MAAM,KAAK,MAAM;EAC/B,IAAI,OAAO,OAAO,KAAK,aAAa,KAAK,GAAG,OAAO;CACpD,QAAQ,CAAC;CAET,MAAM,OAAO,QAAQ,IAAI;CACzB,IAAI,QAAQ,QAAQ,KAAK,WAAW,GAAG;EACtC,MAAM,WAAW,KAAK,MAAM,YAAY,MAAM,GAAG;EACjD,KAAK,MAAM,KAAK,UAAU;GACzB,MAAM,SAASA,KAAU,GAAG,GAAG;GAC/B,IAAI;IACH,MAAM,QAAQ,MAAM,KAAK,MAAM;IAC/B,IAAI,OAAO,OAAO,KAAK,aAAa,KAAK,GAAG,OAAO;GACpD,QAAQ,CAAC;EACV;CACD;CAEA,OAAO;AACR;AAEA,SAAS,aAAa,OAAc;CACnC,IAAI,WAAW,OAAO;CAEtB,MAAM,UAAU,MAAM,MAAM,MAAM,QAAQ,QAAQ,SAAS,IAAI;CAC/D,MAAM,SAAS,MAAM,MAAM,MAAM,QAAQ,QAAQ,SAAS,IAAI;CAE9D,OAAO,QAAQ,MAAM,OAAO,KAAW,MAAM,OAAO,KAAU,WAAa,MAAM,OAAO,MAAU,MAAO;AAC1G;;;;;;;;;;AAWA,eAAsB,eAAe,KAAa,MAA0D;CAE3G,IAAI,CAAC,WAAW,OAAO;EAAE,KAAK,MAAM,YAAY,GAAG;EAAS;CAAK;CAEjE,IAAI,CAAE,MAAM,SAAS,GAAG,GAKvB,KAAK,MAAM,OAAO;EADI;EAAQ;EAAQ;EAAQ;CACjB,GAAG;EAC/B,MAAM,mBAAmB,MAAM,YAAY,GAAG,MAAM,KAAK;EAEzD,IAAI,MAAM,SAAS,gBAAgB,GAAG,OAAO,eAAe,kBAAkB,IAAI;CACnF;CAGD,IAAI,SAAS,KAAK,GAAG,GAUpB,OAAO;EAAE,KATGA,KACX,QAAQ,IAAI,cAAc,eAC1B,YACA,qBACA,QACA,gBAIe;EAAG,MAAM,CAAC,GAAG;GAFb;GAAoB;GAAgB;GAAW;GAAmB;GAAS;EAEzD,GAAG,GAAG,IAAI;CAAE;CAG/C,IAAI,cAAc,KAAK,GAAG,GAIzB,OAAO;EAAE,KAHGA,KAAU,QAAQ,IAAI,cAAc,eAAe,YAAY,SAG3D;EAAG,MAAM;GAFR;GAAM;GAAK,GAAG;EAEA;CAAE;CAGlC,IAAI,QAAQ,KAAK,GAAG,GAInB,OAAO;EAAE,KAHG,QAAQ;EAGD,MAAM,CAAC,GAAG,CAFX,GAEkB,GAAG,GAAG,IAAI;CAAE;CAGjD,OAAO;EAAE,KAAK;EAAW;CAAK;AAC/B;AAEA,eAAsB,oBAAoB,MAA+B;CACxE,OAAO,IAAI,SAAiB,SAAS,WAAW;EAC/C,KAAK,eAAe,KAAK,oBAAoB,OAAO,QAAQ,YAAY;GACvE,IAAI,SAAS,MAAM;IAClB,OAAO,KAAK;IACZ;GACD;GAEA,QAAQ,cAAc,OAAO,KAAK,CAAC,CAAC;EACrC,CAAC;CACF,CAAC;AACF;AAgFA,MAAM,sBAAsB;AAgB5B,SAAgB,IACf,SACA,MACA,UACA,SACa;CACb,MAAM,QAAQ,gBAAgB,KAAK,uBAAuB,UAAU;CAEpE,MAAM,EAAE,OAAO,eAAe,cAAc,QAAQ,GAAG,SAAuD;EAC7G,WAAW;EACX,GAAG;CACJ;CAwFA,OAAO,IAtFa,SAAY,SAAS,WAAW;EACnD,MAAM,OAAO,SACZ,SACA,MACA;GAAE,GAAG;GAAM,QAAQ;EAAa,GAChC,OAAO,OAAiC,QAAQ,WAAW;GAC1D,IAAI,SAAS,cAAc;IAC1B,QAAS,OAAO,QAAQ,KAAK,QAAc;IAE3C;GACD;GAEA,IAAI,SAAS,MAAM;IAClB,IAAI,MAAM,WAAW,WAAW;KAC/B,OACC,IAAI,kBACH,GAAG,QAAQ,GAAG,KAAK,KAAK,GAAG,KAC3B,MACA,MAAM,QAAQ,KAAA,GACd,MAAM,QACN,cAAc,UAAU,YAAY,MAAM,SAAS,YAAY,SAChE,CACD;KAEA;IACD;IAEA,IAAI,oBAAoB,KAAK,MAAM,OAAO,GACzC,MAAM,UAAU,uGAAuG,KAAK,UAAU;IAGvI,IAAI;IACJ,IAAI;IACJ,IAAI,aAAa,UAAU,aAAa,YAAY,aAAa,UAAU;KAG1E,gBAAgB,OAAO,SAAS;KAEhC,gBAAgB,OAAO,SAAS;IACjC,OAAO;KACN,IAAI,UAAU,MACb,MAAM,IAAI,MACT,uBAAuB,SAAS,8CACjC;KAGD,gBAAgB,MAAM,OACrB,OAAO,KAAK,QAAQ,QAAQ,GAC5B,WAAW,EAAY,SAAS,IAAI,KAAA,CACrC;KACA,gBAAgB,MAAM,OACrB,OAAO,KAAK,QAAQ,QAAQ,GAC5B,WAAW,EAAY,SAAS,IAAI,KAAA,CACrC;IACD;IACA,OAAO,IAAI,SAAS,OAAO,eAAe,aAAa,CAAC;IAExD;GACD;GAEA,IAAI,UAAU,CAAC,SAAS,SAAS,OAAO,QAAQ,OAAO,GACtD,OAAO,KAAK,WAAW,QAAQ,GAAG,KAAK,KAAK,GAAG,EAAE,WAAW,QAAQ;GAGrE,IAAI,aAAa,UAAU,aAAa,YAAY,aAAa,UAChE,QAAQ,MAAW;QACb;IACN,IAAI,UAAU,MACb,MAAM,IAAI,MAAM,uBAAuB,SAAS,8CAA8C;IAG/F,QACE,MAAM,OACN,OAAO,KAAK,QAAQ,QAAQ,GAC5B,WAAW,EAAY,SAAS,IAAI,KAAA,CACrC,CACD;GACD;EACD,CACD;EAEA,IAAI,SAAS,MACZ,SAAS,KAAK,OAAO,OAAO,aAAa;CAE3C,CAEa,CAAC,CAAC,cAAc,QAAQ,OAAO,QAAQ,CAAC,CAAC;AACvD;;;;;;;AAQA,SAAgB,SAAS,QAAyB,OAAwB,eAAyC;CAClH,IAAI,UAAU,MAAM;CAEpB,OAAO,GAAG,eAAe,CAAC,CAAC;CAC3B,IAAI,OAAO,UAAU,UACpB,OAAO,IAAI,OAAQ,iBAAiB,MAAyB;MAE7D,OAAO,IAAI,KAAK;AAElB;AAiCA,SAAgB,SACf,SACA,MACA,UACA,SACwC;CACxC,MAAM,QAAQ,gBAAgB,KAAK,uBAAuB,eAAe;CAEzE,MAAM,EAAE,OAAO,eAAe,cAAc,QAAQ,OAAO,SAAS,GAAG,SAAqB;CAkI5F,OAAO,IAhIa,SAAuC,SAAS,WAAW;EAC9E,MAAM,QAAQ,WAAW,MAAA,CAAO,SAAS,MAAM;GAC9C,KAAK,KAAK;GACV,KAAK,KAAK;GAEV,SAAS,KAAK,YAAY,IAAI,KAAA,IAAY,KAAK;GAC/C,QAAQ;GACR,OAAO;IAAC;IAAQ;IAAM;GAAI;EAC3B,CAAC;EACD,MAAM,gBAAgB,aAAa,WAAW,SAAS;EAEvD,MAAM,gBAA0B,CAAC;EACjC,KAAK,QAAQ,GAAG,SAAS,SAAiB,cAAc,KAAK,IAAI,CAAC;EAElE,MAAM,gBAA0B,CAAC;EACjC,KAAK,QAAQ,GAAG,SAAS,SAAiB,cAAc,KAAK,IAAI,CAAC;EAElE,SAAS,SACR,UAC+D;GAC/D,MAAM,SAAS,OAAO,OAAO,aAAa;GAC1C,MAAM,SAAS,OAAO,OAAO,aAAa;GAC1C,IAAI,aAAa,UAAU,aAAa,UACvC,OAAO;IAAE,QAAQ,OAAO,SAAS,QAAQ;IAAQ,QAAQ,OAAO,SAAS,QAAQ;GAAO;GAEzF,IAAI,aAAa,UAChB,OAAO;IAAU;IAAqB;GAAY;GAGnD,IAAI,UAAU,MACb,MAAM,IAAI,MAAM,uBAAuB,SAAS,8CAA8C;GAG/F,OAAO,QAAQ,IAAI,CAClB,OAAO,QAAQ,WAAW,EAAY,SAAS,IAAI,KAAA,CAAS,GAC5D,OAAO,QAAQ,WAAW,EAAY,SAAS,IAAI,KAAA,CAAS,CAC7D,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,oBAAoB;IAC5C,QAAQ;IACR,QAAQ;GACT,EAAE;EACH;EAEA,KAAK,KAAK,SAAS,OAAM,OAAM;GAC9B,IAAI,IAAI,SAAS,cAAc;IAC9B,OAAO,IAAI,kBAAkB,GAAG,QAAQ,GAAG,KAAK,KAAK,GAAG,KAAK,MAAM,KAAA,GAAW,KAAA,GAAW,SAAS,CAAC;IAEnG;GACD;GAEA,MAAM,QAAQ,SAAiB,aAAa;GAC5C,MAAM,EAAE,QAAQ,WAAW,iBAAiB,UAAU,MAAM,QAAQ;GAEpE,OAAO,IAAI,SAAS,IAAI,QAAQ,MAAM,CAAC;EACxC,CAAC;EAED,KAAK,KAAK,SAAS,OAAO,MAAM,WAAW;GAC1C,IAAI,SAAS,cAAc;IAK1B,QAAQ;KAAE,UAAU,QAAQ,KAAA;KAAW,QAAQ,UAAU,KAAA;KAAW,QAAQ,KAAK;IAAO,CAAC;IAEzF;GACD;GAEA,IAAI,SAAS,KAAK,QAAQ;IACzB,MAAM,QAAQ,SAAiB,aAAa;IAC5C,MAAM,EAAE,QAAQ,WAAW,iBAAiB,UAAU,MAAM,QAAQ;IACpE,IAAI,OAAO,UAAU,CAAC,SAAS,SAAS,OAAO,QAAQ,OAAO,GAC7D,OAAO,KAAK,WAAW,QAAQ,GAAG,KAAK,KAAK,GAAG,EAAE,WAAW,QAAQ;IAGrE,IAAI,WAAW,WAAW;KAGzB,OACC,IAAI,kBACH,GAAG,QAAQ,GAAG,KAAK,KAAK,GAAG,KAC3B,MACA,QAAQ,KAAA,GACR,QACA,KAAK,SAAS,YAAY,SAC3B,CACD;KAEA;IACD;IAEA,OACC,IAAI,SACH;KACC,SAAS,iCAAiC;KACpC;KACN,QAAQ,UAAU,KAAA;IACnB,GACA,QACA,MACD,CACD;IAEA;GACD;GAEA,MAAM,QAAQ,SAAY,QAAQ;GAClC,MAAM,EAAE,QAAQ,WAAW,iBAAiB,UAAU,MAAM,QAAQ;GACpE,IAAI,OAAO,UAAU,CAAC,SAAS,SAAS,OAAO,QAAQ,OAAO,GAC7D,OAAO,KACN,WAAW,QAAQ,GAAG,KAAK,KAAK,GAAG,EAAE,WAAW,OAAO,WAAW,WAAW,SAAS,OAAO,SAAS,GACvG;GAMD,QAAQ;IAAE,UAAU,QAAQ,KAAA;IAAW,QAAQ,UAAU,KAAA;IAAmB;IAAgB;GAAO,CAAC;EACrG,CAAC;EAGD,IAAI,KAAK,UAAU,QAAQ,KAAK,UAAU,MACzC,uBAAO,IAAI,MAAM,IAAI,QAAQ,8CAA8C,CAAC;EAG7E,IAAI,SAAS,MACZ,SAAS,KAAK,OAAO,OAAO,aAAa;CAE3C,CAEa,CAAC,CAAC,cAAc,QAAQ,OAAO,QAAQ,CAAC,CAAC;AACvD;AAEA,eAAsB,SAAS,MAAgC;CAC9D,OAAO,IAAI,SAAiB,YAAW,OAAO,MAAM,UAAU,OAAM,QAAO,QAAQ,OAAO,IAAI,CAAC,CAAC;AACjG"}
@@ -1285,8 +1285,8 @@ Linear reports its canceled and duplicate states as `CANCELED`.
1285
1285
  Azure DevOps reports a state in its `Removed` category as `CANCELED`, `Completed` and `Resolved` as `DONE`,
1286
1286
  `InProgress` as `IN_PROGRESS` and `Proposed` as `TO_DO`. No work item read carries the category, so every one (by id,
1287
1287
  the cheap etag check and the project list, on Azure DevOps Server too) classifies its rows from their type's states
1288
- (`GET …/_apis/wit/workItemTypes/{type}/states?api-version=6.0-preview.1`, the preview version Azure DevOps Server 2020
1289
- requires), keyed on the raw `System.State`, and caches each read for five minutes per host, project, type and
1288
+ (`GET …/_apis/wit/workItemTypes/{type}/states`, with a version selected from the installation's REST resource metadata;
1289
+ Services and Server 2020 use `6.0-preview.1`), keyed on the raw `System.State`, and caches each read for five minutes per host, project, type and
1290
1290
  credential. The category, never the name, decides: a state named `Canceled` in the `Completed` category is `DONE`, and
1291
1291
  a state its type doesn't list has no category. When a states read fails, a project list keeps its rows (the failed
1292
1292
  type's without a category) and reports the project as a partial collection with a failure scoped to it; a read by id
@@ -1296,14 +1296,25 @@ or resolved dates. Failed workflows fall back to dates with partial metadata; fa
1296
1296
  before retrying. List, identity and native presentation reads share one cache, including workflow expiry and HTTP
1297
1297
  proxy invalidation.
1298
1298
 
1299
+ TFS 2018 uses its advertised REST versions and collection-level work item routes. A work item must report the
1300
+ requested project, so a collection-level read cannot answer for another project's coordinate. If the type's states
1301
+ omit categories, core supplements them from the project's `backlogconfiguration.workItemTypeMappedStates`, using
1302
+ only explicit type/state pairs. Unmapped states, including Removed on some processes, remain unknown; neither
1303
+ state names, colors nor absence from the backlog imply a category. A point or etag read of such a state fails.
1304
+ Project lists retain mapped row categories and report incomplete workflows as partial collections; selection for
1305
+ an incomplete workflow retains the SDK's date fallback. Exact state-filtered searches and counts refuse incomplete
1306
+ workflow metadata. `criteria.state: 'all'` skips that selection requirement, although search hydration still fails
1307
+ if a returned row's state has no category. Legacy identity strings remain opaque values, never inferred GUIDs.
1308
+
1299
1309
  `listIssuesPage` accepts `state: 'open' | 'closed' | 'all'` for Azure DevOps Services and Server, on repository,
1300
1310
  project and account-wide reads. Omitted means open only. `getSupportedFilters(providerId).issueStates` declares this
1301
1311
  capability. Other git hosts retain their open-only list default and refuse non-open state requests on this facade.
1302
1312
 
1303
1313
  A known Azure workflow category determines normalized `state` and `closed`, with `CANCELED` closing like `DONE`.
1304
1314
  A historical closed date remains available as data but does not close an issue whose category is now open.
1305
- Identity and list reads fall back to the closed date only when the category is unknown. Other providers keep their
1306
- existing derivation.
1315
+ Identity and list reads fall back to the closed date only when the category is unknown. Legacy workflows that
1316
+ needed backlog mappings instead refuse point reads and cheap etag checks for an absent or uncategorized current
1317
+ state, even when all returned states received mappings. Other providers keep their existing derivation.
1307
1318
 
1308
1319
  ### Sprints and iterations
1309
1320
 
@@ -1341,14 +1352,17 @@ Two exceptions carry HTML with `bodyFormat` omitted: Azure DevOps work items and
1341
1352
  (`content.html`). Their `bodyFormat` is left unset rather than changed, so check the provider before treating either
1342
1353
  as Markdown.
1343
1354
 
1344
- An Azure DevOps work item's body is its first rich text field with content, since its type decides where the content
1345
- lives: `System.Description`, then the built-in description fields (`Microsoft.VSTS.TCM.ReproSteps` on Agile, Scrum and
1346
- CMMI Bugs, `Microsoft.VSTS.Common.AcceptanceCriteria`, `Microsoft.VSTS.CMMI.Symptom`), then any other `html` field
1347
- of the organization's field catalog, custom fields first (provider-apis' `getAzureWorkItemDescription`, which every
1348
- read applies). `bodyField` names the field (`{ id: 'Microsoft.VSTS.TCM.ReproSteps', name: 'Repro Steps' }`) so a
1349
- host can label the body; it is absent for other providers. The catalog is read only for a work item no built-in field
1350
- describes, and kept for an hour per organization, credential and client (provider-apis keeps its own for list rows); when it can't be read the body falls back to the built-in
1351
- fields.
1355
+ An Azure DevOps work item's body is its first rich text field with content: `System.Description`, then the built-in
1356
+ description fields (`Microsoft.VSTS.TCM.ReproSteps`, `Microsoft.VSTS.Common.AcceptanceCriteria`,
1357
+ `Microsoft.VSTS.CMMI.Symptom`), then additional HTML fields declared by the organization's field catalog, custom fields
1358
+ first. Every read uses provider-apis' shared selector. `bodyField` names the selected field
1359
+ (`{ id: 'Microsoft.VSTS.TCM.ReproSteps', name: 'Repro Steps' }`) so a host can label the body; it is absent when no body
1360
+ is available and for other providers. The catalog is read only when no built-in field describes the work item, and
1361
+ kept for an hour per organization, credential and client (provider-apis keeps its own for list rows). An unavailable
1362
+ or malformed catalog leaves the body to the built-in fields rather than failing the work item; the caller's
1363
+ cancellation still propagates.
1364
+ An empty Description is preserved when no field has visible content. Direct reads share a catalog cache scoped to
1365
+ the installation, collection, credential and authentication mode, cleared on HTTP proxy changes.
1352
1366
 
1353
1367
  Rendering and conversion remain consumer concerns. In particular, the Jira body is neither ADF nor converted to
1354
1368
  Markdown by this package.
@@ -1433,7 +1447,10 @@ Markdown by this package.
1433
1447
  replaces it. A continuation whose snapshot is gone is refused (warning + `fetchFailed`) rather than resumed
1434
1448
  against a fresh query, where an item moved by the `updated` order could be skipped. Restart without the cursor.
1435
1449
  A cursor is bound to its query and refused under another one. `page` without a cursor walks from page 1 against
1436
- a fresh query. Requests use REST `api-version=5.0`, so Azure DevOps Server 2019 or later is required.
1450
+ a fresh query. Requests negotiate REST versions from the installation's resource metadata, including TFS 2018.
1451
+ Exact state-filtered searches and counts require complete workflow categories; `state: 'all'` skips that
1452
+ selection requirement, but every hydrated row in a legacy workflow that needed backlog mappings still needs an
1453
+ authoritative category.
1437
1454
  - **Pull requests** (`searchPullRequestsPage` / `countPullRequests`) drain every facet — each repository, or each
1438
1455
  project of the `org` (every visible project when only relationships bound the search), times each
1439
1456
  relationship — reading up to 1,000 pull requests per facet, then apply text, draft and dates, union by repository
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.16.0",
4
+ "version": "0.16.2",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": {
7
7
  "name": "GitKraken",
@@ -239,7 +239,7 @@
239
239
  }
240
240
  },
241
241
  "dependencies": {
242
- "@gitkraken/provider-apis": "0.65.4",
242
+ "@gitkraken/provider-apis": "0.65.5",
243
243
  "@octokit/graphql": "9.0.4",
244
244
  "@octokit/request": "10.0.14",
245
245
  "@octokit/request-error": "7.1.1",