1919 let body = issue.body || "";
2020
2121 // ---------- 1) Extract selected Type from the "### Type" section ----------
22+ // Capture the whole Type block (from "### Type" until next heading or EOF)
2223 const typeBlockMatch = body.match(/(^|\n)#{2,}\s*Type\s*\n+([\s\S]*?)(?=\n#{2,}\s|\n*$)/i);
2324 let selected = null;
2425 if (typeBlockMatch) {
@@ -28,17 +29,23 @@ jobs:
2829 }
2930
3031 // ---------- 2) Map to official GitHub issue type ----------
32+ let typeMapped = false;
3133 if (selected) {
3234 const map = { bug: "bug", feature: "feature", task: "task" };
3335 const ghType = map[selected];
3436 if (ghType) {
35- await github.request("PATCH /repos/{owner}/{repo}/issues/{issue_number}", {
36- owner: context.repo.owner,
37- repo: context.repo.repo,
38- issue_number: issue.number,
39- type: ghType,
40- });
41- core.info(`Set GitHub issue type to: ${ghType}`);
37+ try {
38+ await github.request("PATCH /repos/{owner}/{repo}/issues/{issue_number}", {
39+ owner: context.repo.owner,
40+ repo: context.repo.repo,
41+ issue_number: issue.number,
42+ type: ghType,
43+ });
44+ typeMapped = true;
45+ core.info(`Set GitHub issue type to: ${ghType}`);
46+ } catch (e) {
47+ core.warning(`Failed to set GitHub issue type: ${e.message}`);
48+ }
4249 }
4350 } else {
4451 core.info("No Type detected; skipping type mapping.");
6168 const heading = m[2];
6269 const content = m[3];
6370 const end = start + full.length;
64- return
71+ return { re, start, end, heading, content };
72+ }
73+
74+ // Remove entire section if content is exactly "_No response_"
75+ function removeIfNoResponse(headingPattern) {
76+ const sec = findSection(headingPattern);
77+ if (!sec) return false;
78+ if (/^\s*_No response_\s*$/i.test(sec.content.trim())) {
79+ newBody = newBody.slice(0, sec.start) + "\n" + newBody.slice(sec.end);
80+ core.info(`Removed empty section: ${headingPattern}`);
81+ return true;
82+ }
83+ return false;
84+ }
85+
86+ // Rename a heading only if section exists and we didn't remove it
87+ function renameHeading(onlyForType, fromPattern, toHeading) {
88+ if (selected !== onlyForType) return false;
89+ const sec = findSection(fromPattern);
90+ if (!sec) return false;
91+ const before = newBody.slice(0, sec.start);
92+ const after = newBody.slice(sec.end);
93+ const newSection =
94+ (newBody[sec.start - 1] === "\n" ? "\n" : "") +
95+ `### ${toHeading}\n` +
96+ sec.content;
97+ newBody = before + newSection + after;
98+ core.info(`Renamed heading to: ${toHeading}`);
99+ return true;
100+ }
101+
102+ // Generic rename (no type condition), e.g., to strip "(optional)"
103+ function renameHeadingAny(fromPattern, toHeading) {
104+ const sec = findSection(fromPattern);
105+ if (!sec) return false;
106+ const before = newBody.slice(0, sec.start);
107+ const after = newBody.slice(sec.end);
108+ const newSection =
109+ (newBody[sec.start - 1] === "\n" ? "\n" : "") +
110+ `### ${toHeading}\n` +
111+ sec.content;
112+ newBody = before + newSection + after;
113+ core.info(`Renamed heading to: ${toHeading}`);
114+ return true;
115+ }
116+
117+ // Remove a section by exact heading (no content check)
118+ function removeSection(headingPattern) {
119+ const sec = findSection(headingPattern);
120+ if (!sec) return false;
121+ newBody = newBody.slice(0, sec.start) + "\n" + newBody.slice(sec.end);
122+ core.info(`Removed section: ${headingPattern}`);
123+ return true;
124+ }
125+
126+ // ---------- 3) FIRST: remove empty sections ----------
127+ const bugPattern = String.raw`Bug\s+[-—]\s*Steps to Reproduce\s*&\s*Expected Result\s*\(only\s*if\s*Type\s*=\s*Bug\)`;
128+ const featurePattern = String.raw`Feature\s+[-—]\s*Solution\s*&\s*Expected Benefits\s*\(only\s*if\s*Type\s*=\s*Feature\)`;
129+ const bugRemoved = removeIfNoResponse(bugPattern);
130+ const featureRemoved = removeIfNoResponse(featurePattern);
131+
132+ // ---------- 4) THEN: tidy headings ----------
133+ if (!bugRemoved) {
134+ renameHeading("bug", bugPattern, "Steps to Reproduce & Expected Result");
135+ }
136+ if (!featureRemoved) {
137+ renameHeading("feature", featurePattern, "Solution & Expected Benefits");
138+ }
139+ // Strip "(optional)" from "Additional Context (optional)"
140+ renameHeadingAny(String.raw`Additional Context\s*\(optional\)`, "Additional Context");
141+
142+ // ---------- 5) FINALLY: remove the "Type" section if mapping succeeded ----------
143+ if (typeMapped && selected) {
144+ // Remove "### Type" section entirely (whatever its inner content)
145+ removeSection(String.raw`Type`);
146+ } else {
147+ core.info("Keeping 'Type' section (type not mapped or not detected).");
148+ }
149+
150+ // Normalize: collapse >2 blank lines into 1 for cleanliness
151+ newBody = newBody.replace(/\n{3,}/g, "\n\n");
152+
153+ // ---------- 6) Patch body if changed ----------
154+ if (newBody !== body) {
155+ await github.request("PATCH /repos/{owner}/{repo}/issues/{issue_number}", {
156+ owner: context.repo.owner,
157+ repo: context.repo.repo,
158+ issue_number: issue.number,
159+ body: newBody.trim() + "\n",
160+ });
161+ core.info("Issue body updated.");
162+ } else {
163+ core.info("No body changes needed.");
164+ }
0 commit comments