import { matchesFilters } from "~/lib/glob"; import type { ExprValue } from "./expressions"; import type { RefFilters, WorkflowSpec } from "./spec"; import type { RepoRow } from "~/lib/types"; const DEFAULT_PR_TYPES = ["opened", "synchronize", "reopened"]; const DEFAULT_RELEASE_TYPES = ["published"]; const DEFAULT_WORKFLOW_RUN_TYPES = ["completed"]; export type TriggerEvent = | "push" | "pull_request" | "release" | "workflow_dispatch" | "schedule" | "issues" | "issue_comment" | "pull_request_review" | "workflow_run" | "create" | "delete"; export type Trigger = { event: TriggerEvent; action: string | null; ref: string; sha: string; actorId: string | null; actorName: string; title: string; prNumber: number | null; headRef: string | null; baseRef: string | null; changedPaths: string[] | null; inputs: Record; workflowPath: string | null; /** Repository whose objects are checked out (differs from `repo` for fork pull requests). */ sourceRepo: RepoRow; /** Webhook-style payload exposed as `github.event`. */ payload?: Record; /** How many `workflow_run` hops led to this trigger; bounded to stop chains. */ depth?: number; cron?: string; workflowRunName?: string; /** Where workflow definitions are read from when not the checked-out commit (merge queue: the base branch). */ workflowSource?: { repo: RepoRow; sha: string }; /** The checked-out code is not trusted (fork code in a merge group): no secrets, variables or write token. */ untrusted?: boolean; }; function pathsAllowed(filters: RefFilters, changed: string[] | null): boolean { if (changed === null) return true; if (filters.paths && !changed.some((path) => matchesFilters(filters.paths ?? [], path))) return false; if (filters["paths-ignore"] && changed.every((path) => matchesFilters(filters["paths-ignore"] ?? [], path))) { return false; } return true; } function refAllowed(include: string[] | undefined, exclude: string[] | undefined, name: string): boolean { if (include && !matchesFilters(include, name)) return false; if (exclude && matchesFilters(exclude, name)) return false; return true; } export function workflowMatches(spec: WorkflowSpec, trigger: Trigger, path: string): boolean { const on = spec.on; switch (trigger.event) { case "push": { const filters = on.push; if (!filters) return false; const isTag = trigger.ref.startsWith("refs/tags/"); const name = trigger.ref.replace(/^refs\/(heads|tags)\//, ""); const hasBranchFilter = Boolean(filters.branches || filters["branches-ignore"]); const hasTagFilter = Boolean(filters.tags || filters["tags-ignore"]); if (isTag) { if (!hasTagFilter && hasBranchFilter) return false; return refAllowed(filters.tags, filters["tags-ignore"], name); } if (!hasBranchFilter && hasTagFilter) return false; return ( refAllowed(filters.branches, filters["branches-ignore"], name) && pathsAllowed(filters, trigger.changedPaths) ); } case "pull_request": { const filters = on.pull_request; if (!filters) return false; if (!(filters.types ?? DEFAULT_PR_TYPES).includes(trigger.action ?? "")) return false; return ( refAllowed(filters.branches, filters["branches-ignore"], trigger.baseRef ?? "") && pathsAllowed(filters, trigger.changedPaths) ); } case "release": { const filters = on.release; return Boolean(filters) && (filters?.types ?? DEFAULT_RELEASE_TYPES).includes(trigger.action ?? ""); } case "workflow_dispatch": return Boolean(on.workflow_dispatch) && trigger.workflowPath === path; case "schedule": return trigger.workflowPath === path && (on.schedule ?? []).some((entry) => entry.cron === trigger.cron); case "workflow_run": { const filters = on.workflow_run; if (!filters?.workflows?.includes(trigger.workflowRunName ?? "")) return false; if (!(filters.types ?? DEFAULT_WORKFLOW_RUN_TYPES).includes(trigger.action ?? "")) return false; return refAllowed( filters.branches, filters["branches-ignore"], (trigger.headRef ?? "").replace(/^refs\/heads\//, ""), ); } case "issues": case "issue_comment": case "pull_request_review": case "create": case "delete": { const filters = on[trigger.event]; if (!filters) return false; return !filters.types || filters.types.includes(trigger.action ?? ""); } } }