Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 710fdd0fa4 | |||
| 4f5ee28b8a | |||
| 806458b95a | |||
| 2c856e3337 | |||
| a93c34e61b | |||
| cd20491d22 | |||
| 1a6ce6728c | |||
| 3b39f2c8d8 | |||
| ec0eeb1d18 | |||
| 8ef805b9fc | |||
| 6e93fd9a72 | |||
| 9567d84442 | |||
| d79564db5e | |||
| a7a0e87fd8 | |||
| 7050b8de75 | |||
| 2fc3ddee16 | |||
| 284d9733dd | |||
| 94e2b5f6e0 | |||
| 03810d574e | |||
| f52e94c612 | |||
| 9444a0e208 | |||
| 2296060d04 | |||
| 458bfe18a0 | |||
| 4cfb9b5008 | |||
| 06542e382a | |||
| bcdf6ab5fb | |||
| 314f669f10 | |||
| a24275e21b | |||
| 872e620342 | |||
| 6d9c6fd2b1 | |||
| 008021df1c | |||
| d6bc0fdd64 | |||
| 8fd0328109 | |||
| a1f87ce118 | |||
| 3e7122611c | |||
| 9459803aaa | |||
| f74a75cfac |
@@ -29,12 +29,12 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "24"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --no-frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Get package version
|
- name: Get package version
|
||||||
id: version
|
id: version
|
||||||
@@ -60,21 +60,6 @@ jobs:
|
|||||||
echo "✅ Tag ${{ steps.version.outputs.tag }} does not exist - will create release"
|
echo "✅ Tag ${{ steps.version.outputs.tag }} does not exist - will create release"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Verify built files are up to date
|
|
||||||
if: steps.check_tag.outputs.exists == 'false'
|
|
||||||
run: |
|
|
||||||
# Check if there are any uncommitted changes
|
|
||||||
if [[ -n $(git status --porcelain) ]]; then
|
|
||||||
echo "❌ Error: There are uncommitted changes. Built files should be committed via pre-commit hook."
|
|
||||||
git status
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✅ All built files are up to date"
|
|
||||||
|
|
||||||
- name: Build for npm with zshy
|
|
||||||
if: steps.check_tag.outputs.exists == 'false'
|
|
||||||
run: pnpm build:npm
|
|
||||||
|
|
||||||
- name: Create and push tags
|
- name: Create and push tags
|
||||||
if: steps.check_tag.outputs.exists == 'false'
|
if: steps.check_tag.outputs.exists == 'false'
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
# Build the action before committing
|
|
||||||
echo "🔨 Building action..."
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# Add the built files to the commit
|
|
||||||
git add entry.cjs
|
|
||||||
+26
-6
@@ -10,17 +10,37 @@ inputs:
|
|||||||
anthropic_api_key:
|
anthropic_api_key:
|
||||||
description: "Anthropic API key for Claude Code authentication"
|
description: "Anthropic API key for Claude Code authentication"
|
||||||
required: false
|
required: false
|
||||||
github_token:
|
|
||||||
description: "GitHub token for repository access"
|
|
||||||
required: false
|
|
||||||
github_installation_token:
|
github_installation_token:
|
||||||
description: "GitHub App installation token"
|
description: "GitHub App installation token"
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "node20"
|
using: "composite"
|
||||||
main: "entry.cjs"
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
- name: Setup Node.js 24
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
cache: "pnpm"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ github.action_path }}
|
||||||
|
- name: Run agent
|
||||||
|
run: node entry.ts
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ github.action_path }}
|
||||||
|
env:
|
||||||
|
INPUTS_JSON: ${{ toJSON(inputs) }}
|
||||||
|
|
||||||
branding:
|
branding:
|
||||||
icon: "code"
|
icon: "code"
|
||||||
color: "orange"
|
color: "green"
|
||||||
|
|||||||
+32
-108
@@ -16,14 +16,11 @@ 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 +40,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,21 +47,22 @@ 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),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
throw new Error(`Installation failed with exit code ${result.exitCode}: ${result.stderr}`);
|
throw new Error(
|
||||||
|
`Installation failed with exit code ${result.exitCode}: ${result.stderr}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
core.info("Claude Code installed successfully");
|
core.info("Claude Code installed successfully");
|
||||||
@@ -79,47 +76,36 @@ 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",
|
||||||
"--output-format",
|
"--output-format",
|
||||||
"stream-json",
|
"stream-json",
|
||||||
"--verbose",
|
"--verbose",
|
||||||
|
"--debug",
|
||||||
"--permission-mode",
|
"--permission-mode",
|
||||||
"bypassPermissions",
|
"bypassPermissions",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Add MCP configuration if GitHub credentials are available
|
if (!process.env.GITHUB_INSTALLATION_TOKEN) {
|
||||||
if (
|
throw new Error(
|
||||||
process.env.GITHUB_INSTALLATION_TOKEN &&
|
"GITHUB_INSTALLATION_TOKEN is required for GitHub integration"
|
||||||
process.env.REPO_OWNER &&
|
|
||||||
process.env.REPO_NAME
|
|
||||||
) {
|
|
||||||
const mcpConfig = createMcpConfig(
|
|
||||||
process.env.GITHUB_INSTALLATION_TOKEN,
|
|
||||||
process.env.REPO_OWNER,
|
|
||||||
process.env.REPO_NAME
|
|
||||||
);
|
);
|
||||||
console.log("📋 MCP Config:", mcpConfig);
|
|
||||||
args.push("--mcp-config", mcpConfig);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mcpConfig = createMcpConfig(process.env.GITHUB_INSTALLATION_TOKEN);
|
||||||
|
console.log("📋 MCP Config:", mcpConfig);
|
||||||
|
args.push("--mcp-config", mcpConfig);
|
||||||
|
|
||||||
const env = {
|
const env = {
|
||||||
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,13 +156,11 @@ 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 {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Failed to execute Claude Code: ${errorMessage}`,
|
error: `Failed to execute Claude Code: ${errorMessage}`,
|
||||||
@@ -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,14 +181,17 @@ 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],
|
||||||
["cwd", parsedChunk.cwd],
|
["cwd", parsedChunk.cwd],
|
||||||
["permission_mode", parsedChunk.permissionMode],
|
["permission_mode", parsedChunk.permissionMode],
|
||||||
["tools", parsedChunk.tools?.length ? `${parsedChunk.tools.length} tools` : "none"],
|
[
|
||||||
|
"tools",
|
||||||
|
parsedChunk.tools?.length
|
||||||
|
? `${parsedChunk.tools.length} tools`
|
||||||
|
: "none",
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"mcp_servers",
|
"mcp_servers",
|
||||||
parsedChunk.mcp_servers?.length
|
parsedChunk.mcp_servers?.length
|
||||||
@@ -264,39 +211,33 @@ 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 +266,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 +275,14 @@ 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 +295,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,19 +303,12 @@ 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([
|
||||||
["Cost", `$${parsedChunk.total_cost_usd?.toFixed(4) || "0.0000"}`],
|
[
|
||||||
|
"Cost",
|
||||||
|
`$${parsedChunk.total_cost_usd?.toFixed(4) || "0.0000"}`,
|
||||||
|
],
|
||||||
["Input Tokens", parsedChunk.usage?.input_tokens || 0],
|
["Input Tokens", parsedChunk.usage?.input_tokens || 0],
|
||||||
["Output Tokens", parsedChunk.usage?.output_tokens || 0],
|
["Output Tokens", parsedChunk.usage?.output_tokens || 0],
|
||||||
["Duration", `${parsedChunk.duration_ms}ms`],
|
["Duration", `${parsedChunk.duration_ms}ms`],
|
||||||
@@ -396,7 +321,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,52 +2,26 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry point for GitHub Action
|
* Entry point for GitHub Action
|
||||||
* This file is bundled to entry.cjs and called directly by GitHub Actions
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import { type ExecutionInputs, type MainParams, main } from "./main.ts";
|
import { type } from "arktype";
|
||||||
import { setupGitHubInstallationToken } from "./utils/github.ts";
|
import { Inputs, main } from "./main.ts";
|
||||||
|
import packageJson from "./package.json" with { type: "json" };
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Get inputs from GitHub Actions
|
console.log(`🐸 Running pullfrog/action@${packageJson.version}...`);
|
||||||
const prompt = core.getInput("prompt", { required: true });
|
|
||||||
const anthropic_api_key = core.getInput("anthropic_api_key");
|
|
||||||
|
|
||||||
if (!prompt) {
|
const inputsJson = process.env.INPUTS_JSON;
|
||||||
throw new Error("prompt is required");
|
if (!inputsJson) {
|
||||||
|
throw new Error("INPUTS_JSON environment variable not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create params object with new structure
|
const parsed = type("string.json.parse").assert(inputsJson);
|
||||||
const inputs: ExecutionInputs = {
|
const inputs = Inputs.assert(parsed);
|
||||||
prompt,
|
|
||||||
anthropic_api_key,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add optional properties only if they exist
|
const result = await main(inputs);
|
||||||
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 {
|
|
||||||
await setupGitHubInstallationToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
const params: MainParams = {
|
|
||||||
inputs,
|
|
||||||
env: {},
|
|
||||||
cwd: process.cwd(),
|
|
||||||
};
|
|
||||||
|
|
||||||
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,8 +32,4 @@ async function run(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the action
|
await run();
|
||||||
run().catch((error) => {
|
|
||||||
console.error("Action failed:", error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { build } from "esbuild";
|
|
||||||
|
|
||||||
// Build the GitHub Action bundle only
|
|
||||||
// For npm package builds, use zshy (pnpm build:npm)
|
|
||||||
await build({
|
|
||||||
entryPoints: ["./entry.ts"],
|
|
||||||
bundle: true,
|
|
||||||
outfile: "./entry.cjs",
|
|
||||||
format: "cjs",
|
|
||||||
platform: "node",
|
|
||||||
target: "node20",
|
|
||||||
minify: false,
|
|
||||||
sourcemap: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("✅ Build completed successfully!");
|
|
||||||
@@ -6,8 +6,7 @@
|
|||||||
export { ClaudeAgent } from "./agents/claude.ts";
|
export { ClaudeAgent } from "./agents/claude.ts";
|
||||||
export type { Agent, AgentConfig, AgentResult } from "./agents/types.ts";
|
export type { Agent, AgentConfig, AgentResult } from "./agents/types.ts";
|
||||||
export {
|
export {
|
||||||
type ExecutionInputs,
|
type ActionInputs as ExecutionInputs,
|
||||||
type MainParams,
|
|
||||||
type MainResult,
|
type MainResult,
|
||||||
main,
|
main,
|
||||||
} from "./main.ts";
|
} from "./main.ts";
|
||||||
|
|||||||
@@ -1,25 +1,14 @@
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
|
import { type } from "arktype";
|
||||||
import { ClaudeAgent } from "./agents/claude.ts";
|
import { ClaudeAgent } from "./agents/claude.ts";
|
||||||
|
|
||||||
// Expected environment variables that should be passed as inputs
|
export const Inputs = type({
|
||||||
export const EXPECTED_INPUTS: string[] = [
|
prompt: "string",
|
||||||
"ANTHROPIC_API_KEY",
|
"anthropic_api_key?": "string | undefined",
|
||||||
"GITHUB_TOKEN",
|
"github_installation_token?": "string | undefined",
|
||||||
"GITHUB_INSTALLATION_TOKEN",
|
});
|
||||||
];
|
|
||||||
|
|
||||||
export interface ExecutionInputs {
|
export type ActionInputs = typeof Inputs.infer;
|
||||||
prompt: 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 {
|
export interface MainResult {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@@ -27,26 +16,13 @@ export interface MainResult {
|
|||||||
error?: string | undefined;
|
error?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function main(params: MainParams): Promise<MainResult> {
|
export async function main(inputs: ActionInputs): Promise<MainResult> {
|
||||||
try {
|
try {
|
||||||
// Extract inputs from params
|
|
||||||
const { inputs, env, cwd } = params;
|
|
||||||
|
|
||||||
// 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`);
|
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) {
|
||||||
|
|||||||
+15
-8
@@ -1,13 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
* Simple MCP configuration helper for adding our minimal GitHub comment server
|
* Simple MCP configuration helper for adding our minimal GitHub comment server
|
||||||
*/
|
*/
|
||||||
const actionPath = process.env.GITHUB_ACTION_PATH || process.cwd();
|
// const actionPath = process.env.GITHUB_ACTION_PATH || process.cwd();
|
||||||
|
|
||||||
|
import { fromHere } from "@ark/fs";
|
||||||
|
|
||||||
|
const actionPath = fromHere("..");
|
||||||
|
|
||||||
|
export function createMcpConfig(githubInstallationToken: string) {
|
||||||
|
const githubRepository = process.env.GITHUB_REPOSITORY;
|
||||||
|
if (!githubRepository) {
|
||||||
|
throw new Error(
|
||||||
|
"GITHUB_REPOSITORY environment variable is required for MCP GitHub integration"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function createMcpConfig(
|
|
||||||
githubInstallationToken: string,
|
|
||||||
repoOwner: string,
|
|
||||||
repoName: string
|
|
||||||
) {
|
|
||||||
return JSON.stringify(
|
return JSON.stringify(
|
||||||
{
|
{
|
||||||
mcpServers: {
|
mcpServers: {
|
||||||
@@ -16,8 +23,8 @@ export function createMcpConfig(
|
|||||||
args: [`${actionPath}/mcp/server.ts`],
|
args: [`${actionPath}/mcp/server.ts`],
|
||||||
env: {
|
env: {
|
||||||
GITHUB_INSTALLATION_TOKEN: githubInstallationToken,
|
GITHUB_INSTALLATION_TOKEN: githubInstallationToken,
|
||||||
REPO_OWNER: repoOwner,
|
GITHUB_REPOSITORY: githubRepository,
|
||||||
REPO_NAME: repoName,
|
LOG_LEVEL: "debug",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+7
-12
@@ -5,15 +5,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|||||||
import { Octokit } from "@octokit/rest";
|
import { Octokit } from "@octokit/rest";
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { resolveRepoContext } from "../utils/repo-context.ts";
|
||||||
// Get repository information from environment variables
|
|
||||||
const REPO_OWNER = process.env.REPO_OWNER;
|
|
||||||
const REPO_NAME = process.env.REPO_NAME;
|
|
||||||
|
|
||||||
if (!REPO_OWNER || !REPO_NAME) {
|
|
||||||
console.error("Error: REPO_OWNER and REPO_NAME environment variables are required");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = new McpServer({
|
const server = new McpServer({
|
||||||
name: "Minimal GitHub Issue Comment Server",
|
name: "Minimal GitHub Issue Comment Server",
|
||||||
@@ -42,13 +34,16 @@ server.tool(
|
|||||||
throw new Error("GITHUB_INSTALLATION_TOKEN environment variable is required");
|
throw new Error("GITHUB_INSTALLATION_TOKEN environment variable is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve repository context from environment
|
||||||
|
const repoContext = resolveRepoContext();
|
||||||
|
|
||||||
const octokit = new Octokit({
|
const octokit = new Octokit({
|
||||||
auth: githubInstallationToken,
|
auth: githubInstallationToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await octokit.rest.issues.createComment({
|
const result = await octokit.rest.issues.createComment({
|
||||||
owner: REPO_OWNER,
|
owner: repoContext.owner,
|
||||||
repo: REPO_NAME,
|
repo: repoContext.name,
|
||||||
issue_number: issueNumber,
|
issue_number: issueNumber,
|
||||||
body: body,
|
body: body,
|
||||||
});
|
});
|
||||||
@@ -94,4 +89,4 @@ async function runServer() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
runServer().catch(console.error);
|
await runServer();
|
||||||
|
|||||||
+11
-19
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pullfrog/action",
|
"name": "@pullfrog/action",
|
||||||
"version": "0.0.13",
|
"version": "0.0.45",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -12,37 +12,29 @@
|
|||||||
"main.js",
|
"main.js",
|
||||||
"main.d.ts"
|
"main.d.ts"
|
||||||
],
|
],
|
||||||
"directories": {
|
|
||||||
"example": "examples"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"build": "node esbuild.config.js",
|
|
||||||
"build:npm": "zshy",
|
|
||||||
"build:dev": "node esbuild.config.js",
|
|
||||||
"prepare": "husky",
|
|
||||||
"play": "node play.ts",
|
"play": "node play.ts",
|
||||||
"upDeps": "pnpm up --latest"
|
"upDeps": "pnpm up --latest",
|
||||||
|
"lock": "pnpm --ignore-workspace install"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.11.1",
|
"@actions/core": "^1.11.1",
|
||||||
"@modelcontextprotocol/sdk": "^1.17.5",
|
"@ark/fs": "0.49.0",
|
||||||
|
"@modelcontextprotocol/sdk": "^1.20.0",
|
||||||
"@octokit/rest": "^22.0.0",
|
"@octokit/rest": "^22.0.0",
|
||||||
"@octokit/webhooks-types": "^7.6.1",
|
"@octokit/webhooks-types": "^7.6.1",
|
||||||
"arktype": "^2.1.22",
|
"arktype": "^2.1.22",
|
||||||
"dotenv": "^17.2.2",
|
"dotenv": "^17.2.3",
|
||||||
"execa": "^9.6.0",
|
"execa": "^9.6.0",
|
||||||
"table": "^6.9.0"
|
"table": "^6.9.0",
|
||||||
|
"zod": "^3.24.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.10.0",
|
"@types/node": "^24.7.2",
|
||||||
"arg": "^5.0.2",
|
"arg": "^5.0.2",
|
||||||
"esbuild": "^0.25.9",
|
"typescript": "^5.9.3"
|
||||||
"husky": "^9.0.0",
|
|
||||||
"typescript": "^5.3.0",
|
|
||||||
"zshy": "^0.4.1",
|
|
||||||
"zod": "^3.24.4"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -50,7 +42,7 @@
|
|||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/pullfrog/action/issues"
|
"url": "https://github.com/pullfrog/action/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,35 +1,32 @@
|
|||||||
import { existsSync, readFileSync } from "node:fs";
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
import { dirname, extname, join, resolve } from "node:path";
|
import { extname, join, resolve } from "node:path";
|
||||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
|
import { fromHere } from "@ark/fs";
|
||||||
import arg from "arg";
|
import arg from "arg";
|
||||||
import { config } from "dotenv";
|
import { config } from "dotenv";
|
||||||
import { main } from "./main.ts";
|
import { type ActionInputs, main } from "./main.ts";
|
||||||
|
import packageJson from "./package.json" with { type: "json" };
|
||||||
import { runAct } from "./utils/act.ts";
|
import { runAct } from "./utils/act.ts";
|
||||||
|
import { setupGitHubInstallationToken } from "./utils/github.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 __dirname = dirname(__filename);
|
|
||||||
|
|
||||||
export async function run(
|
export async function run(
|
||||||
prompt: string,
|
prompt: string,
|
||||||
options: { act?: boolean } = {}
|
options: { act?: boolean } = {}
|
||||||
): Promise<{ success: boolean; output?: string | undefined; error?: string | undefined }> {
|
): Promise<{ success: boolean; output?: string | undefined; error?: string | undefined }> {
|
||||||
try {
|
try {
|
||||||
|
console.log(`🐸 Running pullfrog/action@${packageJson.version}...`);
|
||||||
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);
|
||||||
|
|
||||||
@@ -39,37 +36,20 @@ 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
|
console.log("🔑 Setting up GitHub installation token...");
|
||||||
const { EXPECTED_INPUTS } = await import("./main.ts");
|
const installationToken = await setupGitHubInstallationToken();
|
||||||
EXPECTED_INPUTS.forEach((inputName) => {
|
process.env.GITHUB_INSTALLATION_TOKEN = installationToken;
|
||||||
const value = process.env[inputName];
|
|
||||||
if (value) {
|
|
||||||
process.env[`INPUT_${inputName.toLowerCase()}`] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Run main with the new params structure
|
console.log("✅ GitHub installation token setup successfully");
|
||||||
const inputs: any = {
|
|
||||||
|
const inputs: ActionInputs = {
|
||||||
prompt,
|
prompt,
|
||||||
anthropic_api_key: process.env.ANTHROPIC_API_KEY || "",
|
anthropic_api_key: process.env.ANTHROPIC_API_KEY,
|
||||||
|
github_installation_token: installationToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add optional properties only if they exist
|
const result = await main(inputs);
|
||||||
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(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Change back to original directory
|
|
||||||
process.chdir(originalCwd);
|
process.chdir(originalCwd);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -89,7 +69,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,
|
||||||
@@ -125,16 +104,13 @@ 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 = fromHere("fixtures", filePath);
|
||||||
const fixturesPath = join(__dirname, "fixtures", filePath);
|
|
||||||
if (existsSync(fixturesPath)) {
|
if (existsSync(fixturesPath)) {
|
||||||
resolvedPath = fixturesPath;
|
resolvedPath = fixturesPath;
|
||||||
} else if (existsSync(filePath)) {
|
} else if (existsSync(filePath)) {
|
||||||
@@ -145,13 +121,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);
|
||||||
@@ -159,7 +133,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);
|
||||||
|
|
||||||
@@ -167,14 +140,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;
|
||||||
|
|||||||
Generated
+31
-454
@@ -11,9 +11,12 @@ importers:
|
|||||||
'@actions/core':
|
'@actions/core':
|
||||||
specifier: ^1.11.1
|
specifier: ^1.11.1
|
||||||
version: 1.11.1
|
version: 1.11.1
|
||||||
|
'@ark/fs':
|
||||||
|
specifier: 0.49.0
|
||||||
|
version: 0.49.0
|
||||||
'@modelcontextprotocol/sdk':
|
'@modelcontextprotocol/sdk':
|
||||||
specifier: ^1.17.5
|
specifier: ^1.20.0
|
||||||
version: 1.19.1
|
version: 1.20.0
|
||||||
'@octokit/rest':
|
'@octokit/rest':
|
||||||
specifier: ^22.0.0
|
specifier: ^22.0.0
|
||||||
version: 22.0.0
|
version: 22.0.0
|
||||||
@@ -24,7 +27,7 @@ importers:
|
|||||||
specifier: ^2.1.22
|
specifier: ^2.1.22
|
||||||
version: 2.1.22
|
version: 2.1.22
|
||||||
dotenv:
|
dotenv:
|
||||||
specifier: ^17.2.2
|
specifier: ^17.2.3
|
||||||
version: 17.2.3
|
version: 17.2.3
|
||||||
execa:
|
execa:
|
||||||
specifier: ^9.6.0
|
specifier: ^9.6.0
|
||||||
@@ -32,28 +35,19 @@ importers:
|
|||||||
table:
|
table:
|
||||||
specifier: ^6.9.0
|
specifier: ^6.9.0
|
||||||
version: 6.9.0
|
version: 6.9.0
|
||||||
devDependencies:
|
|
||||||
'@types/node':
|
|
||||||
specifier: ^20.10.0
|
|
||||||
version: 20.19.19
|
|
||||||
arg:
|
|
||||||
specifier: ^5.0.2
|
|
||||||
version: 5.0.2
|
|
||||||
esbuild:
|
|
||||||
specifier: ^0.25.9
|
|
||||||
version: 0.25.10
|
|
||||||
husky:
|
|
||||||
specifier: ^9.0.0
|
|
||||||
version: 9.1.7
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.3.0
|
|
||||||
version: 5.9.3
|
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.24.4
|
specifier: ^3.24.4
|
||||||
version: 3.25.76
|
version: 3.25.76
|
||||||
zshy:
|
devDependencies:
|
||||||
specifier: ^0.4.1
|
'@types/node':
|
||||||
version: 0.4.3(typescript@5.9.3)
|
specifier: ^24.7.2
|
||||||
|
version: 24.7.2
|
||||||
|
arg:
|
||||||
|
specifier: ^5.0.2
|
||||||
|
version: 5.0.2
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.9.3
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -69,188 +63,23 @@ packages:
|
|||||||
'@actions/io@1.1.3':
|
'@actions/io@1.1.3':
|
||||||
resolution: {integrity: sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==}
|
resolution: {integrity: sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==}
|
||||||
|
|
||||||
|
'@ark/fs@0.49.0':
|
||||||
|
resolution: {integrity: sha512-AEjAQS/bu1CGIRiKK/XLaQ73cSJHixfexq28wNt+kBpQ0h1RwVIVzaGsn/+5IWw6DEbR7LB+3hil5gzrzEeyZQ==}
|
||||||
|
|
||||||
'@ark/schema@0.49.0':
|
'@ark/schema@0.49.0':
|
||||||
resolution: {integrity: sha512-GphZBLpW72iS0v4YkeUtV3YIno35Gimd7+ezbPO9GwEi9kzdUrPVjvf6aXSBAfHikaFc/9pqZOpv3pOXnC71tw==}
|
resolution: {integrity: sha512-GphZBLpW72iS0v4YkeUtV3YIno35Gimd7+ezbPO9GwEi9kzdUrPVjvf6aXSBAfHikaFc/9pqZOpv3pOXnC71tw==}
|
||||||
|
|
||||||
'@ark/util@0.49.0':
|
'@ark/util@0.49.0':
|
||||||
resolution: {integrity: sha512-/BtnX7oCjNkxi2vi6y1399b+9xd1jnCrDYhZ61f0a+3X8x8DxlK52VgEEzyuC2UQMPACIfYrmHkhD3lGt2GaMA==}
|
resolution: {integrity: sha512-/BtnX7oCjNkxi2vi6y1399b+9xd1jnCrDYhZ61f0a+3X8x8DxlK52VgEEzyuC2UQMPACIfYrmHkhD3lGt2GaMA==}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [ppc64]
|
|
||||||
os: [aix]
|
|
||||||
|
|
||||||
'@esbuild/android-arm64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [android]
|
|
||||||
|
|
||||||
'@esbuild/android-arm@0.25.10':
|
|
||||||
resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [arm]
|
|
||||||
os: [android]
|
|
||||||
|
|
||||||
'@esbuild/android-x64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [android]
|
|
||||||
|
|
||||||
'@esbuild/darwin-arm64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [darwin]
|
|
||||||
|
|
||||||
'@esbuild/darwin-x64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [darwin]
|
|
||||||
|
|
||||||
'@esbuild/freebsd-arm64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [freebsd]
|
|
||||||
|
|
||||||
'@esbuild/freebsd-x64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [freebsd]
|
|
||||||
|
|
||||||
'@esbuild/linux-arm64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@esbuild/linux-arm@0.25.10':
|
|
||||||
resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [arm]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@esbuild/linux-ia32@0.25.10':
|
|
||||||
resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [ia32]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@esbuild/linux-loong64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [loong64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@esbuild/linux-mips64el@0.25.10':
|
|
||||||
resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [mips64el]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@esbuild/linux-ppc64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [ppc64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@esbuild/linux-riscv64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [riscv64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@esbuild/linux-s390x@0.25.10':
|
|
||||||
resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [s390x]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@esbuild/linux-x64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@esbuild/netbsd-arm64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [netbsd]
|
|
||||||
|
|
||||||
'@esbuild/netbsd-x64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [netbsd]
|
|
||||||
|
|
||||||
'@esbuild/openbsd-arm64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [openbsd]
|
|
||||||
|
|
||||||
'@esbuild/openbsd-x64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [openbsd]
|
|
||||||
|
|
||||||
'@esbuild/openharmony-arm64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [openharmony]
|
|
||||||
|
|
||||||
'@esbuild/sunos-x64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [sunos]
|
|
||||||
|
|
||||||
'@esbuild/win32-arm64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
'@esbuild/win32-ia32@0.25.10':
|
|
||||||
resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [ia32]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
'@esbuild/win32-x64@0.25.10':
|
|
||||||
resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
'@fastify/busboy@2.1.1':
|
'@fastify/busboy@2.1.1':
|
||||||
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
'@modelcontextprotocol/sdk@1.19.1':
|
'@modelcontextprotocol/sdk@1.20.0':
|
||||||
resolution: {integrity: sha512-3Y2h3MZKjec1eAqSTBclATlX+AbC6n1LgfVzRMJLt3v6w0RCYgwLrjbxPDbhsYHt6Wdqc/aCceNJYgj448ELQQ==}
|
resolution: {integrity: sha512-kOQ4+fHuT4KbR2iq2IjeV32HiihueuOf1vJkq18z08CLZ1UQrTc8BXJpVfxZkq45+inLLD+D4xx4nBjUelJa4Q==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
|
||||||
engines: {node: '>= 8'}
|
|
||||||
|
|
||||||
'@nodelib/fs.stat@2.0.5':
|
|
||||||
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
|
|
||||||
engines: {node: '>= 8'}
|
|
||||||
|
|
||||||
'@nodelib/fs.walk@1.2.8':
|
|
||||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
|
||||||
engines: {node: '>= 8'}
|
|
||||||
|
|
||||||
'@octokit/auth-token@6.0.0':
|
'@octokit/auth-token@6.0.0':
|
||||||
resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==}
|
resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==}
|
||||||
engines: {node: '>= 20'}
|
engines: {node: '>= 20'}
|
||||||
@@ -313,8 +142,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
'@types/node@20.19.19':
|
'@types/node@24.7.2':
|
||||||
resolution: {integrity: sha512-pb1Uqj5WJP7wrcbLU7Ru4QtA0+3kAXrkutGiD26wUKzSMgNNaPARTUDQmElUXp64kh3cWdou3Q0C7qwwxqSFmg==}
|
resolution: {integrity: sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==}
|
||||||
|
|
||||||
accepts@2.0.0:
|
accepts@2.0.0:
|
||||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||||
@@ -351,10 +180,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
|
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
braces@3.0.3:
|
|
||||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
bytes@3.1.2:
|
bytes@3.1.2:
|
||||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -441,11 +266,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
esbuild@0.25.10:
|
|
||||||
resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
escape-html@1.0.3:
|
escape-html@1.0.3:
|
||||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||||
|
|
||||||
@@ -481,27 +301,16 @@ packages:
|
|||||||
fast-deep-equal@3.1.3:
|
fast-deep-equal@3.1.3:
|
||||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||||
|
|
||||||
fast-glob@3.3.3:
|
|
||||||
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
|
|
||||||
engines: {node: '>=8.6.0'}
|
|
||||||
|
|
||||||
fast-json-stable-stringify@2.1.0:
|
fast-json-stable-stringify@2.1.0:
|
||||||
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||||
|
|
||||||
fast-uri@3.1.0:
|
fast-uri@3.1.0:
|
||||||
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
|
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
|
||||||
|
|
||||||
fastq@1.19.1:
|
|
||||||
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
|
||||||
|
|
||||||
figures@6.1.0:
|
figures@6.1.0:
|
||||||
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
|
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
fill-range@7.1.1:
|
|
||||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
finalhandler@2.1.0:
|
finalhandler@2.1.0:
|
||||||
resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==}
|
resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -529,10 +338,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
|
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
|
||||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
|
|
||||||
gopd@1.2.0:
|
gopd@1.2.0:
|
||||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -553,11 +358,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
|
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
|
|
||||||
husky@9.1.7:
|
|
||||||
resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
iconv-lite@0.6.3:
|
iconv-lite@0.6.3:
|
||||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -573,22 +373,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
is-extglob@2.1.1:
|
|
||||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
is-fullwidth-code-point@3.0.0:
|
is-fullwidth-code-point@3.0.0:
|
||||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
is-glob@4.0.3:
|
|
||||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
is-number@7.0.0:
|
|
||||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
|
||||||
engines: {node: '>=0.12.0'}
|
|
||||||
|
|
||||||
is-plain-obj@4.1.0:
|
is-plain-obj@4.1.0:
|
||||||
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -628,14 +416,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
|
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
merge2@1.4.1:
|
|
||||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
|
||||||
engines: {node: '>= 8'}
|
|
||||||
|
|
||||||
micromatch@4.0.8:
|
|
||||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
|
||||||
engines: {node: '>=8.6'}
|
|
||||||
|
|
||||||
mime-db@1.54.0:
|
mime-db@1.54.0:
|
||||||
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
|
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -689,10 +469,6 @@ packages:
|
|||||||
path-to-regexp@8.3.0:
|
path-to-regexp@8.3.0:
|
||||||
resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
|
resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
|
||||||
|
|
||||||
picomatch@2.3.1:
|
|
||||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
|
||||||
engines: {node: '>=8.6'}
|
|
||||||
|
|
||||||
pkce-challenge@5.0.0:
|
pkce-challenge@5.0.0:
|
||||||
resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==}
|
resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==}
|
||||||
engines: {node: '>=16.20.0'}
|
engines: {node: '>=16.20.0'}
|
||||||
@@ -713,9 +489,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
queue-microtask@1.2.3:
|
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
|
||||||
|
|
||||||
range-parser@1.2.1:
|
range-parser@1.2.1:
|
||||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -728,17 +501,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
reusify@1.1.0:
|
|
||||||
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
|
||||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
|
||||||
|
|
||||||
router@2.2.0:
|
router@2.2.0:
|
||||||
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
|
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
|
||||||
engines: {node: '>= 18'}
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
run-parallel@1.2.0:
|
|
||||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
|
||||||
|
|
||||||
safe-buffer@5.2.1:
|
safe-buffer@5.2.1:
|
||||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||||
|
|
||||||
@@ -812,10 +578,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
|
resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
|
||||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
|
||||||
engines: {node: '>=8.0'}
|
|
||||||
|
|
||||||
toidentifier@1.0.1:
|
toidentifier@1.0.1:
|
||||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
@@ -833,8 +595,8 @@ packages:
|
|||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
undici-types@6.21.0:
|
undici-types@7.14.0:
|
||||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
resolution: {integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==}
|
||||||
|
|
||||||
undici@5.29.0:
|
undici@5.29.0:
|
||||||
resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==}
|
resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==}
|
||||||
@@ -878,12 +640,6 @@ packages:
|
|||||||
zod@3.25.76:
|
zod@3.25.76:
|
||||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||||
|
|
||||||
zshy@0.4.3:
|
|
||||||
resolution: {integrity: sha512-onhD/E4OVAOFION4DmXza93+x8XItEdyZibxrtDu2rpAYIg63cKhmYqLwXmK0UNfqchvyabbBDUcJbQg097dZA==}
|
|
||||||
hasBin: true
|
|
||||||
peerDependencies:
|
|
||||||
typescript: '>5.5.0'
|
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@actions/core@1.11.1':
|
'@actions/core@1.11.1':
|
||||||
@@ -902,93 +658,17 @@ snapshots:
|
|||||||
|
|
||||||
'@actions/io@1.1.3': {}
|
'@actions/io@1.1.3': {}
|
||||||
|
|
||||||
|
'@ark/fs@0.49.0': {}
|
||||||
|
|
||||||
'@ark/schema@0.49.0':
|
'@ark/schema@0.49.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ark/util': 0.49.0
|
'@ark/util': 0.49.0
|
||||||
|
|
||||||
'@ark/util@0.49.0': {}
|
'@ark/util@0.49.0': {}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/android-arm64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/android-arm@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/android-x64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/darwin-arm64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/darwin-x64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/freebsd-arm64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/freebsd-x64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/linux-arm64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/linux-arm@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/linux-ia32@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/linux-loong64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/linux-mips64el@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/linux-ppc64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/linux-riscv64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/linux-s390x@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/linux-x64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/netbsd-arm64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/netbsd-x64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/openbsd-arm64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/openbsd-x64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/openharmony-arm64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/sunos-x64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/win32-arm64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/win32-ia32@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@esbuild/win32-x64@0.25.10':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@fastify/busboy@2.1.1': {}
|
'@fastify/busboy@2.1.1': {}
|
||||||
|
|
||||||
'@modelcontextprotocol/sdk@1.19.1':
|
'@modelcontextprotocol/sdk@1.20.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
content-type: 1.0.5
|
content-type: 1.0.5
|
||||||
@@ -1005,18 +685,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
|
||||||
dependencies:
|
|
||||||
'@nodelib/fs.stat': 2.0.5
|
|
||||||
run-parallel: 1.2.0
|
|
||||||
|
|
||||||
'@nodelib/fs.stat@2.0.5': {}
|
|
||||||
|
|
||||||
'@nodelib/fs.walk@1.2.8':
|
|
||||||
dependencies:
|
|
||||||
'@nodelib/fs.scandir': 2.1.5
|
|
||||||
fastq: 1.19.1
|
|
||||||
|
|
||||||
'@octokit/auth-token@6.0.0': {}
|
'@octokit/auth-token@6.0.0': {}
|
||||||
|
|
||||||
'@octokit/core@7.0.5':
|
'@octokit/core@7.0.5':
|
||||||
@@ -1085,9 +753,9 @@ snapshots:
|
|||||||
|
|
||||||
'@sindresorhus/merge-streams@4.0.0': {}
|
'@sindresorhus/merge-streams@4.0.0': {}
|
||||||
|
|
||||||
'@types/node@20.19.19':
|
'@types/node@24.7.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.21.0
|
undici-types: 7.14.0
|
||||||
|
|
||||||
accepts@2.0.0:
|
accepts@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1139,10 +807,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
braces@3.0.3:
|
|
||||||
dependencies:
|
|
||||||
fill-range: 7.1.1
|
|
||||||
|
|
||||||
bytes@3.1.2: {}
|
bytes@3.1.2: {}
|
||||||
|
|
||||||
call-bind-apply-helpers@1.0.2:
|
call-bind-apply-helpers@1.0.2:
|
||||||
@@ -1210,35 +874,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
|
|
||||||
esbuild@0.25.10:
|
|
||||||
optionalDependencies:
|
|
||||||
'@esbuild/aix-ppc64': 0.25.10
|
|
||||||
'@esbuild/android-arm': 0.25.10
|
|
||||||
'@esbuild/android-arm64': 0.25.10
|
|
||||||
'@esbuild/android-x64': 0.25.10
|
|
||||||
'@esbuild/darwin-arm64': 0.25.10
|
|
||||||
'@esbuild/darwin-x64': 0.25.10
|
|
||||||
'@esbuild/freebsd-arm64': 0.25.10
|
|
||||||
'@esbuild/freebsd-x64': 0.25.10
|
|
||||||
'@esbuild/linux-arm': 0.25.10
|
|
||||||
'@esbuild/linux-arm64': 0.25.10
|
|
||||||
'@esbuild/linux-ia32': 0.25.10
|
|
||||||
'@esbuild/linux-loong64': 0.25.10
|
|
||||||
'@esbuild/linux-mips64el': 0.25.10
|
|
||||||
'@esbuild/linux-ppc64': 0.25.10
|
|
||||||
'@esbuild/linux-riscv64': 0.25.10
|
|
||||||
'@esbuild/linux-s390x': 0.25.10
|
|
||||||
'@esbuild/linux-x64': 0.25.10
|
|
||||||
'@esbuild/netbsd-arm64': 0.25.10
|
|
||||||
'@esbuild/netbsd-x64': 0.25.10
|
|
||||||
'@esbuild/openbsd-arm64': 0.25.10
|
|
||||||
'@esbuild/openbsd-x64': 0.25.10
|
|
||||||
'@esbuild/openharmony-arm64': 0.25.10
|
|
||||||
'@esbuild/sunos-x64': 0.25.10
|
|
||||||
'@esbuild/win32-arm64': 0.25.10
|
|
||||||
'@esbuild/win32-ia32': 0.25.10
|
|
||||||
'@esbuild/win32-x64': 0.25.10
|
|
||||||
|
|
||||||
escape-html@1.0.3: {}
|
escape-html@1.0.3: {}
|
||||||
|
|
||||||
etag@1.8.1: {}
|
etag@1.8.1: {}
|
||||||
@@ -1304,30 +939,14 @@ snapshots:
|
|||||||
|
|
||||||
fast-deep-equal@3.1.3: {}
|
fast-deep-equal@3.1.3: {}
|
||||||
|
|
||||||
fast-glob@3.3.3:
|
|
||||||
dependencies:
|
|
||||||
'@nodelib/fs.stat': 2.0.5
|
|
||||||
'@nodelib/fs.walk': 1.2.8
|
|
||||||
glob-parent: 5.1.2
|
|
||||||
merge2: 1.4.1
|
|
||||||
micromatch: 4.0.8
|
|
||||||
|
|
||||||
fast-json-stable-stringify@2.1.0: {}
|
fast-json-stable-stringify@2.1.0: {}
|
||||||
|
|
||||||
fast-uri@3.1.0: {}
|
fast-uri@3.1.0: {}
|
||||||
|
|
||||||
fastq@1.19.1:
|
|
||||||
dependencies:
|
|
||||||
reusify: 1.1.0
|
|
||||||
|
|
||||||
figures@6.1.0:
|
figures@6.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-unicode-supported: 2.1.0
|
is-unicode-supported: 2.1.0
|
||||||
|
|
||||||
fill-range@7.1.1:
|
|
||||||
dependencies:
|
|
||||||
to-regex-range: 5.0.1
|
|
||||||
|
|
||||||
finalhandler@2.1.0:
|
finalhandler@2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
@@ -1368,10 +987,6 @@ snapshots:
|
|||||||
'@sec-ant/readable-stream': 0.4.1
|
'@sec-ant/readable-stream': 0.4.1
|
||||||
is-stream: 4.0.1
|
is-stream: 4.0.1
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
|
||||||
dependencies:
|
|
||||||
is-glob: 4.0.3
|
|
||||||
|
|
||||||
gopd@1.2.0: {}
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
has-symbols@1.1.0: {}
|
has-symbols@1.1.0: {}
|
||||||
@@ -1390,8 +1005,6 @@ snapshots:
|
|||||||
|
|
||||||
human-signals@8.0.1: {}
|
human-signals@8.0.1: {}
|
||||||
|
|
||||||
husky@9.1.7: {}
|
|
||||||
|
|
||||||
iconv-lite@0.6.3:
|
iconv-lite@0.6.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
@@ -1404,16 +1017,8 @@ snapshots:
|
|||||||
|
|
||||||
ipaddr.js@1.9.1: {}
|
ipaddr.js@1.9.1: {}
|
||||||
|
|
||||||
is-extglob@2.1.1: {}
|
|
||||||
|
|
||||||
is-fullwidth-code-point@3.0.0: {}
|
is-fullwidth-code-point@3.0.0: {}
|
||||||
|
|
||||||
is-glob@4.0.3:
|
|
||||||
dependencies:
|
|
||||||
is-extglob: 2.1.1
|
|
||||||
|
|
||||||
is-number@7.0.0: {}
|
|
||||||
|
|
||||||
is-plain-obj@4.1.0: {}
|
is-plain-obj@4.1.0: {}
|
||||||
|
|
||||||
is-promise@4.0.0: {}
|
is-promise@4.0.0: {}
|
||||||
@@ -1436,13 +1041,6 @@ snapshots:
|
|||||||
|
|
||||||
merge-descriptors@2.0.0: {}
|
merge-descriptors@2.0.0: {}
|
||||||
|
|
||||||
merge2@1.4.1: {}
|
|
||||||
|
|
||||||
micromatch@4.0.8:
|
|
||||||
dependencies:
|
|
||||||
braces: 3.0.3
|
|
||||||
picomatch: 2.3.1
|
|
||||||
|
|
||||||
mime-db@1.54.0: {}
|
mime-db@1.54.0: {}
|
||||||
|
|
||||||
mime-types@3.0.1:
|
mime-types@3.0.1:
|
||||||
@@ -1480,8 +1078,6 @@ snapshots:
|
|||||||
|
|
||||||
path-to-regexp@8.3.0: {}
|
path-to-regexp@8.3.0: {}
|
||||||
|
|
||||||
picomatch@2.3.1: {}
|
|
||||||
|
|
||||||
pkce-challenge@5.0.0: {}
|
pkce-challenge@5.0.0: {}
|
||||||
|
|
||||||
pretty-ms@9.3.0:
|
pretty-ms@9.3.0:
|
||||||
@@ -1499,8 +1095,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.0
|
||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
|
||||||
|
|
||||||
range-parser@1.2.1: {}
|
range-parser@1.2.1: {}
|
||||||
|
|
||||||
raw-body@3.0.1:
|
raw-body@3.0.1:
|
||||||
@@ -1512,8 +1106,6 @@ snapshots:
|
|||||||
|
|
||||||
require-from-string@2.0.2: {}
|
require-from-string@2.0.2: {}
|
||||||
|
|
||||||
reusify@1.1.0: {}
|
|
||||||
|
|
||||||
router@2.2.0:
|
router@2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
@@ -1524,10 +1116,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
run-parallel@1.2.0:
|
|
||||||
dependencies:
|
|
||||||
queue-microtask: 1.2.3
|
|
||||||
|
|
||||||
safe-buffer@5.2.1: {}
|
safe-buffer@5.2.1: {}
|
||||||
|
|
||||||
safer-buffer@2.1.2: {}
|
safer-buffer@2.1.2: {}
|
||||||
@@ -1625,10 +1213,6 @@ snapshots:
|
|||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
|
||||||
dependencies:
|
|
||||||
is-number: 7.0.0
|
|
||||||
|
|
||||||
toidentifier@1.0.1: {}
|
toidentifier@1.0.1: {}
|
||||||
|
|
||||||
tunnel@0.0.6: {}
|
tunnel@0.0.6: {}
|
||||||
@@ -1641,7 +1225,7 @@ snapshots:
|
|||||||
|
|
||||||
typescript@5.9.3: {}
|
typescript@5.9.3: {}
|
||||||
|
|
||||||
undici-types@6.21.0: {}
|
undici-types@7.14.0: {}
|
||||||
|
|
||||||
undici@5.29.0:
|
undici@5.29.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1672,10 +1256,3 @@ snapshots:
|
|||||||
zod: 3.25.76
|
zod: 3.25.76
|
||||||
|
|
||||||
zod@3.25.76: {}
|
zod@3.25.76: {}
|
||||||
|
|
||||||
zshy@0.4.3(typescript@5.9.3):
|
|
||||||
dependencies:
|
|
||||||
arg: 5.0.2
|
|
||||||
fast-glob: 3.3.3
|
|
||||||
table: 6.9.0
|
|
||||||
typescript: 5.9.3
|
|
||||||
|
|||||||
@@ -1,356 +0,0 @@
|
|||||||
#!/usr/bin/env tsx
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GitHub App Installation Token Generator
|
|
||||||
*
|
|
||||||
* Generates a temporary installation token for a GitHub App to access a specific repository.
|
|
||||||
* Uses environment variables for configuration and supports multiple installations.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* node scripts/generate-installation-token.ts [--repo owner/name] [--update-env]
|
|
||||||
*
|
|
||||||
* Environment variables required:
|
|
||||||
* GITHUB_APP_ID - GitHub App ID
|
|
||||||
* GITHUB_PRIVATE_KEY - GitHub App private key (PEM format)
|
|
||||||
* REPO_OWNER - Target repository owner (default)
|
|
||||||
* REPO_NAME - Target repository name (default)
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { createSign } from "node:crypto";
|
|
||||||
import { readFileSync, writeFileSync } from "node:fs";
|
|
||||||
import { join } from "node:path";
|
|
||||||
import { config } from "dotenv";
|
|
||||||
|
|
||||||
// Load environment variables
|
|
||||||
config();
|
|
||||||
|
|
||||||
interface GitHubAppConfig {
|
|
||||||
appId: string;
|
|
||||||
privateKey: string;
|
|
||||||
repoOwner: string;
|
|
||||||
repoName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Installation {
|
|
||||||
id: number;
|
|
||||||
account: {
|
|
||||||
login: string;
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Repository {
|
|
||||||
owner: {
|
|
||||||
login: string;
|
|
||||||
};
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface InstallationTokenResponse {
|
|
||||||
token: string;
|
|
||||||
expires_at: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RepositoriesResponse {
|
|
||||||
repositories: Repository[];
|
|
||||||
}
|
|
||||||
|
|
||||||
class GitHubAppTokenGenerator {
|
|
||||||
private config: GitHubAppConfig;
|
|
||||||
|
|
||||||
constructor(config: GitHubAppConfig) {
|
|
||||||
// Process private key to handle escaped newlines
|
|
||||||
config.privateKey = config.privateKey.replace(/\\n/g, "\n");
|
|
||||||
this.config = config;
|
|
||||||
this.validateConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
private validateConfig(): void {
|
|
||||||
const { appId, privateKey, repoOwner, repoName } = this.config;
|
|
||||||
|
|
||||||
if (!appId) {
|
|
||||||
throw new Error("GITHUB_APP_ID environment variable is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!privateKey) {
|
|
||||||
throw new Error("GITHUB_PRIVATE_KEY environment variable is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!repoOwner || !repoName) {
|
|
||||||
throw new Error("REPO_OWNER and REPO_NAME environment variables are required");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!privateKey.includes("BEGIN") || !privateKey.includes("END")) {
|
|
||||||
throw new Error("GITHUB_PRIVATE_KEY must be in PEM format");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a JWT for GitHub App authentication
|
|
||||||
*/
|
|
||||||
private generateJWT(): string {
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
const payload = {
|
|
||||||
iat: now - 60, // issued 1 minute ago to account for clock skew
|
|
||||||
exp: now + 5 * 60, // expires in 5 minutes
|
|
||||||
iss: this.config.appId,
|
|
||||||
};
|
|
||||||
|
|
||||||
const header = {
|
|
||||||
alg: "RS256",
|
|
||||||
typ: "JWT",
|
|
||||||
};
|
|
||||||
|
|
||||||
const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
|
|
||||||
const encodedPayload = this.base64UrlEncode(JSON.stringify(payload));
|
|
||||||
const signaturePart = `${encodedHeader}.${encodedPayload}`;
|
|
||||||
|
|
||||||
const signature = createSign("RSA-SHA256")
|
|
||||||
.update(signaturePart)
|
|
||||||
.sign(this.config.privateKey, "base64")
|
|
||||||
.replace(/\+/g, "-")
|
|
||||||
.replace(/\//g, "_")
|
|
||||||
.replace(/=/g, "");
|
|
||||||
|
|
||||||
return `${signaturePart}.${signature}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private base64UrlEncode(str: string): string {
|
|
||||||
return Buffer.from(str)
|
|
||||||
.toString("base64")
|
|
||||||
.replace(/\+/g, "-")
|
|
||||||
.replace(/\//g, "_")
|
|
||||||
.replace(/=/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes authenticated requests to GitHub API
|
|
||||||
*/
|
|
||||||
private async githubRequest<T>(
|
|
||||||
path: string,
|
|
||||||
options: {
|
|
||||||
method?: string;
|
|
||||||
headers?: Record<string, string>;
|
|
||||||
body?: string;
|
|
||||||
} = {}
|
|
||||||
): Promise<T> {
|
|
||||||
const { method = "GET", headers = {}, body } = options;
|
|
||||||
|
|
||||||
const url = `https://api.github.com${path}`;
|
|
||||||
const requestHeaders = {
|
|
||||||
Accept: "application/vnd.github.v3+json",
|
|
||||||
"User-Agent": "Pullfrog-Installation-Token-Generator/1.0",
|
|
||||||
...headers,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method,
|
|
||||||
headers: requestHeaders,
|
|
||||||
...(body && { body }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
throw new Error(
|
|
||||||
`GitHub API request failed: ${response.status} ${response.statusText}\n${errorText}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.json() as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the installation ID for the target repository
|
|
||||||
*/
|
|
||||||
private async findInstallationId(jwt: string): Promise<number> {
|
|
||||||
console.log("🔍 Finding GitHub App installation...");
|
|
||||||
|
|
||||||
const installations = await this.githubRequest<Installation[]>("/app/installations", {
|
|
||||||
headers: { Authorization: `Bearer ${jwt}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`📋 Found ${installations.length} installation(s)`);
|
|
||||||
|
|
||||||
// Check each installation for access to target repository
|
|
||||||
for (const installation of installations) {
|
|
||||||
console.log(`🔎 Checking installation ${installation.id} (${installation.account.login})`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create a temporary token to check repository access
|
|
||||||
const tempToken = await this.createInstallationToken(jwt, installation.id);
|
|
||||||
const hasAccess = await this.checkRepositoryAccess(tempToken);
|
|
||||||
|
|
||||||
if (hasAccess) {
|
|
||||||
console.log(
|
|
||||||
`✅ Installation ${installation.id} has access to ${this.config.repoOwner}/${this.config.repoName}`
|
|
||||||
);
|
|
||||||
return installation.id;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(
|
|
||||||
`❌ Installation ${installation.id} check failed:`,
|
|
||||||
error instanceof Error ? error.message : String(error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
`No installation found with access to ${this.config.repoOwner}/${this.config.repoName}. ` +
|
|
||||||
"Ensure the GitHub App is installed on the target repository."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the installation token has access to the target repository
|
|
||||||
*/
|
|
||||||
private async checkRepositoryAccess(token: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const response = await this.githubRequest<RepositoriesResponse>(
|
|
||||||
"/installation/repositories",
|
|
||||||
{
|
|
||||||
headers: { Authorization: `token ${token}` },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return response.repositories.some(
|
|
||||||
(repo) => repo.owner.login === this.config.repoOwner && repo.name === this.config.repoName
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an installation access token
|
|
||||||
*/
|
|
||||||
private async createInstallationToken(jwt: string, installationId: number): Promise<string> {
|
|
||||||
const response = await this.githubRequest<InstallationTokenResponse>(
|
|
||||||
`/app/installations/${installationId}/access_tokens`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: { Authorization: `Bearer ${jwt}` },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return response.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a new installation token for the configured repository
|
|
||||||
*/
|
|
||||||
async generateToken(): Promise<{
|
|
||||||
token: string;
|
|
||||||
installationId: number;
|
|
||||||
expiresAt: string;
|
|
||||||
}> {
|
|
||||||
console.log(
|
|
||||||
`🚀 Generating installation token for ${this.config.repoOwner}/${this.config.repoName}`
|
|
||||||
);
|
|
||||||
console.log(`📱 App ID: ${this.config.appId}`);
|
|
||||||
|
|
||||||
// Step 1: Generate JWT for app authentication
|
|
||||||
const jwt = this.generateJWT();
|
|
||||||
console.log("🔐 Generated JWT token");
|
|
||||||
|
|
||||||
// Step 2: Find installation with repository access
|
|
||||||
const installationId = await this.findInstallationId(jwt);
|
|
||||||
|
|
||||||
// Step 3: Create installation access token
|
|
||||||
console.log(`🎫 Creating installation token for installation ${installationId}...`);
|
|
||||||
const token = await this.createInstallationToken(jwt, installationId);
|
|
||||||
|
|
||||||
// Calculate expiration (GitHub tokens expire after 1 hour)
|
|
||||||
const expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString();
|
|
||||||
|
|
||||||
console.log("✅ Installation token generated successfully!");
|
|
||||||
console.log(`🎟️ Token: ${token.substring(0, 20)}...`);
|
|
||||||
console.log(`📅 Expires: ${expiresAt}`);
|
|
||||||
console.log(`🏢 Installation ID: ${installationId}`);
|
|
||||||
|
|
||||||
return { token, installationId, expiresAt };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the .env file with the new installation token
|
|
||||||
*/
|
|
||||||
updateEnvFile(token: string): void {
|
|
||||||
const envPath = join(process.cwd(), ".env");
|
|
||||||
|
|
||||||
try {
|
|
||||||
let envContent = readFileSync(envPath, "utf8");
|
|
||||||
|
|
||||||
// Update or add the installation token
|
|
||||||
const tokenLine = `GITHUB_INSTALLATION_TOKEN=${token}`;
|
|
||||||
const tokenRegex = /^GITHUB_INSTALLATION_TOKEN=.*$/m;
|
|
||||||
|
|
||||||
if (tokenRegex.test(envContent)) {
|
|
||||||
envContent = envContent.replace(tokenRegex, tokenLine);
|
|
||||||
} else {
|
|
||||||
envContent += `\n${tokenLine}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFileSync(envPath, envContent);
|
|
||||||
console.log(`📝 Updated ${envPath} with new installation token`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
"❌ Failed to update .env file:",
|
|
||||||
error instanceof Error ? error.message : String(error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CLI interface
|
|
||||||
*/
|
|
||||||
async function main(): Promise<void> {
|
|
||||||
try {
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
const updateEnv = args.includes("--update-env");
|
|
||||||
|
|
||||||
// Parse repository from args if provided
|
|
||||||
const repoArg = args.find((arg) => arg.startsWith("--repo="));
|
|
||||||
let repoOwner = process.env.REPO_OWNER || "pullfrogai";
|
|
||||||
let repoName = process.env.REPO_NAME || "scratch";
|
|
||||||
|
|
||||||
if (repoArg) {
|
|
||||||
const [owner, name] = repoArg.split("=")[1].split("/");
|
|
||||||
if (owner && name) {
|
|
||||||
repoOwner = owner;
|
|
||||||
repoName = name;
|
|
||||||
} else {
|
|
||||||
throw new Error("Invalid --repo format. Use: --repo=owner/name");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const config: GitHubAppConfig = {
|
|
||||||
appId: process.env.GITHUB_APP_ID!,
|
|
||||||
privateKey: process.env.GITHUB_PRIVATE_KEY!,
|
|
||||||
repoOwner,
|
|
||||||
repoName,
|
|
||||||
};
|
|
||||||
|
|
||||||
const generator = new GitHubAppTokenGenerator(config);
|
|
||||||
const result = await generator.generateToken();
|
|
||||||
|
|
||||||
if (updateEnv) {
|
|
||||||
generator.updateEnvFile(result.token);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("\n🎉 Token generation complete!");
|
|
||||||
|
|
||||||
if (!updateEnv) {
|
|
||||||
console.log("\n💡 To automatically update your .env file, run with --update-env flag");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ Error:", error instanceof Error ? error.message : String(error));
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run if called directly
|
|
||||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
||||||
main();
|
|
||||||
}
|
|
||||||
|
|
||||||
export { GitHubAppTokenGenerator };
|
|
||||||
+16
-15
@@ -2,21 +2,22 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"lib": ["ESNext"],
|
"lib": ["ESNext"],
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"rewriteRelativeImportExtensions": true,
|
"rewriteRelativeImportExtensions": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUncheckedSideEffectImports": true,
|
"noUncheckedSideEffectImports": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"exactOptionalPropertyTypes": true,
|
"exactOptionalPropertyTypes": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"stripInternal": true,
|
"stripInternal": true,
|
||||||
"moduleDetection": "force"
|
"moduleDetection": "force",
|
||||||
|
"useUnknownInCatchVariables": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
+229
-48
@@ -1,4 +1,6 @@
|
|||||||
|
import { createSign } from "node:crypto";
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
|
import { resolveRepoContext } from "./repo-context.ts";
|
||||||
|
|
||||||
export interface InstallationToken {
|
export interface InstallationToken {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -10,62 +12,241 @@ export interface InstallationToken {
|
|||||||
owner?: string;
|
owner?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GitHubAppConfig {
|
||||||
|
appId: string;
|
||||||
|
privateKey: string;
|
||||||
|
repoOwner: string;
|
||||||
|
repoName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Installation {
|
||||||
|
id: number;
|
||||||
|
account: {
|
||||||
|
login: string;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Repository {
|
||||||
|
owner: {
|
||||||
|
login: string;
|
||||||
|
};
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InstallationTokenResponse {
|
||||||
|
token: string;
|
||||||
|
expires_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RepositoriesResponse {
|
||||||
|
repositories: Repository[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkExistingToken(): string | null {
|
||||||
|
const inputToken = core.getInput("github_installation_token");
|
||||||
|
const envToken = process.env.GITHUB_INSTALLATION_TOKEN;
|
||||||
|
return inputToken || envToken || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGitHubActionsEnvironment(): boolean {
|
||||||
|
return Boolean(process.env.GITHUB_ACTIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function acquireTokenViaOIDC(): Promise<string> {
|
||||||
|
core.info("Generating OIDC token...");
|
||||||
|
|
||||||
|
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()) as InstallationToken;
|
||||||
|
core.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`);
|
||||||
|
|
||||||
|
return tokenData.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
const base64UrlEncode = (str: string): string => {
|
||||||
|
return Buffer.from(str)
|
||||||
|
.toString("base64")
|
||||||
|
.replace(/\+/g, "-")
|
||||||
|
.replace(/\//g, "_")
|
||||||
|
.replace(/=/g, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateJWT = (appId: string, privateKey: string): string => {
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
const payload = {
|
||||||
|
iat: now - 60,
|
||||||
|
exp: now + 5 * 60,
|
||||||
|
iss: appId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const header = {
|
||||||
|
alg: "RS256",
|
||||||
|
typ: "JWT",
|
||||||
|
};
|
||||||
|
|
||||||
|
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
||||||
|
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
||||||
|
const signaturePart = `${encodedHeader}.${encodedPayload}`;
|
||||||
|
|
||||||
|
const signature = createSign("RSA-SHA256")
|
||||||
|
.update(signaturePart)
|
||||||
|
.sign(privateKey, "base64")
|
||||||
|
.replace(/\+/g, "-")
|
||||||
|
.replace(/\//g, "_")
|
||||||
|
.replace(/=/g, "");
|
||||||
|
|
||||||
|
return `${signaturePart}.${signature}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const githubRequest = async <T>(
|
||||||
|
path: string,
|
||||||
|
options: {
|
||||||
|
method?: string;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
body?: string;
|
||||||
|
} = {}
|
||||||
|
): Promise<T> => {
|
||||||
|
const { method = "GET", headers = {}, body } = options;
|
||||||
|
|
||||||
|
const url = `https://api.github.com${path}`;
|
||||||
|
const requestHeaders = {
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "Pullfrog-Installation-Token-Generator/1.0",
|
||||||
|
...headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers: requestHeaders,
|
||||||
|
...(body && { body }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(
|
||||||
|
`GitHub API request failed: ${response.status} ${response.statusText}\n${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json() as T;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkRepositoryAccess = async (
|
||||||
|
token: string,
|
||||||
|
repoOwner: string,
|
||||||
|
repoName: string
|
||||||
|
): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await githubRequest<RepositoriesResponse>("/installation/repositories", {
|
||||||
|
headers: { Authorization: `token ${token}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.repositories.some(
|
||||||
|
(repo) => repo.owner.login === repoOwner && repo.name === repoName
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createInstallationToken = async (jwt: string, installationId: number): Promise<string> => {
|
||||||
|
const response = await githubRequest<InstallationTokenResponse>(
|
||||||
|
`/app/installations/${installationId}/access_tokens`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { Authorization: `Bearer ${jwt}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findInstallationId = async (
|
||||||
|
jwt: string,
|
||||||
|
repoOwner: string,
|
||||||
|
repoName: string
|
||||||
|
): Promise<number> => {
|
||||||
|
const installations = await githubRequest<Installation[]>("/app/installations", {
|
||||||
|
headers: { Authorization: `Bearer ${jwt}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const installation of installations) {
|
||||||
|
try {
|
||||||
|
const tempToken = await createInstallationToken(jwt, installation.id);
|
||||||
|
const hasAccess = await checkRepositoryAccess(tempToken, repoOwner, repoName);
|
||||||
|
|
||||||
|
if (hasAccess) {
|
||||||
|
return installation.id;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`No installation found with access to ${repoOwner}/${repoName}. ` +
|
||||||
|
"Ensure the GitHub App is installed on the target repository."
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function acquireTokenViaGitHubApp(): Promise<string> {
|
||||||
|
const repoContext = resolveRepoContext();
|
||||||
|
|
||||||
|
const config: GitHubAppConfig = {
|
||||||
|
appId: process.env.GITHUB_APP_ID!,
|
||||||
|
privateKey: process.env.GITHUB_PRIVATE_KEY?.replace(/\\n/g, "\n")!,
|
||||||
|
repoOwner: repoContext.owner,
|
||||||
|
repoName: repoContext.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
const jwt = generateJWT(config.appId, config.privateKey);
|
||||||
|
const installationId = await findInstallationId(jwt, config.repoOwner, config.repoName);
|
||||||
|
const token = await createInstallationToken(jwt, installationId);
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function acquireNewToken(): Promise<string> {
|
||||||
|
if (isGitHubActionsEnvironment()) {
|
||||||
|
return await acquireTokenViaOIDC();
|
||||||
|
} else {
|
||||||
|
return await acquireTokenViaGitHubApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 existingToken = checkExistingToken();
|
||||||
const inputToken = core.getInput("github_installation_token");
|
|
||||||
const envToken = process.env.GITHUB_INSTALLATION_TOKEN;
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
core.info("Generating OIDC token...");
|
const token = await acquireNewToken();
|
||||||
|
|
||||||
try {
|
core.setSecret(token);
|
||||||
// Generate OIDC token for our API
|
process.env.GITHUB_INSTALLATION_TOKEN = token;
|
||||||
const oidcToken = await core.getIDToken("pullfrog-api");
|
|
||||||
core.info("OIDC token generated successfully");
|
return token;
|
||||||
|
|
||||||
// Exchange OIDC token for installation token
|
|
||||||
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}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
core.setSecret(tokenData.token);
|
|
||||||
|
|
||||||
// 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"}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export interface RepoContext {
|
||||||
|
owner: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve repository context from GITHUB_REPOSITORY environment variable.
|
||||||
|
* Throws if not available.
|
||||||
|
*/
|
||||||
|
export function resolveRepoContext(): RepoContext {
|
||||||
|
const githubRepo = process.env.GITHUB_REPOSITORY;
|
||||||
|
if (!githubRepo) {
|
||||||
|
throw new Error('GITHUB_REPOSITORY environment variable is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const [owner, name] = githubRepo.split('/');
|
||||||
|
if (!owner || !name) {
|
||||||
|
throw new Error(`Invalid GITHUB_REPOSITORY format: ${githubRepo}. Expected 'owner/repo'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { owner, name };
|
||||||
|
}
|
||||||
@@ -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
@@ -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
@@ -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);
|
|
||||||
// }
|
|
||||||
|
|||||||
Reference in New Issue
Block a user