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:
@@ -55,6 +55,14 @@ pnpm dev # Watch mode
|
||||
|
||||
The action is bundled into `entry.cjs` with all dependencies included, eliminating runtime dependency on node_modules.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create `.env` in `/action`:
|
||||
|
||||
```bash
|
||||
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Claude API key
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
- **entry.cjs**: Bundled action entry point (self-contained)
|
||||
|
||||
@@ -10,6 +10,12 @@ inputs:
|
||||
anthropic_api_key:
|
||||
description: "Anthropic API key for Claude Code authentication"
|
||||
required: false
|
||||
github_token:
|
||||
description: "GitHub token for repository access"
|
||||
required: false
|
||||
github_installation_token:
|
||||
description: "GitHub App installation token"
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: "node20"
|
||||
|
||||
@@ -7,43 +7,55 @@
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import { main } from "./main";
|
||||
import { setupGitHubInstallationToken } from "./utils";
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
// Get inputs from GitHub Actions
|
||||
const prompt = core.getInput("prompt", { required: true });
|
||||
const anthropicApiKey = core.getInput("anthropic_api_key", {
|
||||
required: true,
|
||||
});
|
||||
|
||||
if (!anthropicApiKey) {
|
||||
throw new Error("anthropic_api_key is required");
|
||||
}
|
||||
const anthropicApiKey = core.getInput("anthropic_api_key");
|
||||
|
||||
if (!prompt) {
|
||||
throw new Error("prompt is required");
|
||||
}
|
||||
|
||||
// Create params object
|
||||
const params = {
|
||||
// Create params object with new structure
|
||||
const inputs: any = {
|
||||
prompt,
|
||||
anthropicApiKey,
|
||||
anthropic_api_key: anthropicApiKey,
|
||||
};
|
||||
|
||||
// Add optional properties only if they exist
|
||||
const githubToken = core.getInput("github_token") || process.env.GITHUB_TOKEN;
|
||||
if (githubToken) {
|
||||
inputs.github_token = githubToken;
|
||||
}
|
||||
|
||||
const githubInstallationToken =
|
||||
core.getInput("github_installation_token") || process.env.GITHUB_INSTALLATION_TOKEN;
|
||||
if (githubInstallationToken) {
|
||||
inputs.github_installation_token = githubInstallationToken;
|
||||
} else {
|
||||
// Setup GitHub installation token
|
||||
await setupGitHubInstallationToken();
|
||||
}
|
||||
|
||||
const params = {
|
||||
inputs,
|
||||
env: {} as Record<string, string>,
|
||||
cwd: process.cwd(),
|
||||
};
|
||||
|
||||
// Run the main function
|
||||
const result = await main(params);
|
||||
|
||||
// Set outputs
|
||||
core.setOutput("status", result.success ? "success" : "failed");
|
||||
core.setOutput("prompt", prompt);
|
||||
core.setOutput("output", result.output || "");
|
||||
// TODO: Set outputs
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || "Agent execution failed");
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Unknown error occurred";
|
||||
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
||||
core.setFailed(`Action failed: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,7 +1,13 @@
|
||||
import type { MainParams } from "../main";
|
||||
|
||||
const testParams = {
|
||||
prompt: "List all files in the current directory, then create a file called dynamic-test.txt with the content 'This was loaded from a TypeScript file!', then delete it."
|
||||
inputs: {
|
||||
prompt:
|
||||
"List all files in the current directory, then create a file called dynamic-test.txt with the content 'This was loaded from a TypeScript file!', then delete it.",
|
||||
anthropic_api_key: "sk-test-key",
|
||||
},
|
||||
env: {},
|
||||
cwd: process.cwd(),
|
||||
} satisfies MainParams;
|
||||
|
||||
export default testParams;
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
* This exports the main function for programmatic usage
|
||||
*/
|
||||
|
||||
export { main } from "./main";
|
||||
export { ClaudeAgent } from "./agents";
|
||||
export type { Agent, AgentConfig, AgentResult } from "./agents/types";
|
||||
export {
|
||||
type ExecutionInputs,
|
||||
type MainParams,
|
||||
type MainResult,
|
||||
main,
|
||||
} from "./main";
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
import * as core from "@actions/core";
|
||||
import { ClaudeAgent } from "./agents";
|
||||
|
||||
export interface MainParams {
|
||||
// Expected environment variables that should be passed as inputs
|
||||
export const EXPECTED_INPUTS: string[] = [
|
||||
"ANTHROPIC_API_KEY",
|
||||
"GITHUB_TOKEN",
|
||||
"GITHUB_INSTALLATION_TOKEN"
|
||||
];
|
||||
|
||||
export interface ExecutionInputs {
|
||||
prompt: string;
|
||||
anthropicApiKey?: string;
|
||||
anthropic_api_key: string;
|
||||
github_token?: string;
|
||||
github_installation_token?: string;
|
||||
}
|
||||
|
||||
export interface MainParams {
|
||||
inputs: ExecutionInputs;
|
||||
env: Record<string, string>;
|
||||
cwd: string;
|
||||
}
|
||||
|
||||
export interface MainResult {
|
||||
@@ -12,24 +27,28 @@ export interface MainResult {
|
||||
error?: string | undefined;
|
||||
}
|
||||
|
||||
|
||||
export async function main(params: MainParams): Promise<MainResult> {
|
||||
try {
|
||||
// Use provided API key or fall back to environment variable
|
||||
const anthropicApiKey =
|
||||
params.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
|
||||
// Extract inputs from params
|
||||
const { inputs, env, cwd } = params;
|
||||
|
||||
if (!anthropicApiKey) {
|
||||
throw new Error("anthropic_api_key is required");
|
||||
// Set working directory if different from current
|
||||
if (cwd !== process.cwd()) {
|
||||
process.chdir(cwd);
|
||||
}
|
||||
|
||||
// Set environment variables
|
||||
Object.assign(process.env, env);
|
||||
|
||||
core.info(`→ Starting agent run with Claude Code`);
|
||||
|
||||
// Create and install the Claude agent
|
||||
const agent = new ClaudeAgent({ apiKey: anthropicApiKey });
|
||||
const agent = new ClaudeAgent({ apiKey: inputs.anthropic_api_key });
|
||||
await agent.install();
|
||||
|
||||
// Execute the agent with the prompt
|
||||
const result = await agent.execute(params.prompt);
|
||||
const result = await agent.execute(inputs.prompt);
|
||||
|
||||
if (!result.success) {
|
||||
return {
|
||||
@@ -44,8 +63,7 @@ export async function main(params: MainParams): Promise<MainResult> {
|
||||
output: result.output || "",
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Unknown error occurred";
|
||||
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
|
||||
+4
-3
@@ -17,21 +17,22 @@
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build": "node esbuild.config.js",
|
||||
"build:npm": "zshy",
|
||||
"build:dev": "node esbuild.config.js",
|
||||
"prepare": "husky",
|
||||
"play": "tsx --env-file=../.env play.ts"
|
||||
"play": "tsx play.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/core": "^1.11.1",
|
||||
"dotenv": "^17.2.2",
|
||||
"execa": "^9.6.0",
|
||||
"table": "^6.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.0",
|
||||
"commander": "^14.0.0",
|
||||
"dotenv": "^17.2.2",
|
||||
"esbuild": "^0.25.9",
|
||||
"husky": "^9.0.0",
|
||||
"typescript": "^5.3.0",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
+22
-41
@@ -1,59 +1,36 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { existsSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { config, parse } from "dotenv";
|
||||
import { config } from "dotenv";
|
||||
import { buildAction, setupTestRepo } from "./setup";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// 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 {
|
||||
// First, ensure the scratch repo is cloned
|
||||
const tempDir = join(__dirname, "..", ".temp");
|
||||
|
||||
// Check if .temp exists and either reset it or clone it
|
||||
if (existsSync(tempDir)) {
|
||||
console.log("📦 Resetting existing .temp repository...");
|
||||
execSync("git reset --hard HEAD && git clean -fd", {
|
||||
cwd: tempDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
} else {
|
||||
console.log("📦 Cloning pullfrogai/scratch into .temp...");
|
||||
const repoUrl = "git@github.com:pullfrogai/scratch.git";
|
||||
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
||||
}
|
||||
|
||||
const workflowPath = join(tempDir, ".github", "workflows", "pullfrog.yml");
|
||||
const actionPath = join(__dirname, "..");
|
||||
const envPath = join(__dirname, "..", "..", ".env");
|
||||
|
||||
// Load environment variables into process
|
||||
// Setup test repository
|
||||
setupTestRepo({ tempDir });
|
||||
|
||||
// Load environment variables
|
||||
config({ path: envPath });
|
||||
|
||||
// Parse environment variables from .env file to get keys
|
||||
let envVars: string[] = [];
|
||||
try {
|
||||
const content = readFileSync(envPath, "utf8");
|
||||
const parsed = parse(content);
|
||||
envVars = Object.keys(parsed);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Warning: Could not read .env file: ${(error as Error).message}`,
|
||||
);
|
||||
}
|
||||
// Build action bundles
|
||||
buildAction(actionPath);
|
||||
|
||||
// Build fresh bundles with esbuild
|
||||
const actionPath = join(__dirname, "..");
|
||||
console.log("🔨 Building fresh bundles with esbuild...");
|
||||
execSync("node esbuild.config.js", {
|
||||
cwd: actionPath,
|
||||
stdio: "inherit",
|
||||
});
|
||||
const workflowPath = join(tempDir, ".github", "workflows", "pullfrog.yml");
|
||||
|
||||
// Create minimal dist for act (avoids pnpm symlink issues)
|
||||
const distPath = join(actionPath, ".act-dist");
|
||||
console.log("📦 Creating minimal distribution for act...");
|
||||
execSync(`rm -rf "${distPath}" && mkdir -p "${distPath}"`, { shell: true });
|
||||
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) => {
|
||||
@@ -79,11 +56,15 @@ export function runAct(prompt: string): void {
|
||||
`pullfrog/action@v0=${distPath}`, // Use minimal dist without symlinks
|
||||
];
|
||||
|
||||
// Add all environment variables as secrets (without values)
|
||||
envVars.forEach((key) => {
|
||||
actCommandParts.push("-s", key);
|
||||
// Add environment variables as secrets that will be available to the workflow
|
||||
ENV_VARS.forEach((key) => {
|
||||
if (process.env[key]) {
|
||||
actCommandParts.push("-s", key);
|
||||
}
|
||||
});
|
||||
|
||||
// We only need the specific ENV_VARS, no need to add other variables
|
||||
|
||||
const actCommand = actCommandParts.join(" ");
|
||||
|
||||
console.log("🚀 Running act with prompt:");
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
/**
|
||||
* Setup GitHub installation token for the action
|
||||
*/
|
||||
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 envToken = process.env.GITHUB_INSTALLATION_TOKEN;
|
||||
|
||||
const existingToken = inputToken || envToken;
|
||||
if (existingToken) {
|
||||
core.info("Using provided GitHub installation token");
|
||||
return existingToken;
|
||||
}
|
||||
|
||||
core.info("No cached installation token found, generating OIDC token...");
|
||||
|
||||
try {
|
||||
// Generate OIDC token for our API
|
||||
const oidcToken = await core.getIDToken("pullfrog-api");
|
||||
core.info("OIDC token generated successfully");
|
||||
|
||||
// Exchange OIDC token for installation token
|
||||
const apiUrl = process.env.API_URL || "https://pullfrog.ai";
|
||||
|
||||
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.owner}/${tokenData.repository || "all repositories"}`
|
||||
);
|
||||
|
||||
// Set the token as an environment variable for this run
|
||||
process.env.GITHUB_INSTALLATION_TOKEN = tokenData.token;
|
||||
|
||||
return tokenData.token;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to setup GitHub installation token: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from "./files";
|
||||
export * from "./github";
|
||||
export * from "./setup";
|
||||
export * from "./subprocess";
|
||||
export * from "./table";
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { existsSync, rmSync } from "node:fs";
|
||||
|
||||
export interface SetupOptions {
|
||||
tempDir: string;
|
||||
repoUrl?: string;
|
||||
forceClean?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the test repository for running actions
|
||||
*/
|
||||
export function setupTestRepo(options: SetupOptions): void {
|
||||
const {
|
||||
tempDir,
|
||||
repoUrl = "git@github.com:pullfrogai/scratch.git",
|
||||
forceClean = false,
|
||||
} = options;
|
||||
|
||||
// Handle existing temp directory
|
||||
if (existsSync(tempDir)) {
|
||||
if (forceClean) {
|
||||
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" });
|
||||
} else {
|
||||
console.log("📦 Resetting existing .temp repository...");
|
||||
execSync("git reset --hard HEAD && git clean -fd", {
|
||||
cwd: tempDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log("📦 Cloning pullfrogai/scratch into .temp...");
|
||||
execSync(`git clone ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the action bundles
|
||||
*/
|
||||
export function buildAction(actionPath: string): void {
|
||||
console.log("🔨 Building fresh bundles with esbuild...");
|
||||
execSync("node esbuild.config.js", {
|
||||
cwd: actionPath,
|
||||
stdio: "inherit",
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user