From 9bddc90db940cef448b976884c241951c8fdad5e Mon Sep 17 00:00:00 2001 From: liam <31192478+terror@users.noreply.github.com> Date: Wed, 15 May 2024 13:26:18 -0400 Subject: [PATCH] Add changelog page (#574) --- Cargo.lock | 590 ++++++++- Cargo.toml | 2 +- client/src/App.tsx | 2 + client/src/assets/changelog.json | 2000 +++++++++++++++++++++++++++++ client/src/model/ChangelogItem.ts | 5 + client/src/pages/About.tsx | 49 +- client/src/pages/Changelog.tsx | 78 ++ tools/changelog-gen/Cargo.toml | 17 + tools/changelog-gen/README.md | 4 + tools/changelog-gen/justfile | 9 + tools/changelog-gen/prompt.txt | 21 + tools/changelog-gen/src/main.rs | 241 ++++ 12 files changed, 2975 insertions(+), 43 deletions(-) create mode 100644 client/src/assets/changelog.json create mode 100644 client/src/model/ChangelogItem.ts create mode 100644 client/src/pages/Changelog.tsx create mode 100644 tools/changelog-gen/Cargo.toml create mode 100644 tools/changelog-gen/README.md create mode 100644 tools/changelog-gen/justfile create mode 100644 tools/changelog-gen/prompt.txt create mode 100644 tools/changelog-gen/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 2fca517a..d77c675b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,9 +104,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "arrayref" @@ -120,6 +126,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "async-convert" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d416feee97712e43152cd42874de162b8f9b77295b1c85e5d92725cc8310bae" +dependencies = [ + "async-trait", +] + [[package]] name = "async-lock" version = "2.8.0" @@ -139,6 +154,31 @@ dependencies = [ "mongodb", ] +[[package]] +name = "async-openai" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007f03f7e27271451af57ced242d6adfa04204d1275a91ec0952bf441fd8d102" +dependencies = [ + "async-convert", + "backoff", + "base64 0.22.1", + "bytes", + "derive_builder", + "futures", + "rand 0.8.5", + "reqwest 0.12.4", + "reqwest-eventsource", + "secrecy", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + [[package]] name = "async-session" version = "3.0.0" @@ -275,6 +315,20 @@ dependencies = [ "tower-service", ] +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "futures-core", + "getrandom 0.2.14", + "instant", + "pin-project-lite", + "rand 0.8.5", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -302,6 +356,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bincode" version = "1.3.3" @@ -426,6 +486,23 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "changelog-gen" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-openai", + "chrono", + "clap", + "dotenv", + "env_logger 0.11.3", + "log", + "octocrab", + "serde", + "serde_json", + "tokio", +] + [[package]] name = "chrono" version = "0.4.38" @@ -598,9 +675,9 @@ dependencies = [ [[package]] name = "crypto-mac" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" dependencies = [ "generic-array", "subtle", @@ -639,8 +716,18 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core 0.20.8", + "darling_macro 0.20.8", ] [[package]] @@ -657,17 +744,42 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 2.0.60", +] + [[package]] name = "darling_macro" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core", + "darling_core 0.13.4", "quote", "syn 1.0.109", ] +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core 0.20.8", + "quote", + "syn 2.0.60", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -731,6 +843,37 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_builder" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" +dependencies = [ + "darling 0.20.8", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" +dependencies = [ + "derive_builder_core", + "syn 2.0.60", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -845,6 +988,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" version = "0.10.2" @@ -858,6 +1011,19 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -880,6 +1046,17 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + [[package]] name = "extractor" version = "0.0.0" @@ -1221,7 +1398,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.11.1", + "crypto-mac 0.11.0", "digest 0.9.0", ] @@ -1381,6 +1558,7 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", ] [[package]] @@ -1393,7 +1571,7 @@ dependencies = [ "hyper 0.14.28", "log", "rustls 0.20.9", - "rustls-native-certs", + "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.23.4", ] @@ -1412,6 +1590,38 @@ dependencies = [ "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "log", + "rustls 0.22.4", + "rustls-native-certs 0.7.0", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +dependencies = [ + "hyper 1.3.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.3" @@ -1419,6 +1629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", + "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.0", @@ -1426,6 +1637,9 @@ dependencies = [ "pin-project-lite", "socket2 0.5.7", "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -1507,6 +1721,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "ipconfig" version = "0.3.2" @@ -1516,7 +1739,7 @@ dependencies = [ "socket2 0.5.7", "widestring", "windows-sys 0.48.0", - "winreg", + "winreg 0.50.0", ] [[package]] @@ -1525,6 +1748,16 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "iri-string" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f5f6c2df22c009ac44f6f1499308e7a3ac7ba42cd2378475cc691510e1eef1b" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.12" @@ -1560,6 +1793,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem", + "ring 0.17.8", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1700,6 +1948,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -1757,7 +2011,7 @@ dependencies = [ "rand 0.8.5", "rustc_version_runtime", "rustls 0.21.12", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_bytes", "serde_with", @@ -1796,6 +2050,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonempty" version = "0.7.0" @@ -1808,12 +2072,31 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -1844,7 +2127,7 @@ dependencies = [ "getrandom 0.2.14", "http 0.2.12", "rand 0.8.5", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "serde_path_to_error", @@ -1862,6 +2145,45 @@ dependencies = [ "memchr", ] +[[package]] +name = "octocrab" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a8a3df00728324ad654ecd1ed449a60157c55b7ff8c109af3a35989687c367" +dependencies = [ + "arc-swap", + "async-trait", + "base64 0.22.1", + "bytes", + "cfg-if 1.0.0", + "chrono", + "either", + "futures", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls 0.26.0", + "hyper-timeout", + "hyper-util", + "jsonwebtoken", + "once_cell", + "percent-encoding", + "pin-project", + "secrecy", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "snafu", + "tokio", + "tower", + "tower-http", + "tracing", + "url", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -1912,6 +2234,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2356,7 +2688,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.21.12", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", @@ -2370,7 +2702,67 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots", - "winreg", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls 0.26.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.22.4", + "rustls-native-certs 0.7.0", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "tokio", + "tokio-rustls 0.25.0", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "winreg 0.52.0", +] + +[[package]] +name = "reqwest-eventsource" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" +dependencies = [ + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom", + "pin-project-lite", + "reqwest 0.12.4", + "thiserror", ] [[package]] @@ -2562,10 +2954,24 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki 0.102.3", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -2573,7 +2979,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.2", + "rustls-pki-types", "schannel", "security-framework", ] @@ -2587,6 +3006,22 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -2597,6 +3032,17 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustls-webpki" +version = "0.102.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.15" @@ -2660,6 +3106,16 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + [[package]] name = "security-framework" version = "2.10.0" @@ -2724,9 +3180,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.199" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" dependencies = [ "serde_derive", ] @@ -2742,9 +3198,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.199" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" dependencies = [ "proc-macro2", "quote", @@ -2753,9 +3209,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "indexmap", "itoa", @@ -2801,7 +3257,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ - "darling", + "darling 0.13.4", "proc-macro2", "quote", "syn 1.0.109", @@ -2823,7 +3279,7 @@ dependencies = [ "clap", "db", "dotenv", - "env_logger", + "env_logger 0.10.2", "extractor", "futures", "http 1.1.0", @@ -2833,7 +3289,7 @@ dependencies = [ "oauth2", "pretty_assertions", "rayon", - "reqwest", + "reqwest 0.11.27", "rusoto_core", "rusoto_s3", "serde", @@ -2920,6 +3376,18 @@ dependencies = [ "libc", ] +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -2941,6 +3409,27 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "snafu" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75976f4748ab44f6e5332102be424e7c2dc18daeaf7e725f2040c3ebb133512e" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b19911debfb8c2fb1107bc6cb2d61868aaf53a988449213959bb1b5b1ed95f" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "socket2" version = "0.4.10" @@ -3048,9 +3537,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" @@ -3278,6 +3767,28 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.10" @@ -3324,12 +3835,14 @@ dependencies = [ "http-body-util", "http-range-header", "httpdate", + "iri-string", "mime", "mime_guess", "percent-encoding", "pin-project-lite", "tokio", "tokio-util", + "tower", "tower-layer", "tower-service", "tracing", @@ -3652,6 +4165,19 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.69" @@ -3873,6 +4399,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if 1.0.0", + "windows-sys 0.48.0", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 2c2633e7..09fae16c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" publish = false [workspace] -members = ["crates/*"] +members = ["crates/*", "tools/changelog-gen"] [dependencies] anyhow = "1.0.71" diff --git a/client/src/App.tsx b/client/src/App.tsx index 472eabcf..d82cdc76 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -3,6 +3,7 @@ import { Route, Routes } from 'react-router-dom'; import { PrivateRoute } from './components/PrivateRoute'; import { About } from './pages/About'; +import { Changelog } from './pages/Changelog'; import { CoursePage } from './pages/CoursePage'; import { Explore } from './pages/Explore'; import { Home } from './pages/Home'; @@ -18,6 +19,7 @@ const App = () => { } /> } /> + } /> } /> } /> { return ( -
  • +
  • -
    +
    {name}
    {links?.map((link: PersonLink, i) => ( @@ -182,27 +182,36 @@ export const About = () => { /> - - Welcome to mcgill.courses! + + Welcome to{' '} + <span + style={{ + color: 'rgb(197, 31, 31)', + }} + > + mcgill.courses + </span> + ! + mcgill.courses {' '} is an open-sourced, student-made review website for courses offered - and instructors teaching at McGill University. Our platform aims to - provide transparent and accurate information to help with informed - decision-making. We encourage contributions from the McGill community - to ensure the resource remains valuable. + and instructors teaching at{' '} + + McGill University + + . Our platform aims to provide transparent and accurate information to + help with informed decision-making. We encourage contributions from + the McGill community to ensure the resource remains valuable. -
    - Disclaimer: mcgill.courses is not affiliated with McGill University. - - History & The Team + History mcgill.courses @@ -220,9 +229,19 @@ export const About = () => { it has grown into a full-fledged platform with a team of dedicated developers and designers. - {people.map((person) => ( - - ))} +
      + {people.map((person) => ( + + ))} +
    + + For those curious about what the development team has been shipping, + check out our{' '} + + changelog + {' '} + page! + FAQ Contact Us diff --git a/client/src/pages/Changelog.tsx b/client/src/pages/Changelog.tsx new file mode 100644 index 00000000..f4a1e83b --- /dev/null +++ b/client/src/pages/Changelog.tsx @@ -0,0 +1,78 @@ +import { Helmet } from 'react-helmet-async'; + +import changelogItems from '../assets/changelog.json'; +import { Layout } from '../components/Layout'; +import { ChangelogItem } from '../model/ChangelogItem'; + +const typedChangelogItems: Record = changelogItems; + +const parseMonthString = (monthString: string): Date => { + const [month, year] = monthString.split(' '); + return new Date(`${month} 1, ${year}`); +}; + +const sortChangelogItems = ( + items: Record +): [string, ChangelogItem[]][] => { + return Object.entries(items).sort(([a], [b]) => { + const dateA = parseMonthString(a); + const dateB = parseMonthString(b); + return dateB.getTime() - dateA.getTime(); + }); +}; + +export const Changelog = () => { + const sortedChangelogItems = sortChangelogItems(typedChangelogItems); + + return ( + + + Changelog - mcgill.courses + + + + + + + +
    +
    +

    + Changelog +

    +

    + Check out what the development team has been shipping each month. +

    +
    +
    + {sortedChangelogItems.map(([month, items]) => ( +
    +

    + {month} +

    + {items.map((item, index) => ( +
    +

    + - {item.summary.replace('/^- /', '')} ( + + #{item.number} + + ) +

    +
    + ))} +
    + ))} +
    +
    +
    + ); +}; diff --git a/tools/changelog-gen/Cargo.toml b/tools/changelog-gen/Cargo.toml new file mode 100644 index 00000000..aa6da52f --- /dev/null +++ b/tools/changelog-gen/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "changelog-gen" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.83" +async-openai = "0.21.0" +chrono = "0.4.38" +clap = { version = "4.5.4", features = ["derive"] } +dotenv = "0.15.0" +env_logger = "0.11.3" +log = "0.4.21" +octocrab = "0.38.0" +serde = { version = "1.0.201", features = ["derive"] } +serde_json = "1.0.117" +tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } diff --git a/tools/changelog-gen/README.md b/tools/changelog-gen/README.md new file mode 100644 index 00000000..ec66212a --- /dev/null +++ b/tools/changelog-gen/README.md @@ -0,0 +1,4 @@ +### changelog-gen + +**changelog-gen** is a tool that outputs monthly-scoped AI generated summaries for +pull requests present in a GitHub repository. diff --git a/tools/changelog-gen/justfile b/tools/changelog-gen/justfile new file mode 100644 index 00000000..578575b6 --- /dev/null +++ b/tools/changelog-gen/justfile @@ -0,0 +1,9 @@ +set dotenv-load + +export RUST_LOG := 'info' + +fmt: + cargo fmt + +run *args: + cargo run -- {{args}} diff --git a/tools/changelog-gen/prompt.txt b/tools/changelog-gen/prompt.txt new file mode 100644 index 00000000..473823f0 --- /dev/null +++ b/tools/changelog-gen/prompt.txt @@ -0,0 +1,21 @@ +Create a short, non-technical, and detailed blurb describing the purpose and +changes in the following pull request. + +Examples: + +- Added support for using custom templates in `generate` command. +- Fixed an issue with overflowing text in long paragraphs. +- Updated dependencies to improve performance and security. + +Be sure to never generate a summary that starts with 'This pull request does ...'. +We don't want to hear the phrase 'pull request' in the summary. + +Generate sentences in past tense format, as the PR is already merged. Use words such as 'added', +'fixed', 'updated', etc. + +Never include a '-' at the beginning of the sentence. + +Never mention the issue number if its present within the pull request description. + +Be straight to the point and try to be as concise and as easy to understand for non-technical +users as possible. diff --git a/tools/changelog-gen/src/main.rs b/tools/changelog-gen/src/main.rs new file mode 100644 index 00000000..6328aa51 --- /dev/null +++ b/tools/changelog-gen/src/main.rs @@ -0,0 +1,241 @@ +use { + anyhow::{anyhow, Error}, + async_openai::types::{ + ChatCompletionRequestSystemMessageArgs, CreateChatCompletionRequestArgs, + }, + chrono::{DateTime, Utc}, + clap::Parser, + dotenv::dotenv, + env_logger::Env, + log::info, + octocrab::{models, params}, + serde::{Deserialize, Serialize}, + std::{ + collections::HashMap, + fmt::Display, + fs::{self, File}, + path::PathBuf, + process, + }, +}; + +const BASE_URL: &str = "https://github.com"; + +#[derive(Debug)] +struct PullRequest<'a> { + title: Option<&'a str>, + description: Option<&'a str>, + number: u64, + merged_at: Option>, + user: &'a str, + repository: &'a str, +} + +impl Display for PullRequest<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.title.unwrap_or_default()) + } +} + +impl PullRequest<'_> { + async fn summary(&self) -> Result> { + info!("Generating summary for pull request #{}", self.number); + + if self.title.is_none() && self.description.is_none() { + return Ok(None); + } + + let client = async_openai::Client::new(); + + let mut prompt = String::new(); + + prompt.push_str(&fs::read_to_string("prompt.txt")?); + + if let Some(title) = self.title { + prompt.push_str(&format!("\nTitle of the pull request: {}\n", title)); + } + + if let Some(description) = self.description { + prompt.push_str(&format!( + "Description of the pull request: {}\n", + description + )); + } + + let response = client + .chat() + .create( + CreateChatCompletionRequestArgs::default() + .max_tokens(512u16) + .model("gpt-3.5-turbo") + .messages([ChatCompletionRequestSystemMessageArgs::default() + .content(prompt) + .build()? + .into()]) + .build()?, + ) + .await?; + + let summary = response + .choices + .first() + .ok_or(anyhow!("No choices in response"))? + .message + .content + .as_ref() + .ok_or(anyhow!("No content in message"))? + .trim() + .to_string(); + + info!("Generated: {summary}"); + + Ok(Some(summary)) + } + + fn url(self) -> Result { + Ok(format!( + "{}/{}/{}/pull/{}", + BASE_URL, self.user, self.repository, self.number + )) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)] +struct Item { + number: u64, + summary: Option, + url: String, + merged_at: DateTime, +} + +impl Item { + async fn try_from( + pull_request: PullRequest<'_>, + merged_at: DateTime, + ) -> Result { + Ok(Item { + number: pull_request.number, + summary: pull_request.summary().await?, + url: pull_request.url()?, + merged_at, + }) + } +} + +type Entry = HashMap>; + +#[derive(Parser)] +struct Arguments { + #[clap(long, default_value = "../../client/src/assets/changelog.json")] + output: PathBuf, + #[clap(long, value_delimiter = ' ', num_args = 0..)] + regenerate: Vec, + #[clap(long, default_value = "false")] + regenerate_all: bool, + #[clap(long, default_value = "mcgill.courses")] + repo: String, + #[clap(long, default_value = "terror")] + user: String, +} + +impl Arguments { + async fn run(&self) -> Result { + let client = octocrab::instance(); + + let page = client + .pulls(&self.user, &self.repo) + .list() + .state(params::State::All) + .per_page(50) + .send() + .await?; + + let model = client.all_pages::(page).await?; + + info!("Reading existing entries from {}", self.output.display()); + + if !self.output.exists() { + File::create(&self.output)?; + } + + let content = fs::read_to_string(&self.output)?; + + let existing_items = serde_json::from_str::(&content) + .map(|entry| { + entry + .into_iter() + .flat_map(|(_, value)| { + value.into_iter().map(|item| (item.number, item)) + }) + .collect::>() + }) + .unwrap_or_else(|_| HashMap::new()); + + let pull_requests = model + .iter() + .map(|pull_request| PullRequest { + title: pull_request.title.as_deref(), + description: pull_request.body.as_deref(), + number: pull_request.number, + merged_at: pull_request.merged_at, + user: &self.user, + repository: &self.repo, + }) + .collect::>(); + + let mut grouped: Entry = HashMap::new(); + + for pull_request in pull_requests { + if let Some(merged_at) = pull_request.merged_at { + let month = merged_at.format("%B %Y").to_string(); + + if let Some(item) = existing_items.get(&pull_request.number) { + if self.regenerate_all + || self.regenerate.contains(&pull_request.number) + { + grouped + .entry(month) + .or_default() + .push(Item::try_from(pull_request, merged_at).await?); + } else { + grouped.entry(month).or_default().push(item.clone()); + } + } else { + grouped + .entry(month) + .or_default() + .push(Item::try_from(pull_request, merged_at).await?); + } + } + } + + if !grouped.is_empty() { + info!("Writing to {}", self.output.display()); + + for items in grouped.values_mut() { + items.sort_by(|a, b| b.merged_at.cmp(&a.merged_at)); + } + + fs::write(&self.output, serde_json::to_string_pretty(&grouped)?)?; + } + + info!("Generated changelog successfully"); + + Ok(()) + } +} + +type Result = std::result::Result; + +#[tokio::main] +async fn main() { + env_logger::Builder::from_env(Env::default().default_filter_or("info")) + .init(); + + dotenv().ok(); + + if let Err(error) = Arguments::parse().run().await { + eprintln!("error: {error}"); + process::exit(1); + } +}