From e13c5eed00f9944a90c58952596251cbe9c3525e Mon Sep 17 00:00:00 2001 From: ssalbdivad Date: Tue, 23 Sep 2025 12:48:35 -0400 Subject: [PATCH] cleanup, add InstallationToken type --- agents/claude.ts | 6 +- agents/index.ts | 2 - entry.cjs | 148 +++++++++++++++++++++++----------------------- entry.ts | 8 +-- fixtures/basic.ts | 2 +- index.ts | 6 +- main.ts | 2 +- package.json | 4 +- play.ts | 8 +-- tsconfig.json | 57 ++++++------------ utils/act.ts | 10 ++-- utils/github.ts | 13 +++- utils/index.ts | 5 -- 13 files changed, 127 insertions(+), 144 deletions(-) delete mode 100644 agents/index.ts delete mode 100644 utils/index.ts diff --git a/agents/claude.ts b/agents/claude.ts index ead6c2d..d098ea6 100644 --- a/agents/claude.ts +++ b/agents/claude.ts @@ -1,8 +1,8 @@ import { access, constants } from "node:fs/promises"; import * as core from "@actions/core"; -import { boxString, tableString } from "../utils"; -import { spawn } from "../utils/subprocess"; -import type { Agent, AgentConfig, AgentResult } from "./types"; +import { spawn } from "../utils/subprocess.ts"; +import { boxString, tableString } from "../utils/table.ts"; +import type { Agent, AgentConfig, AgentResult } from "./types.ts"; /** * Claude Code agent implementation diff --git a/agents/index.ts b/agents/index.ts deleted file mode 100644 index 15b5d40..0000000 --- a/agents/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./claude"; -export * from "./types"; diff --git a/entry.cjs b/entry.cjs index bf3d116..293c607 100755 --- a/entry.cjs +++ b/entry.cjs @@ -25497,53 +25497,11 @@ var require_src = __commonJS({ var core4 = __toESM(require_core(), 1); // main.ts -var core3 = __toESM(require_core(), 1); +var core2 = __toESM(require_core(), 1); // agents/claude.ts var import_promises = require("node:fs/promises"); -var core2 = __toESM(require_core(), 1); - -// utils/github.ts var core = __toESM(require_core(), 1); -async function setupGitHubInstallationToken() { - const inputToken = core.getInput("github_installation_token"); - const envToken = process.env.GITHUB_INSTALLATION_TOKEN; - const existingToken = inputToken || envToken; - if (existingToken) { - core.setSecret(existingToken); - core.info("Using provided GitHub installation token"); - return existingToken; - } - core.info("Generating OIDC token..."); - try { - const oidcToken = await core.getIDToken("pullfrog-api"); - core.info("OIDC token generated successfully"); - const apiUrl = process.env.API_URL || "https://pullfrog.ai"; - core.info("Exchanging OIDC token for installation token..."); - const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, { - method: "POST", - headers: { - Authorization: `Bearer ${oidcToken}`, - "Content-Type": "application/json" - } - }); - if (!tokenResponse.ok) { - const errorText = await tokenResponse.text(); - throw new Error( - `Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText} - ${errorText}` - ); - } - const tokenData = await tokenResponse.json(); - core.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`); - core.setSecret(tokenData.token); - process.env.GITHUB_INSTALLATION_TOKEN = tokenData.token; - return tokenData.token; - } catch (error2) { - throw new Error( - `Failed to setup GitHub installation token: ${error2 instanceof Error ? error2.message : "Unknown error"}` - ); - } -} // utils/subprocess.ts var import_node_child_process = require("node:child_process"); @@ -25742,10 +25700,10 @@ var ClaudeAgent = class { */ async install() { if (await this.isClaudeInstalled()) { - core2.info("Claude Code is already installed, skipping installation"); + core.info("Claude Code is already installed, skipping installation"); return; } - core2.info("Installing Claude Code..."); + core.info("Installing Claude Code..."); try { const result = await spawn({ cmd: "bash", @@ -25760,7 +25718,7 @@ var ClaudeAgent = class { if (result.exitCode !== 0) { throw new Error(`Installation failed with exit code ${result.exitCode}: ${result.stderr}`); } - core2.info("Claude Code installed successfully"); + core.info("Claude Code installed successfully"); } catch (error2) { throw new Error(`Failed to install Claude Code: ${error2}`); } @@ -25769,7 +25727,7 @@ var ClaudeAgent = class { * Execute Claude Code with the given prompt */ async execute(prompt) { - core2.info("Running Claude Code..."); + core.info("Running Claude Code..."); try { const claudePath = `${process.env.HOME}/.local/bin/claude`; console.log(boxString(prompt, { title: "Prompt" })); @@ -25784,7 +25742,7 @@ var ClaudeAgent = class { const env = { ANTHROPIC_API_KEY: this.apiKey }; - core2.startGroup("\u{1F504} Run details"); + core.startGroup("\u{1F504} Run details"); this.runStats = { toolsUsed: 0, turns: 0, @@ -25818,11 +25776,11 @@ Stderr: ${result.stderr}` ); } const duration = Date.now() - this.runStats.startTime; - core2.info( + core.info( `\u{1F4CA} Run Summary: ${this.runStats.toolsUsed} tools used, ${this.runStats.turns} turns, ${duration}ms duration` ); - core2.info("\u2705 Task complete."); - core2.endGroup(); + core.info("\u2705 Task complete."); + core.endGroup(); return { success: true, output: finalResult, @@ -25835,7 +25793,7 @@ Stderr: ${result.stderr}` }; } catch (error2) { try { - core2.endGroup(); + core.endGroup(); } catch { } const errorMessage = error2 instanceof Error ? error2.message : "Unknown error"; @@ -25853,8 +25811,8 @@ function processJSONChunk(chunk, agent) { switch (parsedChunk.type) { case "system": if (parsedChunk.subtype === "init") { - core2.info(`\u{1F680} Starting Claude Code session...`); - core2.info( + core.info(`\u{1F680} Starting Claude Code session...`); + core.info( tableString([ ["model", parsedChunk.model], ["cwd", parsedChunk.cwd], @@ -25880,51 +25838,51 @@ function processJSONChunk(chunk, agent) { for (const content of parsedChunk.message.content) { if (content.type === "text") { if (content.text.trim()) { - core2.info(boxString(content.text.trim(), { title: "Claude Code" })); + core.info(boxString(content.text.trim(), { title: "Claude Code" })); } } else if (content.type === "tool_use") { if (agent) { agent.runStats.toolsUsed++; } const toolName = content.name; - core2.info(`\u2192 ${toolName}`); + core.info(`\u2192 ${toolName}`); if (content.input) { const input = content.input; if (input.description) { - core2.info(` \u2514\u2500 ${input.description}`); + core.info(` \u2514\u2500 ${input.description}`); } if (input.command) { - core2.info(` \u2514\u2500 command: ${input.command}`); + core.info(` \u2514\u2500 command: ${input.command}`); } if (input.file_path) { - core2.info(` \u2514\u2500 file: ${input.file_path}`); + core.info(` \u2514\u2500 file: ${input.file_path}`); } if (input.content) { const contentPreview = input.content.length > 100 ? `${input.content.substring(0, 100)}...` : input.content; - core2.info(` \u2514\u2500 content: ${contentPreview}`); + core.info(` \u2514\u2500 content: ${contentPreview}`); } if (input.query) { - core2.info(` \u2514\u2500 query: ${input.query}`); + core.info(` \u2514\u2500 query: ${input.query}`); } if (input.pattern) { - core2.info(` \u2514\u2500 pattern: ${input.pattern}`); + core.info(` \u2514\u2500 pattern: ${input.pattern}`); } if (input.url) { - core2.info(` \u2514\u2500 url: ${input.url}`); + core.info(` \u2514\u2500 url: ${input.url}`); } if (input.edits && Array.isArray(input.edits)) { - core2.info(` \u2514\u2500 edits: ${input.edits.length} changes`); + core.info(` \u2514\u2500 edits: ${input.edits.length} changes`); input.edits.forEach((edit, index) => { if (edit.file_path) { - core2.info(` ${index + 1}. ${edit.file_path}`); + core.info(` ${index + 1}. ${edit.file_path}`); } }); } if (input.task) { - core2.info(` \u2514\u2500 task: ${input.task}`); + core.info(` \u2514\u2500 task: ${input.task}`); } if (input.bash_command) { - core2.info(` \u2514\u2500 bash_command: ${input.bash_command}`); + core.info(` \u2514\u2500 bash_command: ${input.bash_command}`); } } } @@ -25936,7 +25894,7 @@ function processJSONChunk(chunk, agent) { for (const content of parsedChunk.message.content) { if (content.type === "tool_result") { if (content.is_error) { - core2.warning(`\u274C Tool error: ${content.content}`); + core.warning(`\u274C Tool error: ${content.content}`); } else { const _resultContent = content.content.trim(); } @@ -25946,7 +25904,7 @@ function processJSONChunk(chunk, agent) { break; case "result": if (parsedChunk.subtype === "success") { - core2.info( + core.info( tableString([ ["Cost", `$${parsedChunk.total_cost_usd?.toFixed(4) || "0.0000"}`], ["Input Tokens", parsedChunk.usage?.input_tokens || 0], @@ -25956,16 +25914,16 @@ function processJSONChunk(chunk, agent) { ]) ); } else { - core2.error(`\u274C Failed: ${parsedChunk.error || "Unknown error"}`); + core.error(`\u274C Failed: ${parsedChunk.error || "Unknown error"}`); } break; default: - core2.debug(`\u{1F4E6} Unknown chunk type: ${parsedChunk.type}`); + core.debug(`\u{1F4E6} Unknown chunk type: ${parsedChunk.type}`); break; } } catch (error2) { - core2.debug(`Failed to parse chunk: ${error2}`); - core2.debug(`Raw chunk: ${chunk.substring(0, 200)}...`); + core.debug(`Failed to parse chunk: ${error2}`); + core.debug(`Raw chunk: ${chunk.substring(0, 200)}...`); } } @@ -25977,7 +25935,7 @@ async function main(params) { process.chdir(cwd); } Object.assign(process.env, env); - core3.info(`\u2192 Starting agent run with Claude Code`); + core2.info(`\u2192 Starting agent run with Claude Code`); const agent = new ClaudeAgent({ apiKey: inputs.anthropic_api_key }); await agent.install(); const result = await agent.execute(inputs.prompt); @@ -26001,6 +25959,48 @@ async function main(params) { } } +// utils/github.ts +var core3 = __toESM(require_core(), 1); +async function setupGitHubInstallationToken() { + const inputToken = core3.getInput("github_installation_token"); + const envToken = process.env.GITHUB_INSTALLATION_TOKEN; + const existingToken = inputToken || envToken; + if (existingToken) { + core3.setSecret(existingToken); + core3.info("Using provided GitHub installation token"); + return existingToken; + } + core3.info("Generating OIDC token..."); + try { + const oidcToken = await core3.getIDToken("pullfrog-api"); + core3.info("OIDC token generated successfully"); + const apiUrl = process.env.API_URL || "https://pullfrog.ai"; + core3.info("Exchanging OIDC token for installation token..."); + const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, { + method: "POST", + headers: { + Authorization: `Bearer ${oidcToken}`, + "Content-Type": "application/json" + } + }); + if (!tokenResponse.ok) { + const errorText = await tokenResponse.text(); + throw new Error( + `Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText} - ${errorText}` + ); + } + const tokenData = await tokenResponse.json(); + core3.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`); + core3.setSecret(tokenData.token); + process.env.GITHUB_INSTALLATION_TOKEN = tokenData.token; + return tokenData.token; + } catch (error2) { + throw new Error( + `Failed to setup GitHub installation token: ${error2 instanceof Error ? error2.message : "Unknown error"}` + ); + } +} + // entry.ts async function run() { try { diff --git a/entry.ts b/entry.ts index 596a553..0fffe73 100644 --- a/entry.ts +++ b/entry.ts @@ -6,8 +6,8 @@ */ import * as core from "@actions/core"; -import { main } from "./main"; -import { setupGitHubInstallationToken } from "./utils"; +import { type MainParams, main } from "./main.ts"; +import { setupGitHubInstallationToken } from "./utils/github.ts"; async function run(): Promise { try { @@ -40,9 +40,9 @@ async function run(): Promise { await setupGitHubInstallationToken(); } - const params = { + const params: MainParams = { inputs, - env: {} as Record, + env: {}, cwd: process.cwd(), }; diff --git a/fixtures/basic.ts b/fixtures/basic.ts index 659e793..27e12c6 100644 --- a/fixtures/basic.ts +++ b/fixtures/basic.ts @@ -1,4 +1,4 @@ -import type { MainParams } from "../main"; +import type { MainParams } from "../main.ts"; const testParams = { inputs: { diff --git a/index.ts b/index.ts index c8d7bb2..c4ccb55 100644 --- a/index.ts +++ b/index.ts @@ -3,11 +3,11 @@ * This exports the main function for programmatic usage */ -export { ClaudeAgent } from "./agents"; -export type { Agent, AgentConfig, AgentResult } from "./agents/types"; +export { ClaudeAgent } from "./agents/claude.ts"; +export type { Agent, AgentConfig, AgentResult } from "./agents/types.ts"; export { type ExecutionInputs, type MainParams, type MainResult, main, -} from "./main"; +} from "./main.ts"; diff --git a/main.ts b/main.ts index af9141b..e9650f3 100644 --- a/main.ts +++ b/main.ts @@ -1,5 +1,5 @@ import * as core from "@actions/core"; -import { ClaudeAgent } from "./agents"; +import { ClaudeAgent } from "./agents/claude.ts"; // Expected environment variables that should be passed as inputs export const EXPECTED_INPUTS: string[] = [ diff --git a/package.json b/package.json index 94b9c92..d14468f 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,12 @@ "build:npm": "zshy", "build:dev": "node esbuild.config.js", "prepare": "husky", - "play": "tsx play.ts" + "play": "node play.ts", + "upDeps": "pnpm up --latest" }, "dependencies": { "@actions/core": "^1.11.1", + "@octokit/webhooks-types": "^7.6.1", "dotenv": "^17.2.2", "execa": "^9.6.0", "table": "^6.9.0" diff --git a/play.ts b/play.ts index 0b70442..8ed52ff 100644 --- a/play.ts +++ b/play.ts @@ -3,9 +3,9 @@ import { dirname, extname, join, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import arg from "arg"; import { config } from "dotenv"; -import { main } from "./main"; -import { runAct } from "./utils/act"; -import { setupTestRepo } from "./utils/setup"; +import { main } from "./main.ts"; +import { runAct } from "./utils/act.ts"; +import { setupTestRepo } from "./utils/setup.ts"; // Load environment variables from .env file config(); @@ -40,7 +40,7 @@ export async function run( console.log("─".repeat(50)); // Set environment variables from our .env for the action to use - const { EXPECTED_INPUTS } = await import("./main"); + const { EXPECTED_INPUTS } = await import("./main.ts"); EXPECTED_INPUTS.forEach((inputName) => { const value = process.env[inputName]; if (value) { diff --git a/tsconfig.json b/tsconfig.json index a06b546..3aacf21 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,45 +1,22 @@ { - // Visit https://aka.ms/tsconfig to read more about this file "compilerOptions": { - // File Layout - // "rootDir": "./src", "outDir": "./dist", - - // Environment Settings - // See also https://aka.ms/tsconfig/module - "module": "esnext", - "moduleResolution": "bundler", - "target": "esnext", - "types": [], - // For nodejs: - // "lib": ["esnext"], - // "types": ["node"], - // and npm install -D @types/node - - // Other Outputs - "sourceMap": true, - "declaration": true, - "declarationMap": true, - - // Stricter Typechecking Options - "noUncheckedIndexedAccess": true, - "exactOptionalPropertyTypes": true, - - // Style Options - // "noImplicitReturns": true, - // "noImplicitOverride": true, - // "noUnusedLocals": true, - // "noUnusedParameters": true, - // "noFallthroughCasesInSwitch": true, - // "noPropertyAccessFromIndexSignature": true, - - // Recommended Options - "strict": true, - "jsx": "react-jsx", - "verbatimModuleSyntax": true, - "isolatedModules": true, - "noUncheckedSideEffectImports": true, - "moduleDetection": "force", - "skipLibCheck": true + "module": "NodeNext", + "target": "ESNext", + "moduleResolution": "NodeNext", + "lib": ["ESNext"], + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "strict": true, + "noUncheckedSideEffectImports": true, + "declaration": true, + "verbatimModuleSyntax": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "stripInternal": true, + "moduleDetection": "force" } } diff --git a/utils/act.ts b/utils/act.ts index 6a14de7..de8e479 100644 --- a/utils/act.ts +++ b/utils/act.ts @@ -3,19 +3,19 @@ import { existsSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { config } from "dotenv"; -import { buildAction, setupTestRepo } from "./setup"; +import { buildAction, setupTestRepo } from "./setup.ts"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); +const tempDir = join(__dirname, "..", ".temp"); +const actionPath = join(__dirname, ".."); +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"]; export function runAct(prompt: string): void { - const tempDir = join(__dirname, "..", ".temp"); - const actionPath = join(__dirname, ".."); - const envPath = join(__dirname, "..", "..", ".env"); - // Setup test repository setupTestRepo({ tempDir }); diff --git a/utils/github.ts b/utils/github.ts index 30b17ec..743302a 100644 --- a/utils/github.ts +++ b/utils/github.ts @@ -1,5 +1,15 @@ import * as core from "@actions/core"; +export interface InstallationToken { + token: string; + expires_at: string; + installation_id: number; + repository: string; + ref: string; + runner_environment: string; + owner?: string; +} + /** * Setup GitHub installation token for the action */ @@ -42,7 +52,8 @@ export async function setupGitHubInstallationToken(): Promise { ); } - const tokenData = await tokenResponse.json(); + // This type is enforced by us when the response is created + const tokenData = (await tokenResponse.json()) as InstallationToken; core.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`); // Mask the token in logs for security diff --git a/utils/index.ts b/utils/index.ts deleted file mode 100644 index 6aa298f..0000000 --- a/utils/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./files"; -export * from "./github"; -export * from "./setup"; -export * from "./subprocess"; -export * from "./table";