feat: integrate OIDC token exchange in GitHub Action
- Add setupGitHubInstallationToken utility for OIDC token generation - Implement automatic token exchange with Pullfrog API endpoint - Add support for multiple authentication methods (input, env, OIDC) - Create setup utilities for test repository management - Update action entry point to handle new token flow - Add environment variable documentation for API key - Remove large bundled dependencies and optimize build - Support both development and production token workflows
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
#!/usr/bin/env tsx
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join, resolve, extname } from "node:path";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { dirname, extname, join, resolve } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { Command } from "commander";
|
||||
import { config } from "dotenv";
|
||||
import { main } from "./main";
|
||||
import { runAct } from "./utils/act";
|
||||
import { setupTestRepo } from "./utils/setup";
|
||||
|
||||
// Load environment variables from .env file
|
||||
config();
|
||||
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -46,9 +49,7 @@ async function loadPrompt(filePath: string): Promise<string> {
|
||||
const module = await import(fileUrl);
|
||||
|
||||
if (!module.default) {
|
||||
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
|
||||
@@ -66,16 +67,11 @@ async function loadPrompt(filePath: string): Promise<string> {
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(
|
||||
`Unsupported file type: ${ext}. Supported types: .txt, .json, .ts`,
|
||||
);
|
||||
throw new Error(`Unsupported file type: ${ext}. Supported types: .txt, .json, .ts`);
|
||||
}
|
||||
}
|
||||
|
||||
async function runPlay(
|
||||
filePath: string,
|
||||
options: { act?: boolean },
|
||||
): Promise<void> {
|
||||
async function runPlay(filePath: string, options: { act?: boolean }): Promise<void> {
|
||||
try {
|
||||
// Load the prompt from the specified file
|
||||
const prompt = await loadPrompt(filePath);
|
||||
@@ -85,56 +81,9 @@ async function runPlay(
|
||||
console.log("🐳 Running with Docker/act...");
|
||||
runAct(prompt);
|
||||
} else {
|
||||
// Clone the test repository and run directly
|
||||
// Setup test repository and run directly
|
||||
const tempDir = join(process.cwd(), ".temp");
|
||||
const repoUrl = "git@github.com:pullfrogai/scratch.git";
|
||||
|
||||
// Remove existing temp directory if it exists
|
||||
if (existsSync(tempDir)) {
|
||||
console.log("🗑️ Removing existing .temp directory...");
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
// Clone the repository
|
||||
console.log("📦 Cloning pullfrogai/scratch into .temp...");
|
||||
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
||||
|
||||
// List of environment variables to copy to .temp
|
||||
const envVarsToCopy = [
|
||||
"ANTHROPIC_API_KEY",
|
||||
"GITHUB_TOKEN",
|
||||
// Add more environment variables here as needed
|
||||
];
|
||||
|
||||
// Build .env content from the list
|
||||
const envLines = envVarsToCopy
|
||||
.map((varName) => `${varName}=${process.env[varName] || ""}`)
|
||||
.join("\n");
|
||||
|
||||
const envPath = join(tempDir, ".env");
|
||||
writeFileSync(envPath, envLines + "\n");
|
||||
console.log("📝 Created .env file in .temp directory with:");
|
||||
|
||||
let hasRequiredVars = true;
|
||||
envVarsToCopy.forEach((varName) => {
|
||||
const hasValue = !!process.env[varName];
|
||||
console.log(` - ${varName}: ${hasValue ? "✓" : "✗ (missing)"}`);
|
||||
|
||||
// Check for required variables
|
||||
if (varName === "ANTHROPIC_API_KEY" && !hasValue) {
|
||||
hasRequiredVars = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasRequiredVars) {
|
||||
console.warn("\n⚠️ Warning: ANTHROPIC_API_KEY is not set or empty.");
|
||||
console.warn(
|
||||
" Please ensure you have a valid API key in your .env file.",
|
||||
);
|
||||
console.warn(
|
||||
" Get your API key from: https://console.anthropic.com/api-keys\n",
|
||||
);
|
||||
}
|
||||
setupTestRepo({ tempDir, forceClean: true });
|
||||
|
||||
// Change to the temp directory
|
||||
process.chdir(tempDir);
|
||||
@@ -145,8 +94,35 @@ async function runPlay(
|
||||
console.log(prompt);
|
||||
console.log("─".repeat(50));
|
||||
|
||||
// Run main with the params object
|
||||
const result = await main({ prompt });
|
||||
// Set environment variables from our .env for the action to use
|
||||
const { EXPECTED_INPUTS } = await import("./main");
|
||||
EXPECTED_INPUTS.forEach((inputName) => {
|
||||
const value = process.env[inputName];
|
||||
if (value) {
|
||||
process.env[`INPUT_${inputName.toLowerCase()}`] = value;
|
||||
}
|
||||
});
|
||||
|
||||
// Run main with the new params structure
|
||||
const inputs: any = {
|
||||
prompt,
|
||||
anthropic_api_key: process.env.ANTHROPIC_API_KEY || "",
|
||||
};
|
||||
|
||||
// Add optional properties only if they exist
|
||||
if (process.env.GITHUB_TOKEN) {
|
||||
inputs.github_token = process.env.GITHUB_TOKEN;
|
||||
}
|
||||
|
||||
if (process.env.GITHUB_INSTALLATION_TOKEN) {
|
||||
inputs.github_installation_token = process.env.GITHUB_INSTALLATION_TOKEN;
|
||||
}
|
||||
|
||||
const result = await main({
|
||||
inputs,
|
||||
env: process.env as Record<string, string>,
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
console.log("✅ Test completed successfully");
|
||||
@@ -171,15 +147,8 @@ program
|
||||
.name("play")
|
||||
.description("Test the Pullfrog action with various prompts")
|
||||
.version("1.0.0")
|
||||
.argument(
|
||||
"[file]",
|
||||
"Prompt file to use (.txt, .json, or .ts)",
|
||||
"fixtures/basic.txt",
|
||||
)
|
||||
.option(
|
||||
"--act",
|
||||
"Use Docker/act to run the action instead of running directly",
|
||||
)
|
||||
.argument("[file]", "Prompt file to use (.txt, .json, or .ts)", "fixtures/basic.txt")
|
||||
.option("--act", "Use Docker/act to run the action instead of running directly")
|
||||
.action(async (file: string, options: { act?: boolean }) => {
|
||||
await runPlay(file, options);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user