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
@@ -18,6 +18,8 @@ const mockGiteaGetFileContent = vi.fn();
const mockGiteaListOrgRepositories = vi.fn();
const mockGiteaCreatePullRequest = vi.fn();
const mockGiteaListPullRequests = vi.fn();
const mockGiteaGetCommitStatus = vi.fn();
const mockGiteaMergePullRequest = vi.fn();
const mockNpmGetPackageChangelog = vi.fn();
const mockNpmGetPackageInfo = vi.fn();
const mockAnalyzePackageJson = vi.fn();
@@ -39,9 +41,11 @@ vi.mock("../../src/services/giteaService.js", () => {
GiteaService: class MockGiteaService {
public createPullRequest = mockGiteaCreatePullRequest;
public getCommitStatus = mockGiteaGetCommitStatus;
public getFileContent = mockGiteaGetFileContent;
public listOrgRepositories = mockGiteaListOrgRepositories;
public listPullRequests = mockGiteaListPullRequests;
public mergePullRequest = mockGiteaMergePullRequest;
},
};
});
@@ -221,6 +225,7 @@ describe("updateOrchestratorService", () => {
const mockUpdates = [ createMockUpdate() ];
mockGiteaListOrgRepositories.mockResolvedValue(mockRepos);
mockGiteaGetFileContent.mockResolvedValue(mockFileContent);
mockGiteaListPullRequests.mockResolvedValue([]);
mockAnalyzePackageJson.mockResolvedValue(mockUpdates);
mockNpmGetPackageChangelog.mockResolvedValue("## Changelog");
mockCloneRepository.mockResolvedValue(createMockClonedRepo());
@@ -267,6 +272,7 @@ describe("updateOrchestratorService", () => {
const mockUpdates = [ createMockUpdate() ];
mockGiteaListOrgRepositories.mockResolvedValue(mockRepos);
mockGiteaGetFileContent.mockResolvedValue(mockFileContent);
mockGiteaListPullRequests.mockResolvedValue([]);
mockAnalyzePackageJson.mockResolvedValue(mockUpdates);
mockCloneRepository.mockResolvedValue(createMockClonedRepo());
mockCreateOrUpdateBranch.mockResolvedValue({
@@ -289,6 +295,7 @@ describe("updateOrchestratorService", () => {
const mockUpdates = [ createMockUpdate() ];
mockGiteaListOrgRepositories.mockResolvedValue(mockRepos);
mockGiteaGetFileContent.mockResolvedValue(mockFileContent);
mockGiteaListPullRequests.mockResolvedValue([]);
mockAnalyzePackageJson.mockResolvedValue(mockUpdates);
mockCloneRepository.mockResolvedValue(createMockClonedRepo());
mockCreateOrUpdateBranch.mockResolvedValue({
@@ -312,6 +319,7 @@ describe("updateOrchestratorService", () => {
const mockUpdates = [ createMockUpdate() ];
mockGiteaListOrgRepositories.mockResolvedValue(mockRepos);
mockGiteaGetFileContent.mockResolvedValue(mockFileContent);
mockGiteaListPullRequests.mockResolvedValue([]);
mockAnalyzePackageJson.mockResolvedValue(mockUpdates);
mockCloneRepository.mockResolvedValue(createMockClonedRepo());
mockCreateOrUpdateBranch.mockResolvedValue({
@@ -354,6 +362,7 @@ describe("updateOrchestratorService", () => {
const mockUpdates = [ createMockUpdate() ];
mockGiteaListOrgRepositories.mockResolvedValue(mockRepos);
mockGiteaGetFileContent.mockResolvedValue(mockFileContent);
mockGiteaListPullRequests.mockResolvedValue([]);
mockAnalyzePackageJson.mockResolvedValue(mockUpdates);
mockCloneRepository.mockResolvedValue(createMockClonedRepo());
mockCreateOrUpdateBranch.mockResolvedValue({
@@ -377,6 +386,7 @@ describe("updateOrchestratorService", () => {
const mockUpdates = [ createMockUpdate() ];
mockGiteaListOrgRepositories.mockResolvedValue(mockRepos);
mockGiteaGetFileContent.mockResolvedValue(mockFileContent);
mockGiteaListPullRequests.mockResolvedValue([]);
mockAnalyzePackageJson.mockResolvedValue(mockUpdates);
mockNpmGetPackageChangelog.mockResolvedValue("## Changelog");
mockCloneRepository.mockResolvedValue(createMockClonedRepo());
@@ -426,6 +436,7 @@ describe("updateOrchestratorService", () => {
const mockCleanup = vi.fn();
mockGiteaListOrgRepositories.mockResolvedValue(mockRepos);
mockGiteaGetFileContent.mockResolvedValue(mockFileContent);
mockGiteaListPullRequests.mockResolvedValue([]);
mockAnalyzePackageJson.mockResolvedValue(mockUpdates);
mockCloneRepository.mockResolvedValue(createMockClonedRepo(mockCleanup));
mockCreateOrUpdateBranch.mockResolvedValue({