1use anyhow::Result;
4use clap::{Args as ClapArgs, Subcommand};
5use colored::Colorize;
6use std::fs;
7use std::path::Path;
8
9use crate::core::Git;
10
11#[derive(ClapArgs, Debug)]
13pub struct Args {
14 #[command(subcommand)]
16 pub command: TemplatesCommand,
17}
18
19#[derive(Subcommand, Debug)]
21pub enum TemplatesCommand {
22 Pr(PrArgs),
24
25 Issue(IssueArgs),
27
28 Codeowners(CodeownersArgs),
30
31 All(AllArgs),
33}
34
35#[derive(ClapArgs, Debug)]
37pub struct PrArgs {
38 #[arg(long, short, default_value = ".github/PULL_REQUEST_TEMPLATE.md")]
40 pub output: String,
41
42 #[arg(long, short)]
44 pub force: bool,
45}
46
47#[derive(ClapArgs, Debug)]
49pub struct IssueArgs {
50 #[arg(long, short, default_value = ".github/ISSUE_TEMPLATE")]
52 pub output: String,
53
54 #[arg(long, short)]
56 pub force: bool,
57}
58
59#[derive(ClapArgs, Debug)]
61pub struct CodeownersArgs {
62 #[arg(long, short, default_value = ".github/CODEOWNERS")]
64 pub output: String,
65
66 #[arg(long, default_value = "@architecture-team")]
68 pub owner: String,
69
70 #[arg(long, short)]
72 pub force: bool,
73}
74
75#[derive(ClapArgs, Debug)]
77pub struct AllArgs {
78 #[arg(long, short)]
80 pub force: bool,
81}
82
83pub fn run(args: Args) -> Result<()> {
89 let git = Git::new();
90 git.check_repository()?;
91
92 match args.command {
93 TemplatesCommand::Pr(pr_args) => run_pr(pr_args),
94 TemplatesCommand::Issue(issue_args) => run_issue(issue_args),
95 TemplatesCommand::Codeowners(codeowners_args) => run_codeowners(codeowners_args),
96 TemplatesCommand::All(all_args) => run_all(all_args),
97 }
98}
99
100fn run_pr(args: PrArgs) -> Result<()> {
102 let output_path = Path::new(&args.output);
103
104 if let Some(parent) = output_path.parent() {
106 if !parent.exists() {
107 fs::create_dir_all(parent)?;
108 }
109 }
110
111 if output_path.exists() && !args.force {
112 anyhow::bail!(
113 "PR template already exists: {}. Use --force to overwrite.",
114 output_path.display()
115 );
116 }
117
118 fs::write(output_path, PR_TEMPLATE)?;
119
120 eprintln!(
121 "{} Generated PR template: {}",
122 "✓".green(),
123 output_path.display().to_string().cyan()
124 );
125
126 Ok(())
127}
128
129fn run_issue(args: IssueArgs) -> Result<()> {
131 let output_dir = Path::new(&args.output);
132
133 if !output_dir.exists() {
134 fs::create_dir_all(output_dir)?;
135 }
136
137 let templates = [
138 ("adr-proposal.md", ADR_PROPOSAL_TEMPLATE),
139 ("adr-amendment.md", ADR_AMENDMENT_TEMPLATE),
140 ("config.yml", ISSUE_CONFIG),
141 ];
142
143 eprintln!("{} Generating issue templates...", "→".blue());
144
145 for (filename, content) in &templates {
146 let file_path = output_dir.join(filename);
147
148 if file_path.exists() && !args.force {
149 eprintln!(
150 " {} {} already exists (use --force to overwrite)",
151 "!".yellow(),
152 filename
153 );
154 continue;
155 }
156
157 fs::write(&file_path, *content)?;
158 eprintln!(" {} Generated {}", "✓".green(), filename.cyan());
159 }
160
161 Ok(())
162}
163
164fn run_codeowners(args: CodeownersArgs) -> Result<()> {
166 let output_path = Path::new(&args.output);
167
168 if let Some(parent) = output_path.parent() {
170 if !parent.exists() {
171 fs::create_dir_all(parent)?;
172 }
173 }
174
175 if output_path.exists() && !args.force {
176 anyhow::bail!(
177 "CODEOWNERS already exists: {}. Use --force to overwrite.",
178 output_path.display()
179 );
180 }
181
182 let content = CODEOWNERS_TEMPLATE.replace("{owner}", &args.owner);
183 fs::write(output_path, content)?;
184
185 eprintln!(
186 "{} Generated CODEOWNERS: {}",
187 "✓".green(),
188 output_path.display().to_string().cyan()
189 );
190 eprintln!(" ADR owner set to: {}", args.owner.cyan());
191
192 Ok(())
193}
194
195fn run_all(args: AllArgs) -> Result<()> {
197 eprintln!("{} Generating all ADR-related templates...", "→".blue());
198 eprintln!();
199
200 run_pr(PrArgs {
202 output: ".github/PULL_REQUEST_TEMPLATE.md".to_string(),
203 force: args.force,
204 })?;
205
206 run_issue(IssueArgs {
208 output: ".github/ISSUE_TEMPLATE".to_string(),
209 force: args.force,
210 })?;
211
212 run_codeowners(CodeownersArgs {
214 output: ".github/CODEOWNERS".to_string(),
215 owner: "@architecture-team".to_string(),
216 force: args.force,
217 })?;
218
219 eprintln!();
220 eprintln!("{} All templates generated!", "✓".green());
221
222 Ok(())
223}
224
225const PR_TEMPLATE: &str = r#"## Description
227
228<!-- Brief description of the changes -->
229
230## Related ADRs
231
232<!-- Reference any related Architecture Decision Records -->
233<!-- Example: Implements ADR-0005: Use PostgreSQL for primary database -->
234
235- [ ] This PR implements/relates to an ADR
236- ADR Reference: <!-- ADR-XXXX -->
237
238## Type of Change
239
240- [ ] Bug fix (non-breaking change that fixes an issue)
241- [ ] New feature (non-breaking change that adds functionality)
242- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
243- [ ] Documentation update
244- [ ] Architecture change (requires ADR)
245
246## Checklist
247
248- [ ] My code follows the project's style guidelines
249- [ ] I have performed a self-review of my code
250- [ ] I have commented my code, particularly in hard-to-understand areas
251- [ ] I have made corresponding changes to the documentation
252- [ ] My changes generate no new warnings
253- [ ] I have added tests that prove my fix is effective or that my feature works
254- [ ] New and existing unit tests pass locally with my changes
255
256## Architecture Decision
257
258<!-- If this PR involves a significant architectural decision, please ensure an ADR exists -->
259
260- [ ] No architectural decision needed
261- [ ] ADR already exists: <!-- ADR-XXXX -->
262- [ ] ADR needs to be created (link to issue: <!-- #XXX -->)
263"#;
264
265const ADR_PROPOSAL_TEMPLATE: &str = r#"---
267name: ADR Proposal
268about: Propose a new Architecture Decision Record
269title: "[ADR] "
270labels: adr, proposal
271assignees: ''
272---
273
274## Context
275
276<!-- What is the issue that we're seeing that is motivating this decision or change? -->
277
278## Decision Drivers
279
280<!-- What factors are influencing this decision? -->
281
282-
283-
284-
285
286## Considered Options
287
288<!-- What options have you considered? -->
289
2901.
2912.
2923.
293
294## Proposed Decision
295
296<!-- What is your proposed decision? -->
297
298## Consequences
299
300### Positive
301
302-
303
304### Negative
305
306-
307
308### Neutral
309
310-
311
312## Additional Context
313
314<!-- Add any other context, diagrams, or screenshots about the decision here -->
315"#;
316
317const ADR_AMENDMENT_TEMPLATE: &str = r#"---
319name: ADR Amendment
320about: Request changes to an existing ADR
321title: "[ADR Amendment] "
322labels: adr, amendment
323assignees: ''
324---
325
326## ADR Reference
327
328<!-- Which ADR are you proposing to amend? -->
329ADR ID:
330
331## Reason for Amendment
332
333<!-- Why does this ADR need to be updated? -->
334
335- [ ] Circumstances have changed
336- [ ] New information available
337- [ ] Implementation revealed issues
338- [ ] Other:
339
340## Proposed Changes
341
342<!-- What changes are you proposing? -->
343
344## Impact Assessment
345
346<!-- What is the impact of this change? -->
347
348### Affected Components
349
350-
351
352### Migration Required
353
354- [ ] Yes
355- [ ] No
356
357### Timeline
358
359<!-- When should this change take effect? -->
360"#;
361
362const ISSUE_CONFIG: &str = r#"blank_issues_enabled: true
364contact_links:
365 - name: ADR Documentation
366 url: https://adr.github.io/
367 about: Learn more about Architecture Decision Records
368"#;
369
370const CODEOWNERS_TEMPLATE: &str = r#"# CODEOWNERS for Architecture Decision Records
372# Generated by git-adr
373
374# ADR-related files require architecture team review
375# Note: git-adr stores ADRs in git notes, not files
376# This covers any exported ADRs or ADR-related documentation
377
378# ADR documentation
379/docs/adr/ {owner}
380/docs/architecture/ {owner}
381
382# Exported ADRs
383/adrs/ {owner}
384
385# GitHub ADR templates
386/.github/ISSUE_TEMPLATE/adr-*.md {owner}
387
388# Architecture-related configs
389/ARCHITECTURE.md {owner}
390"#;