use resource management for main's cleanup

This commit is contained in:
David Blass
2026-01-08 10:26:17 -05:00
committed by GitHub
6 changed files with 558 additions and 518 deletions
+1 -1
View File
@@ -1 +1 @@
v22.14.0 v24.3.0
+530 -476
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -38,7 +38,7 @@ const sharedConfig = {
bundle: true, bundle: true,
format: "esm", format: "esm",
platform: "node", platform: "node",
target: "node20", target: "node24",
minify: false, minify: false,
sourcemap: false, sourcemap: false,
// Bundle all dependencies - GitHub Actions doesn't have node_modules // Bundle all dependencies - GitHub Actions doesn't have node_modules
+11 -24
View File
@@ -21,7 +21,6 @@ import { reportErrorToComment } from "./utils/errorReport.ts";
import { import {
createOctokit, createOctokit,
parseRepoContext, parseRepoContext,
revokeGitHubInstallationToken,
setupGitHubInstallationToken, setupGitHubInstallationToken,
} from "./utils/github.ts"; } from "./utils/github.ts";
import { setupGitAuth, setupGitConfig } from "./utils/setup.ts"; import { setupGitAuth, setupGitConfig } from "./utils/setup.ts";
@@ -54,7 +53,6 @@ export interface MainResult {
// intermediate result types for deterministic context building // intermediate result types for deterministic context building
interface GitHubSetup { interface GitHubSetup {
token: string;
owner: string; owner: string;
name: string; name: string;
octokit: Octokit; octokit: Octokit;
@@ -67,12 +65,11 @@ type ApiKeySetup =
| { success: false; error: string }; | { success: false; error: string };
export async function main(inputs: Inputs): Promise<MainResult> { export async function main(inputs: Inputs): Promise<MainResult> {
let mcpServerClose: (() => Promise<void>) | undefined; const timer = new Timer();
await using tokenRef = await setupGitHubInstallationToken();
let payload: Payload | undefined; let payload: Payload | undefined;
try { try {
const timer = new Timer();
// phase 1: parse and validate inputs // phase 1: parse and validate inputs
payload = parsePayload(inputs); payload = parsePayload(inputs);
Inputs.assert(inputs); Inputs.assert(inputs);
@@ -80,7 +77,7 @@ export async function main(inputs: Inputs): Promise<MainResult> {
// phase 2: fast setup (github + temp dir) // phase 2: fast setup (github + temp dir)
const [githubSetup, sharedTempDir] = await Promise.all([ const [githubSetup, sharedTempDir] = await Promise.all([
initializeGitHub(), initializeGitHub(tokenRef.token),
createTempDirectory(), createTempDirectory(),
]); ]);
timer.checkpoint("githubSetup"); timer.checkpoint("githubSetup");
@@ -108,9 +105,9 @@ export async function main(inputs: Inputs): Promise<MainResult> {
// phase 5: parallel long-running operations (agent install + git auth) // phase 5: parallel long-running operations (agent install + git auth)
const toolState: ToolState = {}; const toolState: ToolState = {};
const [cliPath] = await Promise.all([ const [cliPath] = await Promise.all([
installAgentCli({ agent, token: githubSetup.token }), installAgentCli({ agent, token: tokenRef.token }),
setupGitAuth({ setupGitAuth({
token: githubSetup.token, token: tokenRef.token,
owner: githubSetup.owner, owner: githubSetup.owner,
name: githubSetup.name, name: githubSetup.name,
payload: resolvedPayload, payload: resolvedPayload,
@@ -157,7 +154,7 @@ export async function main(inputs: Inputs): Promise<MainResult> {
const toolContext: ToolContext = { const toolContext: ToolContext = {
owner: githubSetup.owner, owner: githubSetup.owner,
name: githubSetup.name, name: githubSetup.name,
githubInstallationToken: githubSetup.token, githubInstallationToken: tokenRef.token,
octokit: githubSetup.octokit, octokit: githubSetup.octokit,
payload: resolvedPayload, payload: resolvedPayload,
repo: githubSetup.repo, repo: githubSetup.repo,
@@ -170,11 +167,10 @@ export async function main(inputs: Inputs): Promise<MainResult> {
jobId, jobId,
}; };
const { url: mcpServerUrl, close: mcpServerCloseFunc } = await startMcpHttpServer(toolContext); await using mcpHttpServer = await startMcpHttpServer(toolContext);
mcpServerClose = mcpServerCloseFunc; log.info(`🚀 MCP server started at ${mcpHttpServer.url}`);
log.info(`🚀 MCP server started at ${mcpServerUrl}`);
const mcpServers = createMcpConfigs(mcpServerUrl); const mcpServers = createMcpConfigs(mcpHttpServer.url);
log.debug(`📋 MCP Config: ${JSON.stringify(mcpServers, null, 2)}`); log.debug(`📋 MCP Config: ${JSON.stringify(mcpServers, null, 2)}`);
timer.checkpoint("mcpServer"); timer.checkpoint("mcpServer");
@@ -182,8 +178,7 @@ export async function main(inputs: Inputs): Promise<MainResult> {
const ctx: AgentContext = { const ctx: AgentContext = {
...toolContext, ...toolContext,
inputs, inputs,
mcpServerUrl, mcpServerUrl: mcpHttpServer.url,
mcpServerClose: mcpServerCloseFunc,
mcpServers, mcpServers,
cliPath, cliPath,
apiKey: apiKeySetup.apiKey, apiKey: apiKeySetup.apiKey,
@@ -226,11 +221,6 @@ export async function main(inputs: Inputs): Promise<MainResult> {
} catch { } catch {
// error updating comment, but don't let it mask the original error // error updating comment, but don't let it mask the original error
} }
if (mcpServerClose) {
await mcpServerClose();
}
await revokeGitHubInstallationToken();
} }
} }
@@ -322,7 +312,6 @@ export interface ToolContext {
export interface AgentContext extends Readonly<ToolContext> { export interface AgentContext extends Readonly<ToolContext> {
readonly inputs: Inputs; readonly inputs: Inputs;
readonly mcpServerUrl: string; readonly mcpServerUrl: string;
readonly mcpServerClose: () => Promise<void>;
readonly mcpServers: ReturnType<typeof createMcpConfigs>; readonly mcpServers: ReturnType<typeof createMcpConfigs>;
readonly cliPath: string; readonly cliPath: string;
readonly apiKey: string; readonly apiKey: string;
@@ -349,10 +338,9 @@ export interface ToolState {
/** /**
* Initialize GitHub connection: token, octokit, repo data, settings * Initialize GitHub connection: token, octokit, repo data, settings
*/ */
async function initializeGitHub(): Promise<GitHubSetup> { async function initializeGitHub(token: string): Promise<GitHubSetup> {
log.info(`🐸 Running pullfrog/action@${packageJson.version}...`); log.info(`🐸 Running pullfrog/action@${packageJson.version}...`);
const token = await setupGitHubInstallationToken();
const { owner, name } = parseRepoContext(); const { owner, name } = parseRepoContext();
const octokit = createOctokit(token); const octokit = createOctokit(token);
@@ -364,7 +352,6 @@ async function initializeGitHub(): Promise<GitHubSetup> {
]); ]);
return { return {
token,
owner, owner,
name, name,
octokit, octokit,
+2 -2
View File
@@ -64,7 +64,7 @@ async function findAvailablePort(startPort: number): Promise<number> {
*/ */
export async function startMcpHttpServer( export async function startMcpHttpServer(
ctx: ToolContext ctx: ToolContext
): Promise<{ url: string; close: () => Promise<void> }> { ): Promise<{ url: string; [Symbol.asyncDispose]: () => Promise<void> }> {
const server = new FastMCP({ const server = new FastMCP({
name: ghPullfrogMcpName, name: ghPullfrogMcpName,
version: "0.0.1", version: "0.0.1",
@@ -119,7 +119,7 @@ export async function startMcpHttpServer(
return { return {
url, url,
close: async () => { [Symbol.asyncDispose]: async () => {
await server.stop(); await server.stop();
}, },
}; };
+12 -13
View File
@@ -1,6 +1,7 @@
import * as core from "@actions/core"; import * as core from "@actions/core";
import { throttling } from "@octokit/plugin-throttling"; import { throttling } from "@octokit/plugin-throttling";
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import assert from "node:assert/strict";
import { createSign } from "node:crypto"; import { createSign } from "node:crypto";
import { log } from "./cli.ts"; import { log } from "./cli.ts";
import { retry } from "./retry.ts"; import { retry } from "./retry.ts";
@@ -248,31 +249,29 @@ let githubInstallationToken: string | undefined;
/** /**
* Setup GitHub installation token for the action * Setup GitHub installation token for the action
*/ */
export async function setupGitHubInstallationToken(): Promise<string> { export async function setupGitHubInstallationToken() {
assert(!githubInstallationToken, "GitHub installation token is already set.");
const acquiredToken = await acquireNewToken(); const acquiredToken = await acquireNewToken();
core.setSecret(acquiredToken); core.setSecret(acquiredToken);
githubInstallationToken = acquiredToken; githubInstallationToken = acquiredToken;
return acquiredToken; return {
token: acquiredToken,
[Symbol.asyncDispose]() {
githubInstallationToken = undefined;
return revokeGitHubInstallationToken(acquiredToken);
},
};
} }
/** /**
* Get the GitHub installation token from memory * Get the GitHub installation token from memory
*/ */
export function getGitHubInstallationToken(): string { export function getGitHubInstallationToken(): string {
if (!githubInstallationToken) { assert(githubInstallationToken, "GitHub installation token not set. Call setupGitHubInstallationToken first.");
throw new Error("GitHub installation token not set. Call setupGitHubInstallationToken first.");
}
return githubInstallationToken; return githubInstallationToken;
} }
export async function revokeGitHubInstallationToken(): Promise<void> { async function revokeGitHubInstallationToken(token: string): Promise<void> {
if (!githubInstallationToken) {
return;
}
const token = githubInstallationToken;
githubInstallationToken = undefined;
const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com"; const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com";
try { try {