kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
at main 133 lines 3.2 kB view raw
1import type { GitHubConfig } from "../config"; 2 3function slugify(text: string): string { 4 return text 5 .toLowerCase() 6 .replace(/[^a-z0-9]+/g, "-") 7 .replace(/^-|-$/g, "") 8 .slice(0, 50); 9} 10 11export function generateBranchName( 12 pattern: string, 13 projectSlug: string, 14 taskNumber: number, 15 taskTitle: string, 16): string { 17 return pattern 18 .replace("{slug}", projectSlug.toLowerCase()) 19 .replace("{number}", taskNumber.toString()) 20 .replace("{title}", slugify(taskTitle)); 21} 22 23export function createBranchRegex( 24 pattern: string, 25 projectSlug: string, 26): RegExp { 27 const escapedPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); 28 29 const regexPattern = escapedPattern 30 .replace("\\{slug\\}", projectSlug.toLowerCase()) 31 .replace("\\{number\\}", "(\\d+)") 32 .replace("\\{title\\}", "([a-z0-9-]+)"); 33 34 // Allow optional suffix after the pattern (e.g., lif-3-part-1) 35 return new RegExp(`^${regexPattern}(?:-.*)?$`, "i"); 36} 37 38export function extractTaskNumberFromBranch( 39 branchName: string, 40 config: GitHubConfig, 41 projectSlug: string, 42): number | null { 43 if (config.customBranchRegex) { 44 try { 45 const customRegex = new RegExp(config.customBranchRegex, "i"); 46 const match = branchName.match(customRegex); 47 if (match?.[1]) { 48 const num = Number.parseInt(match[1], 10); 49 if (!Number.isNaN(num)) return num; 50 } 51 } catch { 52 console.error("Invalid custom branch regex:", config.customBranchRegex); 53 } 54 return null; 55 } 56 57 const pattern = config.branchPattern || "{slug}-{number}"; 58 const regex = createBranchRegex(pattern, projectSlug); 59 const match = branchName.match(regex); 60 61 if (match?.[1]) { 62 const num = Number.parseInt(match[1], 10); 63 if (!Number.isNaN(num)) return num; 64 } 65 66 return null; 67} 68 69export function extractTaskNumberFromPRTitle(title: string): number | null { 70 const patterns = [ 71 /\[(\d+)\]/, 72 /#(\d+)/, 73 /\((\d+)\)/, 74 /^(\d+)[:\-\s]/, 75 /task[:\-\s]*(\d+)/i, 76 ]; 77 78 for (const pattern of patterns) { 79 const match = title.match(pattern); 80 if (match?.[1]) { 81 const num = Number.parseInt(match[1], 10); 82 if (!Number.isNaN(num)) return num; 83 } 84 } 85 86 return null; 87} 88 89export function extractTaskNumberFromPRBody(body: string): number | null { 90 const patterns = [ 91 /task[:\-\s#]*(\d+)/i, 92 /closes[:\-\s#]*(\d+)/i, 93 /fixes[:\-\s#]*(\d+)/i, 94 /resolves[:\-\s#]*(\d+)/i, 95 ]; 96 97 for (const pattern of patterns) { 98 const match = body.match(pattern); 99 if (match?.[1]) { 100 const num = Number.parseInt(match[1], 10); 101 if (!Number.isNaN(num)) return num; 102 } 103 } 104 105 return null; 106} 107 108export function extractTaskNumber( 109 branchName: string, 110 prTitle: string | undefined, 111 prBody: string | undefined, 112 config: GitHubConfig, 113 projectSlug: string, 114): number | null { 115 const fromBranch = extractTaskNumberFromBranch( 116 branchName, 117 config, 118 projectSlug, 119 ); 120 if (fromBranch) return fromBranch; 121 122 if (prTitle) { 123 const fromTitle = extractTaskNumberFromPRTitle(prTitle); 124 if (fromTitle) return fromTitle; 125 } 126 127 if (prBody) { 128 const fromBody = extractTaskNumberFromPRBody(prBody); 129 if (fromBody) return fromBody; 130 } 131 132 return null; 133}