Publish Image / publish (push) Successful in 36s
Replace the volatile in-memory Map/Set queue with BullMQ backed by Redis for persistence across restarts, automatic retry with exponential backoff, and non-blocking workspace cleanup. - Two queues: workspace-create (concurrency-limited) and workspace-cleanup (with polling instead of sleep-based stop→delete) - Active workspaces tracked in Redis hash for dedup across restarts - Stale sweep every 10 minutes catches orphaned workspaces - Graceful shutdown on SIGTERM/SIGINT - Replace node-cron with BullMQ repeatable jobs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
62 lines
1.6 KiB
TypeScript
62 lines
1.6 KiB
TypeScript
import type { Context } from "hono";
|
|
import type { IssueEvent, TaskRequest } from "../types.js";
|
|
import type { TaskQueue } from "../queue.js";
|
|
import type { Config } from "../config.js";
|
|
|
|
/**
|
|
* Release ticket opened → tag RC for staging.
|
|
* Release ticket labeled staging-verified → tag for production.
|
|
*
|
|
* Replaces: release-process.json
|
|
*/
|
|
export function release(config: Config, queue: TaskQueue) {
|
|
return async (c: Context) => {
|
|
const event = await c.req.json<IssueEvent>();
|
|
|
|
const labels = event.issue.labels.map((l) => l.name);
|
|
const [org, repo] = event.repository.full_name.split("/");
|
|
|
|
const isRelease =
|
|
event.issue.title.toLowerCase().includes("release") ||
|
|
labels.includes("release");
|
|
|
|
if (!isRelease) {
|
|
return c.text("ignored: not a release ticket", 200);
|
|
}
|
|
|
|
let shouldProcess = false;
|
|
|
|
if (event.action === "opened") {
|
|
shouldProcess = true;
|
|
}
|
|
|
|
if (
|
|
(event.action === "label_updated" || event.action === "labeled") &&
|
|
labels.includes("staging-verified") &&
|
|
!labels.includes("deployed-to-production")
|
|
) {
|
|
shouldProcess = true;
|
|
}
|
|
|
|
if (!shouldProcess) {
|
|
return c.text("ignored: no actionable release event", 200);
|
|
}
|
|
|
|
const task: TaskRequest = {
|
|
taskType: "release",
|
|
issueNumber: event.issue.number,
|
|
giteaOrg: org,
|
|
giteaRepo: repo,
|
|
repoCloneUrl: event.repository.clone_url,
|
|
giteaToken: config.giteaDevToken,
|
|
};
|
|
|
|
const queued = await queue.enqueue(task, {
|
|
useReviewAccount: false,
|
|
body: `🤖 Release process started.`,
|
|
});
|
|
|
|
return c.json({ ok: true, queued });
|
|
};
|
|
}
|