Skip to content

Commit 1737796

Browse files
Copilotalexr00
andauthored
Add YAML issue template support (#8277)
* Initial plan * Initial planning for YAML issue template support Co-authored-by: alexr00 <[email protected]> * Add YAML issue template support Co-authored-by: alexr00 <[email protected]> * Clean up test YAML template Co-authored-by: alexr00 <[email protected]> * Address code review feedback: add js-yaml dependency and improve type safety Co-authored-by: alexr00 <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: alexr00 <[email protected]>
1 parent d5d3789 commit 1737796

File tree

5 files changed

+114
-5
lines changed

5 files changed

+114
-5
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4431,6 +4431,7 @@
44314431
"@shikijs/monaco": "^3.7.0",
44324432
"@types/chai": "^4.1.4",
44334433
"@types/glob": "7.1.3",
4434+
"@types/js-yaml": "^4.0.9",
44344435
"@types/lru-cache": "^5.1.0",
44354436
"@types/marked": "^0.7.2",
44364437
"@types/mocha": "^8.2.2",
@@ -4512,6 +4513,7 @@
45124513
"debounce": "^1.2.1",
45134514
"events": "3.2.0",
45144515
"fast-deep-equal": "^3.1.3",
4516+
"js-yaml": "^4.1.1",
45154517
"jsonc-parser": "^3.3.1",
45164518
"jszip": "^3.10.1",
45174519
"lru-cache": "6.0.0",
@@ -4532,4 +4534,4 @@
45324534
"string_decoder": "^1.3.0"
45334535
},
45344536
"license": "MIT"
4535-
}
4537+
}

src/github/folderRepositoryManager.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,10 +1463,13 @@ export class FolderRepositoryManager extends Disposable {
14631463
}
14641464

14651465
async getIssueTemplates(): Promise<vscode.Uri[]> {
1466-
const pattern = '{docs,.github}/ISSUE_TEMPLATE/*.md';
1467-
return vscode.workspace.findFiles(
1468-
new vscode.RelativePattern(this._repository.rootUri, pattern), null
1469-
);
1466+
const mdPattern = '{docs,.github}/ISSUE_TEMPLATE/*.md';
1467+
const ymlPattern = '{docs,.github}/ISSUE_TEMPLATE/*.yml';
1468+
const [mdTemplates, ymlTemplates] = await Promise.all([
1469+
vscode.workspace.findFiles(new vscode.RelativePattern(this._repository.rootUri, mdPattern), null),
1470+
vscode.workspace.findFiles(new vscode.RelativePattern(this._repository.rootUri, ymlPattern), null)
1471+
]);
1472+
return [...mdTemplates, ...ymlTemplates];
14701473
}
14711474

14721475
async getPullRequestTemplateBody(owner: string): Promise<string | undefined> {

src/issues/issueFeatureRegistrar.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { basename } from 'path';
7+
import * as yaml from 'js-yaml';
78
import * as vscode from 'vscode';
89
import { CurrentIssue } from './currentIssue';
910
import { IssueCompletionProvider } from './issueCompletionProvider';
@@ -57,6 +58,7 @@ import {
5758
PermalinkInfo,
5859
pushAndCreatePR,
5960
USER_EXPRESSION,
61+
YamlIssueTemplate,
6062
} from './util';
6163
import { truncate } from '../common/utils';
6264
import { OctokitCommon } from '../github/common';
@@ -1281,13 +1283,78 @@ ${options?.body ?? ''}\n
12811283
}
12821284

