diff --git a/entry.js b/entry.js index 9debf80..8c0929c 100755 --- a/entry.js +++ b/entry.js @@ -40481,7 +40481,7 @@ function query({ // package.json var package_default = { name: "@pullfrog/action", - version: "0.0.90", + version: "0.0.91", type: "module", files: [ "index.js", @@ -40920,22 +40920,39 @@ async function acquireTokenViaOIDC() { log.info("OIDC token generated successfully"); const apiUrl = process.env.API_URL || "https://pullfrog.ai"; log.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" + const timeoutMs = 5e3; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); + try { + const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, { + method: "POST", + headers: { + Authorization: `Bearer ${oidcToken}`, + "Content-Type": "application/json" + }, + signal: controller.signal + }); + clearTimeout(timeoutId); + if (!tokenResponse.ok) { + log.warning( + `Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText}. Falling back to GITHUB_TOKEN.` + ); + return null; } - }); - if (!tokenResponse.ok) { - const errorText = await tokenResponse.text(); - throw new Error( - `Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText} - ${errorText}` - ); + const tokenData = await tokenResponse.json(); + log.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`); + return tokenData.token; + } catch (error2) { + clearTimeout(timeoutId); + if (error2 instanceof Error && error2.name === "AbortError") { + log.warning(`Token exchange timed out after ${timeoutMs}ms. Falling back to GITHUB_TOKEN.`); + } else { + log.warning( + `Token exchange failed: ${error2 instanceof Error ? error2.message : String(error2)}. Falling back to GITHUB_TOKEN.` + ); + } + return null; } - const tokenData = await tokenResponse.json(); - log.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`); - return tokenData.token; } var base64UrlEncode = (str) => { return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); @@ -41039,6 +41056,9 @@ async function acquireNewToken() { return await acquireTokenViaGitHubApp(); } } +function getDefaultGitHubToken() { + return process.env.GITHUB_TOKEN || null; +} async function setupGitHubInstallationToken() { const existingToken = checkExistingToken(); if (existingToken) { @@ -41046,10 +41066,22 @@ async function setupGitHubInstallationToken() { log.info("Using provided GitHub installation token"); return { githubInstallationToken: existingToken, wasAcquired: false }; } - const githubInstallationToken = await acquireNewToken(); - core2.setSecret(githubInstallationToken); - process.env.GITHUB_INSTALLATION_TOKEN = githubInstallationToken; - return { githubInstallationToken, wasAcquired: true }; + const acquiredToken = await acquireNewToken(); + if (!acquiredToken) { + const defaultToken = getDefaultGitHubToken(); + if (!defaultToken) { + throw new Error( + "Failed to acquire installation token and GITHUB_TOKEN is not available. Either install the Pullfrog GitHub App or ensure GITHUB_TOKEN is set." + ); + } + log.info("Using GITHUB_TOKEN (app not installed or token exchange failed)"); + core2.setSecret(defaultToken); + process.env.GITHUB_INSTALLATION_TOKEN = defaultToken; + return { githubInstallationToken: defaultToken, wasAcquired: false }; + } + core2.setSecret(acquiredToken); + process.env.GITHUB_INSTALLATION_TOKEN = acquiredToken; + return { githubInstallationToken: acquiredToken, wasAcquired: true }; } async function revokeInstallationToken(token) { const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com"; diff --git a/package.json b/package.json index 7fe8fcc..8c527e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@pullfrog/action", - "version": "0.0.90", + "version": "0.0.91", "type": "module", "files": [ "index.js", diff --git a/utils/github.ts b/utils/github.ts index 3ea071c..b14a354 100644 --- a/utils/github.ts +++ b/utils/github.ts @@ -53,7 +53,7 @@ function isGitHubActionsEnvironment(): boolean { return Boolean(process.env.GITHUB_ACTIONS); } -async function acquireTokenViaOIDC(): Promise { +async function acquireTokenViaOIDC(): Promise { log.info("Generating OIDC token..."); const oidcToken = await core.getIDToken("pullfrog-api"); @@ -62,25 +62,47 @@ async function acquireTokenViaOIDC(): Promise { const apiUrl = process.env.API_URL || "https://pullfrog.ai"; log.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}` - ); + // Add timeout to prevent long waits (5 seconds) + const timeoutMs = 5000; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); + + try { + const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, { + method: "POST", + headers: { + Authorization: `Bearer ${oidcToken}`, + "Content-Type": "application/json", + }, + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!tokenResponse.ok) { + log.warning( + `Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText}. Falling back to GITHUB_TOKEN.` + ); + return null; + } + + const tokenData = (await tokenResponse.json()) as InstallationToken; + log.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`); + + return tokenData.token; + } catch (error) { + clearTimeout(timeoutId); + + if (error instanceof Error && error.name === "AbortError") { + log.warning(`Token exchange timed out after ${timeoutMs}ms. Falling back to GITHUB_TOKEN.`); + } else { + log.warning( + `Token exchange failed: ${error instanceof Error ? error.message : String(error)}. Falling back to GITHUB_TOKEN.` + ); + } + return null; } - - const tokenData = (await tokenResponse.json()) as InstallationToken; - log.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`); - - return tokenData.token; } const base64UrlEncode = (str: string): string => { @@ -224,7 +246,7 @@ async function acquireTokenViaGitHubApp(): Promise { return token; } -async function acquireNewToken(): Promise { +async function acquireNewToken(): Promise { if (isGitHubActionsEnvironment()) { return await acquireTokenViaOIDC(); } else { @@ -232,9 +254,14 @@ async function acquireNewToken(): Promise { } } +function getDefaultGitHubToken(): string | null { + return process.env.GITHUB_TOKEN || null; +} + /** * Setup GitHub installation token for the action * Returns the token and whether it was acquired (needs revocation) + * Falls back to GITHUB_TOKEN if app is not installed */ export async function setupGitHubInstallationToken(): Promise<{ githubInstallationToken: string; @@ -247,12 +274,27 @@ export async function setupGitHubInstallationToken(): Promise<{ return { githubInstallationToken: existingToken, wasAcquired: false }; } - const githubInstallationToken = await acquireNewToken(); + const acquiredToken = await acquireNewToken(); - core.setSecret(githubInstallationToken); - process.env.GITHUB_INSTALLATION_TOKEN = githubInstallationToken; + // If token acquisition failed (e.g., app not installed), fall back to GITHUB_TOKEN + if (!acquiredToken) { + const defaultToken = getDefaultGitHubToken(); + if (!defaultToken) { + throw new Error( + "Failed to acquire installation token and GITHUB_TOKEN is not available. " + + "Either install the Pullfrog GitHub App or ensure GITHUB_TOKEN is set." + ); + } + log.info("Using GITHUB_TOKEN (app not installed or token exchange failed)"); + core.setSecret(defaultToken); + process.env.GITHUB_INSTALLATION_TOKEN = defaultToken; + return { githubInstallationToken: defaultToken, wasAcquired: false }; + } - return { githubInstallationToken, wasAcquired: true }; + core.setSecret(acquiredToken); + process.env.GITHUB_INSTALLATION_TOKEN = acquiredToken; + + return { githubInstallationToken: acquiredToken, wasAcquired: true }; } /**