import { GITHUB_EMPTY_RESPONSE_SCHEMA, GITHUB_WORKFLOW_DISPATCH_RESULT_SCHEMA, GITHUB_WORKFLOW_JOB_SCHEMA, GITHUB_WORKFLOW_RUN_SCHEMA, GITHUB_WORKFLOW_SCHEMA, type GitHubWorkflowDispatchResult, } from "@automate.ax/integration-contracts/github" import * as z from "zod" import { defineAction } from "../../../automation/actions" import { getGitHubApi } from "../lib/api" import { GITHUB_PAGE_INPUT_SCHEMA, GITHUB_REPOSITORY_INPUT_SCHEMA, GITHUB_WORKFLOW_RUN_INPUT_SCHEMA, } from "../lib/input-schemas" import { GITHUB_ACTIONS_READ_SCOPE, GITHUB_ACTIONS_WRITE_SCOPE, } from "../lib/scopes" const GITHUB_WORKFLOW_ID_SCHEMA = z.union([ z.number().int().positive(), z.string().min(1), ]) const GITHUB_WORKFLOW_RUN_STATUS_SCHEMA = z.enum([ "action_required", "cancelled", "completed", "failure", "in_progress", "neutral", "pending", "queued", "requested", "skipped", "stale", "success", "timed_out", "waiting", ]) const GITHUB_WORKFLOW_DISPATCH_INPUTS_SCHEMA = z .record(z.string(), z.json()) .refine((inputs) => Object.keys(inputs).length <= 25, { error: "Workflow dispatch supports at most 25 inputs.", }) /** Lists GitHub Actions workflows in one repository. */ export const listGitHubWorkflows = defineAction("List GitHub workflows") .describe("Lists GitHub Actions workflows in a repository.") .account("github", GITHUB_ACTIONS_READ_SCOPE) .input(GITHUB_REPOSITORY_INPUT_SCHEMA.extend(GITHUB_PAGE_INPUT_SCHEMA)) .output(GITHUB_WORKFLOW_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi( account.secret, ).rest.actions.listRepoWorkflows({ owner: input.owner, page: input.page, per_page: input.perPage, repo: input.repository, }) return data.workflows }) /** Gets one GitHub Actions workflow by ID or file name. */ export const getGitHubWorkflow = defineAction("Get GitHub workflow") .describe("Gets one GitHub Actions workflow by ID or workflow file name.") .account("github", GITHUB_ACTIONS_READ_SCOPE) .input( GITHUB_REPOSITORY_INPUT_SCHEMA.extend({ /** Numeric workflow ID or workflow file name. */ workflowId: GITHUB_WORKFLOW_ID_SCHEMA, }), ) .output(GITHUB_WORKFLOW_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi( account.secret, ).rest.actions.getWorkflow({ owner: input.owner, repo: input.repository, workflow_id: input.workflowId, }) return data }) /** Directly dispatches a GitHub Actions workflow. */ export const dispatchGitHubWorkflow = defineAction("Dispatch GitHub workflow") .describe( "Directly creates a workflow_dispatch run and returns its identity.", ) .account("github", GITHUB_ACTIONS_WRITE_SCOPE) .input( GITHUB_REPOSITORY_INPUT_SCHEMA.extend({ /** Workflow inputs defined by the workflow_dispatch trigger. */ inputs: GITHUB_WORKFLOW_DISPATCH_INPUTS_SCHEMA.optional(), /** Branch or tag ref to run. */ ref: z.string().min(1), /** Numeric workflow ID or workflow file name. */ workflowId: GITHUB_WORKFLOW_ID_SCHEMA, }), ) .output(GITHUB_WORKFLOW_DISPATCH_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi( account.secret, ).request({ method: "POST", owner: input.owner, ref: input.ref, repo: input.repository, url: "/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches", workflow_id: input.workflowId, ...(input.inputs && { inputs: input.inputs }), }) return data }) /** Enables a disabled GitHub Actions workflow. */ export const enableGitHubWorkflow = defineAction("Enable GitHub workflow") .describe("Enables a disabled GitHub Actions workflow.") .account("github", GITHUB_ACTIONS_WRITE_SCOPE) .input( GITHUB_REPOSITORY_INPUT_SCHEMA.extend({ /** Numeric workflow ID or workflow file name. */ workflowId: GITHUB_WORKFLOW_ID_SCHEMA, }), ) .output(GITHUB_EMPTY_RESPONSE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getGitHubApi(account.secret).rest.actions.enableWorkflow({ owner: input.owner, repo: input.repository, workflow_id: input.workflowId, }) return {} }) /** Disables a GitHub Actions workflow. */ export const disableGitHubWorkflow = defineAction("Disable GitHub workflow") .describe("Disables a GitHub Actions workflow without deleting its file.") .account("github", GITHUB_ACTIONS_WRITE_SCOPE) .input( GITHUB_REPOSITORY_INPUT_SCHEMA.extend({ /** Numeric workflow ID or workflow file name. */ workflowId: GITHUB_WORKFLOW_ID_SCHEMA, }), ) .output(GITHUB_EMPTY_RESPONSE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getGitHubApi(account.secret).rest.actions.disableWorkflow({ owner: input.owner, repo: input.repository, workflow_id: input.workflowId, }) return {} }) /** Lists GitHub Actions workflow runs in one repository. */ export const listGitHubWorkflowRuns = defineAction("List GitHub workflow runs") .describe("Lists and filters GitHub Actions workflow runs.") .account("github", GITHUB_ACTIONS_READ_SCOPE) .input( GITHUB_REPOSITORY_INPUT_SCHEMA.extend({ ...GITHUB_PAGE_INPUT_SCHEMA, /** Filter by actor login. */ actor: z.string().min(1).optional(), /** Filter by branch. */ branch: z.string().min(1).optional(), /** Filter by the check suite that owns the runs. */ checkSuiteId: z.number().int().positive().optional(), /** Filter by creation date range using GitHub search syntax. */ created: z.string().min(1).optional(), /** Filter by the workflow trigger event name. */ event: z.string().min(1).optional(), /** Exclude pull request objects from each run. */ excludePullRequests: z.boolean().optional(), /** Filter by exact head commit SHA. */ headSha: z.string().min(1).optional(), /** Filter by run status or conclusion. */ status: GITHUB_WORKFLOW_RUN_STATUS_SCHEMA.optional(), /** Restrict results to a numeric workflow ID or workflow file name. */ workflowId: GITHUB_WORKFLOW_ID_SCHEMA.optional(), }), ) .output(GITHUB_WORKFLOW_RUN_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const api = getGitHubApi(account.secret) const parameters = { owner: input.owner, page: input.page, per_page: input.perPage, repo: input.repository, ...(input.actor && { actor: input.actor }), ...(input.branch && { branch: input.branch }), ...(input.checkSuiteId !== undefined && { check_suite_id: input.checkSuiteId, }), ...(input.created && { created: input.created }), ...(input.event && { event: input.event }), ...(input.excludePullRequests !== undefined && { exclude_pull_requests: input.excludePullRequests, }), ...(input.headSha && { head_sha: input.headSha }), ...(input.status && { status: input.status }), } const { data } = input.workflowId ? await api.rest.actions.listWorkflowRuns({ ...parameters, workflow_id: input.workflowId, }) : await api.rest.actions.listWorkflowRunsForRepo(parameters) return data.workflow_runs }) /** Gets one GitHub Actions workflow run. */ export const getGitHubWorkflowRun = defineAction("Get GitHub workflow run") .describe("Gets one GitHub Actions workflow run by ID.") .account("github", GITHUB_ACTIONS_READ_SCOPE) .input( GITHUB_WORKFLOW_RUN_INPUT_SCHEMA.extend({ /** Exclude pull request objects from the run. */ excludePullRequests: z.boolean().optional(), }), ) .output(GITHUB_WORKFLOW_RUN_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { data } = await getGitHubApi( account.secret, ).rest.actions.getWorkflowRun({ owner: input.owner, repo: input.repository, run_id: input.runId, ...(input.excludePullRequests !== undefined && { exclude_pull_requests: input.excludePullRequests, }), }) return data }) /** Lists jobs from one GitHub Actions workflow run. */ export const listGitHubWorkflowRunJobs = defineAction( "List GitHub workflow run jobs", ) .describe("Lists jobs for one workflow run or run attempt.") .account("github", GITHUB_ACTIONS_READ_SCOPE) .input( GITHUB_WORKFLOW_RUN_INPUT_SCHEMA.extend({ ...GITHUB_PAGE_INPUT_SCHEMA, /** Specific run attempt. Omit to use the current run attempt. */ attemptNumber: z.number().int().positive().optional(), /** Return jobs from the latest attempt or all attempts. */ filter: z.enum(["all", "latest"]).prefault("latest"), }), ) .output(GITHUB_WORKFLOW_JOB_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const api = getGitHubApi(account.secret) const parameters = { owner: input.owner, page: input.page, per_page: input.perPage, repo: input.repository, run_id: input.runId, } const { data } = input.attemptNumber ? await api.rest.actions.listJobsForWorkflowRunAttempt({ ...parameters, attempt_number: input.attemptNumber, }) : await api.rest.actions.listJobsForWorkflowRun({ ...parameters, filter: input.filter, }) return data.jobs }) /** Cancels a GitHub Actions workflow run. */ export const cancelGitHubWorkflowRun = defineAction( "Cancel GitHub workflow run", ) .describe("Requests normal or force cancellation of a workflow run.") .account("github", GITHUB_ACTIONS_WRITE_SCOPE) .input( GITHUB_WORKFLOW_RUN_INPUT_SCHEMA.extend({ /** Force cancellation when normal cancellation cannot stop the run. */ force: z.boolean().prefault(false), }), ) .output(GITHUB_EMPTY_RESPONSE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const api = getGitHubApi(account.secret) const parameters = { owner: input.owner, repo: input.repository, run_id: input.runId, } if (input.force) { await api.rest.actions.forceCancelWorkflowRun(parameters) } else { await api.rest.actions.cancelWorkflowRun(parameters) } return {} }) /** Re-runs every job in a GitHub Actions workflow run. */ export const rerunGitHubWorkflow = defineAction("Re-run GitHub workflow") .describe("Re-runs every job in a completed GitHub Actions workflow run.") .account("github", GITHUB_ACTIONS_WRITE_SCOPE) .input( GITHUB_WORKFLOW_RUN_INPUT_SCHEMA.extend({ /** Enable runner diagnostic logging for the new attempt. */ enableDebugLogging: z.boolean().optional(), }), ) .output(GITHUB_EMPTY_RESPONSE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getGitHubApi(account.secret).rest.actions.reRunWorkflow({ owner: input.owner, repo: input.repository, run_id: input.runId, ...(input.enableDebugLogging !== undefined && { enable_debug_logging: input.enableDebugLogging, }), }) return {} }) /** Re-runs failed jobs and their dependent jobs in a workflow run. */ export const rerunFailedGitHubWorkflowJobs = defineAction( "Re-run failed GitHub workflow jobs", ) .describe("Re-runs failed jobs and dependent jobs in a workflow run.") .account("github", GITHUB_ACTIONS_WRITE_SCOPE) .input( GITHUB_WORKFLOW_RUN_INPUT_SCHEMA.extend({ /** Enable runner diagnostic logging for the new attempt. */ enableDebugLogging: z.boolean().optional(), }), ) .output(GITHUB_EMPTY_RESPONSE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getGitHubApi(account.secret).rest.actions.reRunWorkflowFailedJobs({ owner: input.owner, repo: input.repository, run_id: input.runId, ...(input.enableDebugLogging !== undefined && { enable_debug_logging: input.enableDebugLogging, }), }) return {} })