@wrongstack/tools 0.287.0 → 0.291.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_fetch-guard.d.ts.map +1 -1
- package/dist/_redact-command.d.ts.map +1 -1
- package/dist/_spawn-stream.d.ts.map +1 -1
- package/dist/audit.js +61 -14
- package/dist/audit.js.map +3 -3
- package/dist/auto-proceed-loop-guard.d.ts +128 -0
- package/dist/auto-proceed-loop-guard.d.ts.map +1 -0
- package/dist/auto-proceed-loop-guard.js +46 -0
- package/dist/auto-proceed-loop-guard.js.map +7 -0
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +97 -19
- package/dist/bash.js.map +3 -3
- package/dist/builtin.d.ts +12 -10
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +1766 -864
- package/dist/builtin.js.map +3 -3
- package/dist/codebase-index/background-indexer.d.ts +1 -1
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/bm25.d.ts +1 -2
- package/dist/codebase-index/bm25.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts +7 -0
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
- package/dist/codebase-index/index-service.d.ts +11 -1
- package/dist/codebase-index/index-service.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +2 -1
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +888 -254
- package/dist/codebase-index/index.js.map +3 -3
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/refs-extractor.d.ts +2 -17
- package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +43 -1
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker-protocol.d.ts +1 -0
- package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +738 -212
- package/dist/codebase-index/worker.js.map +3 -3
- package/dist/codebase-index/writer.d.ts +60 -1
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/e2e.js +477 -0
- package/dist/e2e.js.map +7 -0
- package/dist/exec.d.ts +1 -1
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +60 -5
- package/dist/exec.js.map +3 -3
- package/dist/fetch.js +2 -2
- package/dist/fetch.js.map +2 -2
- package/dist/format.js +61 -14
- package/dist/format.js.map +3 -3
- package/dist/glob.js +2 -2
- package/dist/glob.js.map +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +2 -2
- package/dist/grep.js.map +2 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2001 -873
- package/dist/index.js.map +3 -3
- package/dist/install.js +61 -14
- package/dist/install.js.map +3 -3
- package/dist/kanban.d.ts +21 -2
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +713 -572
- package/dist/kanban.js.map +3 -3
- package/dist/languages/detect.d.ts.map +1 -1
- package/dist/languages/index.js +61 -14
- package/dist/languages/index.js.map +3 -3
- package/dist/languages/types.d.ts +7 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/lint.js +61 -14
- package/dist/lint.js.map +3 -3
- package/dist/outdated.js +61 -14
- package/dist/outdated.js.map +3 -3
- package/dist/pack.js +1763 -861
- package/dist/pack.js.map +3 -3
- package/dist/plan.js +4 -0
- package/dist/plan.js.map +2 -2
- package/dist/process-registry.js +12 -5
- package/dist/process-registry.js.map +2 -2
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js.map +2 -2
- package/dist/search.js +2 -2
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +9 -0
- package/dist/session-kanban.d.ts.map +1 -1
- package/dist/session-kanban.js +197 -3
- package/dist/session-kanban.js.map +2 -2
- package/dist/task.js +4 -0
- package/dist/task.js.map +2 -2
- package/dist/test.js +61 -14
- package/dist/test.js.map +3 -3
- package/dist/todo.js +4 -0
- package/dist/todo.js.map +2 -2
- package/dist/tree.js +2 -2
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +61 -14
- package/dist/typecheck.js.map +3 -3
- package/package.json +13 -5
package/dist/e2e.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/e2e.ts", "../src/_util.ts"],
|
|
4
|
+
"sourcesContent": ["import { open, readdir } from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { Tool } from '@wrongstack/core';\nimport { safeResolveReal } from './_util.js';\n\nexport type E2EFramework = 'playwright' | 'cypress';\nexport type E2EPackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';\n\ninterface E2EPlanInput {\n cwd?: string | undefined;\n framework?: E2EFramework | 'all' | undefined;\n maxDepth?: number | undefined;\n includeSpecs?: boolean | undefined;\n}\n\nexport interface E2EServerHint {\n kind: 'managed' | 'attach';\n command?: string | undefined;\n url?: string | undefined;\n port?: number | undefined;\n source: 'static-config';\n incomplete: boolean;\n}\n\nexport interface E2EExecutionPlan {\n command: string;\n args: string[];\n cwd: string;\n source: 'package-script' | 'framework-default';\n script?: string | undefined;\n}\n\nexport interface E2EProjectPlan {\n framework: E2EFramework;\n root: string;\n configPath?: string | undefined;\n packagePath?: string | undefined;\n packageManager: E2EPackageManager;\n evidence: string[];\n scripts: string[];\n serverHints: E2EServerHint[];\n specPattern: string;\n specCount: number;\n specSamples: string[];\n specsTruncated: boolean;\n execution: E2EExecutionPlan;\n warnings: string[];\n}\n\nexport interface E2EPlanOutput {\n root: string;\n projects: E2EProjectPlan[];\n scannedDirectories: number;\n scanTruncated: boolean;\n warnings: string[];\n}\n\ninterface ScanResult {\n configs: Array<{ framework: E2EFramework; absolutePath: string }>;\n packageFiles: string[];\n scannedDirectories: number;\n truncated: boolean;\n}\n\ninterface PackageInfo {\n path: string;\n scripts: Record<string, string>;\n dependencies: Set<string>;\n packageManager?: E2EPackageManager | undefined;\n}\n\nasync function readBoundedText(filePath: string, maxBytes: number): Promise<string | undefined> {\n let handle;\n try {\n handle = await open(filePath, 'r');\n const info = await handle.stat();\n if (!info.isFile() || info.size > maxBytes) return undefined;\n const buffer = Buffer.alloc(info.size);\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);\n return buffer.subarray(0, bytesRead).toString('utf8');\n } catch {\n return undefined;\n } finally {\n await handle?.close().catch(() => undefined);\n }\n}\n\nconst CONFIG_NAMES = new Map<string, E2EFramework>([\n ['playwright.config.ts', 'playwright'],\n ['playwright.config.js', 'playwright'],\n ['playwright.config.mts', 'playwright'],\n ['playwright.config.mjs', 'playwright'],\n ['playwright.config.cts', 'playwright'],\n ['playwright.config.cjs', 'playwright'],\n ['cypress.config.ts', 'cypress'],\n ['cypress.config.js', 'cypress'],\n ['cypress.config.mts', 'cypress'],\n ['cypress.config.mjs', 'cypress'],\n ['cypress.config.cts', 'cypress'],\n ['cypress.config.cjs', 'cypress'],\n ['cypress.json', 'cypress'],\n]);\n\nconst SKIP_DIRECTORIES = new Set([\n '.git',\n '.hg',\n '.svn',\n '.wrongstack',\n 'node_modules',\n 'dist',\n 'build',\n 'coverage',\n 'coverage-html',\n 'playwright-report',\n 'test-results',\n '.next',\n]);\n\nconst MAX_SCAN_DIRECTORIES = 4_000;\nconst MAX_PACKAGE_BYTES = 512 * 1024;\nconst MAX_CONFIG_BYTES = 512 * 1024;\nconst MAX_SPEC_SAMPLES = 20;\n\nfunction relativePath(root: string, target: string): string {\n const value = path.relative(root, target) || '.';\n return value.split(path.sep).join('/');\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction staticString(source: string, property: string): string | undefined {\n const key = escapeRegExp(property);\n const direct = source.match(\n new RegExp(`(?:['\"]?${key}['\"]?)\\\\s*:\\\\s*(['\"\\\\x60])([^'\"\\\\x60\\\\r\\\\n]*)\\\\1`),\n );\n if (direct?.[2] && !direct[2].includes('${')) return direct[2];\n\n const sameLineFallback = source.match(\n new RegExp(`(?:['\"]?${key}['\"]?)\\\\s*:[^\\\\r\\\\n]{0,300}?(['\"\\\\x60])([^'\"\\\\x60\\\\r\\\\n]*)\\\\1`),\n );\n return sameLineFallback?.[2] && !sameLineFallback[2].includes('${')\n ? sameLineFallback[2]\n : undefined;\n}\n\nfunction staticNumber(source: string, property: string): number | undefined {\n const key = escapeRegExp(property);\n const match = source.match(new RegExp(`(?:['\"]?${key}['\"]?)\\\\s*:\\\\s*(\\\\d{2,5})\\\\b`));\n return match?.[1] ? Number.parseInt(match[1], 10) : undefined;\n}\n\nfunction serverHints(framework: E2EFramework, source: string): E2EServerHint[] {\n if (framework === 'playwright' && /\\bwebServer\\s*:/.test(source)) {\n const command = staticString(source, 'command');\n const url = staticString(source, 'url');\n const port = staticNumber(source, 'port');\n return [\n {\n kind: 'managed',\n ...(command ? { command } : {}),\n ...(url ? { url } : {}),\n ...(port ? { port } : {}),\n source: 'static-config',\n incomplete: !command || (!url && !port),\n },\n ];\n }\n\n const baseUrl = staticString(source, 'baseURL') ?? staticString(source, 'baseUrl');\n return baseUrl\n ? [{ kind: 'attach', url: baseUrl, source: 'static-config', incomplete: false }]\n : [];\n}\n\nfunction configuredSpecPattern(framework: E2EFramework, source: string): string {\n if (framework === 'playwright') {\n const testDir = staticString(source, 'testDir') ?? './tests';\n return `${testDir.replace(/\\/$/, '')}/**/*.{spec,test}.{js,jsx,ts,tsx,mjs,cjs}`;\n }\n return (\n staticString(source, 'specPattern') ??\n 'cypress/{e2e,integration}/**/*.cy.{js,jsx,ts,tsx,mjs,cjs}'\n );\n}\n\nasync function scanWorkspace(\n root: string,\n maxDepth: number,\n signal: AbortSignal,\n): Promise<ScanResult> {\n const result: ScanResult = {\n configs: [],\n packageFiles: [],\n scannedDirectories: 0,\n truncated: false,\n };\n const queue: Array<{ directory: string; depth: number }> = [{ directory: root, depth: 0 }];\n\n while (queue.length > 0) {\n signal.throwIfAborted();\n const current = queue.shift();\n if (!current) break;\n result.scannedDirectories += 1;\n if (result.scannedDirectories > MAX_SCAN_DIRECTORIES) {\n result.truncated = true;\n break;\n }\n\n let entries;\n try {\n entries = await readdir(current.directory, { withFileTypes: true });\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n signal.throwIfAborted();\n const absolutePath = path.join(current.directory, entry.name);\n if (entry.isFile()) {\n if (entry.name === 'package.json') result.packageFiles.push(absolutePath);\n const framework = CONFIG_NAMES.get(entry.name);\n if (framework) result.configs.push({ framework, absolutePath });\n } else if (\n entry.isDirectory() &&\n current.depth < maxDepth &&\n !SKIP_DIRECTORIES.has(entry.name) &&\n !entry.name.startsWith('coverage-') &&\n !entry.name.startsWith('cov-')\n ) {\n queue.push({ directory: absolutePath, depth: current.depth + 1 });\n }\n }\n }\n\n return result;\n}\n\nasync function readPackageInfo(packagePath: string): Promise<PackageInfo | undefined> {\n try {\n const raw = await readBoundedText(packagePath, MAX_PACKAGE_BYTES);\n if (raw === undefined) return undefined;\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const scripts =\n parsed.scripts && typeof parsed.scripts === 'object'\n ? Object.fromEntries(\n Object.entries(parsed.scripts as Record<string, unknown>).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n ),\n )\n : {};\n const dependencies = new Set<string>();\n for (const field of ['dependencies', 'devDependencies', 'optionalDependencies']) {\n const value = parsed[field];\n if (!value || typeof value !== 'object') continue;\n for (const name of Object.keys(value as Record<string, unknown>)) dependencies.add(name);\n }\n const declared =\n typeof parsed.packageManager === 'string' ? (parsed.packageManager.split('@')[0] ?? '') : '';\n const packageManager = ['pnpm', 'npm', 'yarn', 'bun'].includes(declared)\n ? (declared as E2EPackageManager)\n : undefined;\n return { path: packagePath, scripts, dependencies, packageManager };\n } catch {\n return undefined;\n }\n}\n\nfunction frameworkFromPackage(info: PackageInfo): E2EFramework[] {\n const found = new Set<E2EFramework>();\n if (info.dependencies.has('@playwright/test') || info.dependencies.has('playwright')) {\n found.add('playwright');\n }\n if (info.dependencies.has('cypress')) found.add('cypress');\n for (const command of Object.values(info.scripts)) {\n if (/\\bplaywright(?:\\.cmd)?\\b/i.test(command)) found.add('playwright');\n if (/\\bcypress(?:\\.cmd)?\\b/i.test(command)) found.add('cypress');\n }\n return [...found];\n}\n\nasync function detectPackageManager(\n projectRoot: string,\n scanRoot: string,\n declared?: E2EPackageManager,\n): Promise<E2EPackageManager> {\n if (declared) return declared;\n let directory = projectRoot;\n while (true) {\n const names = new Set<string>();\n try {\n for (const entry of await readdir(directory)) names.add(entry);\n } catch {\n // Continue toward the scan root.\n }\n if (names.has('pnpm-lock.yaml')) return 'pnpm';\n if (names.has('yarn.lock')) return 'yarn';\n if (names.has('bun.lock') || names.has('bun.lockb')) return 'bun';\n if (names.has('package-lock.json') || names.has('npm-shrinkwrap.json')) return 'npm';\n if (directory === scanRoot) break;\n const parent = path.dirname(directory);\n const relativeParent = path.relative(scanRoot, parent);\n if (\n parent === directory ||\n relativeParent.startsWith('..') ||\n path.isAbsolute(relativeParent)\n ) {\n break;\n }\n directory = parent;\n }\n return 'npm';\n}\n\nfunction matchingScripts(info: PackageInfo | undefined, framework: E2EFramework): string[] {\n if (!info) return [];\n const matcher =\n framework === 'playwright' ? /\\bplaywright(?:\\.cmd)?\\b/i : /\\bcypress(?:\\.cmd)?\\b/i;\n return Object.entries(info.scripts)\n .filter(([, command]) => matcher.test(command))\n .map(([name]) => name)\n .sort((a, b) => {\n const preferred = ['test:e2e', 'e2e', 'e2e:test'];\n const ai = preferred.indexOf(a);\n const bi = preferred.indexOf(b);\n if (ai !== -1 || bi !== -1) return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);\n return a.localeCompare(b);\n });\n}\n\nfunction executionPlan(\n framework: E2EFramework,\n manager: E2EPackageManager,\n root: string,\n configPath: string | undefined,\n scripts: string[],\n): E2EExecutionPlan {\n const script = scripts[0];\n if (script) {\n return {\n command: manager,\n args: ['run', script],\n cwd: root,\n source: 'package-script',\n script,\n };\n }\n\n const runnerArgs = framework === 'playwright' ? ['playwright', 'test'] : ['cypress', 'run'];\n if (configPath) {\n runnerArgs.push(framework === 'playwright' ? '--config' : '--config-file', configPath);\n }\n return {\n command: manager === 'bun' ? 'bunx' : manager,\n args:\n manager === 'bun'\n ? runnerArgs\n : manager === 'npm'\n ? ['exec', '--', ...runnerArgs]\n : ['exec', ...runnerArgs],\n cwd: root,\n source: 'framework-default',\n };\n}\n\nfunction isSpec(framework: E2EFramework, filename: string): boolean {\n if (framework === 'cypress') return /\\.cy\\.(?:[cm]?[jt]sx?)$/i.test(filename);\n return /\\.(?:spec|test)\\.(?:[cm]?[jt]sx?)$/i.test(filename);\n}\n\nasync function collectSpecs(\n root: string,\n framework: E2EFramework,\n testDirectory: string | undefined,\n signal: AbortSignal,\n): Promise<{ count: number; samples: string[]; truncated: boolean }> {\n const roots = testDirectory\n ? [path.resolve(root, testDirectory)]\n : framework === 'cypress'\n ? [path.join(root, 'cypress', 'e2e'), path.join(root, 'cypress', 'integration')]\n : [path.join(root, 'tests')];\n const samples: string[] = [];\n let count = 0;\n let scanned = 0;\n const queue = [...roots];\n\n while (queue.length > 0) {\n signal.throwIfAborted();\n const directory = queue.shift();\n if (!directory) break;\n scanned += 1;\n if (scanned > MAX_SCAN_DIRECTORIES) return { count, samples, truncated: true };\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n signal.throwIfAborted();\n const target = path.join(directory, entry.name);\n if (entry.isDirectory() && !SKIP_DIRECTORIES.has(entry.name)) queue.push(target);\n else if (entry.isFile() && isSpec(framework, entry.name)) {\n count += 1;\n if (samples.length < MAX_SPEC_SAMPLES) samples.push(relativePath(root, target));\n }\n }\n }\n return { count, samples, truncated: count > samples.length };\n}\n\nasync function readConfig(configPath: string | undefined): Promise<string> {\n if (!configPath) return '';\n return (await readBoundedText(configPath, MAX_CONFIG_BYTES)) ?? '';\n}\n\nfunction nearestPackage(\n projectRoot: string,\n packagesByDirectory: Map<string, PackageInfo>,\n scanRoot: string,\n): PackageInfo | undefined {\n let directory = projectRoot;\n while (true) {\n const found = packagesByDirectory.get(directory);\n if (found) return found;\n if (directory === scanRoot) return undefined;\n const parent = path.dirname(directory);\n if (parent === directory || relativePath(scanRoot, parent).startsWith('..')) return undefined;\n directory = parent;\n }\n}\n\nexport async function discoverE2EProjects(\n root: string,\n options: {\n framework?: E2EFramework | 'all' | undefined;\n maxDepth?: number | undefined;\n includeSpecs?: boolean | undefined;\n signal: AbortSignal;\n },\n): Promise<E2EPlanOutput> {\n const maxDepth = Math.max(0, Math.min(options.maxDepth ?? 5, 8));\n const scan = await scanWorkspace(root, maxDepth, options.signal);\n const packages = (await Promise.all(scan.packageFiles.map(readPackageInfo))).filter(\n (info): info is PackageInfo => Boolean(info),\n );\n const packagesByDirectory = new Map(packages.map((info) => [path.dirname(info.path), info]));\n const candidates = new Map<\n string,\n { framework: E2EFramework; root: string; configPath?: string }\n >();\n\n for (const config of scan.configs) {\n if (\n options.framework &&\n options.framework !== 'all' &&\n config.framework !== options.framework\n ) {\n continue;\n }\n const projectRoot = path.dirname(config.absolutePath);\n candidates.set(`${config.framework}:${projectRoot}`, {\n framework: config.framework,\n root: projectRoot,\n configPath: config.absolutePath,\n });\n }\n\n for (const info of packages) {\n const projectRoot = path.dirname(info.path);\n for (const framework of frameworkFromPackage(info)) {\n if (options.framework && options.framework !== 'all' && framework !== options.framework)\n continue;\n const key = `${framework}:${projectRoot}`;\n if (!candidates.has(key)) candidates.set(key, { framework, root: projectRoot });\n }\n }\n\n const projects: E2EProjectPlan[] = [];\n for (const candidate of candidates.values()) {\n options.signal.throwIfAborted();\n const info = nearestPackage(candidate.root, packagesByDirectory, root);\n const source = await readConfig(candidate.configPath);\n const scripts = matchingScripts(info, candidate.framework);\n const manager = await detectPackageManager(candidate.root, root, info?.packageManager);\n const testDirectory =\n candidate.framework === 'playwright' ? staticString(source, 'testDir') : undefined;\n const resolvedTestDirectory = testDirectory\n ? path.resolve(candidate.root, testDirectory)\n : undefined;\n const relativeTestDirectory = resolvedTestDirectory\n ? path.relative(root, resolvedTestDirectory)\n : undefined;\n const unsafeTestDirectory = Boolean(\n relativeTestDirectory &&\n (relativeTestDirectory.startsWith('..') || path.isAbsolute(relativeTestDirectory)),\n );\n const specs =\n options.includeSpecs === false || unsafeTestDirectory\n ? { count: 0, samples: [], truncated: false }\n : await collectSpecs(candidate.root, candidate.framework, testDirectory, options.signal);\n const warnings: string[] = [];\n if (!candidate.configPath)\n warnings.push('Framework dependency or script found without a config file.');\n if (candidate.configPath && !source) {\n warnings.push('Config could not be read or exceeded the static inspection limit.');\n }\n if (unsafeTestDirectory) {\n warnings.push(\n 'Configured testDir resolves outside the requested discovery root and was not scanned.',\n );\n }\n const hints = serverHints(candidate.framework, source);\n if (hints.some((hint) => hint.incomplete)) {\n warnings.push('Configured server contains dynamic values; only static hints are shown.');\n }\n\n projects.push({\n framework: candidate.framework,\n root: relativePath(root, candidate.root),\n ...(candidate.configPath ? { configPath: relativePath(root, candidate.configPath) } : {}),\n ...(info ? { packagePath: relativePath(root, info.path) } : {}),\n packageManager: manager,\n evidence: [\n ...(candidate.configPath ? [`config:${relativePath(root, candidate.configPath)}`] : []),\n ...(info?.dependencies.has(\n candidate.framework === 'playwright' ? '@playwright/test' : 'cypress',\n )\n ? [`dependency:${candidate.framework === 'playwright' ? '@playwright/test' : 'cypress'}`]\n : []),\n ...scripts.map((script) => `script:${script}`),\n ],\n scripts,\n serverHints: hints,\n specPattern: configuredSpecPattern(candidate.framework, source),\n specCount: specs.count,\n specSamples: specs.samples,\n specsTruncated: specs.truncated,\n execution: executionPlan(\n candidate.framework,\n manager,\n relativePath(root, candidate.root),\n candidate.configPath ? relativePath(candidate.root, candidate.configPath) : undefined,\n scripts,\n ),\n warnings,\n });\n }\n\n projects.sort((a, b) => a.root.localeCompare(b.root) || a.framework.localeCompare(b.framework));\n return {\n root,\n projects,\n scannedDirectories: scan.scannedDirectories,\n scanTruncated: scan.truncated,\n warnings: scan.truncated\n ? [`Discovery stopped after ${MAX_SCAN_DIRECTORIES} directories; narrow cwd or maxDepth.`]\n : [],\n };\n}\n\nexport const e2ePlanTool: Tool<E2EPlanInput, E2EPlanOutput> = {\n name: 'e2e_plan',\n category: 'Code Quality',\n description:\n 'Discover Playwright and Cypress projects and preview bounded E2E execution plans without loading configs or starting processes.',\n usageHint:\n 'Use before running browser E2E tests. The result identifies authoritative configs, package scripts, ' +\n 'static server hints, specs, and exact argv. This tool never imports project config or executes commands.',\n permission: 'auto',\n mutating: false,\n riskTier: 'safe',\n capabilities: ['fs.read'],\n icon: 'test',\n timeoutMs: 30_000,\n inputSchema: {\n type: 'object',\n properties: {\n cwd: { type: 'string', description: 'Directory inside the project to inspect.' },\n framework: {\n type: 'string',\n enum: ['all', 'playwright', 'cypress'],\n description: 'Optional framework filter; defaults to all.',\n },\n maxDepth: {\n type: 'integer',\n minimum: 0,\n maximum: 8,\n description: 'Maximum workspace discovery depth; defaults to 5.',\n },\n includeSpecs: {\n type: 'boolean',\n description: 'Count and sample specs; defaults to true.',\n },\n },\n },\n async execute(input, ctx, opts) {\n const root = await safeResolveReal(input.cwd ?? '.', ctx);\n return discoverE2EProjects(root, {\n framework: input.framework,\n maxDepth: input.maxDepth,\n includeSpecs: input.includeSpecs,\n signal: opts.signal,\n });\n },\n};\n", "import { createHash } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as Core from '@wrongstack/core';\nimport type { Context } from '@wrongstack/core';\n\n/**\n * sha-256 hex of a UTF-8 string. Used by the file tools to record a content\n * hash alongside the mtime in `ctx.recordRead` \u2014 the hash is the authoritative\n * staleness arbiter for `edit` (mtime has a 2 s tolerance window on Windows).\n */\nexport function sha256hex(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n/** Detected package manager for a project directory. */\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm';\n\n/**\n * Detect the project's package manager by inspecting lockfiles in `cwd`.\n * Order: pnpm \u2192 yarn \u2192 npm (default). Missing or unreadable directories fall\n * back to `npm` rather than throwing, so a `safeResolve`-checked cwd that\n * happens to be empty never aborts the tool.\n */\nexport async function detectPackageManager(cwd: string): Promise<PackageManager> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* not pnpm */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* not yarn */\n }\n return 'npm';\n}\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);\n}\n\n/**\n * Roots every file tool may always reach, even in restricted mode: the\n * project root and the user-global `~/.wrongstack` directory (config, memory,\n * sessions, skills). `~/.wrongstack` honors the `WRONGSTACK_HOME` override.\n */\nfunction allowedRoots(ctx: Context): string[] {\n return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];\n}\n\n/** True if `target` is `root` itself or nested inside any of `roots`. */\nfunction isInsideAny(target: string, roots: string[]): boolean {\n return roots.some((root) => {\n const rel = path.relative(root, target);\n return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));\n });\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const target = path.resolve(absPath);\n // Unrestricted filesystem access: skip the project-root containment check.\n if (ctx.allowOutsideProjectRoot) return target;\n if (isInsideAny(target, allowedRoots(ctx))) return target;\n throw new Error(`Path \"${absPath}\" is outside project root \"${path.resolve(ctx.projectRoot)}\"`);\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\n/**\n * Defense against in-root\u2192out-of-root symlink escape (CWE-59). `safeResolve`\n * only does a syntactic `../` check, so a symlink that lives *inside* the\n * project root but points outside still passes it. This resolves the path\n * through `fs.realpath` and re-verifies containment against the realpath of\n * the project root (comparing like-for-like, since the root itself may be a\n * symlink \u2014 macOS `/var`\u2192`/private/var`, Windows 8.3 short names). For a path\n * that does not exist yet (e.g. a `write` to a new file) the nearest existing\n * ancestor directory is checked instead. Throws if the real target escapes.\n *\n * Mirrors the per-file guard already used in `replace.ts`/`grep.ts`; applied\n * to single-file `read`/`edit`/`write` it throws (rather than skips) because\n * the caller named exactly one file.\n */\nexport async function assertRealInsideRoot(absPath: string, ctx: Context): Promise<void> {\n // Unrestricted filesystem access: no symlink-escape check to perform.\n if (ctx.allowOutsideProjectRoot) return;\n // Compare like-for-like against the realpath of each always-allowed root\n // (project root + ~/.wrongstack), since a root may itself be a symlink.\n const realRoots = await Promise.all(\n allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r))),\n );\n let probe = absPath;\n for (;;) {\n let real: string;\n try {\n real = await fsp.realpath(probe);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n const parent = path.dirname(probe);\n if (parent === probe) return; // reached fs root without escaping\n probe = parent;\n continue;\n }\n throw err;\n }\n if (isInsideAny(real, realRoots)) return;\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoots[0]}\"`,\n );\n }\n}\n\n/** `safeResolve` + symlink realpath containment check. Async. */\nexport async function safeResolveReal(input: string, ctx: Context): Promise<string> {\n const abs = safeResolve(input, ctx);\n await assertRealInsideRoot(abs, ctx);\n return abs;\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n\u2026[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]\u2026\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n// \u2500\u2500\u2500 Command-output normalization (token-saving) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Raw process output is full of tokens the model gains nothing from: ANSI\n// escapes, carriage-return progress spam, runs of identical warning lines, and\n// huge tails of build noise. These helpers strip that noise before the output\n// reaches the LLM. They are scoped to COMMAND tools (bash/git/exec and the\n// _spawn-stream consumers) \u2014 never applied to structured/code outputs.\n\n/** Unified byte cap for all command tool output fed to the model. */\nexport const COMMAND_OUTPUT_MAX_BYTES = 32_768;\n\n/** Runs of >= this many identical consecutive lines are collapsed. */\nconst REPEAT_RUN_THRESHOLD = 3;\n\n/**\n * Collapse carriage-return overwrites the way a terminal would: `\\r\\n` becomes\n * `\\n`, and a bare `\\r` (progress redraw) keeps only the text after the LAST\n * `\\r` on its physical line. Without this, a single progress bar that redraws\n * 200 times explodes into 200 lines.\n */\nexport function collapseCarriageReturns(text: string): string {\n const lf = text.replace(/\\r\\n/g, '\\n');\n if (!lf.includes('\\r')) return lf;\n return lf\n .split('\\n')\n .map((line) => (line.includes('\\r') ? line.slice(line.lastIndexOf('\\r') + 1) : line))\n .join('\\n');\n}\n\n/**\n * Collapse a run of `minRun`+ identical consecutive lines into the line once\n * plus a marker. Consecutive-only \u2014 it never reorders or dedups non-adjacent\n * lines, so diffs/source stay intact.\n */\nexport function collapseConsecutiveDuplicates(text: string, minRun = REPEAT_RUN_THRESHOLD): string {\n const lines = text.split('\\n');\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n let j = i + 1;\n while (j < lines.length && lines[j] === lines[i]) j++;\n const run = j - i;\n if (run >= minRun) {\n out.push(lines[i]!, `\u2026 \u27E8repeated ${run}\u00D7\u27E9`);\n } else {\n for (let k = i; k < j; k++) out.push(lines[k]!);\n }\n i = j;\n }\n return out.join('\\n');\n}\n\n/** Largest prefix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeHeadBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(0, mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(0, lo);\n}\n\n/** Largest suffix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeTailBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(s.length - mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(s.length - lo);\n}\n\n/**\n * Truncate to `maxBytes` keeping BOTH ends \u2014 the head (what ran / early context)\n * and the tail (errors and summaries usually land last), biased ~45/55 toward\n * the tail. The result never exceeds `maxBytes`.\n */\nexport function truncateHeadTail(s: string, maxBytes: number): string {\n const total = Buffer.byteLength(s, 'utf8');\n if (total <= maxBytes) return s;\n // Reserve a fixed allowance for the marker so the final string can't exceed\n // the cap even though the dropped-byte count's digit width varies.\n const MARKER_RESERVE = 64;\n const avail = Math.max(0, maxBytes - MARKER_RESERVE);\n const headBudget = Math.floor(avail * 0.45);\n const head = takeHeadBytes(s, headBudget);\n const tail = takeTailBytes(s, avail - Buffer.byteLength(head, 'utf8'));\n const kept = Buffer.byteLength(head, 'utf8') + Buffer.byteLength(tail, 'utf8');\n return `${head}\\n\u2026[truncated ${total - kept} bytes]\u2026\\n${tail}`;\n}\n\n/**\n * Full token-saving pipeline for command tool output: strip ANSI \u2192 collapse\n * carriage-return progress \u2192 trim trailing whitespace \u2192 collapse identical\n * consecutive lines \u2192 squeeze blank-line runs \u2192 head+tail truncate to the cap.\n */\nexport function normalizeCommandOutput(\n raw: string,\n opts: { maxBytes?: number | undefined } = {},\n): string {\n if (!raw) return raw;\n let text = Core.stripAnsi(raw);\n text = collapseCarriageReturns(text);\n text = text.replace(/[ \\t]+$/gm, ''); // trailing whitespace per line\n text = collapseConsecutiveDuplicates(text);\n text = text.replace(/\\n{3,}/g, '\\n\\n'); // >=2 blank lines \u2192 1\n return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);\n}\n"],
|
|
5
|
+
"mappings": ";AAAA,SAAS,MAAM,eAAe;AAC9B,YAAYA,WAAU;;;ACAtB,YAAY,SAAS;AACrB,YAAY,UAAU;AACtB,YAAY,UAAU;AAqCf,SAAS,YAAY,OAAe,KAAsB;AAC/D,SAAY,gBAAW,KAAK,IAAS,eAAU,KAAK,IAAS,aAAQ,IAAI,cAAc,IAAI,KAAK,KAAK;AACvG;AAOA,SAAS,aAAa,KAAwB;AAC5C,SAAO,CAAM,aAAQ,IAAI,WAAW,GAAQ,aAAa,sBAAiB,CAAC,CAAC;AAC9E;AAGA,SAAS,YAAY,QAAgB,OAA0B;AAC7D,SAAO,MAAM,KAAK,CAAC,SAAS;AAC1B,UAAM,MAAW,cAAS,MAAM,MAAM;AACtC,WAAO,QAAQ,MAAO,CAAC,IAAI,WAAW,IAAI,KAAK,CAAM,gBAAW,GAAG;AAAA,EACrE,CAAC;AACH;AAEO,SAAS,iBAAiB,SAAiB,KAAsB;AACtE,QAAM,SAAc,aAAQ,OAAO;AAEnC,MAAI,IAAI,wBAAyB,QAAO;AACxC,MAAI,YAAY,QAAQ,aAAa,GAAG,CAAC,EAAG,QAAO;AACnD,QAAM,IAAI,MAAM,SAAS,OAAO,8BAAmC,aAAQ,IAAI,WAAW,CAAC,GAAG;AAChG;AAEO,SAAS,YAAY,OAAe,KAAsB;AAC/D,SAAO,iBAAiB,YAAY,OAAO,GAAG,GAAG,GAAG;AACtD;AAgBA,eAAsB,qBAAqB,SAAiB,KAA6B;AAEvF,MAAI,IAAI,wBAAyB;AAGjC,QAAM,YAAY,MAAM,QAAQ;AAAA,IAC9B,aAAa,GAAG,EAAE,IAAI,CAAC,MAAU,aAAS,CAAC,EAAE,MAAM,MAAW,aAAQ,CAAC,CAAC,CAAC;AAAA,EAC3E;AACA,MAAI,QAAQ;AACZ,aAAS;AACP,QAAI;AACJ,QAAI;AACF,aAAO,MAAU,aAAS,KAAK;AAAA,IACjC,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,UAAU;AACpD,cAAM,SAAc,aAAQ,KAAK;AACjC,YAAI,WAAW,MAAO;AACtB,gBAAQ;AACR;AAAA,MACF;AACA,YAAM;AAAA,IACR;AACA,QAAI,YAAY,MAAM,SAAS,EAAG;AAClC,UAAM,IAAI;AAAA,MACR,SAAS,OAAO,sDAAsD,UAAU,CAAC,CAAC;AAAA,IACpF;AAAA,EACF;AACF;AAGA,eAAsB,gBAAgB,OAAe,KAA+B;AAClF,QAAM,MAAM,YAAY,OAAO,GAAG;AAClC,QAAM,qBAAqB,KAAK,GAAG;AACnC,SAAO;AACT;;;ADlDA,eAAe,gBAAgB,UAAkB,UAA+C;AAC9F,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,KAAK,UAAU,GAAG;AACjC,UAAM,OAAO,MAAM,OAAO,KAAK;AAC/B,QAAI,CAAC,KAAK,OAAO,KAAK,KAAK,OAAO,SAAU,QAAO;AACnD,UAAM,SAAS,OAAO,MAAM,KAAK,IAAI;AACrC,UAAM,EAAE,UAAU,IAAI,MAAM,OAAO,KAAK,QAAQ,GAAG,OAAO,QAAQ,CAAC;AACnE,WAAO,OAAO,SAAS,GAAG,SAAS,EAAE,SAAS,MAAM;AAAA,EACtD,QAAQ;AACN,WAAO;AAAA,EACT,UAAE;AACA,UAAM,QAAQ,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,EAC7C;AACF;AAEA,IAAM,eAAe,oBAAI,IAA0B;AAAA,EACjD,CAAC,wBAAwB,YAAY;AAAA,EACrC,CAAC,wBAAwB,YAAY;AAAA,EACrC,CAAC,yBAAyB,YAAY;AAAA,EACtC,CAAC,yBAAyB,YAAY;AAAA,EACtC,CAAC,yBAAyB,YAAY;AAAA,EACtC,CAAC,yBAAyB,YAAY;AAAA,EACtC,CAAC,qBAAqB,SAAS;AAAA,EAC/B,CAAC,qBAAqB,SAAS;AAAA,EAC/B,CAAC,sBAAsB,SAAS;AAAA,EAChC,CAAC,sBAAsB,SAAS;AAAA,EAChC,CAAC,sBAAsB,SAAS;AAAA,EAChC,CAAC,sBAAsB,SAAS;AAAA,EAChC,CAAC,gBAAgB,SAAS;AAC5B,CAAC;AAED,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,uBAAuB;AAC7B,IAAM,oBAAoB,MAAM;AAChC,IAAM,mBAAmB,MAAM;AAC/B,IAAM,mBAAmB;AAEzB,SAAS,aAAa,MAAc,QAAwB;AAC1D,QAAM,QAAa,eAAS,MAAM,MAAM,KAAK;AAC7C,SAAO,MAAM,MAAW,SAAG,EAAE,KAAK,GAAG;AACvC;AAEA,SAAS,aAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;AAEA,SAAS,aAAa,QAAgB,UAAsC;AAC1E,QAAM,MAAM,aAAa,QAAQ;AACjC,QAAM,SAAS,OAAO;AAAA,IACpB,IAAI,OAAO,WAAW,GAAG,kDAAkD;AAAA,EAC7E;AACA,MAAI,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,SAAS,IAAI,EAAG,QAAO,OAAO,CAAC;AAE7D,QAAM,mBAAmB,OAAO;AAAA,IAC9B,IAAI,OAAO,WAAW,GAAG,+DAA+D;AAAA,EAC1F;AACA,SAAO,mBAAmB,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,SAAS,IAAI,IAC9D,iBAAiB,CAAC,IAClB;AACN;AAEA,SAAS,aAAa,QAAgB,UAAsC;AAC1E,QAAM,MAAM,aAAa,QAAQ;AACjC,QAAM,QAAQ,OAAO,MAAM,IAAI,OAAO,WAAW,GAAG,8BAA8B,CAAC;AACnF,SAAO,QAAQ,CAAC,IAAI,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AACtD;AAEA,SAAS,YAAY,WAAyB,QAAiC;AAC7E,MAAI,cAAc,gBAAgB,kBAAkB,KAAK,MAAM,GAAG;AAChE,UAAM,UAAU,aAAa,QAAQ,SAAS;AAC9C,UAAM,MAAM,aAAa,QAAQ,KAAK;AACtC,UAAM,OAAO,aAAa,QAAQ,MAAM;AACxC,WAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,QAC7B,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;AAAA,QACrB,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,QACvB,QAAQ;AAAA,QACR,YAAY,CAAC,WAAY,CAAC,OAAO,CAAC;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,aAAa,QAAQ,SAAS,KAAK,aAAa,QAAQ,SAAS;AACjF,SAAO,UACH,CAAC,EAAE,MAAM,UAAU,KAAK,SAAS,QAAQ,iBAAiB,YAAY,MAAM,CAAC,IAC7E,CAAC;AACP;AAEA,SAAS,sBAAsB,WAAyB,QAAwB;AAC9E,MAAI,cAAc,cAAc;AAC9B,UAAM,UAAU,aAAa,QAAQ,SAAS,KAAK;AACnD,WAAO,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC;AAAA,EACtC;AACA,SACE,aAAa,QAAQ,aAAa,KAClC;AAEJ;AAEA,eAAe,cACb,MACA,UACA,QACqB;AACrB,QAAM,SAAqB;AAAA,IACzB,SAAS,CAAC;AAAA,IACV,cAAc,CAAC;AAAA,IACf,oBAAoB;AAAA,IACpB,WAAW;AAAA,EACb;AACA,QAAM,QAAqD,CAAC,EAAE,WAAW,MAAM,OAAO,EAAE,CAAC;AAEzF,SAAO,MAAM,SAAS,GAAG;AACvB,WAAO,eAAe;AACtB,UAAM,UAAU,MAAM,MAAM;AAC5B,QAAI,CAAC,QAAS;AACd,WAAO,sBAAsB;AAC7B,QAAI,OAAO,qBAAqB,sBAAsB;AACpD,aAAO,YAAY;AACnB;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,QAAQ,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;AAAA,IACpE,QAAQ;AACN;AAAA,IACF;AAEA,eAAW,SAAS,SAAS;AAC3B,aAAO,eAAe;AACtB,YAAM,eAAoB,WAAK,QAAQ,WAAW,MAAM,IAAI;AAC5D,UAAI,MAAM,OAAO,GAAG;AAClB,YAAI,MAAM,SAAS,eAAgB,QAAO,aAAa,KAAK,YAAY;AACxE,cAAM,YAAY,aAAa,IAAI,MAAM,IAAI;AAC7C,YAAI,UAAW,QAAO,QAAQ,KAAK,EAAE,WAAW,aAAa,CAAC;AAAA,MAChE,WACE,MAAM,YAAY,KAClB,QAAQ,QAAQ,YAChB,CAAC,iBAAiB,IAAI,MAAM,IAAI,KAChC,CAAC,MAAM,KAAK,WAAW,WAAW,KAClC,CAAC,MAAM,KAAK,WAAW,MAAM,GAC7B;AACA,cAAM,KAAK,EAAE,WAAW,cAAc,OAAO,QAAQ,QAAQ,EAAE,CAAC;AAAA,MAClE;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,gBAAgB,aAAuD;AACpF,MAAI;AACF,UAAM,MAAM,MAAM,gBAAgB,aAAa,iBAAiB;AAChE,QAAI,QAAQ,OAAW,QAAO;AAC9B,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAM,UACJ,OAAO,WAAW,OAAO,OAAO,YAAY,WACxC,OAAO;AAAA,MACL,OAAO,QAAQ,OAAO,OAAkC,EAAE;AAAA,QACxD,CAAC,UAAqC,OAAO,MAAM,CAAC,MAAM;AAAA,MAC5D;AAAA,IACF,IACA,CAAC;AACP,UAAM,eAAe,oBAAI,IAAY;AACrC,eAAW,SAAS,CAAC,gBAAgB,mBAAmB,sBAAsB,GAAG;AAC/E,YAAM,QAAQ,OAAO,KAAK;AAC1B,UAAI,CAAC,SAAS,OAAO,UAAU,SAAU;AACzC,iBAAW,QAAQ,OAAO,KAAK,KAAgC,EAAG,cAAa,IAAI,IAAI;AAAA,IACzF;AACA,UAAM,WACJ,OAAO,OAAO,mBAAmB,WAAY,OAAO,eAAe,MAAM,GAAG,EAAE,CAAC,KAAK,KAAM;AAC5F,UAAM,iBAAiB,CAAC,QAAQ,OAAO,QAAQ,KAAK,EAAE,SAAS,QAAQ,IAClE,WACD;AACJ,WAAO,EAAE,MAAM,aAAa,SAAS,cAAc,eAAe;AAAA,EACpE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,qBAAqB,MAAmC;AAC/D,QAAM,QAAQ,oBAAI,IAAkB;AACpC,MAAI,KAAK,aAAa,IAAI,kBAAkB,KAAK,KAAK,aAAa,IAAI,YAAY,GAAG;AACpF,UAAM,IAAI,YAAY;AAAA,EACxB;AACA,MAAI,KAAK,aAAa,IAAI,SAAS,EAAG,OAAM,IAAI,SAAS;AACzD,aAAW,WAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACjD,QAAI,4BAA4B,KAAK,OAAO,EAAG,OAAM,IAAI,YAAY;AACrE,QAAI,yBAAyB,KAAK,OAAO,EAAG,OAAM,IAAI,SAAS;AAAA,EACjE;AACA,SAAO,CAAC,GAAG,KAAK;AAClB;AAEA,eAAe,qBACb,aACA,UACA,UAC4B;AAC5B,MAAI,SAAU,QAAO;AACrB,MAAI,YAAY;AAChB,SAAO,MAAM;AACX,UAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAI;AACF,iBAAW,SAAS,MAAM,QAAQ,SAAS,EAAG,OAAM,IAAI,KAAK;AAAA,IAC/D,QAAQ;AAAA,IAER;AACA,QAAI,MAAM,IAAI,gBAAgB,EAAG,QAAO;AACxC,QAAI,MAAM,IAAI,WAAW,EAAG,QAAO;AACnC,QAAI,MAAM,IAAI,UAAU,KAAK,MAAM,IAAI,WAAW,EAAG,QAAO;AAC5D,QAAI,MAAM,IAAI,mBAAmB,KAAK,MAAM,IAAI,qBAAqB,EAAG,QAAO;AAC/E,QAAI,cAAc,SAAU;AAC5B,UAAM,SAAc,cAAQ,SAAS;AACrC,UAAM,iBAAsB,eAAS,UAAU,MAAM;AACrD,QACE,WAAW,aACX,eAAe,WAAW,IAAI,KACzB,iBAAW,cAAc,GAC9B;AACA;AAAA,IACF;AACA,gBAAY;AAAA,EACd;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,MAA+B,WAAmC;AACzF,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,QAAM,UACJ,cAAc,eAAe,8BAA8B;AAC7D,SAAO,OAAO,QAAQ,KAAK,OAAO,EAC/B,OAAO,CAAC,CAAC,EAAE,OAAO,MAAM,QAAQ,KAAK,OAAO,CAAC,EAC7C,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI,EACpB,KAAK,CAAC,GAAG,MAAM;AACd,UAAM,YAAY,CAAC,YAAY,OAAO,UAAU;AAChD,UAAM,KAAK,UAAU,QAAQ,CAAC;AAC9B,UAAM,KAAK,UAAU,QAAQ,CAAC;AAC9B,QAAI,OAAO,MAAM,OAAO,GAAI,SAAQ,OAAO,KAAK,KAAK,OAAO,OAAO,KAAK,KAAK;AAC7E,WAAO,EAAE,cAAc,CAAC;AAAA,EAC1B,CAAC;AACL;AAEA,SAAS,cACP,WACA,SACA,MACA,YACA,SACkB;AAClB,QAAM,SAAS,QAAQ,CAAC;AACxB,MAAI,QAAQ;AACV,WAAO;AAAA,MACL,SAAS;AAAA,MACT,MAAM,CAAC,OAAO,MAAM;AAAA,MACpB,KAAK;AAAA,MACL,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,cAAc,eAAe,CAAC,cAAc,MAAM,IAAI,CAAC,WAAW,KAAK;AAC1F,MAAI,YAAY;AACd,eAAW,KAAK,cAAc,eAAe,aAAa,iBAAiB,UAAU;AAAA,EACvF;AACA,SAAO;AAAA,IACL,SAAS,YAAY,QAAQ,SAAS;AAAA,IACtC,MACE,YAAY,QACR,aACA,YAAY,QACV,CAAC,QAAQ,MAAM,GAAG,UAAU,IAC5B,CAAC,QAAQ,GAAG,UAAU;AAAA,IAC9B,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AACF;AAEA,SAAS,OAAO,WAAyB,UAA2B;AAClE,MAAI,cAAc,UAAW,QAAO,2BAA2B,KAAK,QAAQ;AAC5E,SAAO,sCAAsC,KAAK,QAAQ;AAC5D;AAEA,eAAe,aACb,MACA,WACA,eACA,QACmE;AACnE,QAAM,QAAQ,gBACV,CAAM,cAAQ,MAAM,aAAa,CAAC,IAClC,cAAc,YACZ,CAAM,WAAK,MAAM,WAAW,KAAK,GAAQ,WAAK,MAAM,WAAW,aAAa,CAAC,IAC7E,CAAM,WAAK,MAAM,OAAO,CAAC;AAC/B,QAAM,UAAoB,CAAC;AAC3B,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,QAAM,QAAQ,CAAC,GAAG,KAAK;AAEvB,SAAO,MAAM,SAAS,GAAG;AACvB,WAAO,eAAe;AACtB,UAAM,YAAY,MAAM,MAAM;AAC9B,QAAI,CAAC,UAAW;AAChB,eAAW;AACX,QAAI,UAAU,qBAAsB,QAAO,EAAE,OAAO,SAAS,WAAW,KAAK;AAC7E,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;AAAA,IAC5D,QAAQ;AACN;AAAA,IACF;AACA,eAAW,SAAS,SAAS;AAC3B,aAAO,eAAe;AACtB,YAAM,SAAc,WAAK,WAAW,MAAM,IAAI;AAC9C,UAAI,MAAM,YAAY,KAAK,CAAC,iBAAiB,IAAI,MAAM,IAAI,EAAG,OAAM,KAAK,MAAM;AAAA,eACtE,MAAM,OAAO,KAAK,OAAO,WAAW,MAAM,IAAI,GAAG;AACxD,iBAAS;AACT,YAAI,QAAQ,SAAS,iBAAkB,SAAQ,KAAK,aAAa,MAAM,MAAM,CAAC;AAAA,MAChF;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,OAAO,SAAS,WAAW,QAAQ,QAAQ,OAAO;AAC7D;AAEA,eAAe,WAAW,YAAiD;AACzE,MAAI,CAAC,WAAY,QAAO;AACxB,SAAQ,MAAM,gBAAgB,YAAY,gBAAgB,KAAM;AAClE;AAEA,SAAS,eACP,aACA,qBACA,UACyB;AACzB,MAAI,YAAY;AAChB,SAAO,MAAM;AACX,UAAM,QAAQ,oBAAoB,IAAI,SAAS;AAC/C,QAAI,MAAO,QAAO;AAClB,QAAI,cAAc,SAAU,QAAO;AACnC,UAAM,SAAc,cAAQ,SAAS;AACrC,QAAI,WAAW,aAAa,aAAa,UAAU,MAAM,EAAE,WAAW,IAAI,EAAG,QAAO;AACpF,gBAAY;AAAA,EACd;AACF;AAEA,eAAsB,oBACpB,MACA,SAMwB;AACxB,QAAM,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,YAAY,GAAG,CAAC,CAAC;AAC/D,QAAM,OAAO,MAAM,cAAc,MAAM,UAAU,QAAQ,MAAM;AAC/D,QAAM,YAAY,MAAM,QAAQ,IAAI,KAAK,aAAa,IAAI,eAAe,CAAC,GAAG;AAAA,IAC3E,CAAC,SAA8B,QAAQ,IAAI;AAAA,EAC7C;AACA,QAAM,sBAAsB,IAAI,IAAI,SAAS,IAAI,CAAC,SAAS,CAAM,cAAQ,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;AAC3F,QAAM,aAAa,oBAAI,IAGrB;AAEF,aAAW,UAAU,KAAK,SAAS;AACjC,QACE,QAAQ,aACR,QAAQ,cAAc,SACtB,OAAO,cAAc,QAAQ,WAC7B;AACA;AAAA,IACF;AACA,UAAM,cAAmB,cAAQ,OAAO,YAAY;AACpD,eAAW,IAAI,GAAG,OAAO,SAAS,IAAI,WAAW,IAAI;AAAA,MACnD,WAAW,OAAO;AAAA,MAClB,MAAM;AAAA,MACN,YAAY,OAAO;AAAA,IACrB,CAAC;AAAA,EACH;AAEA,aAAW,QAAQ,UAAU;AAC3B,UAAM,cAAmB,cAAQ,KAAK,IAAI;AAC1C,eAAW,aAAa,qBAAqB,IAAI,GAAG;AAClD,UAAI,QAAQ,aAAa,QAAQ,cAAc,SAAS,cAAc,QAAQ;AAC5E;AACF,YAAM,MAAM,GAAG,SAAS,IAAI,WAAW;AACvC,UAAI,CAAC,WAAW,IAAI,GAAG,EAAG,YAAW,IAAI,KAAK,EAAE,WAAW,MAAM,YAAY,CAAC;AAAA,IAChF;AAAA,EACF;AAEA,QAAM,WAA6B,CAAC;AACpC,aAAW,aAAa,WAAW,OAAO,GAAG;AAC3C,YAAQ,OAAO,eAAe;AAC9B,UAAM,OAAO,eAAe,UAAU,MAAM,qBAAqB,IAAI;AACrE,UAAM,SAAS,MAAM,WAAW,UAAU,UAAU;AACpD,UAAM,UAAU,gBAAgB,MAAM,UAAU,SAAS;AACzD,UAAM,UAAU,MAAM,qBAAqB,UAAU,MAAM,MAAM,MAAM,cAAc;AACrF,UAAM,gBACJ,UAAU,cAAc,eAAe,aAAa,QAAQ,SAAS,IAAI;AAC3E,UAAM,wBAAwB,gBACrB,cAAQ,UAAU,MAAM,aAAa,IAC1C;AACJ,UAAM,wBAAwB,wBACrB,eAAS,MAAM,qBAAqB,IACzC;AACJ,UAAM,sBAAsB;AAAA,MAC1B,0BACG,sBAAsB,WAAW,IAAI,KAAU,iBAAW,qBAAqB;AAAA,IACpF;AACA,UAAM,QACJ,QAAQ,iBAAiB,SAAS,sBAC9B,EAAE,OAAO,GAAG,SAAS,CAAC,GAAG,WAAW,MAAM,IAC1C,MAAM,aAAa,UAAU,MAAM,UAAU,WAAW,eAAe,QAAQ,MAAM;AAC3F,UAAM,WAAqB,CAAC;AAC5B,QAAI,CAAC,UAAU;AACb,eAAS,KAAK,6DAA6D;AAC7E,QAAI,UAAU,cAAc,CAAC,QAAQ;AACnC,eAAS,KAAK,mEAAmE;AAAA,IACnF;AACA,QAAI,qBAAqB;AACvB,eAAS;AAAA,QACP;AAAA,MACF;AAAA,IACF;AACA,UAAM,QAAQ,YAAY,UAAU,WAAW,MAAM;AACrD,QAAI,MAAM,KAAK,CAAC,SAAS,KAAK,UAAU,GAAG;AACzC,eAAS,KAAK,yEAAyE;AAAA,IACzF;AAEA,aAAS,KAAK;AAAA,MACZ,WAAW,UAAU;AAAA,MACrB,MAAM,aAAa,MAAM,UAAU,IAAI;AAAA,MACvC,GAAI,UAAU,aAAa,EAAE,YAAY,aAAa,MAAM,UAAU,UAAU,EAAE,IAAI,CAAC;AAAA,MACvF,GAAI,OAAO,EAAE,aAAa,aAAa,MAAM,KAAK,IAAI,EAAE,IAAI,CAAC;AAAA,MAC7D,gBAAgB;AAAA,MAChB,UAAU;AAAA,QACR,GAAI,UAAU,aAAa,CAAC,UAAU,aAAa,MAAM,UAAU,UAAU,CAAC,EAAE,IAAI,CAAC;AAAA,QACrF,GAAI,MAAM,aAAa;AAAA,UACrB,UAAU,cAAc,eAAe,qBAAqB;AAAA,QAC9D,IACI,CAAC,cAAc,UAAU,cAAc,eAAe,qBAAqB,SAAS,EAAE,IACtF,CAAC;AAAA,QACL,GAAG,QAAQ,IAAI,CAAC,WAAW,UAAU,MAAM,EAAE;AAAA,MAC/C;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb,aAAa,sBAAsB,UAAU,WAAW,MAAM;AAAA,MAC9D,WAAW,MAAM;AAAA,MACjB,aAAa,MAAM;AAAA,MACnB,gBAAgB,MAAM;AAAA,MACtB,WAAW;AAAA,QACT,UAAU;AAAA,QACV;AAAA,QACA,aAAa,MAAM,UAAU,IAAI;AAAA,QACjC,UAAU,aAAa,aAAa,UAAU,MAAM,UAAU,UAAU,IAAI;AAAA,QAC5E;AAAA,MACF;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,KAAK,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAC9F,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,oBAAoB,KAAK;AAAA,IACzB,eAAe,KAAK;AAAA,IACpB,UAAU,KAAK,YACX,CAAC,2BAA2B,oBAAoB,uCAAuC,IACvF,CAAC;AAAA,EACP;AACF;AAEO,IAAM,cAAiD;AAAA,EAC5D,MAAM;AAAA,EACN,UAAU;AAAA,EACV,aACE;AAAA,EACF,WACE;AAAA,EAEF,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,UAAU;AAAA,EACV,cAAc,CAAC,SAAS;AAAA,EACxB,MAAM;AAAA,EACN,WAAW;AAAA,EACX,aAAa;AAAA,IACX,MAAM;AAAA,IACN,YAAY;AAAA,MACV,KAAK,EAAE,MAAM,UAAU,aAAa,2CAA2C;AAAA,MAC/E,WAAW;AAAA,QACT,MAAM;AAAA,QACN,MAAM,CAAC,OAAO,cAAc,SAAS;AAAA,QACrC,aAAa;AAAA,MACf;AAAA,MACA,UAAU;AAAA,QACR,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,QACT,aAAa;AAAA,MACf;AAAA,MACA,cAAc;AAAA,QACZ,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,QAAQ,OAAO,KAAK,MAAM;AAC9B,UAAM,OAAO,MAAM,gBAAgB,MAAM,OAAO,KAAK,GAAG;AACxD,WAAO,oBAAoB,MAAM;AAAA,MAC/B,WAAW,MAAM;AAAA,MACjB,UAAU,MAAM;AAAA,MAChB,cAAc,MAAM;AAAA,MACpB,QAAQ,KAAK;AAAA,IACf,CAAC;AAAA,EACH;AACF;",
|
|
6
|
+
"names": ["path"]
|
|
7
|
+
}
|
package/dist/exec.d.ts
CHANGED
package/dist/exec.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../src/exec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../src/exec.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,IAAI,EACV,MAAM,kBAAkB,CAAC;AAU1B,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AA8J1E;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;CAAO,GAAG,IAAI,CAQpI;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAkBD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;CAAO,GAAG,IAAI,CAOjG;AAED,8DAA8D;AAC9D,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,WAAW,CAAC,MAAM,CAAC,CAErD;AAED,kEAAkE;AAClE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAE3C;AA4GD,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B;AAED,UAAU,UAAU;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;;;;;OASG;IACH,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAID,eAAO,MAAM,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,CA0IhD,CAAC"}
|
package/dist/exec.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// src/exec.ts
|
|
2
2
|
import { spawn as spawn2 } from "node:child_process";
|
|
3
|
+
import {
|
|
4
|
+
emitProcessCompleted,
|
|
5
|
+
emitProcessOutput,
|
|
6
|
+
emitProcessStarted
|
|
7
|
+
} from "@wrongstack/core";
|
|
3
8
|
import { toErrorMessage } from "@wrongstack/core/utils/error";
|
|
4
9
|
|
|
5
10
|
// src/_env.ts
|
|
@@ -439,9 +444,14 @@ import { expectDefined } from "@wrongstack/core";
|
|
|
439
444
|
var SENSITIVE_FLAG_PATTERNS = [
|
|
440
445
|
// --flag=value or --flag "value" (value captured up to next space or comma)
|
|
441
446
|
/--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
|
|
442
|
-
// -
|
|
443
|
-
|
|
444
|
-
/
|
|
447
|
+
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
448
|
+
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
449
|
+
// NOTE: synced with @wrongstack/core observability/redact-command.ts.
|
|
450
|
+
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]+/,
|
|
451
|
+
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
452
|
+
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
453
|
+
// redaction function. Synced with core copy.
|
|
454
|
+
/(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
|
|
445
455
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
446
456
|
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
447
457
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
@@ -459,8 +469,10 @@ function redactCommand(cmd) {
|
|
|
459
469
|
const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);
|
|
460
470
|
return `${flag}[REDACTED]`;
|
|
461
471
|
}
|
|
462
|
-
|
|
463
|
-
|
|
472
|
+
if (match.startsWith("--")) {
|
|
473
|
+
return match;
|
|
474
|
+
}
|
|
475
|
+
return `${match.slice(0, 2)}[REDACTED]`;
|
|
464
476
|
});
|
|
465
477
|
}
|
|
466
478
|
return result;
|
|
@@ -2002,12 +2014,30 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
2002
2014
|
resolve2(result);
|
|
2003
2015
|
};
|
|
2004
2016
|
const startedAt = Date.now();
|
|
2017
|
+
let stdoutBytes = 0;
|
|
2018
|
+
let stderrBytes = 0;
|
|
2019
|
+
let telemetryCompleted = false;
|
|
2020
|
+
let timedOut = false;
|
|
2005
2021
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT });
|
|
2006
2022
|
const resolved = resolveWin32Command(cmd);
|
|
2007
2023
|
const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
2008
2024
|
const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
|
|
2009
2025
|
const spawnCmd = shim?.command ?? resolved;
|
|
2010
2026
|
const spawnArgs = shim?.args ?? args;
|
|
2027
|
+
const emitCompletedOnce = (exitCode, pid2, signal2) => {
|
|
2028
|
+
if (telemetryCompleted) return;
|
|
2029
|
+
telemetryCompleted = true;
|
|
2030
|
+
emitProcessCompleted({
|
|
2031
|
+
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
2032
|
+
exitCode,
|
|
2033
|
+
...signal2 ? { signal: signal2 } : {},
|
|
2034
|
+
durationMs: Date.now() - startedAt,
|
|
2035
|
+
stdoutBytes,
|
|
2036
|
+
stderrBytes,
|
|
2037
|
+
timedOut,
|
|
2038
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2039
|
+
});
|
|
2040
|
+
};
|
|
2011
2041
|
let child;
|
|
2012
2042
|
try {
|
|
2013
2043
|
child = spawn2(spawnCmd, spawnArgs, {
|
|
@@ -2020,6 +2050,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
2020
2050
|
});
|
|
2021
2051
|
} catch (err) {
|
|
2022
2052
|
spool.finalize();
|
|
2053
|
+
emitProcessStarted({
|
|
2054
|
+
parentPid: process.pid,
|
|
2055
|
+
command: redactCommand(`${cmd} ${args.join(" ")}`),
|
|
2056
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
2057
|
+
cwd,
|
|
2058
|
+
background: false,
|
|
2059
|
+
startedAt: new Date(startedAt).toISOString()
|
|
2060
|
+
});
|
|
2061
|
+
emitCompletedOnce(1, void 0);
|
|
2023
2062
|
finish({
|
|
2024
2063
|
command: cmd,
|
|
2025
2064
|
args,
|
|
@@ -2032,6 +2071,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
2032
2071
|
});
|
|
2033
2072
|
return;
|
|
2034
2073
|
}
|
|
2074
|
+
emitProcessStarted({
|
|
2075
|
+
...child.pid !== void 0 ? { pid: child.pid } : {},
|
|
2076
|
+
parentPid: process.pid,
|
|
2077
|
+
command: redactCommand(`${cmd} ${args.join(" ")}`),
|
|
2078
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
2079
|
+
cwd,
|
|
2080
|
+
background: false,
|
|
2081
|
+
startedAt: new Date(startedAt).toISOString()
|
|
2082
|
+
});
|
|
2035
2083
|
child.on("error", (err) => {
|
|
2036
2084
|
const isAbort = err && err.code === "ABORT_ERR";
|
|
2037
2085
|
const stderrText = isAbort ? `Aborted: ${err.message}` : err.message;
|
|
@@ -2040,6 +2088,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
2040
2088
|
if (typeof pid === "number") registry.unregister(pid);
|
|
2041
2089
|
registry.afterCall(Date.now() - startedAt, true);
|
|
2042
2090
|
spool.finalize();
|
|
2091
|
+
emitCompletedOnce(isAbort ? 124 : 1, child.pid, isAbort ? "ABORT" : void 0);
|
|
2043
2092
|
finish({
|
|
2044
2093
|
command: cmd,
|
|
2045
2094
|
args,
|
|
@@ -2059,6 +2108,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
2059
2108
|
}
|
|
2060
2109
|
const timer = setTimeout(() => {
|
|
2061
2110
|
killed = true;
|
|
2111
|
+
timedOut = true;
|
|
2062
2112
|
if (typeof pid === "number") registry.kill(pid);
|
|
2063
2113
|
else child.kill("SIGTERM");
|
|
2064
2114
|
}, timeout);
|
|
@@ -2073,11 +2123,15 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
2073
2123
|
}
|
|
2074
2124
|
child.stdout?.on("data", (chunk) => {
|
|
2075
2125
|
const text = chunk.toString();
|
|
2126
|
+
stdoutBytes += chunk.byteLength;
|
|
2127
|
+
emitProcessOutput({ pid, stream: "stdout", chunk });
|
|
2076
2128
|
if (stdout.length < MAX_OUTPUT) stdout += text;
|
|
2077
2129
|
spool.write(text);
|
|
2078
2130
|
});
|
|
2079
2131
|
child.stderr?.on("data", (chunk) => {
|
|
2080
2132
|
const text = chunk.toString();
|
|
2133
|
+
stderrBytes += chunk.byteLength;
|
|
2134
|
+
emitProcessOutput({ pid, stream: "stderr", chunk });
|
|
2081
2135
|
if (stderr.length < MAX_OUTPUT) stderr += text;
|
|
2082
2136
|
spool.write(text);
|
|
2083
2137
|
});
|
|
@@ -2087,6 +2141,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
2087
2141
|
if (typeof pid === "number") registry.unregister(pid);
|
|
2088
2142
|
const durationMs = Date.now() - startedAt;
|
|
2089
2143
|
const exitCode = killed ? 124 : code ?? 1;
|
|
2144
|
+
emitCompletedOnce(exitCode, pid);
|
|
2090
2145
|
registry.afterCall(durationMs, exitCode !== 0);
|
|
2091
2146
|
const spooled = spool.finalize();
|
|
2092
2147
|
finish({
|