CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
templates.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 24cf2ca | 1 | /** |
| 2 | * Issue + PR template loader. Looks for the standard locations and | |
| 3 | * returns the first match. Fails silently (returns null) so new-issue / | |
| 4 | * new-PR forms always render — templates are a convenience, not a | |
| 5 | * requirement. | |
| 6 | */ | |
| 7 | ||
| 8 | import { getBlob, getDefaultBranch } from "../git/repository"; | |
| 9 | ||
| 10 | const ISSUE_PATHS = [ | |
| 11 | ".github/ISSUE_TEMPLATE.md", | |
| 12 | ".github/issue_template.md", | |
| 13 | ".gluecron/ISSUE_TEMPLATE.md", | |
| 14 | "ISSUE_TEMPLATE.md", | |
| 15 | "docs/ISSUE_TEMPLATE.md", | |
| 16 | ]; | |
| 17 | ||
| 18 | const PR_PATHS = [ | |
| 19 | ".github/PULL_REQUEST_TEMPLATE.md", | |
| 20 | ".github/pull_request_template.md", | |
| 21 | ".gluecron/PULL_REQUEST_TEMPLATE.md", | |
| 22 | "PULL_REQUEST_TEMPLATE.md", | |
| 23 | "docs/PULL_REQUEST_TEMPLATE.md", | |
| 24 | ]; | |
| 25 | ||
| 26 | const MAX_TEMPLATE_BYTES = 16 * 1024; | |
| 27 | ||
| 28 | async function loadFirst( | |
| 29 | owner: string, | |
| 30 | repo: string, | |
| 31 | ref: string, | |
| 32 | paths: string[] | |
| 33 | ): Promise<string | null> { | |
| 34 | for (const p of paths) { | |
| 35 | try { | |
| 36 | const blob = await getBlob(owner, repo, ref, p); | |
| 37 | if (blob && !blob.isBinary && blob.content) { | |
| 38 | // Guard against someone committing a 5MB template. | |
| 39 | if (blob.content.length > MAX_TEMPLATE_BYTES) { | |
| 40 | return blob.content.slice(0, MAX_TEMPLATE_BYTES); | |
| 41 | } | |
| 42 | return stripFrontmatter(blob.content); | |
| 43 | } | |
| 44 | } catch { | |
| 45 | // keep looking | |
| 46 | } | |
| 47 | } | |
| 48 | return null; | |
| 49 | } | |
| 50 | ||
| 51 | /** | |
| 52 | * GitHub-style templates can have YAML frontmatter (---\nname: ...\n---). | |
| 53 | * Strip it — we don't use the name/about fields here. | |
| 54 | */ | |
| 55 | function stripFrontmatter(content: string): string { | |
| 56 | if (!content.startsWith("---\n")) return content; | |
| 57 | const end = content.indexOf("\n---", 4); | |
| 58 | if (end < 0) return content; | |
| 59 | return content.slice(end + 4).replace(/^\n+/, ""); | |
| 60 | } | |
| 61 | ||
| 62 | export async function loadIssueTemplate( | |
| 63 | owner: string, | |
| 64 | repo: string | |
| 65 | ): Promise<string | null> { | |
| 66 | try { | |
| 67 | const ref = (await getDefaultBranch(owner, repo)) || "HEAD"; | |
| 68 | return await loadFirst(owner, repo, ref, ISSUE_PATHS); | |
| 69 | } catch { | |
| 70 | return null; | |
| 71 | } | |
| 72 | } | |
| 73 | ||
| 74 | export async function loadPrTemplate( | |
| 75 | owner: string, | |
| 76 | repo: string | |
| 77 | ): Promise<string | null> { | |
| 78 | try { | |
| 79 | const ref = (await getDefaultBranch(owner, repo)) || "HEAD"; | |
| 80 | return await loadFirst(owner, repo, ref, PR_PATHS); | |
| 81 | } catch { | |
| 82 | return null; | |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | export { stripFrontmatter as _stripFrontmatterForTest }; |