mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Automate changelog gen and gh release in actions (#2286)
* Automate changelog gen and gh release * fix tests * fix test 2 * make pull request pull full depth git information * fix fetch-depth * fix inspect action
This commit is contained in:
parent
90b4e55ebc
commit
50510c5858
36
.github/workflows/inspect_next_changelogs.yml
vendored
Normal file
36
.github/workflows/inspect_next_changelogs.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: Inspect next changelogs
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
name: Generate changelogs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- name: Build changelog generator
|
||||
run: cargo build --release -p changelog
|
||||
|
||||
- name: Read yew changelog in this step
|
||||
run: ./target/release/changelog yew minor
|
||||
|
||||
- name: Read yew-router changelog in this step
|
||||
run: ./target/release/changelog yew-router minor
|
||||
|
||||
- name: Read yew-agent changelog in this step
|
||||
run: ./target/release/changelog yew-agent minor
|
||||
37
.github/workflows/publish-yew-agent.yml
vendored
37
.github/workflows/publish-yew-agent.yml
vendored
@ -6,13 +6,13 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
level:
|
||||
description: 'Version Level major|minor|patch'
|
||||
description: "Version Level major|minor|patch"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish yew-agent
|
||||
@ -21,12 +21,13 @@ jobs:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
token: '${{ secrets.YEWTEMPBOT_TOKEN }}'
|
||||
token: "${{ secrets.YEWTEMPBOT_TOKEN }}"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Config Git
|
||||
uses: oleksiyrudenko/gha-git-credentials@v2-latest
|
||||
with:
|
||||
token: '${{ secrets.YEWTEMPBOT_TOKEN }}'
|
||||
token: "${{ secrets.YEWTEMPBOT_TOKEN }}"
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@ -41,12 +42,27 @@ jobs:
|
||||
crate: cargo-release
|
||||
version: 0.18.5
|
||||
|
||||
- name: Build changelog generator
|
||||
run: cargo build --release -p changelog
|
||||
|
||||
- name: Generate changelog
|
||||
uses: mathiasvr/command-output@v1
|
||||
id: changelog
|
||||
with:
|
||||
run: ./target/release/changelog yew-agent ${{ github.event.inputs.level }}
|
||||
|
||||
- name: Commit changelog
|
||||
run: |
|
||||
git add CHANGELOG.md
|
||||
git commit -m "update CHANGELOG.md for yew-agent release"
|
||||
git push origin master
|
||||
|
||||
- name: Release yew-agent
|
||||
run: cargo release ${PUBLISH_LEVEL} --token ${CRATES_TOKEN} --execute --no-confirm --package yew-agent
|
||||
env:
|
||||
PUBLISH_LEVEL: ${{ github.event.inputs.level }}
|
||||
CRATES_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
|
||||
|
||||
|
||||
- name: Get tag
|
||||
id: gettag
|
||||
uses: WyriHaximus/github-action-get-previous-tag@v1
|
||||
@ -57,3 +73,10 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
branch: ${{ steps.gettag.outputs.tag }}
|
||||
|
||||
- name: Create a Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ secrets.YEWTEMPBOT_TOKEN }}
|
||||
tag_name: ${{ steps.gettag.outputs.tag }}
|
||||
body: ${{ steps.changelog.outputs.stdout }}
|
||||
|
||||
37
.github/workflows/publish-yew-only.yml
vendored
37
.github/workflows/publish-yew-only.yml
vendored
@ -7,13 +7,13 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
level:
|
||||
description: 'Version Level major|minor|patch'
|
||||
description: "Version Level major|minor|patch"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish yew
|
||||
@ -22,12 +22,13 @@ jobs:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
token: '${{ secrets.YEWTEMPBOT_TOKEN }}'
|
||||
token: "${{ secrets.YEWTEMPBOT_TOKEN }}"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Config Git
|
||||
uses: oleksiyrudenko/gha-git-credentials@v2-latest
|
||||
with:
|
||||
token: '${{ secrets.YEWTEMPBOT_TOKEN }}'
|
||||
token: "${{ secrets.YEWTEMPBOT_TOKEN }}"
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@ -42,12 +43,27 @@ jobs:
|
||||
crate: cargo-release
|
||||
version: 0.18.5
|
||||
|
||||
- name: Build changelog generator
|
||||
run: cargo build --release -p changelog
|
||||
|
||||
- name: Generate changelog
|
||||
uses: mathiasvr/command-output@v1
|
||||
id: changelog
|
||||
with:
|
||||
run: ./target/release/changelog yew ${{ github.event.inputs.level }}
|
||||
|
||||
- name: Commit changelog
|
||||
run: |
|
||||
git add CHANGELOG.md
|
||||
git commit -m "update CHANGELOG.md for yew release"
|
||||
git push origin master
|
||||
|
||||
- name: Release yew
|
||||
run: cargo release ${PUBLISH_LEVEL} --token ${CRATES_TOKEN} --execute --no-confirm --package yew
|
||||
env:
|
||||
PUBLISH_LEVEL: ${{ github.event.inputs.level }}
|
||||
CRATES_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
|
||||
|
||||
|
||||
- name: Get tag
|
||||
id: gettag
|
||||
uses: WyriHaximus/github-action-get-previous-tag@v1
|
||||
@ -58,3 +74,10 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
branch: ${{ steps.gettag.outputs.tag }}
|
||||
|
||||
- name: Create a Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ secrets.YEWTEMPBOT_TOKEN }}
|
||||
tag_name: ${{ steps.gettag.outputs.tag }}
|
||||
body: ${{ steps.changelog.outputs.stdout }}
|
||||
|
||||
37
.github/workflows/publish-yew-router-only.yml
vendored
37
.github/workflows/publish-yew-router-only.yml
vendored
@ -6,13 +6,13 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
level:
|
||||
description: 'Version Level major|minor|patch'
|
||||
description: "Version Level major|minor|patch"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish yew-router
|
||||
@ -21,12 +21,13 @@ jobs:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
token: '${{ secrets.YEWTEMPBOT_TOKEN }}'
|
||||
token: "${{ secrets.YEWTEMPBOT_TOKEN }}"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Config Git
|
||||
uses: oleksiyrudenko/gha-git-credentials@v2-latest
|
||||
with:
|
||||
token: '${{ secrets.YEWTEMPBOT_TOKEN }}'
|
||||
token: "${{ secrets.YEWTEMPBOT_TOKEN }}"
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@ -41,12 +42,27 @@ jobs:
|
||||
crate: cargo-release
|
||||
version: 0.18.5
|
||||
|
||||
- name: Build changelog generator
|
||||
run: cargo build --release -p changelog
|
||||
|
||||
- name: Generate changelog
|
||||
uses: mathiasvr/command-output@v1
|
||||
id: changelog
|
||||
with:
|
||||
run: ./target/release/changelog yew-router ${{ github.event.inputs.level }}
|
||||
|
||||
- name: Commit changelog
|
||||
run: |
|
||||
git add CHANGELOG.md
|
||||
git commit -m "update CHANGELOG.md for yew-router release"
|
||||
git push origin master
|
||||
|
||||
- name: Release yew-router
|
||||
run: cargo release ${PUBLISH_LEVEL} --token ${CRATES_TOKEN} --execute --no-confirm --package yew-router
|
||||
env:
|
||||
PUBLISH_LEVEL: ${{ github.event.inputs.level }}
|
||||
CRATES_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
|
||||
|
||||
|
||||
- name: Get tag
|
||||
id: gettag
|
||||
uses: WyriHaximus/github-action-get-previous-tag@v1
|
||||
@ -57,3 +73,10 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
branch: ${{ steps.gettag.outputs.tag }}
|
||||
|
||||
- name: Create a Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ secrets.YEWTEMPBOT_TOKEN }}
|
||||
tag_name: ${{ steps.gettag.outputs.tag }}
|
||||
body: ${{ steps.changelog.outputs.stdout }}
|
||||
|
||||
39
.github/workflows/publish-yew-router.yml
vendored
39
.github/workflows/publish-yew-router.yml
vendored
@ -6,13 +6,13 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
level:
|
||||
description: 'Version Level major|minor|patch'
|
||||
description: "Version Level major|minor|patch"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish yew-router
|
||||
@ -21,12 +21,13 @@ jobs:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
token: '${{ secrets.YEWTEMPBOT_TOKEN }}'
|
||||
token: "${{ secrets.YEWTEMPBOT_TOKEN }}"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Config Git
|
||||
uses: oleksiyrudenko/gha-git-credentials@v2-latest
|
||||
with:
|
||||
token: '${{ secrets.YEWTEMPBOT_TOKEN }}'
|
||||
token: "${{ secrets.YEWTEMPBOT_TOKEN }}"
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@ -40,7 +41,22 @@ jobs:
|
||||
with:
|
||||
crate: cargo-release
|
||||
version: 0.18.5
|
||||
|
||||
|
||||
- name: Build changelog generator
|
||||
run: cargo build --release -p changelog
|
||||
|
||||
- name: Generate changelog
|
||||
uses: mathiasvr/command-output@v1
|
||||
id: changelog
|
||||
with:
|
||||
run: ./target/release/changelog yew-router ${{ github.event.inputs.level }}
|
||||
|
||||
- name: Commit changelog
|
||||
run: |
|
||||
git add CHANGELOG.md
|
||||
git commit -m "update CHANGELOG.md for yew-router release"
|
||||
git push origin master
|
||||
|
||||
- name: Release yew-router-macro
|
||||
run: cargo release ${PUBLISH_LEVEL} --token ${CRATES_TOKEN} --execute --no-confirm --package yew-router-macro
|
||||
env:
|
||||
@ -52,7 +68,7 @@ jobs:
|
||||
env:
|
||||
PUBLISH_LEVEL: ${{ github.event.inputs.level }}
|
||||
CRATES_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
|
||||
|
||||
|
||||
- name: Get tag
|
||||
id: gettag
|
||||
uses: WyriHaximus/github-action-get-previous-tag@v1
|
||||
@ -63,3 +79,10 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
branch: ${{ steps.gettag.outputs.tag }}
|
||||
|
||||
- name: Create a Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ secrets.YEWTEMPBOT_TOKEN }}
|
||||
tag_name: ${{ steps.gettag.outputs.tag }}
|
||||
body: ${{ steps.changelog.outputs.stdout }}
|
||||
|
||||
39
.github/workflows/publish-yew.yml
vendored
39
.github/workflows/publish-yew.yml
vendored
@ -6,13 +6,13 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
level:
|
||||
description: 'Version Level major|minor|patch'
|
||||
description: "Version Level major|minor|patch"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish yew
|
||||
@ -21,12 +21,13 @@ jobs:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
token: '${{ secrets.YEWTEMPBOT_TOKEN }}'
|
||||
token: "${{ secrets.YEWTEMPBOT_TOKEN }}"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Config Git
|
||||
uses: oleksiyrudenko/gha-git-credentials@v2-latest
|
||||
with:
|
||||
token: '${{ secrets.YEWTEMPBOT_TOKEN }}'
|
||||
token: "${{ secrets.YEWTEMPBOT_TOKEN }}"
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@ -40,7 +41,22 @@ jobs:
|
||||
with:
|
||||
crate: cargo-release
|
||||
version: 0.18.5
|
||||
|
||||
|
||||
- name: Build changelog generator
|
||||
run: cargo build --release -p changelog
|
||||
|
||||
- name: Generate changelog
|
||||
uses: mathiasvr/command-output@v1
|
||||
id: changelog
|
||||
with:
|
||||
run: ./target/release/changelog yew ${{ github.event.inputs.level }}
|
||||
|
||||
- name: Commit changelog
|
||||
run: |
|
||||
git add CHANGELOG.md
|
||||
git commit -m "update CHANGELOG.md for yew release"
|
||||
git push origin master
|
||||
|
||||
- name: Release yew-macro
|
||||
run: cargo release ${PUBLISH_LEVEL} --token ${CRATES_TOKEN} --execute --no-confirm --package yew-macro
|
||||
env:
|
||||
@ -52,7 +68,7 @@ jobs:
|
||||
env:
|
||||
PUBLISH_LEVEL: ${{ github.event.inputs.level }}
|
||||
CRATES_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
|
||||
|
||||
|
||||
- name: Get tag
|
||||
id: gettag
|
||||
uses: WyriHaximus/github-action-get-previous-tag@v1
|
||||
@ -63,3 +79,10 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
branch: ${{ steps.gettag.outputs.tag }}
|
||||
|
||||
- name: Create a Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ secrets.YEWTEMPBOT_TOKEN }}
|
||||
tag_name: ${{ steps.gettag.outputs.tag }}
|
||||
body: ${{ steps.changelog.outputs.stdout }}
|
||||
|
||||
3
.github/workflows/pull-request.yml
vendored
3
.github/workflows/pull-request.yml
vendored
@ -194,6 +194,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
|
||||
@ -14,3 +14,6 @@ regex = "1"
|
||||
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
structopt = "0.3"
|
||||
strum = { version = "0.23", features = ["derive"] }
|
||||
lazy_static = "1.4.0"
|
||||
semver = "1.0"
|
||||
|
||||
101
tools/changelog/src/cli.rs
Normal file
101
tools/changelog/src/cli.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use crate::create_log_lines::create_log_lines;
|
||||
use crate::get_latest_version::get_latest_version;
|
||||
use crate::new_version_level::NewVersionLevel;
|
||||
use crate::stdout_tag_description_changelog::stdout_tag_description_changelog;
|
||||
use crate::write_changelog_file::write_changelog;
|
||||
use crate::write_log_lines::write_log_lines;
|
||||
use crate::write_version_changelog::write_changelog_file;
|
||||
use crate::yew_package::YewPackage;
|
||||
use anyhow::bail;
|
||||
use anyhow::Result;
|
||||
use semver::Version;
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(StructOpt)]
|
||||
pub struct Cli {
|
||||
/// package to generate changelog for
|
||||
pub package: YewPackage,
|
||||
|
||||
/// package to generate changelog for
|
||||
pub new_version_level: NewVersionLevel,
|
||||
|
||||
/// From ref. (ex. commit hash or for tags "refs/tags/yew-v0.19.3") overrides version level arg
|
||||
pub from: Option<String>,
|
||||
|
||||
/// To commit. (ex. commit hash or for tags "refs/tags/yew-v0.19.3")
|
||||
#[structopt(short, long, default_value = "HEAD")]
|
||||
pub to: String,
|
||||
|
||||
/// Path to changelog file
|
||||
#[structopt(short = "f", long, default_value = "CHANGELOG.md")]
|
||||
pub changelog_path: String,
|
||||
|
||||
/// Skip writing changelog file
|
||||
#[structopt(short, long)]
|
||||
pub skip_file_write: bool,
|
||||
|
||||
/// Skip getting the next version
|
||||
#[structopt(short = "b", long)]
|
||||
pub skip_get_bump_version: bool,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn run(self) -> Result<()> {
|
||||
let Cli {
|
||||
package,
|
||||
from,
|
||||
to,
|
||||
changelog_path,
|
||||
skip_file_write,
|
||||
new_version_level,
|
||||
skip_get_bump_version,
|
||||
} = self;
|
||||
let package_labels = package.as_labels();
|
||||
|
||||
// set up versions and from ref
|
||||
let (from_ref, next_version) = if skip_get_bump_version {
|
||||
let from_ref = match from {
|
||||
Some(some) => some,
|
||||
None => bail!("from required when skip_get_bump_version is true"),
|
||||
};
|
||||
let version = Version::parse("0.0.0")?;
|
||||
(from_ref, version)
|
||||
} else {
|
||||
let latest_version = get_latest_version(&package)?;
|
||||
|
||||
let next_version = new_version_level.bump(latest_version.clone());
|
||||
|
||||
let from_ref = match from {
|
||||
Some(some) => some,
|
||||
None => format!("refs/tags/{}-v{}", package, latest_version),
|
||||
};
|
||||
(from_ref, next_version)
|
||||
};
|
||||
|
||||
// walk over each commit find text, user, issue
|
||||
let log_lines = create_log_lines(from_ref, to, package_labels)?;
|
||||
|
||||
// categorize logs
|
||||
let (fixes, features): (Vec<_>, Vec<_>) = log_lines
|
||||
.into_iter()
|
||||
.partition(|log_line| log_line.message.to_lowercase().contains("fix"));
|
||||
|
||||
// create displayable log lines
|
||||
let fixes_logs = write_log_lines(fixes)?;
|
||||
let features_logs = write_log_lines(features)?;
|
||||
|
||||
if !skip_file_write {
|
||||
// create version changelog
|
||||
let version_changelog =
|
||||
write_changelog_file(&fixes_logs, &features_logs, package, next_version)?;
|
||||
|
||||
// write changelog
|
||||
write_changelog(&changelog_path, &version_changelog)?;
|
||||
}
|
||||
|
||||
// stdout changelog meant for tag description
|
||||
stdout_tag_description_changelog(&fixes_logs, &features_logs)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
95
tools/changelog/src/create_log_line.rs
Normal file
95
tools/changelog/src/create_log_line.rs
Normal file
@ -0,0 +1,95 @@
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use git2::Error;
|
||||
use git2::Oid;
|
||||
use git2::Repository;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::github_issue_labels_fetcher::GitHubIssueLabelsFetcher;
|
||||
use crate::github_user_fetcher::GitHubUsersFetcher;
|
||||
use crate::log_line::LogLine;
|
||||
|
||||
lazy_static! {
|
||||
static ref REGEX_FOR_ISSUE_ID_CAPTURE: Regex = Regex::new(r"\s*\(#(\d+)\)").unwrap();
|
||||
static ref GITHUB_USERS_FETCHER: Mutex<GitHubUsersFetcher> = Default::default();
|
||||
static ref GITHUB_ISSUE_LABELS_FETCHER: Mutex<GitHubIssueLabelsFetcher> = Default::default();
|
||||
static ref PACKAGE_LABELS: Vec<String> = vec![];
|
||||
}
|
||||
|
||||
pub fn create_log_line(
|
||||
repo: &Repository,
|
||||
package_labels: &'static [&'static str],
|
||||
oid: Result<Oid, Error>,
|
||||
) -> Result<Option<LogLine>> {
|
||||
let oid = oid?;
|
||||
let commit = repo.find_commit(oid)?;
|
||||
let commit_first_line = commit
|
||||
.message()
|
||||
.context("Invalid UTF-8 in commit message")?
|
||||
.lines()
|
||||
.next()
|
||||
.context("Missing commit message")?
|
||||
.to_string();
|
||||
let author = commit.author();
|
||||
let email = author.email().context("Missing author's email")?;
|
||||
|
||||
if email.contains("dependabot") || email.contains("github-action") {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mb_captures = REGEX_FOR_ISSUE_ID_CAPTURE
|
||||
.captures_iter(&commit_first_line)
|
||||
.last();
|
||||
|
||||
let captures = match mb_captures {
|
||||
Some(some) => some,
|
||||
None => {
|
||||
eprintln!("Missing issue for commit: {}", oid);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
let match_to_be_stripped = captures.get(0).ok_or_else(|| {
|
||||
anyhow!("Failed to capture first group - issue part of the message like \" (#2263)\"")
|
||||
})?;
|
||||
let mut message = commit_first_line.clone();
|
||||
message.replace_range(match_to_be_stripped.range(), "");
|
||||
|
||||
let issue_id = captures
|
||||
.get(1)
|
||||
.ok_or_else(|| anyhow!("Failed to capture second group - issue id like \"2263\""))?
|
||||
.as_str()
|
||||
.to_string();
|
||||
|
||||
let user = GITHUB_USERS_FETCHER
|
||||
.lock()
|
||||
.map_err(|err| anyhow!("Failed to lock GITHUB_USERS_FETCHER: {}", err))?
|
||||
.fetch_user_by_commit_author(email, oid.to_string())
|
||||
.with_context(|| format!("Could not find GitHub user for commit: {}", oid))?
|
||||
.to_string();
|
||||
|
||||
let issue_labels = GITHUB_ISSUE_LABELS_FETCHER
|
||||
.lock()
|
||||
.map_err(|err| anyhow!("Failed to lock GITHUB_ISSUE_LABELS_FETCHER: {}", err))?
|
||||
.fetch_issue_labels(issue_id.clone())
|
||||
.with_context(|| format!("Could not find GitHub labels for issue: {}", issue_id))?;
|
||||
|
||||
let is_issue_for_this_package = issue_labels
|
||||
.into_iter()
|
||||
.any(|label| package_labels.contains(&label.as_str()));
|
||||
|
||||
if !is_issue_for_this_package {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let log_line = LogLine {
|
||||
message,
|
||||
user,
|
||||
issue_id,
|
||||
};
|
||||
|
||||
Ok(Some(log_line))
|
||||
}
|
||||
35
tools/changelog/src/create_log_lines.rs
Normal file
35
tools/changelog/src/create_log_lines.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use git2::Repository;
|
||||
use git2::Sort;
|
||||
|
||||
use crate::create_log_line::create_log_line;
|
||||
use crate::log_line::LogLine;
|
||||
|
||||
pub fn create_log_lines(
|
||||
from: String,
|
||||
to: String,
|
||||
package_labels: &'static [&'static str],
|
||||
) -> Result<Vec<LogLine>> {
|
||||
let repo = Repository::open_from_env()?;
|
||||
|
||||
let from_oid = repo
|
||||
.revparse_single(&from)
|
||||
.context("Could not find `from` revision")?
|
||||
.id();
|
||||
let to_oid = repo
|
||||
.revparse_single(&to)
|
||||
.context("Could not find `to` revision")?
|
||||
.id();
|
||||
|
||||
let mut revwalk = repo.revwalk()?;
|
||||
revwalk.set_sorting(Sort::TOPOLOGICAL)?;
|
||||
|
||||
revwalk.hide(from_oid)?;
|
||||
revwalk.push(to_oid)?;
|
||||
|
||||
revwalk
|
||||
.into_iter()
|
||||
.filter_map(|oid| create_log_line(&repo, package_labels, oid).transpose())
|
||||
.collect()
|
||||
}
|
||||
26
tools/changelog/src/get_latest_version.rs
Normal file
26
tools/changelog/src/get_latest_version.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use crate::yew_package::YewPackage;
|
||||
use anyhow::Result;
|
||||
use git2::Repository;
|
||||
use semver::Error;
|
||||
use semver::Version;
|
||||
|
||||
pub fn get_latest_version(package: &YewPackage) -> Result<Version> {
|
||||
let common_tag_pattern = format!("{}-v", package);
|
||||
let search_pattern = format!("{}*", common_tag_pattern);
|
||||
|
||||
let mut tags: Vec<Version> = Repository::open_from_env()?
|
||||
.tag_names(Some(&search_pattern))?
|
||||
.iter()
|
||||
.filter_map(|mb_tag| {
|
||||
mb_tag.map(|tag| {
|
||||
let version = tag.replace(&common_tag_pattern, "");
|
||||
Version::parse(&version)
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<Version>, Error>>()?;
|
||||
|
||||
tags.sort();
|
||||
tags.reverse();
|
||||
|
||||
Ok(tags[0].clone())
|
||||
}
|
||||
26
tools/changelog/src/github_fetch.rs
Normal file
26
tools/changelog/src/github_fetch.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use reqwest::blocking::Client;
|
||||
|
||||
pub fn github_fetch<T: DeserializeOwned>(url: &str) -> Result<T> {
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
let request_client = Client::new();
|
||||
let resp = request_client
|
||||
.get(url)
|
||||
.header("user-agent", "reqwest")
|
||||
.header("accept", "application/vnd.github.v3+json")
|
||||
.send()?;
|
||||
let status = resp.status();
|
||||
if !status.is_success() {
|
||||
if let Some(remaining) = resp.headers().get("x-ratelimit-remaining") {
|
||||
if remaining == "0" {
|
||||
bail!("GitHub API limit reached.");
|
||||
}
|
||||
}
|
||||
bail!("GitHub API request error: {}", status);
|
||||
}
|
||||
Ok(resp.json()?)
|
||||
}
|
||||
40
tools/changelog/src/github_issue_labels_fetcher.rs
Normal file
40
tools/changelog/src/github_issue_labels_fetcher.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use anyhow::Result;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::github_fetch::github_fetch;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct BodyListItem {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GitHubIssueLabelsFetcher {
|
||||
cache: HashMap<String, Option<Vec<String>>>,
|
||||
}
|
||||
|
||||
impl GitHubIssueLabelsFetcher {
|
||||
pub fn fetch_issue_labels(&mut self, issue: String) -> Option<Vec<String>> {
|
||||
self.cache
|
||||
.entry(issue.clone())
|
||||
.or_insert_with(|| match Self::inner_fetch(&issue) {
|
||||
Ok(labels) => labels,
|
||||
Err(err) => {
|
||||
eprintln!("fetch_issue_labels Error: {}", err);
|
||||
None
|
||||
}
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn inner_fetch(q: &str) -> Result<Option<Vec<String>>> {
|
||||
let url = format!(
|
||||
"https://api.github.com/repos/yewstack/yew/issues/{}/labels",
|
||||
q,
|
||||
);
|
||||
let body: Vec<BodyListItem> = github_fetch(&url)?;
|
||||
let label_names: Vec<String> = body.into_iter().map(|label| label.name).collect();
|
||||
Ok(Some(label_names))
|
||||
}
|
||||
}
|
||||
48
tools/changelog/src/github_user_fetcher.rs
Normal file
48
tools/changelog/src/github_user_fetcher.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use anyhow::Result;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::github_fetch::github_fetch;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ResponseBody {
|
||||
author: ResponseBodyAuthor,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ResponseBodyAuthor {
|
||||
login: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GitHubUsersFetcher {
|
||||
cache: HashMap<String, Option<String>>,
|
||||
}
|
||||
|
||||
impl GitHubUsersFetcher {
|
||||
pub fn fetch_user_by_commit_author(
|
||||
&mut self,
|
||||
key: impl Into<String>,
|
||||
commit: impl AsRef<str>,
|
||||
) -> Option<&str> {
|
||||
self.cache
|
||||
.entry(key.into())
|
||||
.or_insert_with(|| match Self::inner_fetch(commit) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
eprintln!("fetch_user_by_commit_author Error: {}", err);
|
||||
None
|
||||
}
|
||||
})
|
||||
.as_deref()
|
||||
}
|
||||
|
||||
fn inner_fetch(commit: impl AsRef<str>) -> Result<Option<String>> {
|
||||
let url = format!(
|
||||
"https://api.github.com/repos/yewstack/yew/commits/{}",
|
||||
commit.as_ref(),
|
||||
);
|
||||
let body: ResponseBody = github_fetch(&url)?;
|
||||
Ok(Some(body.author.login))
|
||||
}
|
||||
}
|
||||
16
tools/changelog/src/lib.rs
Normal file
16
tools/changelog/src/lib.rs
Normal file
@ -0,0 +1,16 @@
|
||||
mod cli;
|
||||
pub mod create_log_line;
|
||||
pub mod create_log_lines;
|
||||
pub mod get_latest_version;
|
||||
pub mod github_fetch;
|
||||
pub mod github_issue_labels_fetcher;
|
||||
pub mod github_user_fetcher;
|
||||
pub mod log_line;
|
||||
pub mod new_version_level;
|
||||
pub mod stdout_tag_description_changelog;
|
||||
pub mod write_changelog_file;
|
||||
pub mod write_log_lines;
|
||||
pub mod write_version_changelog;
|
||||
pub mod yew_package;
|
||||
|
||||
pub use cli::Cli;
|
||||
5
tools/changelog/src/log_line.rs
Normal file
5
tools/changelog/src/log_line.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub struct LogLine {
|
||||
pub message: String,
|
||||
pub user: String,
|
||||
pub issue_id: String,
|
||||
}
|
||||
@ -1,326 +1,7 @@
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use anyhow::Result;
|
||||
use changelog::Cli;
|
||||
use structopt::StructOpt;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
Cli::from_args().run()
|
||||
}
|
||||
|
||||
#[derive(StructOpt)]
|
||||
pub struct Cli {
|
||||
/// package to generate changelog for
|
||||
package: String,
|
||||
|
||||
/// 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)]
|
||||
github_issue_labels: GitHubIssueLabels,
|
||||
|
||||
#[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 package: Package = self.package.as_str().try_into()?;
|
||||
|
||||
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))?;
|
||||
|
||||
let is_issue_for_this_package = self
|
||||
.github_issue_labels
|
||||
.is_issue_for_this_package(issue.clone(), package.clone())
|
||||
.with_context(|| format!("Could not find GitHub issue: {}", issue))?;
|
||||
|
||||
if !is_issue_for_this_package {
|
||||
continue;
|
||||
}
|
||||
|
||||
logs.push((first_line.to_string(), user.to_owned(), issue.to_owned()));
|
||||
}
|
||||
|
||||
let (fixes, features): (Vec<_>, Vec<_>) = logs
|
||||
.into_iter()
|
||||
.partition(|(msg, _, _)| msg.to_lowercase().contains("fix"));
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"## ✨ {} **x.y.z** *({})*",
|
||||
self.package,
|
||||
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)?;
|
||||
|
||||
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<String, Option<String>>,
|
||||
}
|
||||
|
||||
impl GitHubUsers {
|
||||
pub fn find_user_by_commit_author(
|
||||
&mut self,
|
||||
key: impl Into<String>,
|
||||
commit: impl AsRef<str>,
|
||||
) -> 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<str>) -> Result<Option<String>> {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let resp = client
|
||||
.get(format!(
|
||||
"https://api.github.com/repos/yewstack/yew/commits/{}",
|
||||
q.as_ref(),
|
||||
))
|
||||
.header("user-agent", "reqwest")
|
||||
.header("accept", "application/vnd.github.v3+json")
|
||||
.send()?;
|
||||
let status = resp.status();
|
||||
if !status.is_success() {
|
||||
if let Some(remaining) = resp.headers().get("x-ratelimit-remaining") {
|
||||
if remaining == "0" {
|
||||
bail!("GitHub API limit reached.");
|
||||
}
|
||||
}
|
||||
bail!("GitHub API request error: {}", status);
|
||||
}
|
||||
let body = resp.json::<GitHubCommitApi>()?;
|
||||
|
||||
Ok(Some(body.author.login))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GitHubIssueLabels {
|
||||
cache: HashMap<String, Option<Vec<String>>>,
|
||||
}
|
||||
|
||||
impl GitHubIssueLabels {
|
||||
pub fn is_issue_for_this_package(&mut self, issue: String, package: Package) -> Option<bool> {
|
||||
let labels = self
|
||||
.cache
|
||||
.entry(issue.clone())
|
||||
.or_insert_with(|| match Self::query_issue_labels(&issue) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
eprintln!("Error: {}", err);
|
||||
None
|
||||
}
|
||||
})
|
||||
.as_deref()?;
|
||||
let package_labels = package.as_labels();
|
||||
|
||||
Some(labels.iter().any(|label| package_labels.contains(label)))
|
||||
}
|
||||
|
||||
fn query_issue_labels(q: &str) -> Result<Option<Vec<String>>> {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
let issue_labels = reqwest::blocking::Client::new();
|
||||
let resp = issue_labels
|
||||
.get(format!(
|
||||
"https://api.github.com/repos/yewstack/yew/issues/{}/labels",
|
||||
q,
|
||||
))
|
||||
.header("user-agent", "reqwest")
|
||||
.header("accept", "application/vnd.github.v3+json")
|
||||
.send()?;
|
||||
let status = resp.status();
|
||||
if !status.is_success() {
|
||||
if let Some(remaining) = resp.headers().get("x-ratelimit-remaining") {
|
||||
if remaining == "0" {
|
||||
bail!("GitHub API limit reached.");
|
||||
}
|
||||
}
|
||||
bail!("GitHub API request error: {}", status);
|
||||
}
|
||||
let body = resp.json::<Vec<GitHubIssueLabelApi>>()?;
|
||||
|
||||
let label_names: Vec<String> = body.into_iter().map(|label| label.name).collect();
|
||||
|
||||
Ok(Some(label_names))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct GitHubCommitApi {
|
||||
author: GitHubCommitAuthorApi,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct GitHubCommitAuthorApi {
|
||||
login: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct GitHubIssueLabelApi {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Package {
|
||||
Yew,
|
||||
YewAgent,
|
||||
YewRouter,
|
||||
}
|
||||
|
||||
impl Package {
|
||||
fn as_labels(&self) -> Vec<String> {
|
||||
match self {
|
||||
Package::Yew => vec![
|
||||
"A-yew".to_string(),
|
||||
"A-yew-macro".to_string(),
|
||||
"macro".to_string(),
|
||||
],
|
||||
Package::YewAgent => vec!["A-yew-agent".to_string()],
|
||||
Package::YewRouter => {
|
||||
vec!["A-yew-router".to_string(), "A-yew-router-macro".to_string()]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Package {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
"yew" => Ok(Package::Yew),
|
||||
"yew-agent" => Ok(Package::YewAgent),
|
||||
"yew-router" => Ok(Package::YewRouter),
|
||||
_ => Err(anyhow!("{} package is not supported for this cli", value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
tools/changelog/src/mod.rs
Normal file
5
tools/changelog/src/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod github_fetch;
|
||||
pub mod github_issue_labels_fetcher;
|
||||
pub mod github_user_fetcher;
|
||||
pub mod log_line;
|
||||
pub mod yew_package;
|
||||
33
tools/changelog/src/new_version_level.rs
Normal file
33
tools/changelog/src/new_version_level.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use semver::Version;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
#[derive(Debug, Clone, EnumString, Display)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum NewVersionLevel {
|
||||
Patch,
|
||||
Minor,
|
||||
Major,
|
||||
}
|
||||
|
||||
impl NewVersionLevel {
|
||||
pub fn bump(&self, current_version: Version) -> Version {
|
||||
match self {
|
||||
NewVersionLevel::Patch => Version {
|
||||
patch: current_version.patch + 1,
|
||||
..current_version
|
||||
},
|
||||
NewVersionLevel::Minor => Version {
|
||||
minor: current_version.minor + 1,
|
||||
patch: 0,
|
||||
..current_version
|
||||
},
|
||||
NewVersionLevel::Major => Version {
|
||||
major: current_version.major + 1,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
..current_version
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
32
tools/changelog/src/stdout_tag_description_changelog.rs
Normal file
32
tools/changelog/src/stdout_tag_description_changelog.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use anyhow::Result;
|
||||
use std::io::stdout;
|
||||
use std::io::Write;
|
||||
|
||||
pub fn stdout_tag_description_changelog(fixes_logs: &[u8], features_logs: &[u8]) -> Result<()> {
|
||||
let mut tag_changelog = Vec::new();
|
||||
|
||||
writeln!(tag_changelog, "# Changelog")?;
|
||||
writeln!(tag_changelog)?;
|
||||
|
||||
if fixes_logs.is_empty() && features_logs.is_empty() {
|
||||
writeln!(tag_changelog, "No changes")?;
|
||||
writeln!(tag_changelog)?;
|
||||
}
|
||||
|
||||
if !fixes_logs.is_empty() {
|
||||
writeln!(tag_changelog, "## 🛠 Fixes")?;
|
||||
writeln!(tag_changelog)?;
|
||||
tag_changelog.extend(fixes_logs);
|
||||
writeln!(tag_changelog)?;
|
||||
}
|
||||
|
||||
if !features_logs.is_empty() {
|
||||
writeln!(tag_changelog, "## ⚡️ Features")?;
|
||||
writeln!(tag_changelog)?;
|
||||
tag_changelog.extend(features_logs);
|
||||
}
|
||||
|
||||
stdout().write_all(&tag_changelog)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
38
tools/changelog/src/write_changelog_file.rs
Normal file
38
tools/changelog/src/write_changelog_file.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
use std::io::Write;
|
||||
|
||||
pub fn write_changelog(changelog_path: &str, version_changelog: &[u8]) -> Result<()> {
|
||||
let old_changelog = File::open(changelog_path)
|
||||
.context(format!("could not open {} for reading", changelog_path))?;
|
||||
let old_changelog_reader = BufReader::new(old_changelog);
|
||||
|
||||
let changelog_path_new = &format!("{}.new", changelog_path);
|
||||
|
||||
let mut new_changelog = fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(changelog_path_new)
|
||||
.context(format!("could not open {} for writing", changelog_path_new))?;
|
||||
|
||||
new_changelog.write_all(version_changelog)?;
|
||||
|
||||
for old_line in old_changelog_reader.lines().skip(2) {
|
||||
writeln!(new_changelog, "{}", old_line?)?;
|
||||
}
|
||||
|
||||
drop(new_changelog);
|
||||
|
||||
fs::remove_file(changelog_path).context(format!("Could not delete {}", changelog_path))?;
|
||||
fs::rename(changelog_path_new, changelog_path).context(format!(
|
||||
"Could not replace {} with {}",
|
||||
changelog_path, changelog_path_new
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
23
tools/changelog/src/write_log_lines.rs
Normal file
23
tools/changelog/src/write_log_lines.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use anyhow::Result;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::log_line::LogLine;
|
||||
|
||||
pub fn write_log_lines(log_lines: Vec<LogLine>) -> Result<Vec<u8>> {
|
||||
let mut logs_list = Vec::default();
|
||||
for LogLine {
|
||||
message,
|
||||
user,
|
||||
issue_id,
|
||||
} in log_lines
|
||||
{
|
||||
writeln!(
|
||||
logs_list,
|
||||
"- {message}. [[@{user}](https://github.com/{user}), [#{issue_id}](https://github.com/yewstack/yew/pull/{issue_id})]",
|
||||
message = message,
|
||||
user = user,
|
||||
issue_id = issue_id
|
||||
)?;
|
||||
}
|
||||
Ok(logs_list)
|
||||
}
|
||||
47
tools/changelog/src/write_version_changelog.rs
Normal file
47
tools/changelog/src/write_version_changelog.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use anyhow::Result;
|
||||
use semver::Version;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::yew_package::YewPackage;
|
||||
|
||||
pub fn write_changelog_file(
|
||||
fixes_logs: &[u8],
|
||||
features_logs: &[u8],
|
||||
package: YewPackage,
|
||||
next_version: Version,
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut version_only_changelog = Vec::default();
|
||||
|
||||
writeln!(version_only_changelog, "# Changelog")?;
|
||||
writeln!(version_only_changelog)?;
|
||||
|
||||
writeln!(
|
||||
version_only_changelog,
|
||||
"## ✨ {package} **{next_version}** *({release_date})* Changelog",
|
||||
next_version = next_version,
|
||||
package = package.to_string(),
|
||||
release_date = chrono::Utc::now().format("%Y-%m-%d")
|
||||
)?;
|
||||
writeln!(version_only_changelog)?;
|
||||
|
||||
if fixes_logs.is_empty() && features_logs.is_empty() {
|
||||
writeln!(version_only_changelog, "No changes")?;
|
||||
writeln!(version_only_changelog)?;
|
||||
}
|
||||
|
||||
if !fixes_logs.is_empty() {
|
||||
writeln!(version_only_changelog, "### 🛠 Fixes")?;
|
||||
writeln!(version_only_changelog)?;
|
||||
version_only_changelog.extend(fixes_logs);
|
||||
writeln!(version_only_changelog)?;
|
||||
}
|
||||
|
||||
if !features_logs.is_empty() {
|
||||
writeln!(version_only_changelog, "### ⚡️ Features")?;
|
||||
writeln!(version_only_changelog)?;
|
||||
version_only_changelog.extend(features_logs);
|
||||
writeln!(version_only_changelog)?;
|
||||
}
|
||||
|
||||
Ok(version_only_changelog)
|
||||
}
|
||||
20
tools/changelog/src/yew_package.rs
Normal file
20
tools/changelog/src/yew_package.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
#[derive(Debug, Clone, EnumString, Display)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
pub enum YewPackage {
|
||||
Yew,
|
||||
YewAgent,
|
||||
YewRouter,
|
||||
}
|
||||
|
||||
impl YewPackage {
|
||||
pub fn as_labels(&self) -> &'static [&'static str] {
|
||||
match self {
|
||||
YewPackage::Yew => &["A-yew", "A-yew-macro", "macro"],
|
||||
YewPackage::YewAgent => &["A-yew-agent"],
|
||||
YewPackage::YewRouter => &["A-yew-router", "A-yew-router-macro"],
|
||||
}
|
||||
}
|
||||
}
|
||||
66
tools/changelog/tests/generate_yew_changelog_file.rs
Normal file
66
tools/changelog/tests/generate_yew_changelog_file.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use changelog::new_version_level::NewVersionLevel;
|
||||
use changelog::yew_package::YewPackage;
|
||||
use changelog::Cli;
|
||||
use chrono::Utc;
|
||||
|
||||
struct FileDeleteOnDrop;
|
||||
|
||||
impl Drop for FileDeleteOnDrop {
|
||||
fn drop(&mut self) {
|
||||
fs::remove_file("tests/test_changelog.md").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_yew_changelog_file() -> Result<()> {
|
||||
// Setup
|
||||
let file_delete_on_drop = FileDeleteOnDrop;
|
||||
|
||||
fs::copy("tests/test_base.md", "tests/test_changelog.md")?;
|
||||
|
||||
// Run
|
||||
let cli_args = Cli {
|
||||
package: YewPackage::from_str("yew").unwrap(),
|
||||
new_version_level: NewVersionLevel::Minor,
|
||||
from: Some("abeb8bc3f1ffabc8a58bd9ba4430cd091a06335a".to_string()),
|
||||
to: "d8ec50150ed27e2835bb1def26d2371a8c2ab750".to_string(),
|
||||
changelog_path: "tests/test_changelog.md".to_string(),
|
||||
skip_file_write: false,
|
||||
skip_get_bump_version: true,
|
||||
};
|
||||
|
||||
cli_args.run().unwrap();
|
||||
|
||||
// Check
|
||||
let expected = File::open("tests/test_expected.md")?;
|
||||
let expected_reader_lines = BufReader::new(expected).lines();
|
||||
|
||||
let after = File::open("tests/test_changelog.md")?;
|
||||
let after_reader_lines = BufReader::new(after).lines();
|
||||
|
||||
let lines = expected_reader_lines.zip(after_reader_lines);
|
||||
|
||||
for (i, (expected_line, after_line)) in lines.enumerate() {
|
||||
if i == 2 {
|
||||
// third line has dynamic things that may break the tests
|
||||
let expected_third_line = expected_line?.replace(
|
||||
"date_goes_here",
|
||||
Utc::now().format("%Y-%m-%d").to_string().as_str(),
|
||||
);
|
||||
assert_eq!(expected_third_line, after_line?);
|
||||
} else {
|
||||
assert_eq!(expected_line?, after_line?);
|
||||
}
|
||||
}
|
||||
|
||||
drop(file_delete_on_drop);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
15
tools/changelog/tests/test_base.md
Normal file
15
tools/changelog/tests/test_base.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Changelog for its generation tests
|
||||
|
||||
## ✨ yew **0.19.0** *(2021-11-26)*
|
||||
|
||||
#### Changelog
|
||||
|
||||
- #### 🛠 Fixes
|
||||
|
||||
- Attempt to fix recursion on display. [[@mibes](https://github.com/mibes), [#2149](https://github.com/yewstack/yew/pull/2149)]
|
||||
- Fix default passive option. [[@mc1098](https://github.com/mc1098), [#2111](https://github.com/yewstack/yew/pull/2111)]
|
||||
|
||||
- #### ⚡️ Features
|
||||
|
||||
- Check event bubbling cancellation at each step of propagation. [[@rjmac](https://github.com/rjmac), [#2191](https://github.com/yewstack/yew/pull/2191)]
|
||||
- Add possibility to cancel bubbling. [[@voidpumpkin](https://github.com/voidpumpkin), [#2172](https://github.com/yewstack/yew/pull/2172)]
|
||||
26
tools/changelog/tests/test_expected.md
Normal file
26
tools/changelog/tests/test_expected.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Changelog
|
||||
|
||||
## ✨ yew **0.0.0** *(date_goes_here)* Changelog
|
||||
|
||||
### 🛠 Fixes
|
||||
|
||||
- Fix defaulted type parameter.. [[@futursolo](https://github.com/futursolo), [#2284](https://github.com/yewstack/yew/pull/2284)]
|
||||
|
||||
### ⚡️ Features
|
||||
|
||||
- Silence some warnings from derive(Properties). [[@WorldSEnder](https://github.com/WorldSEnder), [#2266](https://github.com/yewstack/yew/pull/2266)]
|
||||
- Raw field names in property structs. [[@WorldSEnder](https://github.com/WorldSEnder), [#2273](https://github.com/yewstack/yew/pull/2273)]
|
||||
|
||||
## ✨ yew **0.19.0** *(2021-11-26)*
|
||||
|
||||
#### Changelog
|
||||
|
||||
- #### 🛠 Fixes
|
||||
|
||||
- Attempt to fix recursion on display. [[@mibes](https://github.com/mibes), [#2149](https://github.com/yewstack/yew/pull/2149)]
|
||||
- Fix default passive option. [[@mc1098](https://github.com/mc1098), [#2111](https://github.com/yewstack/yew/pull/2111)]
|
||||
|
||||
- #### ⚡️ Features
|
||||
|
||||
- Check event bubbling cancellation at each step of propagation. [[@rjmac](https://github.com/rjmac), [#2191](https://github.com/yewstack/yew/pull/2191)]
|
||||
- Add possibility to cancel bubbling. [[@voidpumpkin](https://github.com/voidpumpkin), [#2172](https://github.com/yewstack/yew/pull/2172)]
|
||||
Loading…
x
Reference in New Issue
Block a user