Drop usage of execSync, switch to $ util
This commit is contained in:
+10
-5
@@ -1,5 +1,5 @@
|
|||||||
import { execSync } from "node:child_process";
|
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
|
import { $ } from "../utils/shell.ts";
|
||||||
import { contextualize, tool } from "./shared.ts";
|
import { contextualize, tool } from "./shared.ts";
|
||||||
|
|
||||||
export const ListFiles = type({
|
export const ListFiles = type({
|
||||||
@@ -17,14 +17,19 @@ export const ListFilesTool = tool({
|
|||||||
try {
|
try {
|
||||||
// Use git ls-files to list tracked files
|
// Use git ls-files to list tracked files
|
||||||
// This respects .gitignore and gives a clean list of source files
|
// This respects .gitignore and gives a clean list of source files
|
||||||
const command = path === "." ? "git ls-files" : `git ls-files ${path}`;
|
const pathStr = path ?? ".";
|
||||||
const output = execSync(command, { encoding: "utf-8" });
|
const output = $("git", pathStr === "." ? ["ls-files"] : ["ls-files", pathStr], {
|
||||||
|
log: false,
|
||||||
|
});
|
||||||
const files = output.split("\n").filter((f) => f.trim() !== "");
|
const files = output.split("\n").filter((f) => f.trim() !== "");
|
||||||
|
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
// Fallback for non-git environments or untracked files
|
// Fallback for non-git environments or untracked files
|
||||||
const findCmd = `find ${path} -maxdepth 3 -not -path '*/.*' -type f`;
|
const findOutput = $(
|
||||||
const findOutput = execSync(findCmd, { encoding: "utf-8" });
|
"find",
|
||||||
|
[pathStr, "-maxdepth", "3", "-not", "-path", "*/.*", "-type", "f"],
|
||||||
|
{ log: false }
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
files: findOutput.split("\n").filter((f) => f.trim() !== ""),
|
files: findOutput.split("\n").filter((f) => f.trim() !== ""),
|
||||||
method: "find",
|
method: "find",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { execSync } from "node:child_process";
|
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { log } from "../utils/cli.ts";
|
import { log } from "../utils/cli.ts";
|
||||||
|
import { $ } from "../utils/shell.ts";
|
||||||
import { contextualize, tool } from "./shared.ts";
|
import { contextualize, tool } from "./shared.ts";
|
||||||
|
|
||||||
export const PullRequest = type({
|
export const PullRequest = type({
|
||||||
@@ -15,9 +15,7 @@ export const PullRequestTool = tool({
|
|||||||
parameters: PullRequest,
|
parameters: PullRequest,
|
||||||
execute: contextualize(async ({ title, body, base }, ctx) => {
|
execute: contextualize(async ({ title, body, base }, ctx) => {
|
||||||
// Get the current branch name
|
// Get the current branch name
|
||||||
const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", {
|
const currentBranch = $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false });
|
||||||
encoding: "utf8",
|
|
||||||
}).trim();
|
|
||||||
|
|
||||||
log.info(`Current branch: ${currentBranch}`);
|
log.info(`Current branch: ${currentBranch}`);
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
import { execSync } from "node:child_process";
|
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { log } from "../utils/cli.ts";
|
import { log } from "../utils/cli.ts";
|
||||||
|
import { $ } from "../utils/shell.ts";
|
||||||
import { contextualize, tool } from "./shared.ts";
|
import { contextualize, tool } from "./shared.ts";
|
||||||
|
|
||||||
export const PullRequestInfo = type({
|
export const PullRequestInfo = type({
|
||||||
@@ -30,14 +30,14 @@ export const PullRequestInfoTool = tool({
|
|||||||
|
|
||||||
// Automatically fetch and checkout branches for review
|
// Automatically fetch and checkout branches for review
|
||||||
log.info(`Fetching base branch: origin/${baseBranch}`);
|
log.info(`Fetching base branch: origin/${baseBranch}`);
|
||||||
execSync(`git fetch origin ${baseBranch} --depth=20`, { stdio: "inherit" });
|
$("git", ["fetch", "origin", baseBranch, "--depth=20"]);
|
||||||
|
|
||||||
log.info(`Fetching PR branch: origin/${headBranch}`);
|
log.info(`Fetching PR branch: origin/${headBranch}`);
|
||||||
execSync(`git fetch origin ${headBranch}`, { stdio: "inherit" });
|
$("git", ["fetch", "origin", headBranch]);
|
||||||
|
|
||||||
log.info(`Checking out PR branch: origin/${headBranch}`);
|
log.info(`Checking out PR branch: origin/${headBranch}`);
|
||||||
// check out a local branch tracking the remote branch so we can push changes
|
// check out a local branch tracking the remote branch so we can push changes
|
||||||
execSync(`git checkout -B ${headBranch} origin/${headBranch}`, { stdio: "inherit" });
|
$("git", ["checkout", "-B", headBranch, `origin/${headBranch}`]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
number: data.number,
|
number: data.number,
|
||||||
|
|||||||
+1
-2
@@ -2,7 +2,6 @@ import { spawnSync } from "child_process";
|
|||||||
import { existsSync } from "fs";
|
import { existsSync } from "fs";
|
||||||
|
|
||||||
function findCliPath(name: string): string | null {
|
function findCliPath(name: string): string | null {
|
||||||
|
|
||||||
const result = spawnSync("which", [name], { encoding: "utf-8" });
|
const result = spawnSync("which", [name], { encoding: "utf-8" });
|
||||||
if (result.status === 0 && result.stdout) {
|
if (result.status === 0 && result.stdout) {
|
||||||
const cliPath = result.stdout.trim();
|
const cliPath = result.stdout.trim();
|
||||||
@@ -11,6 +10,6 @@ function findCliPath(name: string): string | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(findCliPath("codei"));
|
console.log(findCliPath("codei"));
|
||||||
@@ -8,6 +8,11 @@
|
|||||||
[] add footer to the working comment ("executed by {agent}", link to pullfrog (homepage) w/ small logo?, feedback (create github issue), link to workflow run)- see https://github.com/colinhacks/zod/issues/5459#issuecomment-3548382991
|
[] add footer to the working comment ("executed by {agent}", link to pullfrog (homepage) w/ small logo?, feedback (create github issue), link to workflow run)- see https://github.com/colinhacks/zod/issues/5459#issuecomment-3548382991
|
||||||
[] avoid passing all of process.env into agents: minimum # of vars
|
[] avoid passing all of process.env into agents: minimum # of vars
|
||||||
[] toon encode in prompt
|
[] toon encode in prompt
|
||||||
|
[] branching: use pullfrog/ prefix
|
||||||
|
[] test modifications to existing PRs (proper branching)
|
||||||
|
[] web access settings
|
||||||
|
[] implement Learnings
|
||||||
|
[] implement Billing
|
||||||
|
|
||||||
## MAYBE
|
## MAYBE
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -2,6 +2,7 @@ import { execSync } from "node:child_process";
|
|||||||
import { existsSync, rmSync } from "node:fs";
|
import { existsSync, rmSync } from "node:fs";
|
||||||
import { log } from "./cli.ts";
|
import { log } from "./cli.ts";
|
||||||
import type { RepoContext } from "./github.ts";
|
import type { RepoContext } from "./github.ts";
|
||||||
|
import { $ } from "./shell.ts";
|
||||||
|
|
||||||
export interface SetupOptions {
|
export interface SetupOptions {
|
||||||
tempDir: string;
|
tempDir: string;
|
||||||
@@ -25,7 +26,7 @@ export function setupTestRepo(options: SetupOptions): void {
|
|||||||
rmSync(tempDir, { recursive: true, force: true });
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
|
||||||
log.info("📦 Cloning pullfrogai/scratch into .temp...");
|
log.info("📦 Cloning pullfrogai/scratch into .temp...");
|
||||||
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
$("git", ["clone", repoUrl, tempDir]);
|
||||||
} else {
|
} else {
|
||||||
log.info("📦 Resetting existing .temp repository...");
|
log.info("📦 Resetting existing .temp repository...");
|
||||||
execSync("git reset --hard HEAD && git clean -fd", {
|
execSync("git reset --hard HEAD && git clean -fd", {
|
||||||
@@ -35,7 +36,7 @@ export function setupTestRepo(options: SetupOptions): void {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.info("📦 Cloning pullfrogai/scratch into .temp...");
|
log.info("📦 Cloning pullfrogai/scratch into .temp...");
|
||||||
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
$("git", ["clone", repoUrl, tempDir]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +88,6 @@ export function setupGitAuth(githubToken: string, repoContext: RepoContext): voi
|
|||||||
|
|
||||||
// Update remote URL to embed the token
|
// Update remote URL to embed the token
|
||||||
const remoteUrl = `https://x-access-token:${githubToken}@github.com/${repoContext.owner}/${repoContext.name}.git`;
|
const remoteUrl = `https://x-access-token:${githubToken}@github.com/${repoContext.owner}/${repoContext.name}.git`;
|
||||||
execSync(`git remote set-url origin "${remoteUrl}"`, { stdio: "inherit" });
|
$("git", ["remote", "set-url", "origin", remoteUrl]);
|
||||||
log.info("✓ Updated remote URL with authentication token");
|
log.info("✓ Updated remote URL with authentication token");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
|
||||||
|
interface ShellOptions {
|
||||||
|
cwd?: string;
|
||||||
|
encoding?:
|
||||||
|
| "utf-8"
|
||||||
|
| "utf8"
|
||||||
|
| "ascii"
|
||||||
|
| "base64"
|
||||||
|
| "base64url"
|
||||||
|
| "hex"
|
||||||
|
| "latin1"
|
||||||
|
| "ucs-2"
|
||||||
|
| "ucs2"
|
||||||
|
| "utf16le";
|
||||||
|
log?: boolean;
|
||||||
|
onError?: (result: { status: number; stdout: string; stderr: string }) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a shell command safely using spawnSync with argument arrays.
|
||||||
|
* Prevents shell injection by avoiding string interpolation in shell commands.
|
||||||
|
*
|
||||||
|
* @param cmd - The command to execute
|
||||||
|
* @param args - Array of arguments to pass to the command
|
||||||
|
* @param options - Optional configuration (cwd, encoding, onError)
|
||||||
|
* @returns The trimmed stdout output
|
||||||
|
* @throws Error if command fails and no onError handler is provided
|
||||||
|
*/
|
||||||
|
export function $(cmd: string, args: string[], options?: ShellOptions): string {
|
||||||
|
const encoding = options?.encoding ?? "utf-8";
|
||||||
|
const result = spawnSync(cmd, args, {
|
||||||
|
stdio: ["inherit", "pipe", "pipe"],
|
||||||
|
encoding,
|
||||||
|
cwd: options?.cwd,
|
||||||
|
});
|
||||||
|
|
||||||
|
const stdout = result.stdout ?? "";
|
||||||
|
const stderr = result.stderr ?? "";
|
||||||
|
|
||||||
|
// Write output to process streams so it behaves like stdio: "inherit"
|
||||||
|
// Only log if log option is not explicitly set to false
|
||||||
|
if (options?.log !== false) {
|
||||||
|
if (stdout) {
|
||||||
|
process.stdout.write(stdout);
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
process.stderr.write(stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle errors
|
||||||
|
if (result.status !== 0) {
|
||||||
|
const errorResult = {
|
||||||
|
status: result.status ?? -1,
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options?.onError) {
|
||||||
|
options.onError(errorResult);
|
||||||
|
return stdout.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Command failed with exit code ${errorResult.status}: ${stderr || "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdout.trim();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user