kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import type { GitHubConfig } from "../config";
2import { createOrUpdateExternalLink } from "../services/link-manager";
3import {
4 findAllIntegrationsByRepo,
5 findTaskByNumber,
6 isTaskInFinalState,
7 updateTaskStatus,
8} from "../services/task-service";
9import { extractTaskNumberFromBranch } from "../utils/branch-matcher";
10import { resolveTargetStatus } from "../utils/resolve-column";
11
12type PushPayload = {
13 ref: string;
14 head_commit?: {
15 id: string;
16 message: string;
17 author?: { name: string };
18 timestamp: string;
19 };
20 repository: {
21 owner: { login: string };
22 name: string;
23 html_url: string;
24 };
25};
26
27const PROTECTED_BRANCHES = [
28 "main",
29 "master",
30 "develop",
31 "staging",
32 "production",
33];
34
35export async function handlePush(payload: PushPayload) {
36 const { ref, repository, head_commit } = payload;
37
38 const branchName = ref.replace("refs/heads/", "");
39 console.log(`[Push] Processing branch: ${branchName}`);
40
41 if (PROTECTED_BRANCHES.includes(branchName)) {
42 console.log(`[Push] Skipping protected branch: ${branchName}`);
43 return;
44 }
45
46 const integrations = await findAllIntegrationsByRepo(
47 repository.owner.login,
48 repository.name,
49 );
50
51 if (integrations.length === 0) {
52 console.log(
53 `[Push] No integrations found for ${repository.owner.login}/${repository.name}`,
54 );
55 return;
56 }
57
58 console.log(
59 `[Push] Found ${integrations.length} integration(s) for this repo`,
60 );
61
62 for (const integration of integrations) {
63 if (!integration.project) {
64 continue;
65 }
66
67 const config = JSON.parse(integration.config) as GitHubConfig;
68 const projectSlug = integration.project.slug;
69 console.log(
70 `[Push] Trying project: ${projectSlug}, pattern: ${config.branchPattern}`,
71 );
72
73 const taskNumber = extractTaskNumberFromBranch(
74 branchName,
75 config,
76 projectSlug,
77 );
78
79 if (!taskNumber) {
80 console.log(
81 `[Push] Could not extract task number from branch: ${branchName} (pattern: ${config.branchPattern}, slug: ${projectSlug})`,
82 );
83 continue;
84 }
85
86 console.log(
87 `[Push] Extracted task number: ${taskNumber} for project ${projectSlug}`,
88 );
89
90 const task = await findTaskByNumber(integration.projectId, taskNumber);
91
92 if (!task) {
93 console.log(
94 `[Push] Task #${taskNumber} not found in project ${integration.projectId}`,
95 );
96 continue;
97 }
98
99 console.log(
100 `[Push] Found task: ${task.id}, current status: ${task.status}`,
101 );
102
103 await createOrUpdateExternalLink({
104 taskId: task.id,
105 integrationId: integration.id,
106 resourceType: "branch",
107 externalId: branchName,
108 url: `${repository.html_url}/tree/${branchName}`,
109 title: branchName,
110 metadata: {
111 lastCommit: head_commit
112 ? {
113 sha: head_commit.id,
114 message: head_commit.message,
115 author: head_commit.author?.name,
116 timestamp: head_commit.timestamp,
117 }
118 : null,
119 },
120 });
121
122 const targetStatus = await resolveTargetStatus(
123 integration.projectId,
124 "branch_push",
125 config.statusTransitions?.onBranchPush || "in-progress",
126 );
127 console.log(
128 `[Push] Target status: ${targetStatus}, current: ${task.status}`,
129 );
130
131 const isTaskFinal = await isTaskInFinalState(task);
132
133 if (task.status !== targetStatus && !isTaskFinal) {
134 console.log(
135 `[Push] Updating task ${task.id} status from ${task.status} to ${targetStatus}`,
136 );
137 await updateTaskStatus(task.id, targetStatus);
138 } else {
139 console.log(`[Push] Skipping status update - already ${task.status}`);
140 }
141
142 return;
143 }
144
145 console.log(
146 `[Push] No matching task found in any integrated project for branch: ${branchName}`,
147 );
148}