feat: add auto-merge for non-major dependency updates
Node.js CI / CI (pull_request) Failing after 8s
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 50s

Minori now automatically merges dependency update PRs when:
- The update is NOT a major version bump (to avoid breaking changes)
- The CI checks pass (status = "success")
- An existing PR for the dependency update is found

This reduces manual work for safe, non-breaking dependency updates
whilst still requiring human review for potentially breaking changes.

Changes:
- Add version comparison utility to detect major version bumps
- Add Gitea service methods for commit status and PR merging
- Add auto-merge logic to update orchestrator
- Add comprehensive tests for new functionality
This commit is contained in:
2026-02-20 19:31:03 -08:00
committed by Naomi Carrigan
parent 2bb7208bab
commit 86d8c1ac93
7 changed files with 479 additions and 14 deletions
+78
View File
@@ -7,6 +7,7 @@
import axios, { isAxiosError, type AxiosInstance } from "axios";
import { config } from "../config.js";
import type {
GiteaCombinedStatus,
GiteaFile,
GiteaPullRequest,
GiteaRepository,
@@ -142,6 +143,83 @@ class GiteaService {
);
return data;
}
/**
* Gets the combined commit status for a specific commit.
* @param owner - The repository owner.
* @param repo - The repository name.
* @param sha - The commit SHA.
* @returns The combined status of all checks for the commit.
*/
public async getCommitStatus(
owner: string,
repo: string,
sha: string,
): Promise<GiteaCombinedStatus> {
const { data } = await this.client.get<GiteaCombinedStatus>(
`/repos/${owner}/${repo}/commits/${sha}/status`,
);
return data;
}
/**
* Merges a pull request.
* @param owner - The repository owner.
* @param repo - The repository name.
* @param index - The pull request index number.
* @returns True if the merge was successful, false otherwise.
*/
public async mergePullRequest(
owner: string,
repo: string,
index: number,
): Promise<boolean> {
try {
await this.client.post(
`/repos/${owner}/${repo}/pulls/${String(index)}/merge`,
{
/* eslint-disable @typescript-eslint/naming-convention -- Gitea API uses snake_case */
Do: "merge",
MergeMessageField: "",
MergeTitleField: "",
delete_branch_after_merge: true,
force_merge: false,
head_commit_id: "",
merge_when_checks_succeed: false,
/* eslint-enable @typescript-eslint/naming-convention -- End Gitea API */
},
);
return true;
} catch (error) {
if (isAxiosError(error)) {
return false;
}
throw error;
}
}
/**
* Deletes a repository branch by name.
* @param owner - The repository owner.
* @param repo - The repository name.
* @param branch - The branch name to remove.
* @returns True if successful, false otherwise.
*/
public async deleteBranch(
owner: string,
repo: string,
branch: string,
): Promise<boolean> {
try {
await this.client.delete(`/repos/${owner}/${repo}/branches/${branch}`);
return true;
} catch (error) {
if (isAxiosError(error)) {
return false;
}
throw error;
}
}
}
export { GiteaService };