diff --git a/CHANGELOG.md b/CHANGELOG.md
index b8630cfb4b5..e3aeead8766 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
# Changelog
+## ✨ **0.18.0** *(2021-05-15)*
+
+#### Changelog
+
+- #### 🛠 Fixes
+
+ - Fix missing redirects. [[@siku2](https://github.com/siku2), [#1640](https://github.com/yewstack/yew/pull/1640)]
+ - Remove Drop bound from Task trait. [[@siku2](https://github.com/siku2), [#1627](https://github.com/yewstack/yew/pull/1627)]
+ - Enable std feature for indexmap. [[@jstarry](https://github.com/jstarry), [#1709](https://github.com/yewstack/yew/pull/1709)]
+
+- #### ⚡️ Features
+
+ - Implicit optional attributes. [[@siku2](https://github.com/siku2), [#1637](https://github.com/yewstack/yew/pull/1637)]
+ - Added callback_future_once in yewtil.(#1712). [[@fraillt](https://github.com/fraillt), [#1696](https://github.com/yewstack/yew/pull/1696)]
+ - Added relevant examples section to the docs. [[@oOBoomberOo](https://github.com/oOBoomberOo), [#1695](https://github.com/yewstack/yew/pull/1695)]
+ - Added missing KeyboardService re-export. [[@SOF3](https://github.com/SOF3), [#1694](https://github.com/yewstack/yew/pull/1694)]
+ - Rename internal Agent structs to match Component. [[@jstarry](https://github.com/jstarry), [#1688](https://github.com/yewstack/yew/pull/1688)]
+ - Add discussion link to issue selector. [[@jstarry](https://github.com/jstarry), [#1674](https://github.com/yewstack/yew/pull/1674)]
+ - Update link to Material Design Components. [[@TapioT](https://github.com/TapioT), [#1662](https://github.com/yewstack/yew/pull/1662)]
+ - Extract Classes to a separate macro. [[@cecton](https://github.com/cecton), [#1601](https://github.com/yewstack/yew/pull/1601)]
+ - Improve the "keyed_list" example. [[@titaneric](https://github.com/titaneric), [#1650](https://github.com/yewstack/yew/pull/1650)]
+ - Add documentation for component children. [[@K4rakara](https://github.com/K4rakara), [#1616](https://github.com/yewstack/yew/pull/1616)]
+ - Add a macro for building properties outside of html!. [[@siku2](https://github.com/siku2), [#1599](https://github.com/yewstack/yew/pull/1599)]
+
## ✨ **0.17.4** *(2020-10-18)*
#### Changelog
@@ -301,7 +325,7 @@ Lastly, take note that API docs on https://docs.rs/yew will be using the `"web_s
- Implemented `PartialEq` for `ChildrenRenderer` to allow `children` comparison. [[@jstarry], [#916](https://github.com/yewstack/yew/pull/916)]
- Reduced restrictions on `ComponentLink` methods to improve `Future` support. [[@jplatte], [#931](https://github.com/yewstack/yew/pull/931)]
- Added `referrer`, `referrer_policy` and `integrity` to `FetchOptions`. [[@leo-lb], [#931](https://github.com/yewstack/yew/pull/931)]
-
+
- #### 🛠 Fixes
- Fixed touch event listeners. [[@AlephAlpha], [#872](https://github.com/yewstack/yew/pull/872)]
diff --git a/Cargo.toml b/Cargo.toml
index 227bc064108..036c5fec83b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,4 +44,7 @@ members = [
"examples/todomvc",
"examples/two_apps",
"examples/webgl",
+
+ # Release tools
+ "packages/changelog",
]
diff --git a/README.md b/README.md
index 52703ce06fe..11d6aef5c46 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@
|
Documentation (latest)
|
- Examples
+ Examples
|
Changelog
|
diff --git a/packages/changelog/Cargo.toml b/packages/changelog/Cargo.toml
new file mode 100644
index 00000000000..103d92d9d51
--- /dev/null
+++ b/packages/changelog/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "changelog"
+version = "0.1.0"
+authors = ["Cecile Tonglet "]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1"
+chrono = "0.4"
+git2 = "0.13"
+regex = "1"
+reqwest = { version = "0.11", features = ["blocking", "json"] }
+serde = { version = "1", features = ["derive"] }
+structopt = "0.3"
diff --git a/packages/changelog/src/main.rs b/packages/changelog/src/main.rs
new file mode 100644
index 00000000000..52a33145dd6
--- /dev/null
+++ b/packages/changelog/src/main.rs
@@ -0,0 +1,213 @@
+use anyhow::{bail, Context, Result};
+use serde::Deserialize;
+use std::collections::HashMap;
+use std::fs;
+use std::io;
+use std::io::Write;
+use structopt::StructOpt;
+
+fn main() -> Result<()> {
+ Cli::from_args().run()
+}
+
+#[derive(StructOpt)]
+pub struct Cli {
+ /// From commit.
+ from: String,
+
+ /// To commit.
+ #[structopt(default_value = "HEAD")]
+ to: String,
+
+ #[structopt(skip = Self::open_repository())]
+ repo: git2::Repository,
+
+ #[structopt(skip)]
+ github_users: GitHubUsers,
+
+ #[structopt(skip = regex::Regex::new(r"\s*\(#(\d+)\)").unwrap())]
+ re_issue: regex::Regex,
+}
+
+impl Cli {
+ fn open_repository() -> git2::Repository {
+ match git2::Repository::open(".") {
+ Err(err) => {
+ eprintln!("Error: could not open repository: {}", err);
+ std::process::exit(1);
+ }
+ Ok(repo) => repo,
+ }
+ }
+
+ fn run(&mut self) -> Result<()> {
+ let mut old_changelog =
+ fs::File::open("CHANGELOG.md").context("could not open CHANGELOG.md for reading")?;
+ let mut f = fs::OpenOptions::new()
+ .write(true)
+ .create(true)
+ .truncate(true)
+ .open("CHANGELOG.md.new")
+ .context("could not open CHANGELOG.md.new for writing")?;
+
+ let mut revwalk = self.repo.revwalk()?;
+ revwalk.set_sorting(git2::Sort::TOPOLOGICAL)?;
+
+ let from_object = self
+ .repo
+ .revparse_single(&self.from)
+ .context("Could not find `from` revision")?;
+ let to_object = self
+ .repo
+ .revparse_single(&self.to)
+ .context("Could not find `to` revision")?;
+ revwalk.hide(from_object.id())?;
+ revwalk.push(to_object.id())?;
+
+ let mut logs = Vec::new();
+ for oid in revwalk {
+ let oid = oid?;
+ let commit = self.repo.find_commit(oid)?;
+ let first_line = commit
+ .message()
+ .context("Invalid UTF-8 in commit message")?
+ .lines()
+ .next()
+ .context("Missing commit message")?;
+ let author = commit.author();
+ let email = author.email().context("Missing author's email")?;
+
+ if email.contains("dependabot") {
+ continue;
+ }
+
+ let (issue, first_line) =
+ if let Some(caps) = self.re_issue.captures_iter(first_line).last() {
+ let first_line_stripped = vec![
+ &first_line[..caps.get(0).unwrap().start()],
+ &first_line[caps.get(0).unwrap().end()..],
+ ]
+ .join("");
+ (caps[1].to_string(), first_line_stripped)
+ } else {
+ eprintln!("Missing issue for commit: {}", oid);
+ continue;
+ };
+
+ let user = self
+ .github_users
+ .find_user_by_commit_author(email, oid.to_string())
+ .with_context(|| format!("Could not find GitHub user for commit: {}", oid))?;
+
+ logs.push((first_line.to_string(), user.to_owned(), issue.to_owned()));
+ }
+
+ let (features, fixes): (Vec<_>, Vec<_>) = logs
+ .into_iter()
+ .partition(|(msg, _, _)| msg.to_lowercase().contains("fix"));
+
+ writeln!(
+ f,
+ "## ✨ **x.y.z** *({})*",
+ chrono::Utc::now().format("%Y-%m-%d")
+ )?;
+ writeln!(f)?;
+ writeln!(f, "#### Changelog")?;
+ writeln!(f)?;
+
+ writeln!(f, "- #### 🛠 Fixes")?;
+ writeln!(f)?;
+ for (msg, user, issue) in fixes {
+ writeln!(
+ f,
+ " - {msg}. [[@{user}](https://github.com/{user}), [#{issue}](https://github.com/yewstack/yew/pull/{issue})]",
+ msg = msg,
+ user = user,
+ issue = issue
+ )?;
+ }
+
+ writeln!(f, "- #### ⚡️ Features")?;
+ writeln!(f)?;
+ for (msg, user, issue) in features {
+ writeln!(
+ f,
+ " - {msg}. [[@{user}](https://github.com/{user}), [#{issue}](https://github.com/yewstack/yew/pull/{issue})]",
+ msg = msg,
+ user = user,
+ issue = issue
+ )?;
+ }
+
+ writeln!(f)?;
+ io::copy(&mut old_changelog, &mut f)?;
+
+ drop(old_changelog);
+ drop(f);
+
+ fs::remove_file("CHANGELOG.md").context("Could not delete CHANGELOG.md")?;
+ fs::rename("CHANGELOG.md.new", "CHANGELOG.md")
+ .context("Could not replace CHANGELOG.md with CHANGELOG.md.new")?;
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct GitHubUsers {
+ cache: HashMap>,
+}
+
+impl GitHubUsers {
+ pub fn find_user_by_commit_author(
+ &mut self,
+ key: impl Into,
+ commit: impl AsRef,
+ ) -> Option<&str> {
+ self.cache
+ .entry(key.into())
+ .or_insert_with(|| match Self::query_commit(commit) {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("Error: {}", err);
+ None
+ }
+ })
+ .as_deref()
+ }
+
+ fn query_commit(q: impl AsRef) -> Result