@wrongstack/tools 0.8.4 → 0.8.6
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/builtin.js +166 -64
- package/dist/builtin.js.map +1 -1
- package/dist/codebase-index/index.js +14 -6
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/exec.js +14 -2
- package/dist/exec.js.map +1 -1
- package/dist/fetch.js +48 -3
- package/dist/fetch.js.map +1 -1
- package/dist/git.js +38 -13
- package/dist/git.js.map +1 -1
- package/dist/index.js +166 -64
- package/dist/index.js.map +1 -1
- package/dist/logs.js +22 -11
- package/dist/logs.js.map +1 -1
- package/dist/pack.js +166 -64
- package/dist/pack.js.map +1 -1
- package/dist/replace.js +2 -1
- package/dist/replace.js.map +1 -1
- package/package.json +3 -2
package/dist/logs.js
CHANGED
|
@@ -145,17 +145,35 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
145
145
|
let stdout = "";
|
|
146
146
|
let stderr = "";
|
|
147
147
|
const MAX = 2e5;
|
|
148
|
+
let settled = false;
|
|
149
|
+
const empty = () => ({
|
|
150
|
+
source: `docker:${service}`,
|
|
151
|
+
entries: [],
|
|
152
|
+
total: 0,
|
|
153
|
+
truncated: false,
|
|
154
|
+
stream_mode: false
|
|
155
|
+
});
|
|
156
|
+
const finish = (result) => {
|
|
157
|
+
if (settled) return;
|
|
158
|
+
settled = true;
|
|
159
|
+
clearTimeout(timer);
|
|
160
|
+
resolve2(result);
|
|
161
|
+
};
|
|
148
162
|
const child = spawn("docker", args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"] });
|
|
163
|
+
const timer = setTimeout(() => {
|
|
164
|
+
child.kill("SIGTERM");
|
|
165
|
+
finish(empty());
|
|
166
|
+
}, DOCKER_LOGS_TIMEOUT_MS);
|
|
149
167
|
child.stdout?.on("data", (c) => {
|
|
150
168
|
if (stdout.length < MAX) stdout += c.toString();
|
|
151
169
|
});
|
|
152
170
|
child.stderr?.on("data", (c) => {
|
|
153
171
|
if (stderr.length < MAX) stderr += c.toString();
|
|
154
172
|
});
|
|
155
|
-
child.on("close", (
|
|
173
|
+
child.on("close", () => {
|
|
156
174
|
const output = stdout + stderr;
|
|
157
175
|
const entries = parseLogLines(output, filterRe);
|
|
158
|
-
|
|
176
|
+
finish({
|
|
159
177
|
source: `docker:${service}`,
|
|
160
178
|
entries,
|
|
161
179
|
total: entries.length,
|
|
@@ -163,17 +181,10 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
163
181
|
stream_mode: false
|
|
164
182
|
});
|
|
165
183
|
});
|
|
166
|
-
child.on("error", (
|
|
167
|
-
resolve2({
|
|
168
|
-
source: `docker:${service}`,
|
|
169
|
-
entries: [],
|
|
170
|
-
total: 0,
|
|
171
|
-
truncated: false,
|
|
172
|
-
stream_mode: false
|
|
173
|
-
});
|
|
174
|
-
});
|
|
184
|
+
child.on("error", () => finish(empty()));
|
|
175
185
|
});
|
|
176
186
|
}
|
|
187
|
+
var DOCKER_LOGS_TIMEOUT_MS = 3e3;
|
|
177
188
|
var MAX_TAIL_LINES = 1e5;
|
|
178
189
|
async function fileLogs(path2, lines, filterRe, stream) {
|
|
179
190
|
const { createInterface } = await import('node:readline');
|
package/dist/logs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/_regex.ts","../src/_util.ts","../src/logs.ts"],"names":["resolve","path"],"mappings":"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","file":"logs.js","sourcesContent":["/**\n * Compile a user-supplied regex with conservative bounds against ReDoS.\n *\n * Node's regex engine (V8) is backtracking-based and cannot interrupt a\n * synchronous match — a pattern like `(a+)+$` against a sufficiently long\n * line will pin a worker for seconds. The executor's outer `timeoutMs` only\n * fires between async boundaries, so a long regex eval inside a sync loop\n * is uninterruptible.\n *\n * We can't fully prevent ReDoS without an alternative engine (re2-wasm), but\n * we can sharply limit the blast radius:\n *\n * 1. Cap pattern length — practically all legitimate user patterns are\n * under 256 characters. A 4 KB pattern is almost certainly malicious\n * or a copy-paste accident.\n * 2. Reject patterns containing the most obvious super-linear structures.\n * This is a coarse filter (false-positives are likely; we accept that\n * for hostile-input contexts).\n *\n * Callers should additionally bound the *subject* length (e.g. by capping\n * line size before matching).\n */\n\nconst MAX_PATTERN_LEN = 256;\n\n// Heuristics for catastrophic-backtracking constructs. Not exhaustive; bias\n// toward false-positives in tools that accept LLM-generated input.\nconst DANGEROUS_PATTERNS: ReadonlyArray<RegExp> = [\n // (a+)+, (.*)+, etc — nested quantifier on a group with internal quantifier\n /(\\([^)]*[+*][^)]*\\))[+*]/,\n /(\\(\\?:[^)]*[+*][^)]*\\))[+*]/,\n // Adjacent quantifiers: a++ a*+\n /[+*]{2,}/,\n // Quantifier on alternation with length 2+\n /\\([^|)]+\\|[^)]+\\)[+*][+*]/,\n // Greedy quantifier inside lookahead/lookbehind — (?!.*a+)\n /[\\(\\[][^)\\]]*[+*][^)\\]]*[\\)\\]][^)]*\\?\\??/,\n];\n\nexport interface CompileResult {\n ok: true;\n regex: RegExp;\n}\n\nexport interface CompileFail {\n ok: false;\n reason: string;\n}\n\nexport function compileUserRegex(pattern: string, flags: string): CompileResult | CompileFail {\n if (typeof pattern !== 'string') {\n return { ok: false, reason: 'pattern must be a string' };\n }\n if (pattern.length === 0) {\n return { ok: false, reason: 'pattern is empty' };\n }\n if (pattern.length > MAX_PATTERN_LEN) {\n return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };\n }\n for (const rx of DANGEROUS_PATTERNS) {\n if (rx.test(pattern)) {\n return {\n ok: false,\n reason:\n 'pattern looks vulnerable to catastrophic backtracking — rewrite without nested quantifiers',\n };\n }\n }\n try {\n return { ok: true, regex: new RegExp(pattern, flags) };\n } catch (err) {\n return {\n ok: false,\n reason: err instanceof Error ? err.message : 'invalid regex',\n };\n }\n}\n\n/**\n * Truncate a subject line to a safe length for synchronous regex eval.\n * The cap is conservative; tools that need exact-line matching against very\n * long lines should use ripgrep externally rather than the native walker.\n */\nexport const MAX_SUBJECT_LEN = 64 * 1024;\n\nexport function capSubject(line: string): string {\n return line.length > MAX_SUBJECT_LEN ? line.slice(0, MAX_SUBJECT_LEN) : line;\n}\n","import * as path from 'node:path';\nimport type { Context } from '@wrongstack/core';\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const root = path.resolve(ctx.projectRoot);\n const target = path.resolve(absPath);\n const rel = path.relative(root, target);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\n }\n return target;\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\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…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\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","import { spawn } from 'node:child_process';\r\nimport { buildChildEnv } from '@wrongstack/core';\r\nimport type { Tool } from '@wrongstack/core';\r\nimport { compileUserRegex } from './_regex.js';\r\nimport { safeResolve } from './_util.js';\r\n\r\ninterface LogsInput {\r\n service?: string;\r\n path?: string;\r\n lines?: number;\r\n stream?: boolean;\r\n filter?: string;\r\n since?: '1h' | '6h' | '24h' | 'all';\r\n cwd?: string;\r\n}\r\n\r\ninterface LogEntry {\r\n timestamp: string;\r\n level: string;\r\n message: string;\r\n source?: string;\r\n}\r\n\r\ninterface LogsOutput {\r\n source: string;\r\n entries: LogEntry[];\r\n total: number;\r\n truncated: boolean;\r\n stream_mode: boolean;\r\n}\r\n\r\nexport const logsTool: Tool<LogsInput, LogsOutput> = {\r\n name: 'logs',\r\n category: 'Logs',\r\n description:\r\n 'Stream or fetch logs from a service or file. Supports Docker, systemd, or plain log files.',\r\n usageHint:\r\n 'Set `service` for Docker/systemd, `path` for file. `lines` limits output. `stream` for tail -f behavior. `filter` regex filters lines.',\r\n permission: 'confirm',\r\n mutating: false,\r\n timeoutMs: 30_000,\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n service: {\r\n type: 'string',\r\n description: 'Service name for Docker or systemd journal',\r\n },\r\n path: {\r\n type: 'string',\r\n description: 'Path to log file (alternative to service)',\r\n },\r\n lines: {\r\n type: 'integer',\r\n description: 'Number of log lines to fetch (default: 100, 0 for all)',\r\n minimum: 0,\r\n maximum: 10000,\r\n },\r\n stream: {\r\n type: 'boolean',\r\n description: 'Stream logs continuously (like tail -f) (default: false)',\r\n },\r\n filter: {\r\n type: 'string',\r\n description: 'Regex pattern to filter log lines',\r\n },\r\n since: {\r\n type: 'string',\r\n enum: ['1h', '6h', '24h', 'all'],\r\n description: 'Only show logs since duration',\r\n },\r\n cwd: { type: 'string', description: 'Working directory (default: cwd)' },\r\n },\r\n },\r\n async execute(input, ctx, opts) {\r\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\r\n const lines = input.lines ?? 100;\r\n let filterRe: RegExp | null = null;\r\n if (input.filter) {\r\n const compiled = compileUserRegex(input.filter, 'i');\r\n if (!compiled.ok) {\r\n throw new Error(`logs: ${compiled.reason}`);\r\n }\r\n filterRe = compiled.regex;\r\n }\r\n\r\n if (input.service) {\r\n return await dockerLogs(input.service, lines, filterRe, cwd, opts.signal);\r\n }\r\n\r\n if (input.path) {\r\n return await fileLogs(safeResolve(input.path, ctx), lines, filterRe, input.stream ?? false);\r\n }\r\n\r\n return {\r\n source: 'none',\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n };\r\n },\r\n};\r\n\r\nasync function dockerLogs(\r\n service: string,\r\n lines: number,\r\n filterRe: RegExp | null,\r\n cwd: string,\r\n signal: AbortSignal,\r\n since?: string,\r\n): Promise<LogsOutput> {\r\n const args = ['logs'];\r\n if (lines > 0) args.push('--tail', String(lines));\r\n if (since) {\r\n const sinceMap: Record<string, string> = { '1h': '1h', '6h': '6h', '24h': '24h' };\r\n args.push('--since', sinceMap[since] ?? '1h');\r\n }\r\n // Validate service name to prevent container name injection.\r\n // Docker container names are limited to [a-zA-Z0-9][a-zA-Z0-9._-]+.\r\n if (!/^[a-zA-Z0-9][a-zA-Z0-9._:-]+$/.test(service)) {\r\n return {\r\n source: `docker:${service}`,\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n };\r\n }\r\n args.push('--timestamps', service);\r\n\r\n return new Promise((resolve) => {\r\n let stdout = '';\r\n let stderr = '';\r\n const MAX = 200_000;\r\n\r\n const child = spawn('docker', args, { cwd, signal, env: buildChildEnv(), stdio: ['ignore', 'pipe', 'pipe'] });\r\n child.stdout?.on('data', (c) => {\r\n if (stdout.length < MAX) stdout += c.toString();\r\n });\r\n child.stderr?.on('data', (c) => {\r\n if (stderr.length < MAX) stderr += c.toString();\r\n });\r\n child.on('close', (code) => {\r\n const output = stdout + stderr;\r\n const entries = parseLogLines(output, filterRe);\r\n resolve({\r\n source: `docker:${service}`,\r\n entries,\r\n total: entries.length,\r\n truncated: output.length >= MAX,\r\n stream_mode: false,\r\n });\r\n });\r\n child.on('error', (e) => {\r\n resolve({\r\n source: `docker:${service}`,\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n });\r\n });\r\n });\r\n}\r\n\r\n// Hard cap on tail-window size — `lines: 0` historically meant \"all\" and\r\n// happily buffered an entire multi-GB log into memory. Cap at 100k lines;\r\n// callers that need more should narrow with `filter`.\r\nconst MAX_TAIL_LINES = 100_000;\r\n\r\nasync function fileLogs(\r\n path: string,\r\n lines: number,\r\n filterRe: RegExp | null,\r\n stream: boolean,\r\n): Promise<LogsOutput> {\r\n const { createInterface } = await import('node:readline');\r\n const { createReadStream } = await import('node:fs');\r\n const entries: LogEntry[] = [];\r\n\r\n // Effective tail window: clamp to MAX_TAIL_LINES; treat 0 / negative as\r\n // \"max window\" rather than \"unlimited\" so a malicious /proc/kcore path\r\n // cannot OOM the worker.\r\n const effLines = lines > 0 ? Math.min(lines, MAX_TAIL_LINES) : MAX_TAIL_LINES;\r\n // Rolling window backed by a fixed-size circular buffer — at most\r\n // `effLines` strings live in memory regardless of file size.\r\n const window: string[] = new Array(effLines);\r\n let writeIdx = 0;\r\n let totalLines = 0;\r\n\r\n const rl = createInterface({\r\n input: createReadStream(path),\r\n crlfDelay: Number.POSITIVE_INFINITY,\r\n });\r\n\r\n for await (const line of rl) {\r\n if (filterRe && !filterRe.test(line)) continue;\r\n window[writeIdx] = line;\r\n writeIdx = (writeIdx + 1) % effLines;\r\n totalLines++;\r\n }\r\n\r\n // Read the window back in arrival order.\r\n const ordered: string[] = [];\r\n const start = totalLines >= effLines ? writeIdx : 0;\r\n const count = Math.min(totalLines, effLines);\r\n for (let i = 0; i < count; i++) {\r\n const v = window[(start + i) % effLines];\r\n if (v !== undefined) ordered.push(v);\r\n }\r\n\r\n for (const line of ordered) {\r\n const parsed = parseLine(line);\r\n if (parsed) entries.push(parsed);\r\n }\r\n\r\n return {\r\n source: path,\r\n entries,\r\n total: entries.length,\r\n truncated: totalLines > effLines,\r\n stream_mode: stream,\r\n };\r\n}\r\n\r\nfunction parseLogLines(output: string, filterRe: RegExp | null): LogEntry[] {\r\n const lines = output.split('\\n').filter(Boolean);\r\n const entries: LogEntry[] = [];\r\n\r\n for (const line of lines) {\r\n if (filterRe && !filterRe.test(line)) continue;\r\n const parsed = parseLine(line);\r\n if (parsed) entries.push(parsed);\r\n }\r\n\r\n return entries;\r\n}\r\n\r\nfunction parseLine(line: string): LogEntry | null {\r\n const tsRe = /^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z?)\\s+(?:\\[?(\\w+)\\]?)\\s*(.*)/;\r\n const match = tsRe.exec(line);\r\n\r\n if (match) {\r\n return {\r\n timestamp: match[1] ?? '',\r\n level: match[2]?.toLowerCase() ?? 'info',\r\n message: match[3] ?? '',\r\n };\r\n }\r\n\r\n const levelRe = /(ERROR|WARN|INFO|DEBUG|TRACE)\\s+(.*)/i;\r\n const levelMatch = levelRe.exec(line);\r\n\r\n if (levelMatch) {\r\n return {\r\n timestamp: '',\r\n level: levelMatch[1]?.toLowerCase() ?? 'info',\r\n message: levelMatch[2] ?? line,\r\n };\r\n }\r\n\r\n return {\r\n timestamp: '',\r\n level: 'info',\r\n message: line,\r\n };\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/_regex.ts","../src/_util.ts","../src/logs.ts"],"names":["resolve","path"],"mappings":"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","file":"logs.js","sourcesContent":["/**\n * Compile a user-supplied regex with conservative bounds against ReDoS.\n *\n * Node's regex engine (V8) is backtracking-based and cannot interrupt a\n * synchronous match — a pattern like `(a+)+$` against a sufficiently long\n * line will pin a worker for seconds. The executor's outer `timeoutMs` only\n * fires between async boundaries, so a long regex eval inside a sync loop\n * is uninterruptible.\n *\n * We can't fully prevent ReDoS without an alternative engine (re2-wasm), but\n * we can sharply limit the blast radius:\n *\n * 1. Cap pattern length — practically all legitimate user patterns are\n * under 256 characters. A 4 KB pattern is almost certainly malicious\n * or a copy-paste accident.\n * 2. Reject patterns containing the most obvious super-linear structures.\n * This is a coarse filter (false-positives are likely; we accept that\n * for hostile-input contexts).\n *\n * Callers should additionally bound the *subject* length (e.g. by capping\n * line size before matching).\n */\n\nconst MAX_PATTERN_LEN = 256;\n\n// Heuristics for catastrophic-backtracking constructs. Not exhaustive; bias\n// toward false-positives in tools that accept LLM-generated input.\nconst DANGEROUS_PATTERNS: ReadonlyArray<RegExp> = [\n // (a+)+, (.*)+, etc — nested quantifier on a group with internal quantifier\n /(\\([^)]*[+*][^)]*\\))[+*]/,\n /(\\(\\?:[^)]*[+*][^)]*\\))[+*]/,\n // Adjacent quantifiers: a++ a*+\n /[+*]{2,}/,\n // Quantifier on alternation with length 2+\n /\\([^|)]+\\|[^)]+\\)[+*][+*]/,\n // Greedy quantifier inside lookahead/lookbehind — (?!.*a+)\n /[\\(\\[][^)\\]]*[+*][^)\\]]*[\\)\\]][^)]*\\?\\??/,\n];\n\nexport interface CompileResult {\n ok: true;\n regex: RegExp;\n}\n\nexport interface CompileFail {\n ok: false;\n reason: string;\n}\n\nexport function compileUserRegex(pattern: string, flags: string): CompileResult | CompileFail {\n if (typeof pattern !== 'string') {\n return { ok: false, reason: 'pattern must be a string' };\n }\n if (pattern.length === 0) {\n return { ok: false, reason: 'pattern is empty' };\n }\n if (pattern.length > MAX_PATTERN_LEN) {\n return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };\n }\n for (const rx of DANGEROUS_PATTERNS) {\n if (rx.test(pattern)) {\n return {\n ok: false,\n reason:\n 'pattern looks vulnerable to catastrophic backtracking — rewrite without nested quantifiers',\n };\n }\n }\n try {\n return { ok: true, regex: new RegExp(pattern, flags) };\n } catch (err) {\n return {\n ok: false,\n reason: err instanceof Error ? err.message : 'invalid regex',\n };\n }\n}\n\n/**\n * Truncate a subject line to a safe length for synchronous regex eval.\n * The cap is conservative; tools that need exact-line matching against very\n * long lines should use ripgrep externally rather than the native walker.\n */\nexport const MAX_SUBJECT_LEN = 64 * 1024;\n\nexport function capSubject(line: string): string {\n return line.length > MAX_SUBJECT_LEN ? line.slice(0, MAX_SUBJECT_LEN) : line;\n}\n","import * as path from 'node:path';\nimport type { Context } from '@wrongstack/core';\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const root = path.resolve(ctx.projectRoot);\n const target = path.resolve(absPath);\n const rel = path.relative(root, target);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\n }\n return target;\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\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…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\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","import { spawn } from 'node:child_process';\r\nimport { buildChildEnv } from '@wrongstack/core';\r\nimport type { Tool } from '@wrongstack/core';\r\nimport { compileUserRegex } from './_regex.js';\r\nimport { safeResolve } from './_util.js';\r\n\r\ninterface LogsInput {\r\n service?: string;\r\n path?: string;\r\n lines?: number;\r\n stream?: boolean;\r\n filter?: string;\r\n since?: '1h' | '6h' | '24h' | 'all';\r\n cwd?: string;\r\n}\r\n\r\ninterface LogEntry {\r\n timestamp: string;\r\n level: string;\r\n message: string;\r\n source?: string;\r\n}\r\n\r\ninterface LogsOutput {\r\n source: string;\r\n entries: LogEntry[];\r\n total: number;\r\n truncated: boolean;\r\n stream_mode: boolean;\r\n}\r\n\r\nexport const logsTool: Tool<LogsInput, LogsOutput> = {\r\n name: 'logs',\r\n category: 'Logs',\r\n description:\r\n 'Stream or fetch logs from a service or file. Supports Docker, systemd, or plain log files.',\r\n usageHint:\r\n 'Set `service` for Docker/systemd, `path` for file. `lines` limits output. `stream` for tail -f behavior. `filter` regex filters lines.',\r\n permission: 'confirm',\r\n mutating: false,\r\n timeoutMs: 30_000,\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n service: {\r\n type: 'string',\r\n description: 'Service name for Docker or systemd journal',\r\n },\r\n path: {\r\n type: 'string',\r\n description: 'Path to log file (alternative to service)',\r\n },\r\n lines: {\r\n type: 'integer',\r\n description: 'Number of log lines to fetch (default: 100, 0 for all)',\r\n minimum: 0,\r\n maximum: 10000,\r\n },\r\n stream: {\r\n type: 'boolean',\r\n description: 'Stream logs continuously (like tail -f) (default: false)',\r\n },\r\n filter: {\r\n type: 'string',\r\n description: 'Regex pattern to filter log lines',\r\n },\r\n since: {\r\n type: 'string',\r\n enum: ['1h', '6h', '24h', 'all'],\r\n description: 'Only show logs since duration',\r\n },\r\n cwd: { type: 'string', description: 'Working directory (default: cwd)' },\r\n },\r\n },\r\n async execute(input, ctx, opts) {\r\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\r\n const lines = input.lines ?? 100;\r\n let filterRe: RegExp | null = null;\r\n if (input.filter) {\r\n const compiled = compileUserRegex(input.filter, 'i');\r\n if (!compiled.ok) {\r\n throw new Error(`logs: ${compiled.reason}`);\r\n }\r\n filterRe = compiled.regex;\r\n }\r\n\r\n if (input.service) {\r\n return await dockerLogs(input.service, lines, filterRe, cwd, opts.signal);\r\n }\r\n\r\n if (input.path) {\r\n return await fileLogs(safeResolve(input.path, ctx), lines, filterRe, input.stream ?? false);\r\n }\r\n\r\n return {\r\n source: 'none',\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n };\r\n },\r\n};\r\n\r\nasync function dockerLogs(\r\n service: string,\r\n lines: number,\r\n filterRe: RegExp | null,\r\n cwd: string,\r\n signal: AbortSignal,\r\n since?: string,\r\n): Promise<LogsOutput> {\r\n const args = ['logs'];\r\n if (lines > 0) args.push('--tail', String(lines));\r\n if (since) {\r\n const sinceMap: Record<string, string> = { '1h': '1h', '6h': '6h', '24h': '24h' };\r\n args.push('--since', sinceMap[since] ?? '1h');\r\n }\r\n // Validate service name to prevent container name injection.\r\n // Docker container names are limited to [a-zA-Z0-9][a-zA-Z0-9._-]+.\r\n if (!/^[a-zA-Z0-9][a-zA-Z0-9._:-]+$/.test(service)) {\r\n return {\r\n source: `docker:${service}`,\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n };\r\n }\r\n args.push('--timestamps', service);\r\n\r\n return new Promise((resolve) => {\r\n let stdout = '';\r\n let stderr = '';\r\n const MAX = 200_000;\r\n let settled = false;\r\n\r\n const empty = (): LogsOutput => ({\r\n source: `docker:${service}`,\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n });\r\n const finish = (result: LogsOutput) => {\r\n if (settled) return;\r\n settled = true;\r\n clearTimeout(timer);\r\n resolve(result);\r\n };\r\n\r\n const child = spawn('docker', args, { cwd, signal, env: buildChildEnv(), stdio: ['ignore', 'pipe', 'pipe'] });\r\n\r\n // `docker logs --tail N` reads recent lines and exits — fast when the\r\n // daemon is up. But if the daemon is unreachable (common on CI runners\r\n // with no running Docker), the CLI can hang on the socket connection and\r\n // emit neither `close` nor `error`. Kill it and return empty so the tool\r\n // (and its tests) never hang.\r\n const timer = setTimeout(() => {\r\n child.kill('SIGTERM');\r\n finish(empty());\r\n }, DOCKER_LOGS_TIMEOUT_MS);\r\n\r\n child.stdout?.on('data', (c) => {\r\n if (stdout.length < MAX) stdout += c.toString();\r\n });\r\n child.stderr?.on('data', (c) => {\r\n if (stderr.length < MAX) stderr += c.toString();\r\n });\r\n child.on('close', () => {\r\n const output = stdout + stderr;\r\n const entries = parseLogLines(output, filterRe);\r\n finish({\r\n source: `docker:${service}`,\r\n entries,\r\n total: entries.length,\r\n truncated: output.length >= MAX,\r\n stream_mode: false,\r\n });\r\n });\r\n child.on('error', () => finish(empty()));\r\n });\r\n}\r\n\r\n/**\r\n * Hard ceiling for a `docker logs` read. The daemon may be unreachable on CI\r\n * (no Docker running), where the CLI hangs on the socket without ever exiting.\r\n */\r\nconst DOCKER_LOGS_TIMEOUT_MS = 3_000;\r\n\r\n// Hard cap on tail-window size — `lines: 0` historically meant \"all\" and\r\n// happily buffered an entire multi-GB log into memory. Cap at 100k lines;\r\n// callers that need more should narrow with `filter`.\r\nconst MAX_TAIL_LINES = 100_000;\r\n\r\nasync function fileLogs(\r\n path: string,\r\n lines: number,\r\n filterRe: RegExp | null,\r\n stream: boolean,\r\n): Promise<LogsOutput> {\r\n const { createInterface } = await import('node:readline');\r\n const { createReadStream } = await import('node:fs');\r\n const entries: LogEntry[] = [];\r\n\r\n // Effective tail window: clamp to MAX_TAIL_LINES; treat 0 / negative as\r\n // \"max window\" rather than \"unlimited\" so a malicious /proc/kcore path\r\n // cannot OOM the worker.\r\n const effLines = lines > 0 ? Math.min(lines, MAX_TAIL_LINES) : MAX_TAIL_LINES;\r\n // Rolling window backed by a fixed-size circular buffer — at most\r\n // `effLines` strings live in memory regardless of file size.\r\n const window: string[] = new Array(effLines);\r\n let writeIdx = 0;\r\n let totalLines = 0;\r\n\r\n const rl = createInterface({\r\n input: createReadStream(path),\r\n crlfDelay: Number.POSITIVE_INFINITY,\r\n });\r\n\r\n for await (const line of rl) {\r\n if (filterRe && !filterRe.test(line)) continue;\r\n window[writeIdx] = line;\r\n writeIdx = (writeIdx + 1) % effLines;\r\n totalLines++;\r\n }\r\n\r\n // Read the window back in arrival order.\r\n const ordered: string[] = [];\r\n const start = totalLines >= effLines ? writeIdx : 0;\r\n const count = Math.min(totalLines, effLines);\r\n for (let i = 0; i < count; i++) {\r\n const v = window[(start + i) % effLines];\r\n if (v !== undefined) ordered.push(v);\r\n }\r\n\r\n for (const line of ordered) {\r\n const parsed = parseLine(line);\r\n if (parsed) entries.push(parsed);\r\n }\r\n\r\n return {\r\n source: path,\r\n entries,\r\n total: entries.length,\r\n truncated: totalLines > effLines,\r\n stream_mode: stream,\r\n };\r\n}\r\n\r\nfunction parseLogLines(output: string, filterRe: RegExp | null): LogEntry[] {\r\n const lines = output.split('\\n').filter(Boolean);\r\n const entries: LogEntry[] = [];\r\n\r\n for (const line of lines) {\r\n if (filterRe && !filterRe.test(line)) continue;\r\n const parsed = parseLine(line);\r\n if (parsed) entries.push(parsed);\r\n }\r\n\r\n return entries;\r\n}\r\n\r\nfunction parseLine(line: string): LogEntry | null {\r\n const tsRe = /^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z?)\\s+(?:\\[?(\\w+)\\]?)\\s*(.*)/;\r\n const match = tsRe.exec(line);\r\n\r\n if (match) {\r\n return {\r\n timestamp: match[1] ?? '',\r\n level: match[2]?.toLowerCase() ?? 'info',\r\n message: match[3] ?? '',\r\n };\r\n }\r\n\r\n const levelRe = /(ERROR|WARN|INFO|DEBUG|TRACE)\\s+(.*)/i;\r\n const levelMatch = levelRe.exec(line);\r\n\r\n if (levelMatch) {\r\n return {\r\n timestamp: '',\r\n level: levelMatch[1]?.toLowerCase() ?? 'info',\r\n message: levelMatch[2] ?? line,\r\n };\r\n }\r\n\r\n return {\r\n timestamp: '',\r\n level: 'info',\r\n message: line,\r\n };\r\n}\r\n"]}
|
package/dist/pack.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { spawn,
|
|
1
|
+
import { spawn, execFileSync, spawnSync } from 'node:child_process';
|
|
2
2
|
import { buildChildEnv, stripAnsi, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, compileGlob, loadPlan, emptyPlan, clearPlan, savePlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, formatPlan } from '@wrongstack/core';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
-
import { dirname } from 'node:path';
|
|
4
|
+
import { resolve, sep, dirname } from 'node:path';
|
|
5
5
|
import * as os from 'node:os';
|
|
6
6
|
import * as fs11 from 'node:fs/promises';
|
|
7
7
|
import { stat } from 'node:fs/promises';
|
|
@@ -11,6 +11,7 @@ import { statSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
|
11
11
|
import * as ts from 'typescript';
|
|
12
12
|
import * as dns from 'node:dns/promises';
|
|
13
13
|
import * as net from 'node:net';
|
|
14
|
+
import { Agent } from 'undici';
|
|
14
15
|
|
|
15
16
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
16
17
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -65,8 +66,8 @@ async function* spawnStream(opts) {
|
|
|
65
66
|
let spawnFailed = false;
|
|
66
67
|
for (; ; ) {
|
|
67
68
|
while (queue.length === 0) {
|
|
68
|
-
await new Promise((
|
|
69
|
-
waiter =
|
|
69
|
+
await new Promise((resolve7) => {
|
|
70
|
+
waiter = resolve7;
|
|
70
71
|
});
|
|
71
72
|
}
|
|
72
73
|
const chunk = queue.shift();
|
|
@@ -741,10 +742,10 @@ var bashTool = {
|
|
|
741
742
|
queue.push(c);
|
|
742
743
|
}
|
|
743
744
|
};
|
|
744
|
-
const next = () => new Promise((
|
|
745
|
+
const next = () => new Promise((resolve7) => {
|
|
745
746
|
const c = queue.shift();
|
|
746
|
-
if (c)
|
|
747
|
-
else resolveNext =
|
|
747
|
+
if (c) resolve7(c);
|
|
748
|
+
else resolveNext = resolve7;
|
|
748
749
|
});
|
|
749
750
|
let lastFlush = Date.now();
|
|
750
751
|
const flush = () => {
|
|
@@ -1716,7 +1717,7 @@ function syncGoParse(filePath, content, lang) {
|
|
|
1716
1717
|
mkdirSync(tmpDir, { recursive: true });
|
|
1717
1718
|
const scriptPath = path.join(tmpDir, "parse.go");
|
|
1718
1719
|
writeFileSync(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
1719
|
-
const stdout =
|
|
1720
|
+
const stdout = execFileSync("go", ["run", scriptPath], {
|
|
1720
1721
|
input: content,
|
|
1721
1722
|
timeout: 15e3,
|
|
1722
1723
|
encoding: "utf8",
|
|
@@ -1962,7 +1963,7 @@ function syncPyParse(filePath, lang) {
|
|
|
1962
1963
|
mkdirSync(tmpDir, { recursive: true });
|
|
1963
1964
|
const scriptPath = path.join(tmpDir, "parse.py");
|
|
1964
1965
|
writeFileSync(scriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
1965
|
-
const stdout =
|
|
1966
|
+
const stdout = execFileSync("python", [scriptPath, filePath], {
|
|
1966
1967
|
timeout: 15e3,
|
|
1967
1968
|
encoding: "utf8",
|
|
1968
1969
|
windowsHide: true
|
|
@@ -2000,11 +2001,19 @@ function parseSymbols4(opts) {
|
|
|
2000
2001
|
}
|
|
2001
2002
|
function checkNativeParser() {
|
|
2002
2003
|
try {
|
|
2003
|
-
|
|
2004
|
+
execFileSync("rustc", ["--version"], { stdio: "pipe" });
|
|
2004
2005
|
const toolsDir = path.join(process.cwd(), "tools");
|
|
2005
2006
|
try {
|
|
2006
|
-
|
|
2007
|
-
"cargo
|
|
2007
|
+
execFileSync(
|
|
2008
|
+
"cargo",
|
|
2009
|
+
[
|
|
2010
|
+
"metadata",
|
|
2011
|
+
"--no-deps",
|
|
2012
|
+
"--format-version",
|
|
2013
|
+
"1",
|
|
2014
|
+
"--manifest-path",
|
|
2015
|
+
path.join(toolsDir, "Cargo.toml")
|
|
2016
|
+
],
|
|
2008
2017
|
{ stdio: "pipe" }
|
|
2009
2018
|
);
|
|
2010
2019
|
return true;
|
|
@@ -2961,7 +2970,7 @@ function findGitDir(cwd) {
|
|
|
2961
2970
|
return null;
|
|
2962
2971
|
}
|
|
2963
2972
|
function runGit(args, cwd, signal) {
|
|
2964
|
-
return new Promise((
|
|
2973
|
+
return new Promise((resolve7) => {
|
|
2965
2974
|
let stdout = "";
|
|
2966
2975
|
let stderr = "";
|
|
2967
2976
|
const child = spawn("git", args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -2971,8 +2980,8 @@ function runGit(args, cwd, signal) {
|
|
|
2971
2980
|
child.stderr?.on("data", (c) => {
|
|
2972
2981
|
stderr += c.toString();
|
|
2973
2982
|
});
|
|
2974
|
-
child.on("close", (code) =>
|
|
2975
|
-
child.on("error", (e) =>
|
|
2983
|
+
child.on("close", (code) => resolve7({ stdout, stderr, exitCode: code ?? 0 }));
|
|
2984
|
+
child.on("error", (e) => resolve7({ stdout: "", stderr: e.message, exitCode: 1 }));
|
|
2976
2985
|
});
|
|
2977
2986
|
}
|
|
2978
2987
|
async function fileDiff(input, ctx, signal) {
|
|
@@ -3324,8 +3333,20 @@ var BLOCKED_ARG_PATTERNS = {
|
|
|
3324
3333
|
// python -c/--command executes arbitrary code; python -m runs modules
|
|
3325
3334
|
python: [/-c$/, /^--command$/, /^-m$/, /^--module$/],
|
|
3326
3335
|
// git --exec=<cmd> runs arbitrary commands via upload-pack/receive-pack;
|
|
3327
|
-
// -C <dir> changes working directory, bypassing cwd sandbox
|
|
3328
|
-
|
|
3336
|
+
// -C <dir> changes working directory, bypassing cwd sandbox;
|
|
3337
|
+
// -c/--config <k>=<v> injects config that runs commands
|
|
3338
|
+
// (e.g. core.sshCommand, core.pager, http.proxy, alias.x=!cmd).
|
|
3339
|
+
git: [
|
|
3340
|
+
/^--exec=/,
|
|
3341
|
+
/^--upload-pack=/,
|
|
3342
|
+
/^--receive-pack=/,
|
|
3343
|
+
/^-C$/,
|
|
3344
|
+
/^-c$/,
|
|
3345
|
+
/^--config$/,
|
|
3346
|
+
/^-c=/,
|
|
3347
|
+
/^--config=/,
|
|
3348
|
+
/^--config-env=/
|
|
3349
|
+
],
|
|
3329
3350
|
// node -r/--require preloads arbitrary modules; --eval executes code
|
|
3330
3351
|
node: [/^-r$/, /^--require$/, /^-e$/, /^--eval$/, /^--prof-process$/],
|
|
3331
3352
|
// go run could execute arbitrary .go files; -ldflags could inject build-time code
|
|
@@ -3448,7 +3469,7 @@ var execTool = {
|
|
|
3448
3469
|
}
|
|
3449
3470
|
};
|
|
3450
3471
|
function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
3451
|
-
return new Promise((
|
|
3472
|
+
return new Promise((resolve7) => {
|
|
3452
3473
|
let stdout = "";
|
|
3453
3474
|
let stderr = "";
|
|
3454
3475
|
let killed = false;
|
|
@@ -3482,7 +3503,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
3482
3503
|
const durationMs = Date.now() - startedAt;
|
|
3483
3504
|
const exitCode = killed ? 124 : code ?? 1;
|
|
3484
3505
|
registry.afterCall(durationMs, exitCode !== 0);
|
|
3485
|
-
|
|
3506
|
+
resolve7({
|
|
3486
3507
|
command: cmd,
|
|
3487
3508
|
args,
|
|
3488
3509
|
stdout: stdout.slice(0, MAX_OUTPUT2),
|
|
@@ -3496,7 +3517,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
3496
3517
|
clearTimeout(timer);
|
|
3497
3518
|
if (typeof pid === "number") registry.unregister(pid);
|
|
3498
3519
|
registry.afterCall(Date.now() - startedAt, true);
|
|
3499
|
-
|
|
3520
|
+
resolve7({
|
|
3500
3521
|
command: cmd,
|
|
3501
3522
|
args,
|
|
3502
3523
|
stdout: stdout.slice(0, MAX_OUTPUT2),
|
|
@@ -3511,6 +3532,48 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
3511
3532
|
var MAX_BYTES = 131072;
|
|
3512
3533
|
var TIMEOUT_MS2 = 2e4;
|
|
3513
3534
|
var ALLOW_PRIVATE = process.env["WRONGSTACK_FETCH_ALLOW_PRIVATE"] === "1";
|
|
3535
|
+
function guardedLookup(hostname, options, callback) {
|
|
3536
|
+
dns.lookup(hostname, { all: true }).then((records) => {
|
|
3537
|
+
const family = options?.family;
|
|
3538
|
+
const byFamily = family === 4 || family === 6 ? records.filter((r) => r.family === family) : records;
|
|
3539
|
+
const list = byFamily.length > 0 ? byFamily : records;
|
|
3540
|
+
if (!ALLOW_PRIVATE) {
|
|
3541
|
+
for (const r of list) {
|
|
3542
|
+
const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
|
|
3543
|
+
if (bad) {
|
|
3544
|
+
callback(
|
|
3545
|
+
Object.assign(new Error(`fetch: resolved to private address ${r.address}`), {
|
|
3546
|
+
code: "EAI_FAIL"
|
|
3547
|
+
})
|
|
3548
|
+
);
|
|
3549
|
+
return;
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
if (options?.all) {
|
|
3554
|
+
callback(
|
|
3555
|
+
null,
|
|
3556
|
+
list.map((r) => ({ address: r.address, family: r.family }))
|
|
3557
|
+
);
|
|
3558
|
+
return;
|
|
3559
|
+
}
|
|
3560
|
+
const first = list[0];
|
|
3561
|
+
if (!first) {
|
|
3562
|
+
callback(
|
|
3563
|
+
Object.assign(new Error(`fetch: no address for ${hostname}`), { code: "ENOTFOUND" })
|
|
3564
|
+
);
|
|
3565
|
+
return;
|
|
3566
|
+
}
|
|
3567
|
+
callback(null, first.address, first.family);
|
|
3568
|
+
}).catch((err) => callback(err));
|
|
3569
|
+
}
|
|
3570
|
+
var pinnedAgent;
|
|
3571
|
+
function getPinnedDispatcher() {
|
|
3572
|
+
if (!pinnedAgent) {
|
|
3573
|
+
pinnedAgent = new Agent({ connect: { lookup: guardedLookup } });
|
|
3574
|
+
}
|
|
3575
|
+
return pinnedAgent;
|
|
3576
|
+
}
|
|
3514
3577
|
async function fetchWithRedirectLimit(url, maxRedirects, signal) {
|
|
3515
3578
|
const headers = {
|
|
3516
3579
|
"user-agent": "WrongStack/1.0 (+https://wrongstack.com)",
|
|
@@ -3527,11 +3590,13 @@ async function fetchWithRedirectLimit(url, maxRedirects, signal) {
|
|
|
3527
3590
|
throw new Error("fetch: redirect to http:// blocked (HTTPS required by default)");
|
|
3528
3591
|
}
|
|
3529
3592
|
await assertNotPrivate(parsed.hostname);
|
|
3530
|
-
const
|
|
3593
|
+
const init = {
|
|
3531
3594
|
redirect: "manual",
|
|
3532
3595
|
signal,
|
|
3533
|
-
headers
|
|
3534
|
-
|
|
3596
|
+
headers,
|
|
3597
|
+
dispatcher: getPinnedDispatcher()
|
|
3598
|
+
};
|
|
3599
|
+
const res = await fetch(currentUrl, init);
|
|
3535
3600
|
if (res.status < 300 || res.status > 399) {
|
|
3536
3601
|
return res;
|
|
3537
3602
|
}
|
|
@@ -3973,6 +4038,10 @@ var gitTool = {
|
|
|
3973
4038
|
truncated: false
|
|
3974
4039
|
};
|
|
3975
4040
|
}
|
|
4041
|
+
if (input.command === "worktree") {
|
|
4042
|
+
const guard = validateWorktreeInput(input, ctx.projectRoot);
|
|
4043
|
+
if (guard) return guard;
|
|
4044
|
+
}
|
|
3976
4045
|
const gitDir = findGitDir2(ctx.cwd, ctx.projectRoot);
|
|
3977
4046
|
if (!gitDir) {
|
|
3978
4047
|
return {
|
|
@@ -3987,13 +4056,34 @@ var gitTool = {
|
|
|
3987
4056
|
return await runGit2(args, gitDir, opts.signal);
|
|
3988
4057
|
}
|
|
3989
4058
|
};
|
|
4059
|
+
function validateWorktreeInput(input, projectRoot) {
|
|
4060
|
+
const reject = (stderr) => ({
|
|
4061
|
+
command: "worktree",
|
|
4062
|
+
stdout: "",
|
|
4063
|
+
stderr,
|
|
4064
|
+
exitCode: 1,
|
|
4065
|
+
truncated: false
|
|
4066
|
+
});
|
|
4067
|
+
if (input.branch?.startsWith("-")) return reject(`unsafe branch name: ${input.branch}`);
|
|
4068
|
+
if (input.worktreePath?.startsWith("-")) {
|
|
4069
|
+
return reject(`unsafe worktree path: ${input.worktreePath}`);
|
|
4070
|
+
}
|
|
4071
|
+
if ((input.worktreeAction === "add" || input.worktreeAction === "remove") && input.worktreePath) {
|
|
4072
|
+
const root = resolve(projectRoot);
|
|
4073
|
+
const abs = resolve(root, input.worktreePath);
|
|
4074
|
+
if (abs !== root && !abs.startsWith(root + sep)) {
|
|
4075
|
+
return reject(`unsafe worktree path (escapes project root): ${input.worktreePath}`);
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
return null;
|
|
4079
|
+
}
|
|
3990
4080
|
function findGitDir2(cwd, projectRoot) {
|
|
3991
4081
|
const root = projectRoot;
|
|
3992
4082
|
let dir = cwd;
|
|
3993
4083
|
for (let i = 0; i < 20; i++) {
|
|
3994
4084
|
try {
|
|
3995
4085
|
const stat11 = statSync(`${dir}/.git`);
|
|
3996
|
-
if (stat11.isDirectory()) return dir;
|
|
4086
|
+
if (stat11.isDirectory() || stat11.isFile()) return dir;
|
|
3997
4087
|
} catch {
|
|
3998
4088
|
}
|
|
3999
4089
|
if (dir === root) break;
|
|
@@ -4049,14 +4139,14 @@ function buildArgs(input) {
|
|
|
4049
4139
|
switch (input.worktreeAction) {
|
|
4050
4140
|
case "list":
|
|
4051
4141
|
return ["worktree", "list"];
|
|
4052
|
-
case "add":
|
|
4053
|
-
return [
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4142
|
+
case "add": {
|
|
4143
|
+
if (!input.worktreePath) return ["worktree", "list"];
|
|
4144
|
+
const add = ["worktree", "add"];
|
|
4145
|
+
if (input.newBranch && input.branch) add.push("-b", input.branch);
|
|
4146
|
+
add.push(input.worktreePath);
|
|
4147
|
+
if (!input.newBranch && input.branch) add.push(input.branch);
|
|
4148
|
+
return add;
|
|
4149
|
+
}
|
|
4060
4150
|
case "remove":
|
|
4061
4151
|
return [
|
|
4062
4152
|
"worktree",
|
|
@@ -4074,7 +4164,7 @@ function buildArgs(input) {
|
|
|
4074
4164
|
}
|
|
4075
4165
|
}
|
|
4076
4166
|
function runGit2(args, cwd, signal) {
|
|
4077
|
-
return new Promise((
|
|
4167
|
+
return new Promise((resolve7) => {
|
|
4078
4168
|
let stdout = "";
|
|
4079
4169
|
let stderr = "";
|
|
4080
4170
|
const child = spawn("git", args, {
|
|
@@ -4094,7 +4184,7 @@ function runGit2(args, cwd, signal) {
|
|
|
4094
4184
|
}
|
|
4095
4185
|
});
|
|
4096
4186
|
child.on("error", (err) => {
|
|
4097
|
-
|
|
4187
|
+
resolve7({
|
|
4098
4188
|
command: args[0],
|
|
4099
4189
|
stdout,
|
|
4100
4190
|
stderr: err.message,
|
|
@@ -4103,7 +4193,7 @@ function runGit2(args, cwd, signal) {
|
|
|
4103
4193
|
});
|
|
4104
4194
|
});
|
|
4105
4195
|
child.on("close", (code) => {
|
|
4106
|
-
|
|
4196
|
+
resolve7({
|
|
4107
4197
|
command: args[0],
|
|
4108
4198
|
stdout: stdout.slice(0, MAX_OUTPUT3),
|
|
4109
4199
|
stderr: stderr.slice(0, MAX_OUTPUT3),
|
|
@@ -4289,13 +4379,13 @@ var grepTool = {
|
|
|
4289
4379
|
}
|
|
4290
4380
|
};
|
|
4291
4381
|
async function detectRg(signal) {
|
|
4292
|
-
return new Promise((
|
|
4382
|
+
return new Promise((resolve7) => {
|
|
4293
4383
|
try {
|
|
4294
4384
|
const p = spawn("rg", ["--version"], { env: buildChildEnv(), stdio: "ignore", signal });
|
|
4295
|
-
p.on("error", () =>
|
|
4296
|
-
p.on("close", (code) =>
|
|
4385
|
+
p.on("error", () => resolve7(false));
|
|
4386
|
+
p.on("close", (code) => resolve7(code === 0));
|
|
4297
4387
|
} catch {
|
|
4298
|
-
|
|
4388
|
+
resolve7(false);
|
|
4299
4389
|
}
|
|
4300
4390
|
});
|
|
4301
4391
|
}
|
|
@@ -4902,21 +4992,39 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
4902
4992
|
};
|
|
4903
4993
|
}
|
|
4904
4994
|
args.push("--timestamps", service);
|
|
4905
|
-
return new Promise((
|
|
4995
|
+
return new Promise((resolve7) => {
|
|
4906
4996
|
let stdout = "";
|
|
4907
4997
|
let stderr = "";
|
|
4908
4998
|
const MAX = 2e5;
|
|
4999
|
+
let settled = false;
|
|
5000
|
+
const empty = () => ({
|
|
5001
|
+
source: `docker:${service}`,
|
|
5002
|
+
entries: [],
|
|
5003
|
+
total: 0,
|
|
5004
|
+
truncated: false,
|
|
5005
|
+
stream_mode: false
|
|
5006
|
+
});
|
|
5007
|
+
const finish = (result) => {
|
|
5008
|
+
if (settled) return;
|
|
5009
|
+
settled = true;
|
|
5010
|
+
clearTimeout(timer);
|
|
5011
|
+
resolve7(result);
|
|
5012
|
+
};
|
|
4909
5013
|
const child = spawn("docker", args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"] });
|
|
5014
|
+
const timer = setTimeout(() => {
|
|
5015
|
+
child.kill("SIGTERM");
|
|
5016
|
+
finish(empty());
|
|
5017
|
+
}, DOCKER_LOGS_TIMEOUT_MS);
|
|
4910
5018
|
child.stdout?.on("data", (c) => {
|
|
4911
5019
|
if (stdout.length < MAX) stdout += c.toString();
|
|
4912
5020
|
});
|
|
4913
5021
|
child.stderr?.on("data", (c) => {
|
|
4914
5022
|
if (stderr.length < MAX) stderr += c.toString();
|
|
4915
5023
|
});
|
|
4916
|
-
child.on("close", (
|
|
5024
|
+
child.on("close", () => {
|
|
4917
5025
|
const output = stdout + stderr;
|
|
4918
5026
|
const entries = parseLogLines(output, filterRe);
|
|
4919
|
-
|
|
5027
|
+
finish({
|
|
4920
5028
|
source: `docker:${service}`,
|
|
4921
5029
|
entries,
|
|
4922
5030
|
total: entries.length,
|
|
@@ -4924,17 +5032,10 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
4924
5032
|
stream_mode: false
|
|
4925
5033
|
});
|
|
4926
5034
|
});
|
|
4927
|
-
child.on("error", (
|
|
4928
|
-
resolve6({
|
|
4929
|
-
source: `docker:${service}`,
|
|
4930
|
-
entries: [],
|
|
4931
|
-
total: 0,
|
|
4932
|
-
truncated: false,
|
|
4933
|
-
stream_mode: false
|
|
4934
|
-
});
|
|
4935
|
-
});
|
|
5035
|
+
child.on("error", () => finish(empty()));
|
|
4936
5036
|
});
|
|
4937
5037
|
}
|
|
5038
|
+
var DOCKER_LOGS_TIMEOUT_MS = 3e3;
|
|
4938
5039
|
var MAX_TAIL_LINES = 1e5;
|
|
4939
5040
|
async function fileLogs(path18, lines, filterRe, stream) {
|
|
4940
5041
|
const { createInterface } = await import('node:readline');
|
|
@@ -5058,7 +5159,7 @@ async function detectManager2(cwd) {
|
|
|
5058
5159
|
return "npm";
|
|
5059
5160
|
}
|
|
5060
5161
|
function runOutdated(manager, args, cwd, signal) {
|
|
5061
|
-
return new Promise((
|
|
5162
|
+
return new Promise((resolve7) => {
|
|
5062
5163
|
let stdout = "";
|
|
5063
5164
|
let stderr = "";
|
|
5064
5165
|
const MAX = 1e5;
|
|
@@ -5071,10 +5172,10 @@ function runOutdated(manager, args, cwd, signal) {
|
|
|
5071
5172
|
});
|
|
5072
5173
|
child.on("close", (code) => {
|
|
5073
5174
|
const result = parseOutdatedOutput(stdout, code ?? 0);
|
|
5074
|
-
|
|
5175
|
+
resolve7(result);
|
|
5075
5176
|
});
|
|
5076
5177
|
child.on("error", (e) => {
|
|
5077
|
-
|
|
5178
|
+
resolve7({
|
|
5078
5179
|
exit_code: 1,
|
|
5079
5180
|
packages: [],
|
|
5080
5181
|
total: 0,
|
|
@@ -5204,7 +5305,7 @@ function stripPathComponents(p, strip) {
|
|
|
5204
5305
|
return parts.slice(strip).join("/");
|
|
5205
5306
|
}
|
|
5206
5307
|
function runPatch(args, cwd, signal) {
|
|
5207
|
-
return new Promise((
|
|
5308
|
+
return new Promise((resolve7) => {
|
|
5208
5309
|
let stdout = "";
|
|
5209
5310
|
let stderr = "";
|
|
5210
5311
|
const env = { ...buildChildEnv(), LANG: "C", LC_ALL: "C" };
|
|
@@ -5215,8 +5316,8 @@ function runPatch(args, cwd, signal) {
|
|
|
5215
5316
|
child.stderr?.on("data", (c) => {
|
|
5216
5317
|
stderr += c.toString();
|
|
5217
5318
|
});
|
|
5218
|
-
child.on("close", (code) =>
|
|
5219
|
-
child.on("error", (e) =>
|
|
5319
|
+
child.on("close", (code) => resolve7({ exitCode: code ?? 1, stdout, stderr }));
|
|
5320
|
+
child.on("error", (e) => resolve7({ exitCode: 1, stdout: "", stderr: e.message }));
|
|
5220
5321
|
});
|
|
5221
5322
|
}
|
|
5222
5323
|
function extractPatchedFiles(output) {
|
|
@@ -5465,6 +5566,7 @@ var replaceTool = {
|
|
|
5465
5566
|
const dryRun = input.dry_run ?? false;
|
|
5466
5567
|
const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
|
|
5467
5568
|
const fileList = await resolveFiles2(filesInput, ctx, globRe);
|
|
5569
|
+
const realRoot = await fs11.realpath(ctx.projectRoot).catch(() => ctx.projectRoot);
|
|
5468
5570
|
const results = [];
|
|
5469
5571
|
let totalReplacements = 0;
|
|
5470
5572
|
for (const absPath of fileList) {
|
|
@@ -5480,7 +5582,7 @@ var replaceTool = {
|
|
|
5480
5582
|
} catch {
|
|
5481
5583
|
continue;
|
|
5482
5584
|
}
|
|
5483
|
-
const rel = path.relative(
|
|
5585
|
+
const rel = path.relative(realRoot, realPath);
|
|
5484
5586
|
if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
|
|
5485
5587
|
const stat11 = await fs11.stat(realPath).catch(() => null);
|
|
5486
5588
|
if (!stat11 || !stat11.isFile()) continue;
|
|
@@ -5558,13 +5660,13 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
5558
5660
|
return await globNative(pattern, base, extraGlob);
|
|
5559
5661
|
}
|
|
5560
5662
|
function checkRg() {
|
|
5561
|
-
return new Promise((
|
|
5663
|
+
return new Promise((resolve7) => {
|
|
5562
5664
|
try {
|
|
5563
5665
|
const p = spawn("rg", ["--version"], { env: buildChildEnv(), stdio: "ignore" });
|
|
5564
|
-
p.on("error", () =>
|
|
5565
|
-
p.on("close", (code) =>
|
|
5666
|
+
p.on("error", () => resolve7(false));
|
|
5667
|
+
p.on("close", (code) => resolve7(code === 0));
|
|
5566
5668
|
} catch {
|
|
5567
|
-
|
|
5669
|
+
resolve7(false);
|
|
5568
5670
|
}
|
|
5569
5671
|
});
|
|
5570
5672
|
}
|
|
@@ -5576,10 +5678,10 @@ function spawnRgFind(pattern, base) {
|
|
|
5576
5678
|
buf += chunk.toString();
|
|
5577
5679
|
});
|
|
5578
5680
|
return {
|
|
5579
|
-
promise: new Promise((
|
|
5681
|
+
promise: new Promise((resolve7, reject) => {
|
|
5580
5682
|
child.on("error", reject);
|
|
5581
5683
|
child.on("close", () => {
|
|
5582
|
-
|
|
5684
|
+
resolve7(buf.split("\n").filter(Boolean));
|
|
5583
5685
|
});
|
|
5584
5686
|
})
|
|
5585
5687
|
};
|