Skip to content

Commit

Permalink
Merge pull request #981 from hannobraun/automation
Browse files Browse the repository at this point in the history
Import release automation tool from website repo
  • Loading branch information
hannobraun authored Aug 23, 2022
2 parents 777aeba + 7fb0ae9 commit 619559c
Show file tree
Hide file tree
Showing 9 changed files with 634 additions and 4 deletions.
360 changes: 356 additions & 4 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ members = [
"models/star",
"models/test",

"tools/automator",
"tools/export-validator",
"tools/release-operator",
]
Expand Down
19 changes: 19 additions & 0 deletions tools/automator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "automator"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
anyhow = "1.0.62"
chrono = "0.4.22"
octocrab = "0.17.0"
url = "2.2.2"

[dependencies.clap]
version = "3.2.17"
features = ["derive"]

[dependencies.tokio]
version = "1.20.1"
features = ["full"]
19 changes: 19 additions & 0 deletions tools/automator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# `automator`

CLI tool that automates Fornjot development tasks (mostly release automation).

## Usage

Install `automator`, so you can use it in other repositories (most relevantly, the [website repository](https://github.com/hannobraun/www.fornjot.app)):

``` sh
cargo install --path tools/automator/
```

To learn how to use it, run the following command:

``` sh
automator --help
```

Please also refer to the [release procedure](../../RELEASES.md), which is the main use case for `automator`, as of this writing.
122 changes: 122 additions & 0 deletions tools/automator/src/announcement.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use std::{fmt::Write, path::PathBuf};

use anyhow::Context;
use chrono::{Date, Datelike, Utc};
use tokio::{
fs::{self, File},
io::AsyncWriteExt,
};

use crate::pull_requests::PullRequest;

pub async fn create_release_announcement(
last_release_date: Date<Utc>,
version: String,
) -> anyhow::Result<()> {
let now = Utc::now();

let year = now.year();
let week = now.iso_week().week();

let pull_requests =
PullRequest::fetch_since_last_release(last_release_date)
.await?
.into_values();

let mut file = create_file(year, week).await?;
generate_announcement(week, version, pull_requests, &mut file).await?;

Ok(())
}

async fn create_file(year: i32, week: u32) -> anyhow::Result<File> {
let dir =
PathBuf::from(format!("content/blog/weekly-release/{year}-w{week}"));
let file = dir.join("index.md");

fs::create_dir_all(&dir).await.with_context(|| {
format!("Failed to create directory `{}`", dir.display())
})?;
let file = File::create(&file).await.with_context(|| {
format!("Failed to create file `{}`", file.display())
})?;

Ok(file)
}

async fn generate_announcement(
week: u32,
version: String,
pull_requests: impl IntoIterator<Item = PullRequest>,
file: &mut File,
) -> anyhow::Result<()> {
let mut pull_request_links = String::new();

for PullRequest { number, html_url } in pull_requests {
let link = format!("[#{number}]: {html_url}\n");

pull_request_links.push_str(&link);
}

let mut buf = String::new();
write!(
buf,
"\
+++
title = \"Weekly Release - 2022-W{week}\"
[extra]
version = \"{version}\"
+++
**TASK: Write introduction.**
### Sponsors
Fornjot is supported by [@webtrax-oz](https://github.com/webtrax-oz), [@lthiery](https://github.com/lthiery), [@Yatekii](https://github.com/Yatekii), [@martindederer](https://github.com/martindederer), [@hobofan](https://github.com/hobofan), [@ahdinosaur](https://github.com/ahdinosaur), [@thawkins](https://github.com/thawkins), [@bollian](https://github.com/bollian), [@rozgo](https://github.com/rozgo), and [my other awesome sponsors](https://github.com/sponsors/hannobraun). Thank you!
If you want Fornjot to be stable and sustainable long-term, please consider [supporting me](https://github.com/sponsors/hannobraun) too.
### End-user improvements
Improvements to Fornjot and its documentation that are visible to end-users.
**TASK: Add end-user improvements.**
### Ecosystem improvements
Improvements to the Fornjot ecosystem that are relevant to developers who are building on top of Fornjot components.
#### `fj-kernel`
**TASK: Add ecosystem improvements.**
### Internal Improvements
Improvements that are relevant to developers working on Fornjot itself.
**TASK: Add internal improvements.**
### Issue of the Week
**TASK: Write.**
### Outlook
**TASK: Write.**
{pull_request_links}\
"
)?;

file.write_all(buf.as_bytes()).await?;

Ok(())
}
24 changes: 24 additions & 0 deletions tools/automator/src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use chrono::{Date, NaiveDate, Utc};

#[derive(clap::Parser)]
pub enum Args {
CreateReleaseAnnouncement(CreateReleaseAnnouncement),
}

impl Args {
pub fn parse() -> Self {
<Self as clap::Parser>::parse()
}
}

#[derive(clap::Parser)]
pub struct CreateReleaseAnnouncement {
pub last_release_date: NaiveDate,
pub version: String,
}

impl CreateReleaseAnnouncement {
pub fn last_release_date(&self) -> Date<Utc> {
Date::from_utc(self.last_release_date, Utc)
}
}
9 changes: 9 additions & 0 deletions tools/automator/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mod announcement;
mod args;
mod pull_requests;
mod run;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
run::run().await
}
69 changes: 69 additions & 0 deletions tools/automator/src/pull_requests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use std::collections::BTreeMap;

use anyhow::anyhow;
use chrono::{Date, Utc};
use octocrab::params::{pulls::Sort, Direction, State};
use url::Url;

pub struct PullRequest {
pub number: u64,
pub html_url: Url,
}

impl PullRequest {
pub async fn fetch_since_last_release(
last_release_date: Date<Utc>,
) -> anyhow::Result<BTreeMap<u64, Self>> {
let mut pull_requests = BTreeMap::new();
let mut page = 1u32;

'outer: loop {
println!("Fetching page {}...", page);
let pull_request_page = octocrab::instance()
.pulls("hannobraun", "Fornjot")
.list()
.state(State::Closed)
.sort(Sort::Updated)
.direction(Direction::Descending)
.per_page(100) // this is the maximum number of results per page
.page(page)
.send()
.await?;

for pull_request in pull_request_page.items {
if let Some(updated_at) = pull_request.updated_at {
if updated_at.date() < last_release_date {
// This pull request has been updated before the last
// release. Since we sort pull requests by
// updated-descending, that means all following pull
// requests have been updated before the last release,
// and thus couldn't have been merged after.
break 'outer;
}
}

if let Some(merged_at) = pull_request.merged_at {
if merged_at.date() >= last_release_date {
let number = pull_request.number;
let html_url =
pull_request.html_url.ok_or_else(|| {
anyhow!("Pull request is missing URL")
})?;

let pull_request = Self { number, html_url };

pull_requests.insert(pull_request.number, pull_request);
}
}
}

if pull_request_page.next.is_some() {
page += 1;
} else {
break;
}
}

Ok(pull_requests)
}
}
15 changes: 15 additions & 0 deletions tools/automator/src/run.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use anyhow::Context;

use crate::{announcement::create_release_announcement, args::Args};

pub async fn run() -> anyhow::Result<()> {
match Args::parse() {
Args::CreateReleaseAnnouncement(args) => {
create_release_announcement(args.last_release_date(), args.version)
.await
.context("Failed to create release announcement")?;
}
}

Ok(())
}

0 comments on commit 619559c

Please sign in to comment.