Unverified Commit ae9a5ed5 authored by John DiSanti's avatar John DiSanti Committed by GitHub
Browse files

Add support for automated model updates to the changelog tool (#1360)

parent 6b3a033a
Loading
Loading
Loading
Loading
+79 −32
Original line number Diff line number Diff line
@@ -2,11 +2,20 @@
# It is not intended for manual editing.
version = 3

[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
 "winapi",
]

[[package]]
name = "anyhow"
version = "1.0.50"
version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc78c299ae753905840c5d3ba036c51f61ce5a98a83f98d9c9d29dffd427f71"
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"

[[package]]
name = "atty"
@@ -33,9 +42,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"

[[package]]
name = "cargo_toml"
version = "0.10.1"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6d613611c914a7db07f28526941ce1e956d2f977b0c5e2014fbfa42230d420f"
checksum = "363c7cfaa15f101415c4ac9e68706ca4a2277773932828b33f96e59d28c68e62"
dependencies = [
 "serde",
 "serde_derive",
@@ -44,16 +53,16 @@ dependencies = [

[[package]]
name = "clap"
version = "3.1.7"
version = "3.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c67e7973e74896f4bba06ca2dcfd28d54f9cb8c035e940a32b88ed48f5f5ecf2"
checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d"
dependencies = [
 "atty",
 "bitflags",
 "clap_derive",
 "clap_lex",
 "indexmap",
 "lazy_static",
 "os_str_bytes",
 "strsim",
 "termcolor",
 "textwrap",
@@ -72,6 +81,31 @@ dependencies = [
 "syn",
]

[[package]]
name = "clap_lex"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
dependencies = [
 "os_str_bytes",
]

[[package]]
name = "ctor"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
dependencies = [
 "quote",
 "syn",
]

[[package]]
name = "diff"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"

[[package]]
name = "hashbrown"
version = "0.11.2"
@@ -111,21 +145,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"

[[package]]
name = "libc"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"

[[package]]
name = "memchr"
version = "2.4.1"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"

[[package]]
name = "num-integer"
version = "0.1.44"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
 "autocfg",
 "num-traits",
@@ -163,8 +191,26 @@ name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"

[[package]]
name = "output_vt100"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
dependencies = [
 "winapi",
]

[[package]]
name = "pretty_assertions"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563"
dependencies = [
 "memchr",
 "ansi_term",
 "ctor",
 "diff",
 "output_vt100",
]

[[package]]
@@ -193,18 +239,18 @@ dependencies = [

[[package]]
name = "proc-macro2"
version = "1.0.32"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
dependencies = [
 "unicode-xid",
]

[[package]]
name = "quote"
version = "1.0.10"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
 "proc-macro2",
]
@@ -218,6 +264,7 @@ dependencies = [
 "clap",
 "lazy_static",
 "ordinal",
 "pretty_assertions",
 "serde",
 "time",
 "toml",
@@ -225,18 +272,18 @@ dependencies = [

[[package]]
name = "serde"
version = "1.0.130"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
dependencies = [
 "serde_derive",
]

[[package]]
name = "serde_derive"
version = "1.0.130"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
dependencies = [
 "proc-macro2",
 "quote",
@@ -251,9 +298,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"

[[package]]
name = "syn"
version = "1.0.82"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52"
dependencies = [
 "proc-macro2",
 "quote",
@@ -287,18 +334,18 @@ dependencies = [

[[package]]
name = "toml"
version = "0.5.8"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
 "serde",
]

[[package]]
name = "unicode-xid"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"

[[package]]
name = "version_check"
+3 −0
Original line number Diff line number Diff line
@@ -19,3 +19,6 @@ serde = { version = "1", features = ["derive"]}
lazy_static = "1.4.0"
time = { version = "0.3.9", features = ["local-offset"]}
ordinal = "0.3.2"

[dev-dependencies]
pretty_assertions = "1.2.1"
+265 −98
Original line number Diff line number Diff line
@@ -36,8 +36,29 @@ fn maintainers() -> Vec<&'static str> {
        .collect()
}

enum ChangelogEntry {
    HandAuthored(HandAuthoredEntry),
    AwsSdkModel(SdkModelEntry),
}

impl ChangelogEntry {
    fn hand_authored(&self) -> Option<&HandAuthoredEntry> {
        match self {
            ChangelogEntry::HandAuthored(hand_authored) => Some(hand_authored),
            _ => None,
        }
    }

    fn aws_sdk_model(&self) -> Option<&SdkModelEntry> {
        match self {
            ChangelogEntry::AwsSdkModel(sdk_model) => Some(sdk_model),
            _ => None,
        }
    }
}

#[derive(Deserialize)]
struct ChangelogEntry {
struct HandAuthoredEntry {
    message: String,
    meta: Meta,
    author: String,
@@ -45,6 +66,92 @@ struct ChangelogEntry {
    references: Vec<Reference>,
}

impl HandAuthoredEntry {
    /// Validate a changelog entry to ensure it follows standards
    fn validate(&self) -> Result<()> {
        if self.author.is_empty() {
            bail!("Author must be set (was empty)");
        }
        if !self.author.chars().all(|c| c.is_alphanumeric() || c == '-') {
            bail!("Author must be valid GitHub username: [a-zA-Z0-9\\-]")
        }
        if self.references.is_empty() {
            bail!("Changelog entry must refer to at least one pull request or issue");
        }

        Ok(())
    }

    /// Write a changelog entry to [out]
    ///
    /// Example output:
    /// `- Add a feature (smithy-rs#123, @contributor)`
    fn render(&self, mut out: &mut String) {
        let mut meta = String::new();
        if self.meta.bug {
            meta.push('🐛');
        }
        if self.meta.breaking {
            meta.push('⚠');
        }
        if self.meta.tada {
            meta.push('🎉');
        }
        if !meta.is_empty() {
            meta.push(' ');
        }
        let mut references = self
            .references
            .iter()
            .map(Reference::to_md_link)
            .collect::<Vec<_>>();
        if !maintainers().contains(&self.author.to_ascii_lowercase().as_str()) {
            references.push(format!("@{}", self.author.to_ascii_lowercase()));
        };
        if !references.is_empty() {
            write!(meta, "({}) ", references.join(", ")).unwrap();
        }
        write!(
            &mut out,
            "- {meta}{message}",
            meta = meta,
            message = indented_message(&self.message),
        )
        .unwrap();
    }
}

#[derive(Deserialize)]
enum SdkModelChangeKind {
    Documentation,
    Feature,
}

#[derive(Deserialize)]
struct SdkModelEntry {
    /// SDK module name (e.g., "aws-sdk-s3" for S3)
    module: String,
    /// SDK module version number (e.g., "0.14.0")
    version: String,
    /// What changed
    kind: SdkModelChangeKind,
    /// More details about the change
    message: String,
}

impl SdkModelEntry {
    fn render(&self, out: &mut String) {
        write!(
            out,
            "- `{module}` ({version}): {message}",
            module = self.module,
            version = self.version,
            message = self.message
        )
        .unwrap();
    }
}

struct Reference {
    repo: String,
    number: usize,
@@ -93,46 +200,6 @@ impl FromStr for Reference {
    }
}

impl ChangelogEntry {
    /// Write a changelog entry to [out]
    ///
    /// Example output:
    /// `- Add a feature (smithy-rs#123, @contributor)`
    fn render(&self, mut out: &mut String) {
        let mut meta = String::new();
        if self.meta.bug {
            meta.push('🐛');
        }
        if self.meta.breaking {
            meta.push('⚠');
        }
        if self.meta.tada {
            meta.push('🎉');
        }
        if !meta.is_empty() {
            meta.push(' ');
        }
        let mut references = self
            .references
            .iter()
            .map(Reference::to_md_link)
            .collect::<Vec<_>>();
        if !maintainers().contains(&self.author.to_ascii_lowercase().as_str()) {
            references.push(format!("@{}", self.author.to_ascii_lowercase()));
        };
        if !references.is_empty() {
            write!(meta, "({}) ", references.join(", ")).unwrap();
        }
        write!(
            &mut out,
            "- {meta}{message}",
            meta = meta,
            message = indented_message(&self.message),
        )
        .unwrap();
    }
}

fn indented_message(message: &str) -> String {
    let mut out = String::new();
    for (idx, line) in message.lines().enumerate() {
@@ -158,9 +225,39 @@ struct Meta {
pub(crate) struct Changelog {
    #[serde(rename = "smithy-rs")]
    #[serde(default)]
    smithy_rs: Vec<ChangelogEntry>,
    smithy_rs: Vec<HandAuthoredEntry>,
    #[serde(rename = "aws-sdk-rust")]
    #[serde(default)]
    aws_sdk_rust: Vec<HandAuthoredEntry>,
    #[serde(rename = "aws-sdk-model")]
    #[serde(default)]
    sdk_models: Vec<SdkModelEntry>,
}

impl Changelog {
    fn into_entries(mut self) -> ChangelogEntries {
        self.aws_sdk_rust.sort_by_key(|entry| !entry.meta.tada);
        self.sdk_models.sort_by(|a, b| a.module.cmp(&b.module));
        self.smithy_rs.sort_by_key(|entry| !entry.meta.tada);

        ChangelogEntries {
            smithy_rs: self
                .smithy_rs
                .into_iter()
                .map(ChangelogEntry::HandAuthored)
                .collect(),
            aws_sdk_rust: self
                .aws_sdk_rust
                .into_iter()
                .map(ChangelogEntry::HandAuthored)
                .chain(self.sdk_models.into_iter().map(ChangelogEntry::AwsSdkModel))
                .collect(),
        }
    }
}

struct ChangelogEntries {
    smithy_rs: Vec<ChangelogEntry>,
    aws_sdk_rust: Vec<ChangelogEntry>,
}

@@ -187,11 +284,10 @@ fn no_uncommited_changes(path: &Path) -> Result<()> {

pub(crate) fn update_changelogs(
    changelog_next: impl AsRef<Path>,
    smithy_rs: impl AsRef<Path>,
    aws_sdk_rust: impl AsRef<Path>,
    smithy_rs_version: &str,
    aws_sdk_rust_version: &str,
    date: &str,
    smithy_rs_path: impl AsRef<Path>,
    aws_sdk_rust_path: impl AsRef<Path>,
    smithy_rs_release_header: &str,
    aws_sdk_rust_release_header: &str,
) -> Result<()> {
    no_uncommited_changes(changelog_next.as_ref()).context(
        "CHANGELOG.next.toml had unstaged changes. Refusing to perform changelog update.",
@@ -202,47 +298,39 @@ pub(crate) fn update_changelogs(
            errs
        ))
    })?;
    for (entries, path, version) in [
        (changelog.smithy_rs, smithy_rs.as_ref(), smithy_rs_version),
    let ChangelogEntries {
        smithy_rs,
        aws_sdk_rust,
    } = changelog.into_entries();
    for (entries, path, release_header) in [
        (smithy_rs, smithy_rs_path.as_ref(), smithy_rs_release_header),
        (
            changelog.aws_sdk_rust,
            aws_sdk_rust.as_ref(),
            aws_sdk_rust_version,
            aws_sdk_rust,
            aws_sdk_rust_path.as_ref(),
            aws_sdk_rust_release_header,
        ),
    ] {
        no_uncommited_changes(path)
            .with_context(|| format!("{} had unstaged changes", path.display()))?;
        let mut update = USE_UPDATE_CHANGELOGS.to_string();
        update.push('\n');
        update.push_str(&render(entries, version, date));
        update.push_str(&render(&entries, release_header));
        let current = std::fs::read_to_string(path)?.replace(USE_UPDATE_CHANGELOGS, "");
        update.push_str(&current);
        std::fs::write(path, update)?;
    }
    std::fs::write(changelog_next.as_ref(), EXAMPLE_ENTRY.trim())?;
    eprintln!("Changelogs updated:\n  SDK: {aws_sdk_rust_version}\n  Smithy: {smithy_rs_version}\n  Date: {date}");
    eprintln!("Changelogs updated!");
    Ok(())
}

/// Convert a list of changelog entries into markdown
fn render(mut entries: Vec<ChangelogEntry>, version: &str, date: &str) -> String {
    entries.sort_by_key(|ent| !ent.meta.tada);
    let mut out = String::new();
    let header = format!("{version} ({date})", version = version, date = date);
    out.push_str(&header);
    out.push('\n');
    for _ in 0..header.len() {
        out.push('=');
    }
    out.push('\n');
    let (breaking, non_breaking) = entries
        .iter()
        .partition::<Vec<_>, _>(|entry| entry.meta.breaking);
fn render_handauthored<'a>(entries: impl Iterator<Item = &'a HandAuthoredEntry>, out: &mut String) {
    let (breaking, non_breaking) = entries.partition::<Vec<_>, _>(|entry| entry.meta.breaking);

    if !breaking.is_empty() {
        out.push_str("**Breaking Changes:**\n");
        for change in breaking {
            change.render(&mut out);
            change.render(out);
            out.push('\n');
        }
        out.push('\n')
@@ -251,15 +339,59 @@ fn render(mut entries: Vec<ChangelogEntry>, version: &str, date: &str) -> String
    if !non_breaking.is_empty() {
        out.push_str("**New this release:**\n");
        for change in non_breaking {
            change.render(&mut out);
            change.render(out);
            out.push('\n');
        }
        out.push('\n');
    }
}

fn render_sdk_model_entries<'a>(
    entries: impl Iterator<Item = &'a SdkModelEntry>,
    out: &mut String,
) {
    let (features, docs) =
        entries.partition::<Vec<_>, _>(|entry| matches!(entry.kind, SdkModelChangeKind::Feature));
    if !features.is_empty() {
        out.push_str("**Service Features:**\n");
        for entry in features {
            entry.render(out);
            out.push('\n');
        }
        out.push('\n');
    }
    if !docs.is_empty() {
        out.push_str("**Service Documentation:**\n");
        for entry in docs {
            entry.render(out);
            out.push('\n');
        }
        out.push('\n');
    }
}

/// Convert a list of changelog entries into markdown
fn render(entries: &[ChangelogEntry], release_header: &str) -> String {
    let mut out = String::new();
    out.push_str(release_header);
    out.push('\n');
    for _ in 0..release_header.len() {
        out.push('=');
    }
    out.push('\n');

    render_handauthored(
        entries.iter().filter_map(ChangelogEntry::hand_authored),
        &mut out,
    );
    render_sdk_model_entries(
        entries.iter().filter_map(ChangelogEntry::aws_sdk_model),
        &mut out,
    );

    let mut external_contribs = entries
        .iter()
        .map(|entry| entry.author.to_ascii_lowercase())
        .filter_map(|entry| entry.hand_authored().map(|e| e.author.to_ascii_lowercase()))
        .filter(|author| !maintainers().contains(&author.as_str()))
        .collect::<Vec<_>>();
    external_contribs.sort();
@@ -272,10 +404,18 @@ fn render(mut entries: Vec<ChangelogEntry>, version: &str, date: &str) -> String
                .iter()
                .filter(|entry| {
                    entry
                        .author
                        .eq_ignore_ascii_case(contributor_handle.as_str())
                        .hand_authored()
                        .map(|e| e.author.eq_ignore_ascii_case(contributor_handle.as_str()))
                        .unwrap_or(false)
                })
                .flat_map(|entry| {
                    entry
                        .hand_authored()
                        .unwrap()
                        .references
                        .iter()
                        .map(|it| it.to_md_link())
                })
                .flat_map(|entry| entry.references.iter().map(|it| it.to_md_link()))
                .collect::<Vec<_>>();
            contribution_references.sort();
            contribution_references.dedup();
@@ -292,25 +432,6 @@ fn render(mut entries: Vec<ChangelogEntry>, version: &str, date: &str) -> String
    out
}

/// Validate a changelog entry to ensure it follows standards
fn validate(entry: &ChangelogEntry) -> Result<()> {
    if entry.author.is_empty() {
        bail!("Author must be set (was empty)");
    }
    if !entry
        .author
        .chars()
        .all(|c| c.is_alphanumeric() || c == '-')
    {
        bail!("Author must be valid GitHub username: [a-zA-Z0-9\\-]")
    }
    if entry.references.is_empty() {
        bail!("Changelog entry must refer to at least one pull request or issue");
    }

    Ok(())
}

pub(crate) struct ChangelogNext;
impl Lint for ChangelogNext {
    fn name(&self) -> &str {
@@ -341,7 +462,7 @@ fn check_changelog_next(path: impl AsRef<Path>) -> std::result::Result<Changelog
        .map_err(|e| vec![LintError::via_display(e)])?;
    let mut errors = vec![];
    for entry in parsed.aws_sdk_rust.iter().chain(parsed.smithy_rs.iter()) {
        if let Err(e) = validate(entry) {
        if let Err(e) = entry.validate() {
            errors.push(LintError::via_display(e))
        }
    }
@@ -354,7 +475,7 @@ fn check_changelog_next(path: impl AsRef<Path>) -> std::result::Result<Changelog

#[cfg(test)]
mod test {
    use crate::changelog::{render, Changelog};
    use crate::changelog::{render, Changelog, ChangelogEntries};

    #[test]
    fn end_to_end_changelog() {
@@ -398,11 +519,33 @@ blah blah
"""
meta = { breaking = false, tada = true, bug = false }
references = ["smithy-rs#446"]

[[aws-sdk-model]]
module = "aws-sdk-s3"
version = "0.14.0"
kind = "Feature"
message = "Some new API to do X"

[[aws-sdk-model]]
module = "aws-sdk-ec2"
version = "0.12.0"
kind = "Documentation"
message = "Updated some docs"

[[aws-sdk-model]]
module = "aws-sdk-ec2"
version = "0.12.0"
kind = "Feature"
message = "Some API change"
        "#;
        let changelog: Changelog = toml::from_str(changelog_toml).expect("valid changelog");
        let rendered = render(changelog.smithy_rs, "v0.3.0", "January 4th, 2022");
        let ChangelogEntries {
            aws_sdk_rust,
            smithy_rs,
        } = changelog.into_entries();

        let expected = r#"
        let smithy_rs_rendered = render(&smithy_rs, "v0.3.0 (January 4th, 2022)");
        let smithy_rs_expected = r#"
v0.3.0 (January 4th, 2022)
==========================
**Breaking Changes:**
@@ -422,6 +565,30 @@ Thank you for your contributions! ❤
- @external-contrib ([smithy-rs#446](https://github.com/awslabs/smithy-rs/issues/446))
"#
        .trim_start();
        assert_eq!(expected, rendered);
        pretty_assertions::assert_str_eq!(smithy_rs_expected, smithy_rs_rendered);

        let aws_sdk_rust_rendered = render(&aws_sdk_rust, "v0.1.0 (January 4th, 2022)");
        let aws_sdk_expected = r#"
v0.1.0 (January 4th, 2022)
==========================
**Breaking Changes:**
- ⚠ ([smithy-rs#445](https://github.com/awslabs/smithy-rs/issues/445)) I made a major change to update the AWS SDK

**New this release:**
- 🎉 ([smithy-rs#446](https://github.com/awslabs/smithy-rs/issues/446), @external-contrib) I made a change to update the code generator

**Service Features:**
- `aws-sdk-ec2` (0.12.0): Some API change
- `aws-sdk-s3` (0.14.0): Some new API to do X

**Service Documentation:**
- `aws-sdk-ec2` (0.12.0): Updated some docs

**Contributors**
Thank you for your contributions! ❤
- @external-contrib ([smithy-rs#446](https://github.com/awslabs/smithy-rs/issues/446))
"#
        .trim_start();
        pretty_assertions::assert_str_eq!(aws_sdk_expected, aws_sdk_rust_rendered);
    }
}
+43 −26
Original line number Diff line number Diff line
@@ -65,12 +65,9 @@ enum Args {
        dry_run: Option<bool>,
    },
    UpdateChangelog {
        /// Whether or not independent crate versions are being used (defaults to false)
        #[clap(long)]
        smithy_version: Option<String>,
        #[clap(long)]
        sdk_version: Option<String>,
        #[clap(long)]
        date: Option<String>,
        independent_versioning: bool,
    },
}

@@ -168,28 +165,56 @@ fn main() -> Result<()> {
            }
        }
        Args::UpdateChangelog {
            smithy_version,
            sdk_version,
            date,
            independent_versioning,
        } => {
            let changelog_next_path = repo_root().join("CHANGELOG.next.toml");
            let changelog_path = repo_root().join("CHANGELOG.md");
            let aws_changelog_path = repo_root().join("aws/SDK_CHANGELOG.md");
            if independent_versioning {
                let header = date_header()?;
                changelog::update_changelogs(
                    changelog_next_path,
                    changelog_path,
                    aws_changelog_path,
                    &header,
                    &header,
                )?
            } else {
                let auto = auto_changelog_meta()?;
                changelog::update_changelogs(
                repo_root().join("CHANGELOG.next.toml"),
                repo_root().join("CHANGELOG.md"),
                repo_root().join("aws/SDK_CHANGELOG.md"),
                &smithy_version.unwrap_or(auto.smithy_version),
                &sdk_version.unwrap_or(auto.sdk_version),
                &date.unwrap_or(auto.date),
                    changelog_next_path,
                    changelog_path,
                    aws_changelog_path,
                    &release_header_sync_versioned(&auto.smithy_version)?,
                    &release_header_sync_versioned(&auto.sdk_version)?,
                )?
            }
        }
    }
    Ok(())
}

struct ChangelogMeta {
    smithy_version: String,
    sdk_version: String,
    date: String,
}

fn date_header() -> Result<String> {
    let now = OffsetDateTime::now_local()?;
    Ok(format!(
        "{month} {day}, {year}",
        month = now.date().month(),
        day = Ordinal(now.date().day()),
        year = now.date().year()
    ))
}

fn release_header_sync_versioned(version: &str) -> Result<String> {
    Ok(format!(
        "v{version} ({date})",
        version = version,
        date = date_header()?
    ))
}

/// Discover the new version for the changelog from gradle.properties and the date.
@@ -206,17 +231,9 @@ fn auto_changelog_meta() -> Result<ChangelogMeta> {
    };
    let smithy_version = load_gradle_prop("smithy.rs.runtime.crate.version")?;
    let sdk_version = load_gradle_prop("aws.sdk.version")?;
    let now = OffsetDateTime::now_local()?;
    let date = format!(
        "{month} {day}, {year}",
        month = now.date().month(),
        day = Ordinal(now.date().day()),
        year = now.date().year()
    );
    Ok(ChangelogMeta {
        smithy_version,
        sdk_version,
        date,
    })
}