-
-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #981 from hannobraun/automation
Import release automation tool from website repo
- Loading branch information
Showing
9 changed files
with
634 additions
and
4 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |