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: CiCommand,
17}
18
19#[derive(Subcommand, Debug)]
21pub enum CiCommand {
22 Github(GithubArgs),
24
25 Gitlab(GitlabArgs),
27}
28
29#[derive(ClapArgs, Debug)]
31pub struct GithubArgs {
32 #[arg(long, short, default_value = ".github/workflows")]
34 pub output: String,
35
36 #[arg(long, short)]
38 pub force: bool,
39
40 #[arg(long, default_value = "true")]
42 pub validation: bool,
43
44 #[arg(long, default_value = "true")]
46 pub sync: bool,
47}
48
49#[derive(ClapArgs, Debug)]
51pub struct GitlabArgs {
52 #[arg(long, short, default_value = ".gitlab-ci.yml")]
54 pub output: String,
55
56 #[arg(long, short)]
58 pub force: bool,
59
60 #[arg(long, default_value = "true")]
62 pub validation: bool,
63
64 #[arg(long, default_value = "true")]
66 pub sync: bool,
67}
68
69pub fn run(args: Args) -> Result<()> {
75 let git = Git::new();
76 git.check_repository()?;
77
78 match args.command {
79 CiCommand::Github(github_args) => run_github(github_args),
80 CiCommand::Gitlab(gitlab_args) => run_gitlab(gitlab_args),
81 }
82}
83
84fn run_github(args: GithubArgs) -> Result<()> {
86 let output_dir = Path::new(&args.output);
87
88 if !output_dir.exists() {
89 fs::create_dir_all(output_dir)?;
90 eprintln!("{} Created {}", "✓".green(), args.output.cyan());
91 }
92
93 let workflow_path = output_dir.join("adr.yml");
94
95 if workflow_path.exists() && !args.force {
96 anyhow::bail!(
97 "Workflow file already exists: {}. Use --force to overwrite.",
98 workflow_path.display()
99 );
100 }
101
102 let workflow = generate_github_workflow(args.validation, args.sync);
103 fs::write(&workflow_path, workflow)?;
104
105 eprintln!(
106 "{} Generated GitHub Actions workflow: {}",
107 "✓".green(),
108 workflow_path.display().to_string().cyan()
109 );
110
111 eprintln!();
112 eprintln!("{} Workflow includes:", "→".blue());
113 if args.validation {
114 eprintln!(" • ADR validation on pull requests");
115 }
116 if args.sync {
117 eprintln!(" • ADR sync on push to main branch");
118 }
119
120 Ok(())
121}
122
123fn run_gitlab(args: GitlabArgs) -> Result<()> {
125 let output_path = Path::new(&args.output);
126
127 if output_path.exists() && !args.force {
128 anyhow::bail!(
129 "GitLab CI file already exists: {}. Use --force to overwrite.",
130 output_path.display()
131 );
132 }
133
134 let config = generate_gitlab_ci(args.validation, args.sync);
135 fs::write(output_path, config)?;
136
137 eprintln!(
138 "{} Generated GitLab CI configuration: {}",
139 "✓".green(),
140 output_path.display().to_string().cyan()
141 );
142
143 eprintln!();
144 eprintln!("{} Configuration includes:", "→".blue());
145 if args.validation {
146 eprintln!(" • ADR validation job");
147 }
148 if args.sync {
149 eprintln!(" • ADR sync job");
150 }
151
152 Ok(())
153}
154
155fn generate_github_workflow(validation: bool, sync: bool) -> String {
157 let mut workflow = String::new();
158
159 workflow.push_str(
160 r#"# ADR (Architecture Decision Records) CI/CD Workflow
161# Generated by git-adr
162
163name: ADR
164
165on:
166 push:
167 branches: [main, master]
168 pull_request:
169 branches: [main, master]
170
171env:
172 GIT_ADR_VERSION: "1.0.0"
173
174jobs:
175"#,
176 );
177
178 if validation {
179 workflow.push_str(
180 r#"
181 validate:
182 name: Validate ADRs
183 runs-on: ubuntu-latest
184 if: github.event_name == 'pull_request'
185 steps:
186 - uses: actions/checkout@v4
187 with:
188 fetch-depth: 0
189
190 - name: Install git-adr
191 run: |
192 curl -sSL https://github.com/zircote/git-adr/releases/download/v${{ env.GIT_ADR_VERSION }}/git-adr-x86_64-unknown-linux-gnu.tar.gz | tar xz
193 sudo mv git-adr /usr/local/bin/
194
195 - name: Fetch ADR notes
196 run: |
197 git fetch origin 'refs/notes/*:refs/notes/*' || true
198
199 - name: List ADRs
200 run: git-adr list
201
202 - name: Validate ADR references
203 run: |
204 # Check for ADR references in commit messages
205 COMMITS=$(git log --format="%s" origin/${{ github.base_ref }}..HEAD)
206 echo "Checking commits for ADR references..."
207 echo "$COMMITS" | grep -i "ADR-" && echo "✓ Found ADR references" || echo "→ No ADR references found"
208"#,
209 );
210 }
211
212 if sync {
213 workflow.push_str(
214 r#"
215 sync:
216 name: Sync ADRs
217 runs-on: ubuntu-latest
218 if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
219 steps:
220 - uses: actions/checkout@v4
221 with:
222 fetch-depth: 0
223 token: ${{ secrets.GITHUB_TOKEN }}
224
225 - name: Install git-adr
226 run: |
227 curl -sSL https://github.com/zircote/git-adr/releases/download/v${{ env.GIT_ADR_VERSION }}/git-adr-x86_64-unknown-linux-gnu.tar.gz | tar xz
228 sudo mv git-adr /usr/local/bin/
229
230 - name: Configure git
231 run: |
232 git config user.name "github-actions[bot]"
233 git config user.email "github-actions[bot]@users.noreply.github.com"
234
235 - name: Fetch ADR notes
236 run: |
237 git fetch origin 'refs/notes/*:refs/notes/*' || true
238
239 - name: Sync ADRs
240 run: |
241 git-adr sync || echo "→ No changes to sync"
242"#,
243 );
244 }
245
246 workflow
247}
248
249fn generate_gitlab_ci(validation: bool, sync: bool) -> String {
251 let mut config = String::new();
252
253 config.push_str(
254 r#"# ADR (Architecture Decision Records) CI/CD Configuration
255# Generated by git-adr
256
257stages:
258 - validate
259 - sync
260
261variables:
262 GIT_ADR_VERSION: "1.0.0"
263
264.install-git-adr: &install-git-adr
265 - curl -sSL https://github.com/zircote/git-adr/releases/download/v${GIT_ADR_VERSION}/git-adr-x86_64-unknown-linux-gnu.tar.gz | tar xz
266 - mv git-adr /usr/local/bin/
267 - git fetch origin 'refs/notes/*:refs/notes/*' || true
268"#,
269 );
270
271 if validation {
272 config.push_str(
273 r#"
274
275validate-adrs:
276 stage: validate
277 image: ubuntu:latest
278 rules:
279 - if: $CI_PIPELINE_SOURCE == "merge_request_event"
280 before_script:
281 - apt-get update && apt-get install -y curl git
282 - *install-git-adr
283 script:
284 - git-adr list
285 - |
286 echo "Checking for ADR references in commits..."
287 git log --format="%s" ${CI_MERGE_REQUEST_DIFF_BASE_SHA}..HEAD | grep -i "ADR-" && echo "✓ Found ADR references" || echo "→ No ADR references found"
288"#,
289 );
290 }
291
292 if sync {
293 config.push_str(
294 r#"
295
296sync-adrs:
297 stage: sync
298 image: ubuntu:latest
299 rules:
300 - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
301 before_script:
302 - apt-get update && apt-get install -y curl git
303 - git config user.name "GitLab CI"
304 - git config user.email "ci@gitlab.com"
305 - *install-git-adr
306 script:
307 - git-adr sync || echo "→ No changes to sync"
308"#,
309 );
310 }
311
312 config
313}