12831285
private getDataFromTemplate(template: string): IssueTemplate {
1286+
// Try to parse as YAML first (YAML templates have a different structure)
1287+
try {
1288+
const parsed = yaml.load(template);
1289+
// Check if it looks like a YAML issue template (has name and body fields)
1290+
if (parsed && typeof parsed === 'object' && (parsed as YamlIssueTemplate).name && (parsed as YamlIssueTemplate).body) {
1291+
// This is a YAML template
1292+
return this.parseYamlTemplate(parsed as YamlIssueTemplate);
1293+
}
1294+
} catch (e) {
1295+
// Not a valid YAML, continue to Markdown parsing
1296+
}
1297+
1298+
// Parse as Markdown frontmatter template
12841299
const title = template.match(/title:\s*(.*)/)?.[1]?.replace(/^["']|["']$/g, '');
12851300
const name = template.match(/name:\s*(.*)/)?.[1]?.replace(/^["']|["']$/g, '');
12861301
const about = template.match(/about:\s*(.*)/)?.[1]?.replace(/^["']|["']$/g, '');
12871302
const body = template.match(/---([\s\S]*)---([\s\S]*)/)?.[2];
12881303
return { title, name, about, body };
12891304
}
12901305

1306+
private parseYamlTemplate(parsed: YamlIssueTemplate): IssueTemplate {
1307+
const name = parsed.name;
1308+
const about = parsed.description || parsed.about;
1309+
const title = parsed.title;
1310+
1311+
// Convert YAML body fields to markdown
1312+
let body = '';
1313+
if (parsed.body && Array.isArray(parsed.body)) {
1314+
for (const field of parsed.body) {
1315+
if (field.type === 'markdown' && field.attributes?.value) {
1316+
body += field.attributes.value + '\n\n';
1317+
} else if (field.type === 'textarea' && field.attributes?.label) {
1318+
body += `## ${field.attributes.label}\n\n`;
1319+
if (field.attributes.description) {
1320+
body += `${field.attributes.description}\n\n`;
1321+
}
1322+
if (field.attributes.placeholder) {
1323+
body += `${field.attributes.placeholder}\n\n`;
1324+
} else if (field.attributes.value) {
1325+
body += `${field.attributes.value}\n\n`;
1326+
}
1327+
} else if (field.type === 'input' && field.attributes?.label) {
1328+
body += `## ${field.attributes.label}\n\n`;
1329+
if (field.attributes.description) {
1330+
body += `${field.attributes.description}\n\n`;
1331+
}
1332+
if (field.attributes.placeholder) {
1333+
body += `${field.attributes.placeholder}\n\n`;
1334+
}
1335+
} else if (field.type === 'dropdown' && field.attributes?.label) {
1336+
body += `## ${field.attributes.label}\n\n`;
1337+
if (field.attributes.description) {
1338+
body += `${field.attributes.description}\n\n`;
1339+
}
1340+
if (field.attributes.options && Array.isArray(field.attributes.options)) {
1341+
body += field.attributes.options.map((opt: string | { label?: string }) => typeof opt === 'string' ? `- ${opt}` : `- ${opt.label || ''}`).join('\n') + '\n\n';
1342+
}
1343+
} else if (field.type === 'checkboxes' && field.attributes?.label) {
1344+
body += `## ${field.attributes.label}\n\n`;
1345+
if (field.attributes.description) {
1346+
body += `${field.attributes.description}\n\n`;
1347+
}
1348+
if (field.attributes.options && Array.isArray(field.attributes.options)) {
1349+
body += field.attributes.options.map((opt: { label?: string } | string) => `- [ ] ${typeof opt === 'string' ? opt : opt.label || ''}`).join('\n') + '\n\n';
1350+
}
1351+
}
1352+
}
1353+
}
1354+
1355+
return { title, name, about, body: body.trim() || undefined };
1356+
}
1357+
12911358
private async doCreateIssue(
12921359
document: vscode.TextDocument | undefined,
12931360
newIssue: NewIssue | undefined,

src/issues/util.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,31 @@ export interface IssueTemplate {
9494
body: string | undefined
9595
}
9696

97+
export interface YamlIssueTemplate {
98+
name?: string;
99+
description?: string;
100+
about?: string;
101+
title?: string;
102+
labels?: string[];
103+
assignees?: string[];
104+
body?: YamlTemplateField[];
105+
}
106+
107+
export interface YamlTemplateField {
108+
type: 'markdown' | 'textarea' | 'input' | 'dropdown' | 'checkboxes';
109+
id?: string;
110+
attributes?: {
111+
label?: string;
112+
description?: string;
113+
placeholder?: string;
114+
value?: string;
115+
options?: (string | { label?: string; required?: boolean })[];
116+
};
117+
validations?: {
118+
required?: boolean;
119+
};
120+
}
121+
97122
const HEAD = 'HEAD';
98123
const UPSTREAM = 1;
99124
const UPS = 2;

yarn.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,11 @@
885885
"@types/istanbul-lib-coverage" "*"
886886
"@types/istanbul-lib-report" "*"
887887

888+
"@types/js-yaml@^4.0.9":
889+
version "4.0.9"
890+
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2"
891+
integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==
892+
888893
"@types/json-schema@^7.0.15":
889894
version "7.0.15"
890895
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
@@ -4760,6 +4765,13 @@ [email protected], js-yaml@^4.1.0:
47604765
dependencies:
47614766
argparse "^2.0.1"
47624767

4768+
js-yaml@^4.1.1:
4769+
version "4.1.1"
4770+
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b"
4771+
integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
4772+
dependencies:
4773+
argparse "^2.0.1"
4774+
47634775
47644776
version "3.0.2"
47654777
resolved "https://registry.yarnpkg.com/jsdom-global/-/jsdom-global-3.0.2.tgz#6bd299c13b0c4626b2da2c0393cd4385d606acb9"

0 commit comments

Comments
 (0)