mime-todo-cli/src/issue.js
2026-03-15 05:11:59 +01:00

89 lines
3.3 KiB
JavaScript

// mime-todo/lib/issue.ts
// Valid status transitions
const VALID_TRANSITIONS = {
"open": ["in-progress", "hold", "cancelled"],
"in-progress": ["done", "hold", "open", "cancelled"],
"hold": ["open", "in-progress", "cancelled"],
"done": ["open"],
"cancelled": ["open"],
};
export function validateStatusTransition(from, to) {
if (from === to)
return null; // no-op is fine
const allowed = VALID_TRANSITIONS[from];
if (!allowed?.includes(to)) {
return `Invalid status transition: ${from}${to}. Allowed from ${from}: ${allowed?.join(", ") ?? "none"}`;
}
return null;
}
export function parseRelationships(text) {
const relationships = {};
// Handle empty relationships
if (!text || text.trim() === "") {
return relationships;
}
// Simple parsing for format like "dependsOn:3"
const parts = text.split(/,\s*/).filter(p => p.trim() !== "");
for (const part of parts) {
const [key, value] = part.split(":").map(s => s.trim());
if (key && value) {
relationships[key] = value.split(/[\s]+/).map(Number).filter(n => !isNaN(n));
}
}
return relationships;
}
export function parseIssue(text) {
const lines = text.split(/\r?\n/);
const issue = {};
let inDescription = false;
const descLines = [];
const bodyLines = [];
for (const line of lines) {
if (!inDescription) {
if (line.startsWith("ID:"))
issue.id = Number(line.slice(3).trim());
else if (line.startsWith("Type:"))
issue.type = line.slice(5).trim();
else if (line.startsWith("Title:"))
issue.title = line.slice(6).trim();
else if (line.startsWith("Status:"))
issue.status = line.slice(7).trim();
else if (line.startsWith("Priority:"))
issue.priority = line.slice(9).trim();
else if (line.startsWith("Created:"))
issue.created = line.slice(8).trim();
else if (line.startsWith("DueStart:"))
issue.dueStart = line.slice(9).trim();
else if (line.startsWith("DueEnd:"))
issue.dueEnd = line.slice(7).trim();
else if (line.startsWith("Module:"))
issue.module = line.slice("Module:".length).trim();
else if (line.startsWith("Relationships:")) {
const rel = line.slice("Relationships:".length).trim();
issue.relationships = parseRelationships(rel);
}
else if (line.startsWith("Description:")) {
inDescription = true;
descLines.push(line.slice("Description:".length).trim());
}
}
else {
// Description continuation
if (line.trim() === "") {
// blank line ends description
inDescription = false;
continue;
}
if (/^\s+/.test(line)) {
descLines.push(line.trim());
continue;
}
// first non-indented line ends description
inDescription = false;
bodyLines.push(line);
}
}
issue.description = descLines.join("\n");
issue.body = bodyLines.join("\n");
return issue;
}