// lib/util/doors.mjs // The `### Doors` list of a CHANGELOG release section: every delivery says // whether it is a one-way door (cannot be walked back without a cost to users) // or a two-way door (can). Moved here from scripts/release-check.mjs, which // re-exports these names, because lib never imports scripts and the review // evidence writer reads the door too. Door types are credited to // mattpocock/skills@c55ee46 (idea only, no text copied). export const DOOR_LINE_RE = /^- (one-way|two-way): \S/; /** * The CHANGELOG section for `version`, heading line excluded, or null. Matches * `## v1.2.3 …`, `## [1.2.3] …` and `## 1.2.3 …` exactly: 1.1.0 is not 1.10.0 * and not 1.1.0-pre. */ export function releaseSection(text, version) { if (typeof text !== 'string' || !version) return null; const lines = text.split('\n'); const headingVersion = (line) => { const m = line.match(/^##\s+\[?v?([^\]\s(]+)\]?/); return m ? m[1] : null; }; let start = -1; for (let i = 0; i < lines.length; i++) { if (/^##\s/.test(lines[i]) && headingVersion(lines[i]) === version) { start = i + 1; break; } } if (start === -1) return null; let end = lines.length; for (let i = start; i < lines.length; i++) { if (/^##\s/.test(lines[i])) { end = i; break; } } return lines.slice(start, end).join('\n'); } /** * The `### Doors` list of a release section. Top-level `- ` lines must read * `- one-way: …` or `- two-way: …`; indented lines continue the entry above. * @returns {{ found: boolean, entries: Array<{door: string, text: string}>, bad: string[] }} */ export function parseDoors(section) { const lines = String(section ?? '').split('\n'); const start = lines.findIndex((l) => /^###\s+Doors\s*$/.test(l)); if (start === -1) return { found: false, entries: [], bad: [] }; const entries = []; const bad = []; for (let i = start + 1; i < lines.length; i++) { const line = lines[i]; if (/^#{2,3}\s/.test(line)) break; if (!line.startsWith('- ')) continue; const m = line.match(DOOR_LINE_RE); if (m) entries.push({ door: m[1], text: line.slice(m[0].length - 1).trim() }); else bad.push(line); } return { found: true, entries, bad }; }