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
135 changes: 0 additions & 135 deletions .github/configs/.cspell.yml

This file was deleted.

156 changes: 156 additions & 0 deletions .github/scripts/monitor-remote-workflow.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// @ts-nocheck

/**
* Monitor a remote workflow run that was triggered via `repository_dispatch`.
*
* Designed to be invoked from `actions/github-script`. Configuration is read
* from `INPUT_*` environment variables (via `core.getInput`):
* - OWNER (required) Target repository owner.
* - REPO (required) Target repository name.
* - WORKFLOW_FILE (required) Target workflow file name (e.g. `swa.yml`).
* - DISPATCH_STARTED_AT (required) ISO timestamp captured just before dispatch.
* - MAX_WAIT_SECONDS (optional) Overall timeout in seconds (default 900).
* - POLL_INTERVAL_SECONDS (optional) Poll interval in seconds (default 10).
*
* On success it exposes `run_id`, `run_url`, and `conclusion` outputs. On
* failure (remote run failed or timed out) it calls `core.setFailed` with a
* summary of the failed jobs and steps.
*
* @param {{ github: any, core: any }} param0
*/
export default async ({ github, core }) => {
try {
const remoteOwner = core.getInput("OWNER", { required: true });
const remoteRepo = core.getInput("REPO", { required: true });
const remoteWorkflowFile = core.getInput("WORKFLOW_FILE", {
required: true,
});
const dispatchStartedAt = core.getInput("DISPATCH_STARTED_AT", {
required: true,
});

const maxWaitSeconds = Number(core.getInput("MAX_WAIT_SECONDS") || "900");
const pollIntervalSeconds = Number(
core.getInput("POLL_INTERVAL_SECONDS") || "10",
);

/** @param {number} ms */
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

core.info(
`Waiting for remote workflow run in ${remoteOwner}/${remoteRepo}...`,
);

const findLatestRun = async () => {
const response = await github.rest.actions.listWorkflowRuns({
owner: remoteOwner,
repo: remoteRepo,
workflow_id: remoteWorkflowFile,
event: "repository_dispatch",
per_page: 20,
});

const candidateRuns = (response.data.workflow_runs || [])
/** @param {any} run */
.filter((run) => run.created_at >= dispatchStartedAt)
/** @param {any} left @param {any} right */
.sort((left, right) => left.created_at.localeCompare(right.created_at));

return candidateRuns.length > 0
? candidateRuns[candidateRuns.length - 1]
: undefined;
};

let run;
for (
let elapsed = 0;
elapsed < maxWaitSeconds;
elapsed += pollIntervalSeconds
) {
run = await findLatestRun();
if (run) {
break;
}

await sleep(pollIntervalSeconds * 1000);
}

if (!run) {
core.setFailed(
`Timed out waiting for remote workflow run to start: https://github.com/${remoteOwner}/${remoteRepo}/actions/workflows/${remoteWorkflowFile}`,
);
return;
}

const runUrl =
run.html_url ||
`https://github.com/${remoteOwner}/${remoteRepo}/actions/runs/${run.id}`;

core.info(`Monitoring remote run id: ${run.id}`);
core.info(`Remote workflow run URL: ${runUrl}`);

for (
let elapsed = 0;
elapsed < maxWaitSeconds;
elapsed += pollIntervalSeconds
) {
const runResponse = await github.rest.actions.getWorkflowRun({
owner: remoteOwner,
repo: remoteRepo,
run_id: run.id,
});

const status = runResponse.data.status;
const conclusion = runResponse.data.conclusion || "";

if (status === "completed") {
core.setOutput("run_id", String(run.id));
core.setOutput("run_url", runUrl);
core.setOutput("conclusion", conclusion);

if (conclusion === "success") {
core.info("Remote workflow completed successfully");
return;
}

const jobsResponse = await github.rest.actions.listJobsForWorkflowRun({
owner: remoteOwner,
repo: remoteRepo,
run_id: run.id,
per_page: 100,
});

const failedJobs = (jobsResponse.data.jobs || []).filter(
/** @param {any} job */
(job) => job.conclusion !== "success",
);
const failedJobText = failedJobs
/** @param {any} job */
.map((job) => {
const failedSteps = (job.steps || [])
/** @param {any} step */
.filter((step) => step.conclusion === "failure")
/** @param {any} step */
.map((step) => ` - Step: ${step.name}`)
.join("\n");

return `- Job: ${job.name} [${job.conclusion}]${failedSteps ? `\n${failedSteps}` : ""}`;
})
.join("\n");

core.setFailed(
`Remote workflow failed with conclusion: ${conclusion}\nRemote workflow run: ${runUrl}${failedJobText ? `\nFailed jobs/steps:\n${failedJobText}` : ""}`,
);
return;
}

await sleep(pollIntervalSeconds * 1000);
}

core.setOutput("run_id", String(run.id));
core.setOutput("run_url", runUrl);
core.setFailed(`Timed out waiting for remote workflow completion: ${runUrl}`);
} catch (error) {
core.setFailed(error instanceof Error ? error.message : String(error));
}
};
Loading
Loading