CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | /**
* Enqueue `on: pull_request` workflow runs when a PR is opened.
*
* `.gluecron/workflows/*.yml` files with `on: pull_request` (or the
* `[pull_request]` / `{pull_request: {...}}` shapes — all normalised by
* `normaliseOn()` in workflow-parser.ts) were parsed and stored into the
* `workflows` table on every push (see push-workflow-sync.ts), but nothing
* ever called `enqueueRun()` with `event: "pull_request"` — every call site
* only ever passed "push", "workflow_dispatch", "manual", or "schedule". A
* workflow declaring `on: pull_request` was silently dead: parsed, stored,
* never run, no error anywhere. This module is the missing wiring, called
* from the PR-creation handler (src/routes/pulls.tsx).
*
* Workflows are already synced into the `workflows` table by push (the
* `.gluecron/workflows/` dir only changes via push), so unlike
* push-workflow-sync.ts this module does NOT re-read the git tree — it just
* queries the existing table for non-disabled workflows whose `on:` events
* include `pull_request`.
*
* Scope: PR **open** only, mirroring the task that introduced this file.
* GitHub also fires `on: pull_request` for `synchronize` (new commits pushed
* to an open PR's head branch) and `closed` — those are NOT wired here; a
* follow-up would hook the same query into the PR-synchronize and close
* paths.
*
* Best-effort: a broken row or a failing enqueue is recorded in `errors`
* and skipped, never thrown — a bug here must not break PR creation.
*/
import { and, eq } from "drizzle-orm";
import { db } from "../db";
import { workflows } from "../db/schema";
import { enqueueRun as realEnqueueRun } from "./workflow-runner";
export interface PrWorkflowSyncResult {
enqueued: number;
errors: string[];
}
async function loadPullRequestEligibleWorkflows(
repositoryId: string
): Promise<Array<{ id: string; onEvents: string }>> {
return db
.select({ id: workflows.id, onEvents: workflows.onEvents })
.from(workflows)
.where(
and(eq(workflows.repositoryId, repositoryId), eq(workflows.disabled, false))
);
}
export async function enqueuePullRequestWorkflows(
opts: {
repositoryId: string;
headBranch: string;
headSha: string;
triggeredBy?: string | null;
},
// Injectable for tests — avoids mock.module() on ../db or ./workflow-runner,
// both of which are imported by dozens of unrelated test files and would
// leak a global mock across the whole test run (same rationale as
// push-workflow-sync.ts / codeowners.ts's requiredOwnersApproved).
deps: {
enqueueRun: typeof realEnqueueRun;
loadWorkflows: typeof loadPullRequestEligibleWorkflows;
} = { enqueueRun: realEnqueueRun, loadWorkflows: loadPullRequestEligibleWorkflows }
): Promise<PrWorkflowSyncResult> {
const result: PrWorkflowSyncResult = { enqueued: 0, errors: [] };
let rows: Array<{ id: string; onEvents: string }>;
try {
rows = await deps.loadWorkflows(opts.repositoryId);
} catch (err) {
result.errors.push(
`query: ${err instanceof Error ? err.message : String(err)}`
);
return result;
}
for (const row of rows) {
let onEvents: unknown;
try {
onEvents = JSON.parse(row.onEvents);
} catch {
result.errors.push(`${row.id}: malformed onEvents JSON`);
continue;
}
if (!Array.isArray(onEvents) || !onEvents.includes("pull_request")) continue;
try {
await deps.enqueueRun({
workflowId: row.id,
repositoryId: opts.repositoryId,
event: "pull_request",
ref: opts.headBranch,
commitSha: opts.headSha,
triggeredBy: opts.triggeredBy ?? null,
});
result.enqueued += 1;
} catch (err) {
result.errors.push(
`enqueue ${row.id}: ${err instanceof Error ? err.message : String(err)}`
);
}
}
return result;
}
|