Skip to content

Commit

Permalink
Import release automation tool from website repo
Browse files Browse the repository at this point in the history
Having it in the website repository where it is used was convenient, but
it's outgrowing it a bit, I find. Here we have much better
infrastructure for maintaining Rust code.

In addition, there might be use cases for the tool in this repository,
in the future.
  • Loading branch information
hannobraun committed Aug 23, 2022
1 parent 777aeba commit 7fb0ae9
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 7fb0ae9

Please sign in to comment.