Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23590,6 +23590,7 @@ var version = "1.3.3";
var API_VERSION = "2026-03-10";
async function run() {
info(`\u{1F3C3} Workflow Dispatch Action v${version}`);
let dispatchReqInFlightTo;
try {
const workflowRef = getInput("workflow");
const token = getInput("token");
Expand All @@ -23601,7 +23602,7 @@ async function run() {
try {
inputs = JSON.parse(inputsJson);
} catch (e) {
throw new Error(`Failed to parse 'inputs' JSON string: ${e instanceof Error ? e.message : String(e)}`, {
throw new Error(`\u{1F92F} Failed to parse 'inputs' JSON string: ${e instanceof Error ? e.message : String(e)}`, {
cause: e
});
}
Expand All @@ -23620,9 +23621,10 @@ async function run() {
return workflow.name === workflowRef || workflow.id.toString() === workflowRef || workflow.path.endsWith(`/${workflowRef}`) || // Add a leading / to avoid matching workflow with same suffix
workflow.path == workflowRef;
});
if (!foundWorkflow) throw new Error(`Unable to find workflow '${workflowRef}' in ${owner}/${repo} \u{1F625}`);
if (!foundWorkflow) throw new Error(`\u{1F625} Unable to find workflow '${workflowRef}' in ${owner}/${repo}`);
info(`\u{1F50E} Found workflow, id: ${foundWorkflow.id}, name: ${foundWorkflow.name}, path: ${foundWorkflow.path}`);
info("\u{1F680} Calling GitHub API to dispatch workflow...");
dispatchReqInFlightTo = `'${foundWorkflow.path}' (id: ${foundWorkflow.id}) in '${owner}/${repo}' on ref '${ref}'`;
const dispatchResp = await octokit.request(
`POST /repos/${owner}/${repo}/actions/workflows/${foundWorkflow.id}/dispatches`,
{
Expand All @@ -23632,6 +23634,7 @@ async function run() {
headers: { "x-github-api-version": API_VERSION }
}
);
dispatchReqInFlightTo = void 0;
info(`\u{1F3C6} API response status: ${dispatchResp.status}`);
info(`\u{1F310} Run URL: ${dispatchResp.data.html_url}`);
const waitForCompletion = getInput("wait-for-completion") === "true";
Expand Down Expand Up @@ -23682,20 +23685,40 @@ Note: The workflow is still running but we have stopped waiting. You can check t
);
const conclusion = finalRunData.conclusion;
if (conclusion === "failure") {
setFailed(`Workflow run failed. Check the run details here: ${dispatchResp.data.html_url}`);
setFailed(`\u{1F92F} Workflow run failed. Check the run details here: ${dispatchResp.data.html_url}`);
} else if (conclusion === "cancelled") {
setFailed(`Workflow run was cancelled. Check the run details here: ${dispatchResp.data.html_url}`);
setFailed(`\u{1F5D1}\uFE0F Workflow run was cancelled. Check the run details here: ${dispatchResp.data.html_url}`);
} else {
info(`\u{1F389} Workflow conclusion: ${conclusion}`);
}
}
} catch (error2) {
const e = error2;
if (e.message.endsWith("a disabled workflow")) {
warning("Workflow is disabled, no action was taken");
const e = error2 ?? {};
const message = (typeof error2 === "string" ? error2 : typeof e.message === "string" ? e.message : "").trim();
if (message.endsWith("a disabled workflow")) {
warning("\u{1F6AB} Workflow is disabled, no action was taken");
return;
}
setFailed(e.message);
const oneLine = message.replace(/\s+/g, " ");
const detail = oneLine.length > 512 ? `${oneLine.slice(0, 512)}\u2026` : oneLine;
const status = typeof e.status === "number" && e.status > 0 ? e.status : void 0;
let err;
if (status === void 0) {
err = detail || (error2 instanceof Error ? `\u274C ${error2.name} without a message` : "\u2049\uFE0F Unknown error (no error message)");
} else {
err = status === 500 && !e.response ? "\u{1F4E1} Network error (no response)" : `\u{1F6A8} HTTP ${status}`;
if (typeof e.request?.method === "string" && typeof e.request?.url === "string") {
err += ` from '${e.request.method} ${e.request.url}'`;
}
err += `: ${detail || "(response body remained empty)"}`;
}
if (dispatchReqInFlightTo) {
err = `\u{1F4A5} Failed to dispatch workflow ${dispatchReqInFlightTo}: ${err}`;
if (status === void 0 || status >= 500) {
err += ".\nThis was a server or network error, so GitHub may still have created the workflow run.\nCheck the run history of the workflow before dispatching again!";
}
}
setFailed(err);
}
}
run();
Expand Down
64 changes: 55 additions & 9 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Workflow = {
// =============================================================================
async function run(): Promise<void> {
core.info(`🏃 Workflow Dispatch Action v${PackageJSON.version}`)
let dispatchReqInFlightTo: string | undefined
try {
// Required inputs
const workflowRef = core.getInput('workflow')
Expand All @@ -40,7 +41,7 @@ async function run(): Promise<void> {
try {
inputs = JSON.parse(inputsJson)
} catch (e) {
throw new Error(`Failed to parse 'inputs' JSON string: ${e instanceof Error ? e.message : String(e)}`, {
throw new Error(`🤯 Failed to parse 'inputs' JSON string: ${e instanceof Error ? e.message : String(e)}`, {
cause: e,
})
}
Expand Down Expand Up @@ -72,12 +73,16 @@ async function run(): Promise<void> {
) // Or it stays in top level directory
})

if (!foundWorkflow) throw new Error(`Unable to find workflow '${workflowRef}' in ${owner}/${repo} 😥`)
if (!foundWorkflow) throw new Error(`😥 Unable to find workflow '${workflowRef}' in ${owner}/${repo}`)

core.info(`🔎 Found workflow, id: ${foundWorkflow.id}, name: ${foundWorkflow.name}, path: ${foundWorkflow.path}`)

// Call workflow_dispatch API
//
// Retain where the dispatch request is going to in order for the exception
// handler to distinguish a failed dispatch from a failure of a later step
core.info('🚀 Calling GitHub API to dispatch workflow...')
dispatchReqInFlightTo = `'${foundWorkflow.path}' (id: ${foundWorkflow.id}) in '${owner}/${repo}' on ref '${ref}'`
const dispatchResp = await octokit.request(
`POST /repos/${owner}/${repo}/actions/workflows/${foundWorkflow.id}/dispatches`,
{
Expand All @@ -87,6 +92,7 @@ async function run(): Promise<void> {
headers: { 'x-github-api-version': API_VERSION },
},
)
dispatchReqInFlightTo = undefined

core.info(`🏆 API response status: ${dispatchResp.status}`)
core.info(`🌐 Run URL: ${dispatchResp.data.html_url}`)
Expand All @@ -110,7 +116,8 @@ async function run(): Promise<void> {
) {
if ((Date.now() - startTime) / 1000 > timeoutSeconds) {
core.warning(
`⚠️ Workflow run did not complete within ${timeoutSeconds} seconds, timing out.\nNote: The workflow is still running but we have stopped waiting. You can check the run status here: ${dispatchResp.data.html_url}`,
`⚠️ Workflow run did not complete within ${timeoutSeconds} seconds, timing out.\n` +
`Note: The workflow is still running but we have stopped waiting. You can check the run status here: ${dispatchResp.data.html_url}`,
)
runStatus = 'timed_out'
break
Expand Down Expand Up @@ -155,22 +162,61 @@ async function run(): Promise<void> {

// Set this action to failed if the triggered workflow run failed or was cancelled
if (conclusion === 'failure') {
core.setFailed(`Workflow run failed. Check the run details here: ${dispatchResp.data.html_url}`)
core.setFailed(`🤯 Workflow run failed. Check the run details here: ${dispatchResp.data.html_url}`)
} else if (conclusion === 'cancelled') {
core.setFailed(`Workflow run was cancelled. Check the run details here: ${dispatchResp.data.html_url}`)
core.setFailed(`🗑️ Workflow run was cancelled. Check the run details here: ${dispatchResp.data.html_url}`)
} else {
core.info(`🎉 Workflow conclusion: ${conclusion}`)
}
}
} catch (error) {
const e = error as Error
// Anything could have been caught here, including errors without a message and non-Error values
const e = (error ?? {}) as {
message?: unknown
status?: unknown
request?: { method?: unknown; url?: unknown }
response?: { headers?: Record<string, unknown> }
}
const message = (typeof error === 'string' ? error : typeof e.message === 'string' ? e.message : '').trim()

if (e.message.endsWith('a disabled workflow')) {
core.warning('Workflow is disabled, no action was taken')
if (message.endsWith('a disabled workflow')) {
core.warning('🚫 Workflow is disabled, no action was taken')
return
}

core.setFailed(e.message)
// Octokit builds an error message from the response body, which is empty for many 5xx responses,
// and empty 'error' messages are not retained in the logs; name the failure based on HTTP status as well
//
// The body is collapsed to one line and cut to a limit, as GitHub error pages can be several KiB of HTML
const oneLine = message.replace(/\s+/g, ' ')
const detail = oneLine.length > 512 ? `${oneLine.slice(0, 512)}…` : oneLine
const status = typeof e.status === 'number' && e.status > 0 ? e.status : undefined
let err: string
if (status === undefined) {
err =
detail ||
(error instanceof Error ? `❌ ${error.name} without a message` : '⁉️ Unknown error (no error message)') // Not an HTTP error, just a plain Error, or any other thrown non-Error value
} else {
// Octokit reports a network failure (no response at all) as status 500 without a response object
err = status === 500 && !e.response ? '📡 Network error (no response)' : `🚨 HTTP ${status}`
if (typeof e.request?.method === 'string' && typeof e.request?.url === 'string') {
err += ` from '${e.request.method} ${e.request.url}'`
}
err += `: ${detail || '(response body remained empty)'}`
}

if (dispatchReqInFlightTo) {
err = `💥 Failed to dispatch workflow ${dispatchReqInFlightTo}: ${err}`
if (status === undefined || status >= 500) {
// A server error or network failure does not tell whether GitHub processed the request!
err +=
'.\n' +
'This was a server or network error, so GitHub may still have created the workflow run.\n' +
'Check the run history of the workflow before dispatching again!'
}
}

core.setFailed(err)
}
}

Expand Down