cleanup comments

This commit is contained in:
ssalbdivad
2025-10-09 16:33:11 -04:00
parent f74a75cfac
commit 9459803aaa
11 changed files with 7 additions and 185 deletions
+1 -85
View File
@@ -16,14 +16,12 @@ export class ClaudeAgent implements Agent {
startTime: 0, startTime: 0,
}; };
// $: ExecaMethod;
constructor(config: AgentConfig) { constructor(config: AgentConfig) {
if (!config.apiKey) { if (!config.apiKey) {
throw new Error("Claude agent requires an API key"); throw new Error("Claude agent requires an API key");
} }
this.apiKey = config.apiKey; this.apiKey = config.apiKey;
// Removed execa dependency - using spawn utility instead
} }
/** /**
@@ -43,7 +41,6 @@ export class ClaudeAgent implements Agent {
* Install Claude Code CLI * Install Claude Code CLI
*/ */
async install(): Promise<void> { async install(): Promise<void> {
// Check if Claude Code is already installed
if (await this.isClaudeInstalled()) { if (await this.isClaudeInstalled()) {
core.info("Claude Code is already installed, skipping installation"); core.info("Claude Code is already installed, skipping installation");
return; return;
@@ -51,16 +48,12 @@ export class ClaudeAgent implements Agent {
core.info("Installing Claude Code..."); core.info("Installing Claude Code...");
try { try {
// Use shell execution to properly handle the pipe
const result = await spawn({ const result = await spawn({
cmd: "bash", cmd: "bash",
args: ["-c", "curl -fsSL https://claude.ai/install.sh | bash -s 1.0.93"], args: ["-c", "curl -fsSL https://claude.ai/install.sh | bash -s 1.0.93"],
env: { ANTHROPIC_API_KEY: this.apiKey }, env: { ANTHROPIC_API_KEY: this.apiKey },
timeout: 120000, // 2 minute timeout timeout: 120000, // 2 minute timeout
onStdout: () => { onStdout: () => {},
// no logs
// process.stdout.write(chunk)
},
onStderr: (chunk) => process.stderr.write(chunk), onStderr: (chunk) => process.stderr.write(chunk),
}); });
@@ -79,14 +72,10 @@ export class ClaudeAgent implements Agent {
*/ */
async execute(prompt: string): Promise<AgentResult> { async execute(prompt: string): Promise<AgentResult> {
core.info("Running Claude Code..."); core.info("Running Claude Code...");
// printTable([[prompt]]);
try { try {
// Execute Claude Code with the prompt directly using proper headless mode
// core.info(`Executing Claude Code with prompt: ${prompt.substring(0, 100)}...`);
const claudePath = `${process.env.HOME}/.local/bin/claude`; const claudePath = `${process.env.HOME}/.local/bin/claude`;
// console.log("Using Claude Code from:", claudePath);
console.log(boxString(prompt, { title: "Prompt" })); console.log(boxString(prompt, { title: "Prompt" }));
const args = [ const args = [
"--print", "--print",
@@ -97,7 +86,6 @@ export class ClaudeAgent implements Agent {
"bypassPermissions", "bypassPermissions",
]; ];
// Add MCP configuration if GitHub credentials are available
if ( if (
process.env.GITHUB_INSTALLATION_TOKEN && process.env.GITHUB_INSTALLATION_TOKEN &&
process.env.REPO_OWNER && process.env.REPO_OWNER &&
@@ -116,10 +104,8 @@ export class ClaudeAgent implements Agent {
ANTHROPIC_API_KEY: this.apiKey, ANTHROPIC_API_KEY: this.apiKey,
}; };
// Start a collapsible log group for streaming output
core.startGroup("🔄 Run details"); core.startGroup("🔄 Run details");
// Initialize run statistics
this.runStats = { this.runStats = {
toolsUsed: 0, toolsUsed: 0,
turns: 0, turns: 0,
@@ -129,7 +115,6 @@ export class ClaudeAgent implements Agent {
const finalResult = ""; const finalResult = "";
const totalCost = 0; const totalCost = 0;
// run Claude Code with the prompt
const result = await spawn({ const result = await spawn({
cmd: claudePath, cmd: claudePath,
args, args,
@@ -137,37 +122,21 @@ export class ClaudeAgent implements Agent {
input: prompt, input: prompt,
timeout: 10 * 60 * 1000, // 10 minutes timeout: 10 * 60 * 1000, // 10 minutes
onStdout: (_chunk) => { onStdout: (_chunk) => {
// console.log(chunk);
processJSONChunk(_chunk, this); processJSONChunk(_chunk, this);
}, },
onStderr: (_chunk) => { onStderr: (_chunk) => {
if (_chunk.trim()) { if (_chunk.trim()) {
// core.warning(`[warn] ${chunk}`);
processJSONChunk(_chunk, this); processJSONChunk(_chunk, this);
} }
}, },
}); });
// throw on non-zero exit code
if (result.exitCode !== 0) { if (result.exitCode !== 0) {
throw new Error( throw new Error(
`Command failed with exit code ${result.exitCode}\n\nStdout: ${result.stdout}\n\nStderr: ${result.stderr}` `Command failed with exit code ${result.exitCode}\n\nStdout: ${result.stdout}\n\nStderr: ${result.stderr}`
); );
} }
// Process the complete buffered stdout to extract final results
// if (result.stdout.trim()) {
// const lines = result.stdout.trim().split("\n");
// for (const line of lines) {
// if (line.trim()) {
// const chunkResult = processJsonChunk(line);
// if (chunkResult.finalResult) finalResult = chunkResult.finalResult;
// if (chunkResult.totalCost) totalCost = chunkResult.totalCost;
// }
// }
// }
// Log run summary
const duration = Date.now() - this.runStats.startTime; const duration = Date.now() - this.runStats.startTime;
core.info( core.info(
`📊 Run Summary: ${this.runStats.toolsUsed} tools used, ${this.runStats.turns} turns, ${duration}ms duration` `📊 Run Summary: ${this.runStats.toolsUsed} tools used, ${this.runStats.turns} turns, ${duration}ms duration`
@@ -187,11 +156,9 @@ export class ClaudeAgent implements Agent {
}, },
}; };
} catch (error: any) { } catch (error: any) {
// Ensure group is closed even if error occurs before group is started
try { try {
core.endGroup(); core.endGroup();
} catch { } catch {
// Group might not have been started, ignore
} }
const errorMessage = error instanceof Error ? error.message : "Unknown error"; const errorMessage = error instanceof Error ? error.message : "Unknown error";
return { return {
@@ -202,34 +169,11 @@ export class ClaudeAgent implements Agent {
} }
} }
/**
* Process a JSON chunk line and extract result data
*/
// function processJsonChunk(line: string): { finalResult?: string; totalCost?: number } {
// try {
// const chunk = JSON.parse(line.trim());
// processJSONChunk(chunk);
// // Collect final result and cost data
// if (chunk.type === "result" && chunk.result) {
// return {
// finalResult: chunk.result,
// totalCost: chunk.total_cost_usd || 0,
// };
// }
// return {};
// } catch {
// core.debug(`Failed to parse JSON line: ${line}`);
// return {};
// }
// }
/** /**
* Pretty print a JSON chunk based on its type * Pretty print a JSON chunk based on its type
*/ */
function processJSONChunk(chunk: string, agent?: ClaudeAgent): void { function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
try { try {
// Parse the JSON string first
console.log(chunk); console.log(chunk);
const parsedChunk = JSON.parse(chunk.trim()); const parsedChunk = JSON.parse(chunk.trim());
@@ -237,8 +181,6 @@ function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
case "system": case "system":
if (parsedChunk.subtype === "init") { if (parsedChunk.subtype === "init") {
core.info(`🚀 Starting Claude Code session...`); core.info(`🚀 Starting Claude Code session...`);
// core.info(`📁 Working directory: ${parsedChunk.cwd}`);
// core.info(`🔑 Permission mode: ${parsedChunk.permissionMode}`);
core.info( core.info(
tableString([ tableString([
["model", parsedChunk.model], ["model", parsedChunk.model],
@@ -264,39 +206,31 @@ function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
case "assistant": case "assistant":
if (parsedChunk.message?.content) { if (parsedChunk.message?.content) {
// Track turns
if (agent) { if (agent) {
agent.runStats.turns++; agent.runStats.turns++;
} }
for (const content of parsedChunk.message.content) { for (const content of parsedChunk.message.content) {
if (content.type === "text") { if (content.type === "text") {
// Skip empty text content
if (content.text.trim()) { if (content.text.trim()) {
core.info(boxString(content.text.trim(), { title: "Claude Code" })); core.info(boxString(content.text.trim(), { title: "Claude Code" }));
} }
} else if (content.type === "tool_use") { } else if (content.type === "tool_use") {
// Track tools used
if (agent) { if (agent) {
agent.runStats.toolsUsed++; agent.runStats.toolsUsed++;
} }
// Enhanced tool usage logging
const toolName = content.name; const toolName = content.name;
// const toolId = content.id;
core.info(`${toolName}`); core.info(`${toolName}`);
// Log tool-specific details based on tool type
if (content.input) { if (content.input) {
const input = content.input; const input = content.input;
// Common tool input fields
if (input.description) { if (input.description) {
core.info(` └─ ${input.description}`); core.info(` └─ ${input.description}`);
} }
// Tool-specific input fields
if (input.command) { if (input.command) {
core.info(` └─ command: ${input.command}`); core.info(` └─ command: ${input.command}`);
} }
@@ -325,7 +259,6 @@ function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
core.info(` └─ url: ${input.url}`); core.info(` └─ url: ${input.url}`);
} }
// For multi-edit or complex operations
if (input.edits && Array.isArray(input.edits)) { if (input.edits && Array.isArray(input.edits)) {
core.info(` └─ edits: ${input.edits.length} changes`); core.info(` └─ edits: ${input.edits.length} changes`);
input.edits.forEach((edit: any, index: number) => { input.edits.forEach((edit: any, index: number) => {
@@ -335,19 +268,15 @@ function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
}); });
} }
// For task operations
if (input.task) { if (input.task) {
core.info(` └─ task: ${input.task}`); core.info(` └─ task: ${input.task}`);
} }
// For bash operations with specific details
if (input.bash_command) { if (input.bash_command) {
core.info(` └─ bash_command: ${input.bash_command}`); core.info(` └─ bash_command: ${input.bash_command}`);
} }
} }
// Log tool ID for debugging
// core.debug(` 🔗 Tool ID: ${toolId}`);
} }
} }
} }
@@ -360,9 +289,6 @@ function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
if (content.is_error) { if (content.is_error) {
core.warning(`❌ Tool error: ${content.content}`); core.warning(`❌ Tool error: ${content.content}`);
} else { } else {
// Enhanced tool result logging
const _resultContent = content.content.trim();
// do nothing for now. usually useless in headless more.
} }
} }
} }
@@ -371,15 +297,6 @@ function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
case "result": case "result":
if (parsedChunk.subtype === "success") { if (parsedChunk.subtype === "success") {
// Claude already prints something almost identical to this, so skip for now
// if (parsedChunk.result) {
// core.info(
// boxString(parsedChunk.result.trim(), {
// title: "🤖 Claude Code",
// maxWidth: 70,
// }),
// );
// }
core.info( core.info(
tableString([ tableString([
@@ -396,7 +313,6 @@ function processJSONChunk(chunk: string, agent?: ClaudeAgent): void {
break; break;
default: default:
// Log unknown chunk types for debugging
core.debug(`📦 Unknown chunk type: ${parsedChunk.type}`); core.debug(`📦 Unknown chunk type: ${parsedChunk.type}`);
break; break;
} }
+3 -5
View File
@@ -25580,7 +25580,7 @@ async function spawn(options) {
durationMs durationMs
}); });
}); });
child.on("error", (error2) => { child.on("error", (_error) => {
const durationMs = Date.now() - startTime; const durationMs = Date.now() - startTime;
if (timeoutId) { if (timeoutId) {
clearTimeout(timeoutId); clearTimeout(timeoutId);
@@ -25609,8 +25609,8 @@ function tableString(rows, options) {
return lineIndex === 0 || options?.title && lineIndex === 1 || lineIndex === rowCount; return lineIndex === 0 || options?.title && lineIndex === 1 || lineIndex === rowCount;
} }
} = options || {}; } = options || {};
if (options?.title) { if (title) {
rows.unshift([options.title]); rows.unshift([title]);
} }
const tableOutput = (0, import_table.table)(rows, { const tableOutput = (0, import_table.table)(rows, {
drawHorizontalLine, drawHorizontalLine,
@@ -25698,7 +25698,6 @@ var ClaudeAgent = class {
turns: 0, turns: 0,
startTime: 0 startTime: 0
}; };
// $: ExecaMethod;
constructor(config) { constructor(config) {
if (!config.apiKey) { if (!config.apiKey) {
throw new Error("Claude agent requires an API key"); throw new Error("Claude agent requires an API key");
@@ -25927,7 +25926,6 @@ function processJSONChunk(chunk, agent) {
if (content.is_error) { if (content.is_error) {
core.warning(`\u274C Tool error: ${content.content}`); core.warning(`\u274C Tool error: ${content.content}`);
} else { } else {
const _resultContent = content.content.trim();
} }
} }
} }
-5
View File
@@ -11,7 +11,6 @@ import { setupGitHubInstallationToken } from "./utils/github.ts";
async function run(): Promise<void> { async function run(): Promise<void> {
try { try {
// Get inputs from GitHub Actions
const prompt = core.getInput("prompt", { required: true }); const prompt = core.getInput("prompt", { required: true });
const anthropic_api_key = core.getInput("anthropic_api_key"); const anthropic_api_key = core.getInput("anthropic_api_key");
@@ -19,13 +18,11 @@ async function run(): Promise<void> {
throw new Error("prompt is required"); throw new Error("prompt is required");
} }
// Create params object with new structure
const inputs: ExecutionInputs = { const inputs: ExecutionInputs = {
prompt, prompt,
anthropic_api_key, anthropic_api_key,
}; };
// Add optional properties only if they exist
const githubToken = core.getInput("github_token") || process.env.GITHUB_TOKEN; const githubToken = core.getInput("github_token") || process.env.GITHUB_TOKEN;
if (githubToken) { if (githubToken) {
inputs.github_token = githubToken; inputs.github_token = githubToken;
@@ -47,7 +44,6 @@ async function run(): Promise<void> {
const result = await main(params); const result = await main(params);
// TODO: Set outputs
if (!result.success) { if (!result.success) {
throw new Error(result.error || "Agent execution failed"); throw new Error(result.error || "Agent execution failed");
@@ -58,7 +54,6 @@ async function run(): Promise<void> {
} }
} }
// Run the action
run().catch((error) => { run().catch((error) => {
console.error("Action failed:", error); console.error("Action failed:", error);
process.exit(1); process.exit(1);
-6
View File
@@ -1,7 +1,6 @@
import * as core from "@actions/core"; import * as core from "@actions/core";
import { ClaudeAgent } from "./agents/claude.ts"; import { ClaudeAgent } from "./agents/claude.ts";
// Expected environment variables that should be passed as inputs
export const EXPECTED_INPUTS: string[] = [ export const EXPECTED_INPUTS: string[] = [
"ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY",
"GITHUB_TOKEN", "GITHUB_TOKEN",
@@ -29,24 +28,19 @@ export interface MainResult {
export async function main(params: MainParams): Promise<MainResult> { export async function main(params: MainParams): Promise<MainResult> {
try { try {
// Extract inputs from params
const { inputs, env, cwd } = params; const { inputs, env, cwd } = params;
// Set working directory if different from current
if (cwd !== process.cwd()) { if (cwd !== process.cwd()) {
process.chdir(cwd); process.chdir(cwd);
} }
// Set environment variables
Object.assign(process.env, env); Object.assign(process.env, env);
core.info(`→ Starting agent run with Claude Code`); core.info(`→ Starting agent run with Claude Code`);
// Create and install the Claude agent
const agent = new ClaudeAgent({ apiKey: inputs.anthropic_api_key }); const agent = new ClaudeAgent({ apiKey: inputs.anthropic_api_key });
await agent.install(); await agent.install();
// Execute the agent with the prompt
const result = await agent.execute(inputs.prompt); const result = await agent.execute(inputs.prompt);
if (!result.success) { if (!result.success) {
-19
View File
@@ -8,7 +8,6 @@ import { runAct } from "./utils/act.ts";
import { generateInstallationToken } from "./utils/generate-installation-token.ts"; import { generateInstallationToken } from "./utils/generate-installation-token.ts";
import { setupTestRepo } from "./utils/setup.ts"; import { setupTestRepo } from "./utils/setup.ts";
// Load environment variables from .env file
config(); config();
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@@ -20,17 +19,14 @@ export async function run(
): Promise<{ success: boolean; output?: string | undefined; error?: string | undefined }> { ): Promise<{ success: boolean; output?: string | undefined; error?: string | undefined }> {
try { try {
if (options.act) { if (options.act) {
// Use Docker/act to run the action
console.log("🐳 Running with Docker/act..."); console.log("🐳 Running with Docker/act...");
runAct(prompt); runAct(prompt);
return { success: true }; return { success: true };
} }
// Setup test repository and run directly
const tempDir = join(process.cwd(), ".temp"); const tempDir = join(process.cwd(), ".temp");
setupTestRepo({ tempDir, forceClean: true }); setupTestRepo({ tempDir, forceClean: true });
// Change to the temp directory
const originalCwd = process.cwd(); const originalCwd = process.cwd();
process.chdir(tempDir); process.chdir(tempDir);
@@ -40,7 +36,6 @@ export async function run(
console.log(prompt); console.log(prompt);
console.log("─".repeat(50)); console.log("─".repeat(50));
// Set environment variables from our .env for the action to use
const { EXPECTED_INPUTS } = await import("./main.ts"); const { EXPECTED_INPUTS } = await import("./main.ts");
EXPECTED_INPUTS.forEach((inputName) => { EXPECTED_INPUTS.forEach((inputName) => {
const value = process.env[inputName]; const value = process.env[inputName];
@@ -49,13 +44,11 @@ export async function run(
} }
}); });
// Run main with the new params structure
const inputs: any = { const inputs: any = {
prompt, prompt,
anthropic_api_key: process.env.ANTHROPIC_API_KEY || "", anthropic_api_key: process.env.ANTHROPIC_API_KEY || "",
}; };
// Add optional properties only if they exist
if (process.env.GITHUB_TOKEN) { if (process.env.GITHUB_TOKEN) {
inputs.github_token = process.env.GITHUB_TOKEN; inputs.github_token = process.env.GITHUB_TOKEN;
} }
@@ -65,7 +58,6 @@ export async function run(
inputs.github_installation_token = installationToken; inputs.github_installation_token = installationToken;
console.log("✅ GitHub installation token generated successfully"); console.log("✅ GitHub installation token generated successfully");
// Set installation token in environment for Claude agent MCP configuration
const envWithToken = { const envWithToken = {
...process.env, ...process.env,
GITHUB_INSTALLATION_TOKEN: installationToken, GITHUB_INSTALLATION_TOKEN: installationToken,
@@ -77,7 +69,6 @@ export async function run(
cwd: process.cwd(), cwd: process.cwd(),
}); });
// Change back to original directory
process.chdir(originalCwd); process.chdir(originalCwd);
if (result.success) { if (result.success) {
@@ -97,7 +88,6 @@ export async function run(
} }
} }
// CLI execution when run directly
if (import.meta.url === `file://${process.argv[1]}`) { if (import.meta.url === `file://${process.argv[1]}`) {
const args = arg({ const args = arg({
"--help": Boolean, "--help": Boolean,
@@ -133,15 +123,12 @@ Examples:
let prompt: string; let prompt: string;
if (args["--raw"]) { if (args["--raw"]) {
// Use raw prompt string
prompt = args["--raw"]; prompt = args["--raw"];
} else { } else {
// Load prompt from file
const filePath = args._[0] || "fixtures/basic.txt"; const filePath = args._[0] || "fixtures/basic.txt";
const ext = extname(filePath).toLowerCase(); const ext = extname(filePath).toLowerCase();
let resolvedPath: string; let resolvedPath: string;
// First try as fixtures path
const fixturesPath = join(__dirname, "fixtures", filePath); const fixturesPath = join(__dirname, "fixtures", filePath);
if (existsSync(fixturesPath)) { if (existsSync(fixturesPath)) {
resolvedPath = fixturesPath; resolvedPath = fixturesPath;
@@ -153,13 +140,11 @@ Examples:
switch (ext) { switch (ext) {
case ".txt": { case ".txt": {
// Plain text - pass directly as prompt
prompt = readFileSync(resolvedPath, "utf8").trim(); prompt = readFileSync(resolvedPath, "utf8").trim();
break; break;
} }
case ".json": { case ".json": {
// JSON - stringify and pass as prompt
const content = readFileSync(resolvedPath, "utf8"); const content = readFileSync(resolvedPath, "utf8");
const parsed = JSON.parse(content); const parsed = JSON.parse(content);
prompt = JSON.stringify(parsed, null, 2); prompt = JSON.stringify(parsed, null, 2);
@@ -167,7 +152,6 @@ Examples:
} }
case ".ts": { case ".ts": {
// TypeScript - dynamic import and stringify default export
const fileUrl = pathToFileURL(resolvedPath).href; const fileUrl = pathToFileURL(resolvedPath).href;
const module = await import(fileUrl); const module = await import(fileUrl);
@@ -175,14 +159,11 @@ Examples:
throw new Error(`TypeScript file ${filePath} must have a default export`); throw new Error(`TypeScript file ${filePath} must have a default export`);
} }
// If it's a string, use it directly
if (typeof module.default === "string") { if (typeof module.default === "string") {
prompt = module.default; prompt = module.default;
} else if (typeof module.default === "object" && module.default.prompt) { } else if (typeof module.default === "object" && module.default.prompt) {
// If it's a MainParams object with a prompt field, extract the prompt
prompt = module.default.prompt; prompt = module.default.prompt;
} else { } else {
// Otherwise stringify it
prompt = JSON.stringify(module.default, null, 2); prompt = JSON.stringify(module.default, null, 2);
} }
break; break;
-13
View File
@@ -12,27 +12,21 @@ const tempDir = join(__dirname, "..", ".temp");
const actionPath = join(__dirname, ".."); const actionPath = join(__dirname, "..");
const envPath = join(__dirname, "..", "..", ".env"); const envPath = join(__dirname, "..", "..", ".env");
// Environment variables that should be passed as secrets to the workflow
const ENV_VARS = ["ANTHROPIC_API_KEY", "GITHUB_INSTALLATION_TOKEN"]; const ENV_VARS = ["ANTHROPIC_API_KEY", "GITHUB_INSTALLATION_TOKEN"];
export function runAct(prompt: string): void { export function runAct(prompt: string): void {
// Setup test repository
setupTestRepo({ tempDir }); setupTestRepo({ tempDir });
// Load environment variables
config({ path: envPath }); config({ path: envPath });
// Build action bundles
buildAction(actionPath); buildAction(actionPath);
const workflowPath = join(tempDir, ".github", "workflows", "pullfrog.yml"); const workflowPath = join(tempDir, ".github", "workflows", "pullfrog.yml");
// Create minimal dist for act (avoids pnpm symlink issues)
const distPath = join(actionPath, ".act-dist"); const distPath = join(actionPath, ".act-dist");
console.log("📦 Creating minimal distribution for act..."); console.log("📦 Creating minimal distribution for act...");
execSync(`rm -rf "${distPath}" && mkdir -p "${distPath}"`, { shell: "/bin/bash" }); execSync(`rm -rf "${distPath}" && mkdir -p "${distPath}"`, { shell: "/bin/bash" });
// Copy only necessary files (bundled, no node_modules needed)
["action.yml", "entry.cjs", "index.cjs", "package.json"].forEach((file) => { ["action.yml", "entry.cjs", "index.cjs", "package.json"].forEach((file) => {
const src = join(actionPath, file); const src = join(actionPath, file);
if (existsSync(src)) { if (existsSync(src)) {
@@ -41,8 +35,6 @@ export function runAct(prompt: string): void {
}); });
try { try {
// Build the act command with input directly
// Properly escape the prompt for shell
const escapedPrompt = prompt.replace(/'/g, "'\\''"); const escapedPrompt = prompt.replace(/'/g, "'\\''");
const actCommandParts = [ const actCommandParts = [
@@ -56,14 +48,12 @@ export function runAct(prompt: string): void {
`pullfrog/action@v0=${distPath}`, // Use minimal dist without symlinks `pullfrog/action@v0=${distPath}`, // Use minimal dist without symlinks
]; ];
// Add environment variables as secrets that will be available to the workflow
ENV_VARS.forEach((key) => { ENV_VARS.forEach((key) => {
if (process.env[key]) { if (process.env[key]) {
actCommandParts.push("-s", key); actCommandParts.push("-s", key);
} }
}); });
// We only need the specific ENV_VARS, no need to add other variables
const actCommand = actCommandParts.join(" "); const actCommand = actCommandParts.join(" ");
@@ -73,15 +63,12 @@ export function runAct(prompt: string): void {
console.log("─".repeat(50)); console.log("─".repeat(50));
console.log(""); console.log("");
// Execute act
execSync(actCommand, { execSync(actCommand, {
stdio: "inherit", stdio: "inherit",
cwd: join(__dirname, "..", ".."), cwd: join(__dirname, "..", ".."),
}); });
// Clean up
execSync(`rm -rf "${distPath}"`); execSync(`rm -rf "${distPath}"`);
} catch (error) { } catch (error) {
// Clean up on error
execSync(`rm -rf "${distPath}"`); execSync(`rm -rf "${distPath}"`);
console.error("❌ Act execution failed:", (error as Error).message); console.error("❌ Act execution failed:", (error as Error).message);
process.exit(1); process.exit(1);
-1
View File
@@ -165,7 +165,6 @@ const findInstallationId = async (jwt: string, repoOwner: string, repoName: stri
return installation.id; return installation.id;
} }
} catch { } catch {
// Installation doesn't have access to repository
} }
} }
-7
View File
@@ -14,13 +14,11 @@ export interface InstallationToken {
* Setup GitHub installation token for the action * Setup GitHub installation token for the action
*/ */
export async function setupGitHubInstallationToken(): Promise<string> { export async function setupGitHubInstallationToken(): Promise<string> {
// Check if we have an installation token from inputs or environment
const inputToken = core.getInput("github_installation_token"); const inputToken = core.getInput("github_installation_token");
const envToken = process.env.GITHUB_INSTALLATION_TOKEN; const envToken = process.env.GITHUB_INSTALLATION_TOKEN;
const existingToken = inputToken || envToken; const existingToken = inputToken || envToken;
if (existingToken) { if (existingToken) {
// Mask the existing token in logs for security
core.setSecret(existingToken); core.setSecret(existingToken);
core.info("Using provided GitHub installation token"); core.info("Using provided GitHub installation token");
return existingToken; return existingToken;
@@ -29,11 +27,9 @@ export async function setupGitHubInstallationToken(): Promise<string> {
core.info("Generating OIDC token..."); core.info("Generating OIDC token...");
try { try {
// Generate OIDC token for our API
const oidcToken = await core.getIDToken("pullfrog-api"); const oidcToken = await core.getIDToken("pullfrog-api");
core.info("OIDC token generated successfully"); core.info("OIDC token generated successfully");
// Exchange OIDC token for installation token
const apiUrl = process.env.API_URL || "https://pullfrog.ai"; const apiUrl = process.env.API_URL || "https://pullfrog.ai";
core.info("Exchanging OIDC token for installation token..."); core.info("Exchanging OIDC token for installation token...");
@@ -52,14 +48,11 @@ export async function setupGitHubInstallationToken(): Promise<string> {
); );
} }
// This type is enforced by us when the response is created
const tokenData = (await tokenResponse.json()) as InstallationToken; const tokenData = (await tokenResponse.json()) as InstallationToken;
core.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`); core.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`);
// Mask the token in logs for security
core.setSecret(tokenData.token); core.setSecret(tokenData.token);
// Set the token as an environment variable for this run
process.env.GITHUB_INSTALLATION_TOKEN = tokenData.token; process.env.GITHUB_INSTALLATION_TOKEN = tokenData.token;
return tokenData.token; return tokenData.token;
-2
View File
@@ -17,13 +17,11 @@ export function setupTestRepo(options: SetupOptions): void {
forceClean = false, forceClean = false,
} = options; } = options;
// Handle existing temp directory
if (existsSync(tempDir)) { if (existsSync(tempDir)) {
if (forceClean) { if (forceClean) {
console.log("🗑️ Removing existing .temp directory..."); console.log("🗑️ Removing existing .temp directory...");
rmSync(tempDir, { recursive: true, force: true }); rmSync(tempDir, { recursive: true, force: true });
// Clone the repository
console.log("📦 Cloning pullfrogai/scratch into .temp..."); console.log("📦 Cloning pullfrogai/scratch into .temp...");
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" }); execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
} else { } else {
+1 -10
View File
@@ -28,13 +28,11 @@ export async function spawn(options: SpawnOptions): Promise<SpawnResult> {
let stderrBuffer = ""; let stderrBuffer = "";
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Spawn the child process
const child = nodeSpawn(cmd, args, { const child = nodeSpawn(cmd, args, {
env: env ? { ...process.env, ...env } : process.env, env: env ? { ...process.env, ...env } : process.env,
stdio: ["pipe", "pipe", "pipe"], stdio: ["pipe", "pipe", "pipe"],
}); });
// Set up timeout if specified
let timeoutId: NodeJS.Timeout | undefined; let timeoutId: NodeJS.Timeout | undefined;
let isTimedOut = false; let isTimedOut = false;
@@ -43,7 +41,6 @@ export async function spawn(options: SpawnOptions): Promise<SpawnResult> {
isTimedOut = true; isTimedOut = true;
child.kill("SIGTERM"); child.kill("SIGTERM");
// If SIGTERM doesn't work, use SIGKILL after 5 seconds
setTimeout(() => { setTimeout(() => {
if (!child.killed) { if (!child.killed) {
child.kill("SIGKILL"); child.kill("SIGKILL");
@@ -52,7 +49,6 @@ export async function spawn(options: SpawnOptions): Promise<SpawnResult> {
}, timeout); }, timeout);
} }
// Handle stdout streaming
if (child.stdout) { if (child.stdout) {
child.stdout.on("data", (data: Buffer) => { child.stdout.on("data", (data: Buffer) => {
const chunk = data.toString(); const chunk = data.toString();
@@ -61,7 +57,6 @@ export async function spawn(options: SpawnOptions): Promise<SpawnResult> {
}); });
} }
// Handle stderr streaming
if (child.stderr) { if (child.stderr) {
child.stderr.on("data", (data: Buffer) => { child.stderr.on("data", (data: Buffer) => {
const chunk = data.toString(); const chunk = data.toString();
@@ -70,7 +65,6 @@ export async function spawn(options: SpawnOptions): Promise<SpawnResult> {
}); });
} }
// Handle process completion
child.on("close", (exitCode) => { child.on("close", (exitCode) => {
const durationMs = Date.now() - startTime; const durationMs = Date.now() - startTime;
@@ -91,15 +85,13 @@ export async function spawn(options: SpawnOptions): Promise<SpawnResult> {
}); });
}); });
// Handle process errors child.on("error", (_error) => {
child.on("error", (error) => {
const durationMs = Date.now() - startTime; const durationMs = Date.now() - startTime;
if (timeoutId) { if (timeoutId) {
clearTimeout(timeoutId); clearTimeout(timeoutId);
} }
// Still return buffered output even on error
resolve({ resolve({
stdout: stdoutBuffer, stdout: stdoutBuffer,
stderr: stderrBuffer, stderr: stderrBuffer,
@@ -108,7 +100,6 @@ export async function spawn(options: SpawnOptions): Promise<SpawnResult> {
}); });
}); });
// Send input if provided
if (input && child.stdin) { if (input && child.stdin) {
child.stdin.write(input); child.stdin.write(input);
child.stdin.end(); child.stdin.end();
+2 -32
View File
@@ -22,8 +22,8 @@ export function tableString(
}, },
} = options || {}; } = options || {};
if (options?.title) { if (title) {
rows.unshift([options.title]); rows.unshift([title]);
} }
const tableOutput = table(rows, { const tableOutput = table(rows, {
@@ -140,33 +140,3 @@ export function boxString(
return result; return result;
} }
// /**
// * Create a simple two-column table for displaying key-value pairs
// * @param data - Array of [key, value] pairs
// * @param title - Optional table title
// * @param indent - Optional indentation string
// */
// export function printKeyValueTable(
// data: [string, string][],
// title?: string,
// indent?: string
// ): void {
// const rows: string[][] = [["Key", "Value"], ...data];
// const options: Parameters<typeof printTable>[1] = {};
// if (title !== undefined) options.title = title;
// if (indent !== undefined) options.indent = indent;
// printTable(rows, options);
// }
// /**
// * Create a path resolution table (specific use case)
// * @param pathData - Array of [location, resolvedPath] pairs
// * @param indent - Optional indentation string
// */
// export function printPathTable(pathData: [string, string][], indent?: string): void {
// const rows: string[][] = [["Location", "Resolved path"], ...pathData];
// const options: Parameters<typeof printTable>[1] = {};
// if (indent !== undefined) options.indent = indent;
// printTable(rows, options);
// }