diff --git a/.gitignore b/.gitignore index bdeae76..03bd3b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ -# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,node -# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,macos,node +# Created by https://www.toptal.com/developers/gitignore/api/node,macos,visualstudiocode,asdf +# Edit at https://www.toptal.com/developers/gitignore?templates=node,macos,visualstudiocode,asdf + +### asdf ### +/.tool-versions ### macOS ### # General @@ -76,7 +79,7 @@ bower_components build/Release # Dependency directories -/node_modules +node_modules/ jspm_packages/ # Snowpack dependency directory (https://snowpack.dev/) @@ -126,6 +129,7 @@ out # Nuxt.js build / generate output .nuxt +dist # Gatsby files .cache/ @@ -192,4 +196,4 @@ out .history .ionide -# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,node +# End of https://www.toptal.com/developers/gitignore/api/node,macos,visualstudiocode,asdf \ No newline at end of file diff --git a/action.yml b/action.yml index 6e93b55..8019f74 100644 --- a/action.yml +++ b/action.yml @@ -1,4 +1,4 @@ -name: "Render Deploy Action - fork" +name: "Render Deploy Action" description: "Trigger a Render service deploy" inputs: service-id: @@ -11,5 +11,5 @@ inputs: description: "Should job wait for deployment to succeed" required: false runs: - using: "node16" + using: "node24" main: "dist/index.js" diff --git a/dist/index.js b/dist/index.js index 8f07c9f..8ee88e1 100644 --- a/dist/index.js +++ b/dist/index.js @@ -8740,24 +8740,63 @@ const APIKEY = core.getInput("api-key") || process.env.APIKEY; const WAIT_FOR_SUCCESS = core.getInput("wait-for-success") || process.env.WAIT_FOR_SUCCESS; +const RENDER_HEADERS = { Authorization: `Bearer ${APIKEY}` }; + +async function parseJsonResponse(response) { + const text = await response.text(); + if (!text.trim()) { + return null; + } + try { + return JSON.parse(text); + } catch { + throw new Error( + `Render API returned non-JSON response (HTTP ${response.status}): ${text.slice(0, 200)}`, + ); + } +} + +async function fetchLatestDeploy() { + const response = await fetch( + `https://api.render.com/v1/services/${SERVICEID}/deploys?limit=1`, + { headers: RENDER_HEADERS }, + ); + + if (!response.ok) { + const text = await response.text(); + throw new Error( + `Could not list deploys (HTTP ${response.status}): ${text.slice(0, 200)}`, + ); + } + + const deploys = await parseJsonResponse(response); + if (!deploys || deploys.length === 0) { + throw new Error("No deploys found after triggering deploy"); + } + return deploys[0]; +} + async function retrieveStatus(deployId) { const response = await fetch( - "https://api.render.com/v1/services/" + SERVICEID + "/deploys/" + deployId, - { - headers: { Authorization: `Bearer ${APIKEY}` }, - }, + `https://api.render.com/v1/services/${SERVICEID}/deploys/${deployId}`, + { headers: RENDER_HEADERS }, ); - const data = await response.json(); if (response.ok) { + const data = await parseJsonResponse(response); return data.status; } else { - throw Error("Could not retrieve deploy information.") + const text = await response.text(); + throw new Error( + `Could not retrieve deploy information (HTTP ${response.status}): ${text.slice(0, 200)}`, + ); } } -async function waitForSuccess(data) { - let previousStatus = ""; +async function waitForSuccess(data, currentStatus) { + core.info(`Waiting for deploy to succeed`); + + let previousStatus = currentStatus; while (true) { await new Promise((res) => { setTimeout(res, 10000); @@ -8766,7 +8805,7 @@ async function waitForSuccess(data) { const status = await retrieveStatus(data.id); if (status !== previousStatus) { - core.info(`Deploy status: ${status}`); + core.info(`Deploy status changed: ${status}`); previousStatus = status; } @@ -8784,31 +8823,48 @@ async function waitForSuccess(data) { async function run() { const response = await fetch( - "https://api.render.com/v1/services/" + SERVICEID + "/deploys", + `https://api.render.com/v1/services/${SERVICEID}/deploys`, { method: "POST", - headers: { Authorization: `Bearer ${APIKEY}` }, + headers: RENDER_HEADERS, }, ); - const data = await response.json(); - if (response.status === 401) { core.setFailed( "Render Deploy Action: Unauthorized. Please check your API key.", ); return; } else if (!response.ok) { + const text = await response.text(); core.setFailed( - `Deploy error: ${data.message} (status code ${response.status})`, + `Deploy error (HTTP ${response.status}): ${text.slice(0, 200)}`, ); return; } - core.info(`Deploy ${data.status} - Commit: ${data.commit.message}`); + let data = await parseJsonResponse(response); + + // HTTP 202 (Accepted) may return an empty body — the deploy was queued but + // the response doesn't include deploy details. Poll the deploys list instead. + if (!data) { + core.info( + `Deploy accepted (HTTP ${response.status}) with empty body, fetching latest deploy...`, + ); + data = await fetchLatestDeploy(); + } + + let ref = "unknown"; + if (data.commit) { + ref = `git commit: ${data.commit.message}`; + } else if (data.image) { + ref = `image: ${data.image.ref} SHA: ${data.image.sha}`; + } + core.info(`Deploy triggered for ${ref}`); + core.info(`Status: ${data.status}`); if (WAIT_FOR_SUCCESS) { - await waitForSuccess(data); + await waitForSuccess(data, data.status); } } diff --git a/src/action.js b/src/action.js index fcbad36..24566e5 100644 --- a/src/action.js +++ b/src/action.js @@ -6,24 +6,63 @@ const APIKEY = core.getInput("api-key") || process.env.APIKEY; const WAIT_FOR_SUCCESS = core.getInput("wait-for-success") || process.env.WAIT_FOR_SUCCESS; +const RENDER_HEADERS = { Authorization: `Bearer ${APIKEY}` }; + +async function parseJsonResponse(response) { + const text = await response.text(); + if (!text.trim()) { + return null; + } + try { + return JSON.parse(text); + } catch { + throw new Error( + `Render API returned non-JSON response (HTTP ${response.status}): ${text.slice(0, 200)}`, + ); + } +} + +async function fetchLatestDeploy() { + const response = await fetch( + `https://api.render.com/v1/services/${SERVICEID}/deploys?limit=1`, + { headers: RENDER_HEADERS }, + ); + + if (!response.ok) { + const text = await response.text(); + throw new Error( + `Could not list deploys (HTTP ${response.status}): ${text.slice(0, 200)}`, + ); + } + + const deploys = await parseJsonResponse(response); + if (!deploys || deploys.length === 0) { + throw new Error("No deploys found after triggering deploy"); + } + return deploys[0]; +} + async function retrieveStatus(deployId) { const response = await fetch( - "https://api.render.com/v1/services/" + SERVICEID + "/deploys/" + deployId, - { - headers: { Authorization: `Bearer ${APIKEY}` }, - }, + `https://api.render.com/v1/services/${SERVICEID}/deploys/${deployId}`, + { headers: RENDER_HEADERS }, ); - const data = await response.json(); if (response.ok) { + const data = await parseJsonResponse(response); return data.status; } else { - throw Error("Could not retrieve deploy information.") + const text = await response.text(); + throw new Error( + `Could not retrieve deploy information (HTTP ${response.status}): ${text.slice(0, 200)}`, + ); } } -async function waitForSuccess(data) { - let previousStatus = ""; +async function waitForSuccess(data, currentStatus) { + core.info(`Waiting for deploy to succeed`); + + let previousStatus = currentStatus; while (true) { await new Promise((res) => { setTimeout(res, 10000); @@ -32,7 +71,7 @@ async function waitForSuccess(data) { const status = await retrieveStatus(data.id); if (status !== previousStatus) { - core.info(`Deploy status: ${status}`); + core.info(`Deploy status changed: ${status}`); previousStatus = status; } @@ -50,31 +89,48 @@ async function waitForSuccess(data) { async function run() { const response = await fetch( - "https://api.render.com/v1/services/" + SERVICEID + "/deploys", + `https://api.render.com/v1/services/${SERVICEID}/deploys`, { method: "POST", - headers: { Authorization: `Bearer ${APIKEY}` }, + headers: RENDER_HEADERS, }, ); - const data = await response.json(); - if (response.status === 401) { core.setFailed( "Render Deploy Action: Unauthorized. Please check your API key.", ); return; } else if (!response.ok) { + const text = await response.text(); core.setFailed( - `Deploy error: ${data.message} (status code ${response.status})`, + `Deploy error (HTTP ${response.status}): ${text.slice(0, 200)}`, ); return; } - core.info(`Deploy ${data.status} - Commit: ${data.commit.message}`); + let data = await parseJsonResponse(response); + + // HTTP 202 (Accepted) may return an empty body — the deploy was queued but + // the response doesn't include deploy details. Poll the deploys list instead. + if (!data) { + core.info( + `Deploy accepted (HTTP ${response.status}) with empty body, fetching latest deploy...`, + ); + data = await fetchLatestDeploy(); + } + + let ref = "unknown"; + if (data.commit) { + ref = `git commit: ${data.commit.message}`; + } else if (data.image) { + ref = `image: ${data.image.ref} SHA: ${data.image.sha}`; + } + core.info(`Deploy triggered for ${ref}`); + core.info(`Status: ${data.status}`); if (WAIT_FOR_SUCCESS) { - await waitForSuccess(data); + await waitForSuccess(data, data.status); } }