Compare commits

...

6 Commits

Author SHA1 Message Date
David Blass 854e3d5e4d add debug 2025-11-06 19:19:26 -05:00
David Blass 5bb1b779a8 iter 2025-11-06 19:13:42 -05:00
David Blass 599264694e try again 2025-11-06 19:08:25 -05:00
David Blass b9c15e9f38 fix github config 2025-11-06 19:05:57 -05:00
Pullfrog Action 7ef44eb254 try esm action 2025-11-06 19:03:19 -05:00
David Blass 5a21d40d27 start mcp server in memory 2025-11-06 17:56:06 -05:00
12 changed files with 508 additions and 262 deletions
+1 -4
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# Ensure lockfile is up to date
echo "🔒 Updating lockfile..."
pnpm install --lockfile-only
@@ -10,4 +7,4 @@ echo "🔨 Building action..."
pnpm build
# Add the built files and lockfile to the commit
git add entry.cjs pnpm-lock.yaml
git add entry.js pnpm-lock.yaml
+1 -1
View File
@@ -13,7 +13,7 @@ inputs:
runs:
using: "node20"
main: "entry.cjs"
main: "entry.js"
branding:
icon: "code"
-39
View File
@@ -1,39 +0,0 @@
#!/usr/bin/env node
/**
* Build entry point - wraps entry.ts to avoid top-level await in CJS output
*/
import * as core from "@actions/core";
import { type } from "arktype";
import { Inputs, main } from "./main.ts";
import packageJson from "./package.json" with { type: "json" };
import { log } from "./utils/cli.ts";
async function run(): Promise<void> {
try {
log.info(`🐸 Running pullfrog/action@${packageJson.version}...`);
const inputsJson = process.env.INPUTS_JSON;
if (!inputsJson) {
throw new Error("INPUTS_JSON environment variable not found");
}
const parsed = type("string.json.parse").assert(inputsJson);
const inputs = Inputs.assert(parsed);
const result = await main(inputs);
if (!result.success) {
throw new Error(result.error || "Agent execution failed");
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
core.setFailed(`Action failed: ${errorMessage}`);
}
}
// Wrap in IIFE to avoid top-level await in CJS
(async () => {
await run();
})();
-183
View File
File diff suppressed because one or more lines are too long
Executable
+391
View File
File diff suppressed because one or more lines are too long
+9 -9
View File
@@ -5,22 +5,22 @@
*/
import * as core from "@actions/core";
import { type } from "arktype";
import { Inputs, main } from "./main.ts";
import { type Inputs, main } from "./main.ts";
import { createMcpServer } from "./mcp/server.ts";
import packageJson from "./package.json" with { type: "json" };
import { log } from "./utils/cli.ts";
// Export createMcpServer so it can be called from the spawned MCP process
export { createMcpServer };
async function run(): Promise<void> {
try {
log.info(`🐸 Running pullfrog/action@${packageJson.version}...`);
const inputsJson = process.env.INPUTS_JSON;
if (!inputsJson) {
throw new Error("INPUTS_JSON environment variable not found");
}
const parsed = type("string.json.parse").assert(inputsJson);
const inputs = Inputs.assert(parsed);
const inputs: Inputs = {
prompt: core.getInput("prompt", { required: true }),
anthropic_api_key: core.getInput("anthropic_api_key") || undefined,
};
const result = await main(inputs);
+14 -6
View File
@@ -3,17 +3,25 @@ import { build } from "esbuild";
// Build the GitHub Action bundle only
// For npm package builds, use zshy (pnpm build:npm)
await build({
entryPoints: ["./entry.build.ts"],
entryPoints: ["./entry.ts"],
bundle: true,
outfile: "./entry.cjs",
format: "cjs",
outfile: "./entry.js",
format: "esm",
platform: "node",
target: "node20",
minify: true,
sourcemap: false,
// @actions/core is provided by GitHub Actions runtime, but we still need it bundled
// for local testing. However, we can mark it to reduce duplication if needed.
external: [],
// Bundle all dependencies - GitHub Actions doesn't have node_modules
// Only mark optional peer dependencies as external
external: [
"@valibot/to-json-schema",
"effect",
"sury",
],
// Provide a proper require shim for CommonJS modules bundled into ESM
banner: {
js: `import { createRequire } from 'module'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; const require = createRequire(import.meta.url); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename);`,
},
// Enable tree-shaking to remove unused code
treeShaking: true,
// Drop console statements in production (but keep for debugging)
+17
View File
@@ -24,6 +24,23 @@ export async function main(inputs: Inputs): Promise<MainResult> {
try {
log.info("Starting agent run...");
// Debug logging for git repo detection
log.debug(`Current working directory: ${process.cwd()}`);
log.debug(`GITHUB_ACTIONS: ${process.env.GITHUB_ACTIONS}`);
log.debug(`GITHUB_WORKSPACE: ${process.env.GITHUB_WORKSPACE}`);
try {
const { execSync } = await import("node:child_process");
const gitDir = execSync("git rev-parse --git-dir", {
encoding: "utf-8",
stdio: "pipe",
}).trim();
log.debug(`Git directory found: ${gitDir}`);
} catch (error) {
log.debug(
`Git directory check failed: ${error instanceof Error ? error.message : String(error)}`
);
}
setupGitConfig();
const githubInstallationToken = await setupGitHubInstallationToken();
+10 -4
View File
@@ -3,11 +3,8 @@
*/
import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
import { fromHere } from "@ark/fs";
import { parseRepoContext } from "../utils/github.ts";
const actionPath = fromHere("..");
export const ghPullfrogMcpName = "gh-pullfrog";
export type McpName = typeof ghPullfrogMcpName;
@@ -18,10 +15,19 @@ export function createMcpConfigs(githubInstallationToken: string): McpConfigs {
const repoContext = parseRepoContext();
const githubRepository = `${repoContext.owner}/${repoContext.name}`;
// Get absolute path to entry.js - use GITHUB_ACTION_PATH if available, otherwise current directory
const entryPath = process.env.GITHUB_ACTION_PATH
? `${process.env.GITHUB_ACTION_PATH}/entry.js`
: `${process.cwd()}/entry.js`;
return {
[ghPullfrogMcpName]: {
command: "node",
args: [`${actionPath}/mcp/server.ts`],
args: [
"--input-type=module",
"-e",
`import('${entryPath.replace(/'/g, "\\'")}').then(m => m.createMcpServer())`,
],
env: {
GITHUB_INSTALLATION_TOKEN: githubInstallationToken,
GITHUB_REPOSITORY: githubRepository,
+15 -13
View File
@@ -8,18 +8,20 @@ import { PullRequestInfoTool } from "./prInfo.ts";
import { ReviewTool } from "./review.ts";
import { addTools } from "./shared.ts";
const server = new FastMCP({
name: "gh-pullfrog",
version: "0.0.1",
});
export function createMcpServer(): void {
const server = new FastMCP({
name: "gh-pullfrog",
version: "0.0.1",
});
addTools(server, [
CreateCommentTool,
EditCommentTool,
IssueTool,
PullRequestTool,
ReviewTool,
PullRequestInfoTool,
]);
addTools(server, [
CreateCommentTool,
EditCommentTool,
IssueTool,
PullRequestTool,
ReviewTool,
PullRequestInfoTool,
]);
server.start();
server.start();
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@pullfrog/action",
"version": "0.0.74",
"version": "0.0.79",
"type": "module",
"files": [
"index.js",
+49 -2
View File
@@ -41,17 +41,64 @@ export function setupTestRepo(options: SetupOptions): void {
/**
* Setup git configuration to avoid identity errors
* Only runs in GitHub Actions environment to avoid overwriting local git config
*/
export function setupGitConfig(): void {
// Only set up git config in GitHub Actions environment
// In local development, use the user's existing git config
if (!process.env.GITHUB_ACTIONS) {
log.info("⚠️ Skipping git configuration setup (not in GitHub Actions)");
return;
}
// Debug logging for git repo detection
log.debug(`setupGitConfig: Current working directory: ${process.cwd()}`);
log.debug(`setupGitConfig: GITHUB_WORKSPACE: ${process.env.GITHUB_WORKSPACE}`);
// Check if we're in a git repository before trying to set config
log.debug(`setupGitConfig: Checking for git repository...`);
try {
const gitDir = execSync("git rev-parse --git-dir", { encoding: "utf-8", stdio: "pipe" }).trim();
log.debug(`setupGitConfig: Git directory found: ${gitDir}`);
} catch (error) {
log.warning(
`⚠️ Skipping git configuration setup (not in a git repository): ${error instanceof Error ? error.message : String(error)}`
);
try {
const dirContents = execSync("ls -la", { encoding: "utf-8", stdio: "pipe" }).trim();
log.debug(`setupGitConfig: Current directory contents:\n${dirContents}`);
} catch {
// Ignore if ls fails
}
return;
}
log.info("🔧 Setting up git configuration...");
execSync('git config user.email "action@pullfrog.ai"', { stdio: "inherit" });
execSync('git config user.name "Pullfrog Action"', { stdio: "inherit" });
try {
execSync('git config user.email "action@pullfrog.ai"', { stdio: "pipe" });
execSync('git config user.name "Pullfrog Action"', { stdio: "pipe" });
log.debug("setupGitConfig: ✓ Git configuration set successfully");
} catch (error) {
// If git config fails, log warning but don't fail the action
// This can happen if we're not in a git repo or git isn't available
log.warning(
`Failed to set git config: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Setup git authentication using GitHub token
* Only runs in GitHub Actions environment to avoid breaking local git remotes
*/
export function setupGitAuth(githubToken: string, repoContext: RepoContext): void {
// Only set up git auth in GitHub Actions environment
// In local testing, this would overwrite the real git remote with fake credentials
if (!process.env.GITHUB_ACTIONS) {
log.info("⚠️ Skipping git authentication setup (not in GitHub Actions)");
return;
}
log.info("🔐 Setting up git authentication...");
// Remove existing git auth headers that actions/checkout might have set