From cb2f04fb75da4c226a8a513b5810058ed1e3679a Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 21 Apr 2025 18:27:46 -0600 Subject: [PATCH] Split project into crates, migrate to maud --- ...642188eec62eec0846a4a25a8110459997f1.json} | 12 +- ...fce34ebcff88a5fe321b31a56af31e8eda83.json} | 20 +- ...a7d10d0edc60d25c1809bb94935e468873fd.json} | 12 +- Cargo.lock | 1862 +++++++++-------- Cargo.toml | 65 +- {templates => assets}/og.svg | 0 crates/auth/Cargo.toml | 18 + .../auth.rs => crates/auth/src/lib.rs | 46 +- crates/core/Cargo.toml | 15 + {src => crates/core/src}/config.rs | 0 crates/core/src/lib.rs | 83 + {src => crates/core/src}/models.rs | 18 +- {src => crates/core/src}/util.rs | 0 crates/db/Cargo.toml | 20 + src/db/mod.rs => crates/db/src/lib.rs | 54 +- crates/github/Cargo.toml | 26 + crates/github/src/changes.rs | 283 +++ src/github/mod.rs => crates/github/src/lib.rs | 62 +- .../github.rs => crates/github/src/webhook.rs | 327 +-- crates/images/Cargo.toml | 20 + {src/handlers => crates/images/src}/badge.rs | 2 +- .../assets.rs => crates/images/src/lib.rs | 29 +- {src => crates/images/src}/svg.rs | 8 +- .../handlers => crates/images/src}/treemap.rs | 32 - crates/scripts/Cargo.toml | 17 + crates/scripts/src/css.rs | 24 + {src/handlers => crates/scripts/src}/js.rs | 88 +- crates/scripts/src/lib.rs | 80 + crates/web/Cargo.toml | 42 + {src => crates/web/src}/cron.rs | 8 +- {src => crates/web/src}/frogress/mod.rs | 5 +- crates/web/src/handlers/common.rs | 194 ++ crates/web/src/handlers/mod.rs | 66 + crates/web/src/handlers/project.rs | 278 +++ {src => crates/web/src}/handlers/report.rs | 387 ++-- crates/web/src/handlers/treemap.rs | 34 + {src => crates/web/src}/main.rs | 48 +- crates/web/src/proto.rs | 20 + src/handlers/css.rs | 46 - src/handlers/mod.rs | 160 -- src/handlers/og.rs | 19 - src/handlers/project.rs | 272 --- src/templates.rs | 69 - templates/fragments/footer.html | 14 - templates/fragments/header.html | 15 - templates/fragments/nav-links.html | 15 - templates/oldgraphs.html | 55 - templates/projects.html | 99 - templates/report.html | 126 -- templates/treemap.svg | 7 - 50 files changed, 2707 insertions(+), 2495 deletions(-) rename .sqlx/{query-269859ed391626ddbc2af37381a13c36976f48e6a89aaaa03da8ea66baccddff.json => query-23a6be61c86478402eb00bb9fd64642188eec62eec0846a4a25a8110459997f1.json} (75%) rename .sqlx/{query-b492f0562501ae39110cf2df7a545cd0c676b438b91d63b915988c17d5bb29b6.json => query-912ad9086ec8b4caa4acf3e6642cfce34ebcff88a5fe321b31a56af31e8eda83.json} (88%) rename .sqlx/{query-60a01415ef25ea6a2fc7f3287e0e6972404e351401f8aeaedbc74b4c934c777c.json => query-ef595aa7d32d7d21d9721a794295a7d10d0edc60d25c1809bb94935e468873fd.json} (77%) rename {templates => assets}/og.svg (100%) create mode 100644 crates/auth/Cargo.toml rename src/handlers/auth.rs => crates/auth/src/lib.rs (89%) create mode 100644 crates/core/Cargo.toml rename {src => crates/core/src}/config.rs (100%) create mode 100644 crates/core/src/lib.rs rename {src => crates/core/src}/models.rs (87%) rename {src => crates/core/src}/util.rs (100%) create mode 100644 crates/db/Cargo.toml rename src/db/mod.rs => crates/db/src/lib.rs (97%) create mode 100644 crates/github/Cargo.toml create mode 100644 crates/github/src/changes.rs rename src/github/mod.rs => crates/github/src/lib.rs (92%) rename src/handlers/github.rs => crates/github/src/webhook.rs (55%) create mode 100644 crates/images/Cargo.toml rename {src/handlers => crates/images/src}/badge.rs (98%) rename src/handlers/assets.rs => crates/images/src/lib.rs (55%) rename {src => crates/images/src}/svg.rs (88%) rename {src/handlers => crates/images/src}/treemap.rs (58%) create mode 100644 crates/scripts/Cargo.toml create mode 100644 crates/scripts/src/css.rs rename {src/handlers => crates/scripts/src}/js.rs (60%) create mode 100644 crates/scripts/src/lib.rs create mode 100644 crates/web/Cargo.toml rename {src => crates/web/src}/cron.rs (91%) rename {src => crates/web/src}/frogress/mod.rs (98%) create mode 100644 crates/web/src/handlers/common.rs create mode 100644 crates/web/src/handlers/mod.rs create mode 100644 crates/web/src/handlers/project.rs rename {src => crates/web/src}/handlers/report.rs (65%) create mode 100644 crates/web/src/handlers/treemap.rs rename {src => crates/web/src}/main.rs (79%) create mode 100644 crates/web/src/proto.rs delete mode 100644 src/handlers/css.rs delete mode 100644 src/handlers/mod.rs delete mode 100644 src/handlers/og.rs delete mode 100644 src/handlers/project.rs delete mode 100644 src/templates.rs delete mode 100644 templates/fragments/footer.html delete mode 100644 templates/fragments/header.html delete mode 100644 templates/fragments/nav-links.html delete mode 100644 templates/oldgraphs.html delete mode 100644 templates/projects.html delete mode 100644 templates/report.html delete mode 100644 templates/treemap.svg diff --git a/.sqlx/query-269859ed391626ddbc2af37381a13c36976f48e6a89aaaa03da8ea66baccddff.json b/.sqlx/query-23a6be61c86478402eb00bb9fd64642188eec62eec0846a4a25a8110459997f1.json similarity index 75% rename from .sqlx/query-269859ed391626ddbc2af37381a13c36976f48e6a89aaaa03da8ea66baccddff.json rename to .sqlx/query-23a6be61c86478402eb00bb9fd64642188eec62eec0846a4a25a8110459997f1.json index e97d556..f436972 100644 --- a/.sqlx/query-269859ed391626ddbc2af37381a13c36976f48e6a89aaaa03da8ea66baccddff.json +++ b/.sqlx/query-23a6be61c86478402eb00bb9fd64642188eec62eec0846a4a25a8110459997f1.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT owner, repo, name, short_name, default_category, default_version, platform, workflow_id\n FROM projects\n WHERE id = ?\n ", + "query": "\n SELECT owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments\n FROM projects\n WHERE id = ?\n ", "describe": { "columns": [ { @@ -42,6 +42,11 @@ "name": "workflow_id", "ordinal": 7, "type_info": "Text" + }, + { + "name": "enable_pr_comments", + "ordinal": 8, + "type_info": "Bool" } ], "parameters": { @@ -55,8 +60,9 @@ true, true, true, - true + true, + false ] }, - "hash": "269859ed391626ddbc2af37381a13c36976f48e6a89aaaa03da8ea66baccddff" + "hash": "23a6be61c86478402eb00bb9fd64642188eec62eec0846a4a25a8110459997f1" } diff --git a/.sqlx/query-b492f0562501ae39110cf2df7a545cd0c676b438b91d63b915988c17d5bb29b6.json b/.sqlx/query-912ad9086ec8b4caa4acf3e6642cfce34ebcff88a5fe321b31a56af31e8eda83.json similarity index 88% rename from .sqlx/query-b492f0562501ae39110cf2df7a545cd0c676b438b91d63b915988c17d5bb29b6.json rename to .sqlx/query-912ad9086ec8b4caa4acf3e6642cfce34ebcff88a5fe321b31a56af31e8eda83.json index 117eb8a..9c12b17 100644 --- a/.sqlx/query-b492f0562501ae39110cf2df7a545cd0c676b438b91d63b915988c17d5bb29b6.json +++ b/.sqlx/query-912ad9086ec8b4caa4acf3e6642cfce34ebcff88a5fe321b31a56af31e8eda83.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n projects.id AS \"project_id!\",\n owner AS \"owner!\",\n repo AS \"repo!\",\n name,\n short_name,\n default_category,\n default_version,\n platform,\n workflow_id,\n git_commit,\n git_commit_message,\n MAX(timestamp) AS \"timestamp: time::OffsetDateTime\",\n JSON_GROUP_ARRAY(version ORDER BY version)\n FILTER (WHERE version IS NOT NULL) AS versions\n FROM projects LEFT JOIN reports ON (\n reports.project_id = projects.id\n AND reports.timestamp = (\n SELECT MAX(timestamp)\n FROM reports\n WHERE project_id = projects.id\n )\n )\n GROUP BY projects.id\n ORDER BY MAX(timestamp) DESC\n ", + "query": "\n SELECT\n projects.id AS \"project_id!\",\n owner AS \"owner!\",\n repo AS \"repo!\",\n name,\n short_name,\n default_category,\n default_version,\n platform,\n workflow_id,\n enable_pr_comments AS \"enable_pr_comments!\",\n git_commit,\n git_commit_message,\n MAX(timestamp) AS \"timestamp: time::OffsetDateTime\",\n JSON_GROUP_ARRAY(version ORDER BY version)\n FILTER (WHERE version IS NOT NULL) AS versions\n FROM projects LEFT JOIN reports ON (\n reports.project_id = projects.id\n AND reports.timestamp = (\n SELECT MAX(timestamp)\n FROM reports\n WHERE project_id = projects.id\n )\n )\n GROUP BY projects.id\n ORDER BY MAX(timestamp) DESC\n ", "describe": { "columns": [ { @@ -49,24 +49,29 @@ "type_info": "Text" }, { - "name": "git_commit", + "name": "enable_pr_comments!", "ordinal": 9, - "type_info": "Text" + "type_info": "Bool" }, { - "name": "git_commit_message", + "name": "git_commit", "ordinal": 10, "type_info": "Text" }, { - "name": "timestamp: time::OffsetDateTime", + "name": "git_commit_message", "ordinal": 11, "type_info": "Text" }, { - "name": "versions", + "name": "timestamp: time::OffsetDateTime", "ordinal": 12, "type_info": "Text" + }, + { + "name": "versions", + "ordinal": 13, + "type_info": "Text" } ], "parameters": { @@ -85,8 +90,9 @@ true, true, true, + true, true ] }, - "hash": "b492f0562501ae39110cf2df7a545cd0c676b438b91d63b915988c17d5bb29b6" + "hash": "912ad9086ec8b4caa4acf3e6642cfce34ebcff88a5fe321b31a56af31e8eda83" } diff --git a/.sqlx/query-60a01415ef25ea6a2fc7f3287e0e6972404e351401f8aeaedbc74b4c934c777c.json b/.sqlx/query-ef595aa7d32d7d21d9721a794295a7d10d0edc60d25c1809bb94935e468873fd.json similarity index 77% rename from .sqlx/query-60a01415ef25ea6a2fc7f3287e0e6972404e351401f8aeaedbc74b4c934c777c.json rename to .sqlx/query-ef595aa7d32d7d21d9721a794295a7d10d0edc60d25c1809bb94935e468873fd.json index 363f1f2..753237b 100644 --- a/.sqlx/query-60a01415ef25ea6a2fc7f3287e0e6972404e351401f8aeaedbc74b4c934c777c.json +++ b/.sqlx/query-ef595aa7d32d7d21d9721a794295a7d10d0edc60d25c1809bb94935e468873fd.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT id AS \"id!\", owner, repo, name, short_name, default_category, default_version, platform, workflow_id\n FROM projects\n WHERE owner = ? COLLATE NOCASE AND repo = ? COLLATE NOCASE\n ", + "query": "\n SELECT id AS \"id!\", owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments\n FROM projects\n WHERE owner = ? COLLATE NOCASE AND repo = ? COLLATE NOCASE\n ", "describe": { "columns": [ { @@ -47,6 +47,11 @@ "name": "workflow_id", "ordinal": 8, "type_info": "Text" + }, + { + "name": "enable_pr_comments", + "ordinal": 9, + "type_info": "Bool" } ], "parameters": { @@ -61,8 +66,9 @@ true, true, true, - true + true, + false ] }, - "hash": "60a01415ef25ea6a2fc7f3287e0e6972404e351401f8aeaedbc74b4c934c777c" + "hash": "ef595aa7d32d7d21d9721a794295a7d10d0edc60d25c1809bb94935e468873fd" } diff --git a/Cargo.lock b/Cargo.lock index c7857d2..9056732 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,12 +11,6 @@ dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -44,7 +38,7 @@ dependencies = [ "getrandom 0.2.15", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -109,9 +103,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -124,36 +118,37 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] @@ -194,7 +189,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -228,9 +223,9 @@ checksum = "7330592adf847ee2e3513587b4db2db410a0d751378654e7e993d9adcbe5c795" [[package]] name = "async-compression" -version = "0.4.14" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998282f8f49ccd6116b0ed8a4de0fbd3151697920e7c7533416d6e25e76434a7" +checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64" dependencies = [ "brotli", "flate2", @@ -253,52 +248,15 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-openai" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d76e2f5af19477d6254415acc95ba97c6cc6f3b1e3cb4676b7f0fab8194298" -dependencies = [ - "async-openai-macros", - "backoff", - "base64 0.22.1", - "bytes", - "derive_builder", - "eventsource-stream", - "futures", - "rand 0.8.5", - "reqwest", - "reqwest-eventsource", - "secrecy", - "serde", - "serde_json", - "thiserror 2.0.12", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", -] - -[[package]] -name = "async-openai-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0289cba6d5143bfe8251d57b4a8cac036adf158525a76533a7082ba65ec76398" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -338,9 +296,9 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" dependencies = [ "arrayvec", ] @@ -408,21 +366,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", -] - -[[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.15", - "instant", - "pin-project-lite", - "rand 0.8.5", - "tokio", + "syn 2.0.100", ] [[package]] @@ -434,7 +378,7 @@ dependencies = [ "addr2line", "cfg-if", "libc", - "miniz_oxide 0.8.8", + "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", @@ -454,7 +398,7 @@ dependencies = [ "regex", "seahash", "serde", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] @@ -490,15 +434,15 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" dependencies = [ - "outref 0.5.1", + "outref 0.5.2", "vsimd", ] [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] name = "bincode" @@ -532,9 +476,9 @@ dependencies = [ [[package]] name = "bitstream-io" -version = "2.5.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" [[package]] name = "bitvec" @@ -550,9 +494,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.4" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" dependencies = [ "arrayref", "arrayvec", @@ -583,9 +527,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.1" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -593,9 +537,9 @@ dependencies = [ [[package]] name = "built" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" [[package]] name = "bumpalo" @@ -636,9 +580,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" [[package]] name = "byteorder" @@ -669,9 +613,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.28" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "jobserver", "libc", @@ -702,9 +646,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", @@ -712,23 +656,23 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "clap" -version = "4.5.20" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" dependencies = [ "anstream", "anstyle", @@ -738,9 +682,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codemap" @@ -756,9 +700,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "compact_str" @@ -857,9 +801,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" dependencies = [ "core-foundation-sys", "libc", @@ -873,9 +817,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core_maths" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" dependencies = [ "libm", ] @@ -888,9 +832,9 @@ checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -921,27 +865,27 @@ dependencies = [ [[package]] name = "croner" -version = "2.0.6" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b42c74d1c9fc4680c245ca7287bd631fe73eb364657268b0e65bafdec83d47" +checksum = "38fd53511eaf0b00a185613875fee58b208dfce016577d0ad4bb548e1c4fb3ee" dependencies = [ "chrono", ] [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -958,9 +902,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] @@ -973,9 +917,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -1016,7 +960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -1025,41 +969,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b7ef7a4be509357f4804d0a22e830daddb48f19fd604e4ad32ddce04a94c36" -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.87", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.87", -] - [[package]] name = "dashmap" version = "5.5.3" @@ -1089,9 +998,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "data-url" @@ -1109,65 +1018,157 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" [[package]] -name = "decompal-rs" +name = "decomp-dev-auth" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "base64 0.22.1", + "decomp-dev-core", + "octocrab", + "rand 0.9.1", + "serde", + "time", + "tower-sessions", + "tracing", + "url", +] + +[[package]] +name = "decomp-dev-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "mime", + "objdiff-core", + "serde", + "time", + "tracing", + "url", +] + +[[package]] +name = "decomp-dev-db" +version = "0.1.0" +dependencies = [ + "anyhow", + "blake3", + "decomp-dev-core", + "futures-util", + "hex", + "moka", + "objdiff-core", + "prost", + "serde_json", + "sqlx", + "time", + "tracing", + "zstd", +] + +[[package]] +name = "decomp-dev-github" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "decomp-dev-core", + "decomp-dev-db", + "futures-util", + "hex", + "hmac", + "http", + "jsonwebtoken", + "objdiff-core", + "octocrab", + "regex", + "serde", + "serde_json", + "sha2", + "time", + "tokio", + "tracing", + "zip", +] + +[[package]] +name = "decomp-dev-images" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "badge-maker", + "decomp-dev-core", + "image", + "mime", + "objdiff-core", + "palette", + "resvg", + "serde", + "streemap", + "tokio", + "tracing", +] + +[[package]] +name = "decomp-dev-scripts" version = "0.1.0" dependencies = [ "anyhow", "ariadne", - "async-openai", "axum", - "badge-maker", - "base64 0.22.1", - "blake3", - "bytes", - "futures-util", + "decomp-dev-core", "grass", - "hex", - "hmac", - "human-size", - "image", - "itertools 0.14.0", - "jsonwebtoken", "lightningcss", "mime", - "minijinja", - "minijinja-autoreload", - "moka", - "objdiff-core", - "octocrab", "oxc", - "palette", + "thiserror 2.0.12", + "tokio", +] + +[[package]] +name = "decomp-dev-web" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "bytes", + "decomp-dev-auth", + "decomp-dev-core", + "decomp-dev-db", + "decomp-dev-github", + "decomp-dev-images", + "decomp-dev-scripts", + "image", + "itertools 0.14.0", + "maud", + "mime", + "objdiff-core", "prost", - "rand 0.9.1", - "regex", "reqwest", - "resvg", "serde", "serde_json", "serde_yaml", - "sha2", - "sqlx", - "streemap", "time", "timeago", "tokio", "tokio-cron-scheduler", "tower", "tower-http", + "tower-livereload", "tower-sessions", "tower-sessions-sqlx-store", "tracing", "tracing-subscriber", "url", - "zip", - "zstd", ] [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", @@ -1192,38 +1193,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", -] - -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -1238,6 +1208,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -1246,9 +1227,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dtoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" [[package]] name = "dtoa-short" @@ -1261,36 +1242,27 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] -[[package]] -name = "encoding_rs" -version = "0.8.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1306,9 +1278,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", @@ -1317,36 +1289,24 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener", "pin-project-lite", ] -[[package]] -name = "eventsource-stream" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" -dependencies = [ - "futures-core", - "nom 7.1.3", - "pin-project-lite", -] - [[package]] name = "exr" -version = "1.72.0" +version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" dependencies = [ "bit_field", - "flume", "half", "lebe", - "miniz_oxide 0.7.4", + "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", @@ -1360,37 +1320,31 @@ checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] -[[package]] -name = "filetime" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.59.0", -] - [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.1" @@ -1398,7 +1352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", - "miniz_oxide 0.8.8", + "miniz_oxide", ] [[package]] @@ -1409,9 +1363,9 @@ checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" [[package]] name = "flume" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", @@ -1424,6 +1378,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "fontconfig-parser" version = "0.5.7" @@ -1456,15 +1416,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "funty" version = "2.0.0" @@ -1538,7 +1489,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -1553,12 +1504,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" - [[package]] name = "futures-util" version = "0.3.31" @@ -1577,6 +1522,19 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +dependencies = [ + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1657,9 +1615,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" dependencies = [ "atomic-waker", "bytes", @@ -1676,9 +1634,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", @@ -1708,14 +1666,19 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "hashlink" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.2", ] [[package]] @@ -1736,15 +1699,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" [[package]] name = "hex" @@ -1772,18 +1729,18 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "http" -version = "1.1.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -1802,12 +1759,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -1815,15 +1772,15 @@ dependencies = [ [[package]] name = "http-range-header" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -1831,12 +1788,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "human-size" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9994b79e8c1a39b3166c63ae7823bb2b00831e2a96a31399c50fe69df408eaeb" - [[package]] name = "hyper" version = "1.6.0" @@ -1860,9 +1811,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.3" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http", @@ -1879,9 +1830,9 @@ dependencies = [ [[package]] name = "hyper-timeout" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ "hyper", "hyper-util", @@ -1912,16 +1863,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core", + "windows-core 0.61.0", ] [[package]] @@ -1934,33 +1886,156 @@ dependencies = [ ] [[package]] -name = "ident_case" -version = "1.0.1" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "image" -version = "0.25.2" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", "color_quant", "exr", "gif", - "image-webp 0.1.3", + "image-webp", "num-traits", "png", "qoi", @@ -1972,16 +2047,6 @@ dependencies = [ "zune-jpeg", ] -[[package]] -name = "image-webp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" -dependencies = [ - "byteorder-lite", - "quick-error", -] - [[package]] name = "image-webp" version = "0.2.1" @@ -2000,50 +2065,21 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] name = "imgref" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.2", "serde", ] -[[package]] -name = "inotify" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" -dependencies = [ - "bitflags 1.3.2", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "interpolate_name" version = "0.2.4" @@ -2052,20 +2088,20 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bd7eced44cfe2cebc674adb2a7124a754a4b5269288d22e9f39f8fada3562d" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ "memchr", "serde", @@ -2073,13 +2109,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2132,10 +2168,11 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.2", "libc", ] @@ -2157,11 +2194,11 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.3.0" +version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "js-sys", "pem", "ring", @@ -2170,26 +2207,6 @@ dependencies = [ "simple_asn1", ] -[[package]] -name = "kqueue" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - [[package]] name = "kurbo" version = "0.11.1" @@ -2232,31 +2249,19 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libfuzzer-sys" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" dependencies = [ "arbitrary", "cc", - "once_cell", ] [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.9.0", - "libc", - "redox_syscall", -] +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libsqlite3-sys" @@ -2310,9 +2315,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" @@ -2327,9 +2338,22 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] [[package]] name = "loop9" @@ -2361,6 +2385,30 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "maud" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8156733e27020ea5c684db5beac5d1d611e1272ab17901a49466294b84fc217e" +dependencies = [ + "axum-core", + "http", + "itoa", + "maud_macros", +] + +[[package]] +name = "maud_macros" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7261b00f3952f617899bc012e3dbd56e4f0110a038175929fa5d18e5a19913ca" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.100", +] + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -2368,6 +2416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", + "rayon", ] [[package]] @@ -2395,12 +2444,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memo-map" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" - [[package]] name = "memoffset" version = "0.9.1" @@ -2420,7 +2463,7 @@ dependencies = [ "miette-derive", "owo-colors", "textwrap", - "thiserror 1.0.64", + "thiserror 1.0.69", "unicode-width 0.1.14", ] @@ -2432,7 +2475,7 @@ checksum = "bf45bf44ab49be92fd1227a3be6fc6f617f1a337c06af54981048574d8783147" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -2451,43 +2494,12 @@ dependencies = [ "unicase", ] -[[package]] -name = "minijinja" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98642a6dfca91122779a307b77cd07a4aa951fbe32232aaf5bad9febc66be754" -dependencies = [ - "memo-map", - "self_cell", - "serde", - "serde_json", -] - -[[package]] -name = "minijinja-autoreload" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfa27f77378f5239b971abf45c16b56f6f207c0186f5b62ee2aee1be19dd6ce" -dependencies = [ - "minijinja", - "notify", -] - [[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.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.8" @@ -2500,23 +2512,10 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - -[[package]] -name = "mio" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" -dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", @@ -2524,25 +2523,23 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.8" +version = "0.12.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f" +checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" dependencies = [ "async-lock", - "async-trait", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "event-listener", "futures-util", - "once_cell", + "loom", "parking_lot", - "quanta", + "portable-atomic", "rustc_version", "smallvec", "tagptr", - "thiserror 1.0.64", - "triomphe", + "thiserror 1.0.69", "uuid", ] @@ -2589,23 +2586,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" -[[package]] -name = "notify" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" -dependencies = [ - "bitflags 1.3.2", - "filetime", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "mio 0.8.11", - "walkdir", - "windows-sys 0.45.0", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2657,7 +2637,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -2701,15 +2681,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "objdiff-core" version = "2.5.0" @@ -2728,9 +2699,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -2778,15 +2749,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "outref" @@ -2796,9 +2767,9 @@ checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" [[package]] name = "outref" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" [[package]] name = "overload" @@ -2808,9 +2779,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owo-colors" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" +checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" [[package]] name = "oxc" @@ -2880,7 +2851,7 @@ checksum = "a2b1072cb32335b47745c9ea9d614a6d5d36ba9f528fce1da428d5f7e20c3ed1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -2894,7 +2865,7 @@ dependencies = [ "nonmax", "oxc_index", "oxc_syntax", - "petgraph", + "petgraph 0.6.5", "rustc-hash", ] @@ -3165,7 +3136,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -3235,9 +3206,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pbjson" @@ -3263,9 +3234,9 @@ dependencies = [ [[package]] name = "pem" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ "base64 0.22.1", "serde", @@ -3292,7 +3263,17 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset", + "fixedbitset 0.4.2", + "indexmap", +] + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset 0.5.7", "indexmap", ] @@ -3318,9 +3299,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand 0.8.5", @@ -3336,7 +3317,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -3356,29 +3337,29 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.6" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.6" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -3409,23 +3390,29 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" -version = "0.17.14" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.8.8", + "miniz_oxide", ] +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + [[package]] name = "powerfmt" version = "0.2.0" @@ -3434,11 +3421,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.24", ] [[package]] @@ -3449,47 +3436,59 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.22" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] name = "proc-macro2" -version = "1.0.87" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] -name = "profiling" -version = "1.0.15" +name = "proc-macro2-diagnostics" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "version_check", +] + +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] name = "prost" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ "bytes", "prost-derive", @@ -3497,43 +3496,42 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ - "bytes", "heck", - "itertools 0.13.0", + "itertools 0.14.0", "log", "multimap", "once_cell", - "petgraph", + "petgraph 0.7.1", "prettyplease", "prost", "prost-types", "regex", - "syn 2.0.87", + "syn 2.0.100", "tempfile", ] [[package]] name = "prost-derive" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] name = "prost-types" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" dependencies = [ "prost", ] @@ -3567,21 +3565,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "quanta" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" -dependencies = [ - "crossbeam-utils", - "libc", - "once_cell", - "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", - "web-sys", - "winapi", -] - [[package]] name = "quick-error" version = "2.0.1" @@ -3644,9 +3627,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -3752,34 +3735,26 @@ dependencies = [ "rand_chacha 0.3.1", "simd_helpers", "system-deps", - "thiserror 1.0.64", + "thiserror 1.0.69", "v_frame", "wasm-bindgen", ] [[package]] name = "ravif" -version = "0.11.10" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" +checksum = "d6a5f31fcf7500f9401fea858ea4ab5525c99f2322cfcee732c0e6c74208c0c6" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", + "rayon", "rgb", ] -[[package]] -name = "raw-cpuid" -version = "11.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" -dependencies = [ - "bitflags 2.9.0", -] - [[package]] name = "rayon" version = "1.10.0" @@ -3802,22 +3777,22 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ "bitflags 2.9.0", ] [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick 1.1.3", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -3832,9 +3807,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick 1.1.3", "memchr", @@ -3871,7 +3846,6 @@ dependencies = [ "async-compression", "base64 0.22.1", "bytes", - "encoding_rs", "futures-core", "futures-util", "h2", @@ -3885,7 +3859,6 @@ dependencies = [ "js-sys", "log", "mime", - "mime_guess", "once_cell", "percent-encoding", "pin-project-lite", @@ -3906,27 +3879,10 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", "windows-registry", ] -[[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 7.1.3", - "pin-project-lite", - "reqwest", - "thiserror 1.0.64", -] - [[package]] name = "resvg" version = "0.45.1" @@ -3934,7 +3890,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" dependencies = [ "gif", - "image-webp 0.2.1", + "image-webp", "log", "pico-args", "rgb", @@ -3955,15 +3911,14 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] @@ -4037,9 +3992,9 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rsa" -version = "0.9.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest", @@ -4063,9 +4018,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -4078,22 +4033,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ "bitflags 2.9.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.14" +version = "0.23.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ "log", "once_cell", @@ -4106,12 +4061,11 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", @@ -4128,18 +4082,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" dependencies = [ "web-time", ] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "ring", "rustls-pki-types", @@ -4148,9 +4102,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "rustybuzz" @@ -4172,9 +4126,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "ryu-js" @@ -4182,24 +4136,21 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -4218,15 +4169,14 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" dependencies = [ - "serde", "zeroize", ] [[package]] name = "security-framework" -version = "2.11.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags 2.9.0", "core-foundation", @@ -4237,25 +4187,19 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", ] -[[package]] -name = "self_cell" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" - [[package]] name = "semver" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "seq-macro" @@ -4280,7 +4224,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -4297,9 +4241,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ "itoa", "serde", @@ -4427,21 +4371,21 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple_asn1" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 1.0.64", + "thiserror 2.0.12", "time", ] [[package]] name = "simplecss" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" dependencies = [ "log", ] @@ -4472,9 +4416,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" dependencies = [ "serde", ] @@ -4503,7 +4447,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -4535,21 +4479,11 @@ dependencies = [ "der", ] -[[package]] -name = "sqlformat" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" -dependencies = [ - "nom 7.1.3", - "unicode_categories", -] - [[package]] name = "sqlx" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" +checksum = "f3c3a85280daca669cfd3bcb68a337882a8bc57ec882f72c5d13a430613a738e" dependencies = [ "sqlx-core", "sqlx-macros", @@ -4560,38 +4494,32 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" +checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3" dependencies = [ - "atoi", - "byteorder", + "base64 0.22.1", "bytes", - "chrono", "crc", "crossbeam-queue", "either", "event-listener", - "futures-channel", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "hashlink", - "hex", "indexmap", "log", "memchr", "once_cell", - "paste", "percent-encoding", "serde", "serde_json", "sha2", "smallvec", - "sqlformat", - "thiserror 1.0.64", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -4601,22 +4529,22 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" +checksum = "7f4200e0fde19834956d4252347c12a083bdcb237d7a1a1446bffd8768417dce" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] name = "sqlx-macros-core" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" +checksum = "882ceaa29cade31beca7129b6beeb05737f44f82dbe2a9806ecea5a7093d00b7" dependencies = [ "dotenvy", "either", @@ -4632,7 +4560,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.87", + "syn 2.0.100", "tempfile", "tokio", "url", @@ -4640,16 +4568,15 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" +checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7" dependencies = [ "atoi", "base64 0.22.1", "bitflags 2.9.0", "byteorder", "bytes", - "chrono", "crc", "digest", "dotenvy", @@ -4676,7 +4603,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 1.0.64", + "thiserror 2.0.12", "time", "tracing", "whoami", @@ -4684,21 +4611,19 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" +checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6" dependencies = [ "atoi", "base64 0.22.1", "bitflags 2.9.0", "byteorder", - "chrono", "crc", "dotenvy", "etcetera", "futures-channel", "futures-core", - "futures-io", "futures-util", "hex", "hkdf", @@ -4716,7 +4641,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 1.0.64", + "thiserror 2.0.12", "time", "tracing", "whoami", @@ -4724,12 +4649,11 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" +checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc" dependencies = [ "atoi", - "chrono", "flume", "futures-channel", "futures-core", @@ -4742,11 +4666,18 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", + "thiserror 2.0.12", "time", "tracing", "url", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -4755,9 +4686,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "str_indices" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9557cb6521e8d009c51a8666f09356f4b817ba9ba0981a305bd86aee47bd35c" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" [[package]] name = "streemap" @@ -4823,9 +4754,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -4834,13 +4765,24 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -4874,12 +4816,12 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.13.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.3.2", "once_cell", "rustix", "windows-sys 0.59.0", @@ -4898,11 +4840,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.64", + "thiserror-impl 1.0.69", ] [[package]] @@ -4916,13 +4858,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -4933,7 +4875,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -4965,9 +4907,7 @@ checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", - "libc", "num-conv", - "num_threads", "powerfmt", "serde", "time-core", @@ -4995,9 +4935,6 @@ name = "timeago" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1710e589de0a76aaf295cd47a6699f6405737dbfd3cf2b75c92d000b548d0e6" -dependencies = [ - "chrono", -] [[package]] name = "tiny-skia" @@ -5026,10 +4963,20 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.8.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -5049,7 +4996,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", + "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -5081,17 +5028,16 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] @@ -5121,9 +5067,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -5142,9 +5088,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", @@ -5191,9 +5137,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" dependencies = [ "async-compression", "base64 0.22.1", @@ -5226,6 +5172,20 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" +[[package]] +name = "tower-livereload" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa6b29b17d4540f2bd9ec304ad39d280c4bdf291d0ea6c4123eeba10939af84" +dependencies = [ + "bytes", + "http", + "http-body", + "pin-project-lite", + "tokio", + "tower", +] + [[package]] name = "tower-service" version = "0.3.3" @@ -5292,7 +5252,7 @@ dependencies = [ "async-trait", "rmp-serde", "sqlx", - "thiserror 1.0.64", + "thiserror 1.0.69", "time", "tower-sessions-core", ] @@ -5317,7 +5277,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", ] [[package]] @@ -5343,9 +5303,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -5359,12 +5319,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "triomphe" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" - [[package]] name = "try-lock" version = "0.2.5" @@ -5382,24 +5336,21 @@ dependencies = [ [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-bidi-mirroring" @@ -5415,15 +5366,15 @@ checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" [[package]] name = "unicode-id-start" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97e2a3c5fc9de285c0e805d98eba666adb4b2d9e1049ce44821ff7707cc34e91" +checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-linebreak" @@ -5476,12 +5427,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -5496,9 +5441,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -5533,6 +5478,18 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -5541,11 +5498,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.10.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.3.2", ] [[package]] @@ -5561,9 +5518,9 @@ dependencies = [ [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -5595,16 +5552,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "want" version = "0.3.1" @@ -5657,7 +5604,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -5692,7 +5639,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5706,19 +5653,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" version = "0.3.77" @@ -5748,9 +5682,9 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "whoami" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ "redox_syscall", "wasite", @@ -5772,15 +5706,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -5788,14 +5713,85 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ + "windows-core 0.58.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.2", + "windows-strings 0.4.0", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "windows-link" version = "0.1.1" @@ -5808,11 +5804,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ - "windows-result", - "windows-strings", + "windows-result 0.3.2", + "windows-strings 0.3.1", "windows-targets 0.53.0", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.3.2" @@ -5822,6 +5827,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-strings" version = "0.3.1" @@ -5831,6 +5846,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -6111,9 +6135,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.6.20" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] @@ -6127,6 +6151,18 @@ dependencies = [ "bitflags 2.9.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -6148,14 +6184,46 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", ] [[package]] @@ -6166,7 +6234,39 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.100", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", ] [[package]] @@ -6175,6 +6275,28 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "zip" version = "2.6.1" @@ -6191,27 +6313,27 @@ dependencies = [ [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", @@ -6234,9 +6356,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 5cfc252..64e30fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,56 +1,37 @@ -[package] -name = "decompal-rs" -version = "0.1.0" -edition = "2021" +[workspace] +resolver = "3" +members = [ + "crates/auth", + "crates/core", + "crates/db", + "crates/github", + "crates/images", + "crates/scripts", + "crates/web", +] -[dependencies] +[workspace.package] +version = "0.1.0" +edition = "2024" + +[workspace.dependencies] anyhow = "1.0" -ariadne = { version = "0.4", features = ["auto-color"] } axum = { version = "0.8", features = ["macros"] } -badge-maker = "0.3" -blake3 = "1.5" -bytes = "1.7" futures-util = "0.3.31" -grass = "0.13" hex = "0.4" -human-size = "0.4" image = "0.25" -itertools = "0.14" -lightningcss = "1.0.0-alpha.65" mime = "0.3" -minijinja = { version = "2.9", features = ["loader", "json"] } -minijinja-autoreload = "2.9" -moka = { version = "0.12", features = ["future"] } objdiff-core = { version = "2.5", features = ["bindings"] } #objdiff-core = { path = "../objdiff/objdiff-core", features = ["bindings"] } octocrab = { version = "0.44", features = ["stream"] } -oxc = { version = "0.31", features = ["codegen", "minifier", "transformer", "semantic"] } -palette = "0.7" -prost = "0.13" -regex = "1.11" -reqwest = { version = "0.12", default-features = false, features = ["json", "gzip", "zstd", "charset", "http2", "rustls-tls-native-roots"] } -resvg = "0.45" +reqwest = { version = "0.12", default-features = false, features = ["json", "gzip", "zstd", "http2", "rustls-tls-native-roots"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_yaml = "0.9" -sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "chrono"] } -streemap = "0.1" -timeago = { version = "0.4.2", default-features = false, features = ["chrono"] } +time = { version = "0.3", features = ["serde", "macros"] } tokio = { version = "1.40", features = ["full"] } -tokio-cron-scheduler = "0.13" -tower = { version = "0.5", features = ["full"] } -tower-http = { version = "0.6", features = ["full"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -url = { version = "2.5", features = ["serde"] } -zip = { version = "2.6", default-features = false, features = ["flate2", "deflate-flate2"] } -zstd = "0.13" -jsonwebtoken = "9.3" -hmac = "0.12" -sha2 = "0.10" -async-openai = "0.28" tower-sessions = "0.14" -tower-sessions-sqlx-store = { version = "0.15", features = ["sqlite"] } -time = { version = "0.3", features = ["local-offset"] } -rand = "0.9" -base64 = "0.22" +tracing = "0.1" +url = { version = "2.5", features = ["serde"] } + +[profile.dev.package.sqlx-macros] +opt-level = 3 diff --git a/templates/og.svg b/assets/og.svg similarity index 100% rename from templates/og.svg rename to assets/og.svg diff --git a/crates/auth/Cargo.toml b/crates/auth/Cargo.toml new file mode 100644 index 0000000..7bf0bb8 --- /dev/null +++ b/crates/auth/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "decomp-dev-auth" +version.workspace = true +edition.workspace = true +publish = false + +[dependencies] +anyhow.workspace = true +axum.workspace = true +base64 = "0.22" +rand = "0.9" +decomp-dev-core = { path = "../core" } +octocrab.workspace = true +time.workspace = true +serde.workspace = true +tower-sessions.workspace = true +tracing.workspace = true +url.workspace = true diff --git a/src/handlers/auth.rs b/crates/auth/src/lib.rs similarity index 89% rename from src/handlers/auth.rs rename to crates/auth/src/lib.rs index bae0f47..80bbe37 100644 --- a/src/handlers/auth.rs +++ b/crates/auth/src/lib.rs @@ -1,17 +1,16 @@ -use anyhow::{anyhow, bail, Context}; +use anyhow::{Context, anyhow, bail}; use axum::{ extract::{FromRef, FromRequestParts, OptionalFromRequestParts, Query, State}, - http::{header::ACCEPT, request::Parts, StatusCode}, + http::{StatusCode, header::ACCEPT, request::Parts}, response::{IntoResponse, Redirect, Response}, }; -use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; -use octocrab::{models::Author, Octocrab}; -use rand::{rngs::OsRng, TryRngCore}; +use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; +use decomp_dev_core::{AppError, config::GitHubConfig}; +use octocrab::{Octocrab, models::Author}; +use rand::{TryRngCore, rngs::OsRng}; use time::{Duration, UtcDateTime}; use tower_sessions::Session; -use crate::{config::GitHubConfig, handlers::AppError, AppState}; - const GITHUB_OAUTH_STATE: &str = "github_oauth_state"; const CURRENT_USER: &str = "current_user"; @@ -37,21 +36,23 @@ impl From for octocrab::auth::OAuth { } } +pub type Profile = Author; + #[derive(Clone, serde::Serialize, serde::Deserialize)] pub struct CurrentUser { pub oauth: StoredOAuth, - pub profile: Author, + pub profile: Profile, } pub async fn login( session: Session, - State(state): State, + State(config): State, current_user: Option, ) -> Result { if current_user.is_some() { return Ok(Redirect::to("/").into_response()); } - let Some(config) = &state.config.github.oauth else { + let Some(config) = &config.oauth else { tracing::warn!("No GitHub OAuth config found"); return Ok((StatusCode::INTERNAL_SERVER_ERROR, "No GitHub OAuth config").into_response()); }; @@ -134,7 +135,7 @@ struct RefreshAccessToken<'a> { pub async fn oauth( session: Session, Query(OAuthQuery { code, state: oauth_state }): Query, - State(state): State, + State(config): State, ) -> Result { let existing_state = session.get::(GITHUB_OAUTH_STATE).await?; let Some(existing_state) = existing_state else { @@ -147,7 +148,7 @@ pub async fn oauth( } session.remove_value(GITHUB_OAUTH_STATE).await?; - let current_user = fetch_access_token(&state.config.github, &code).await?; + let current_user = fetch_access_token(&config, &code).await?; session.insert(CURRENT_USER, current_user).await?; Ok(Redirect::to("/").into_response()) @@ -214,7 +215,7 @@ async fn refresh_access_token( impl FromRequestParts for CurrentUser where - AppState: FromRef, + GitHubConfig: FromRef, S: Send + Sync, { type Rejection = (StatusCode, &'static str); @@ -228,7 +229,7 @@ where impl OptionalFromRequestParts for CurrentUser where - AppState: FromRef, + GitHubConfig: FromRef, S: Send + Sync, { type Rejection = (StatusCode, &'static str); @@ -238,7 +239,7 @@ where state: &S, ) -> Result, Self::Rejection> { let session = Session::from_request_parts(parts, state).await?; - let app_state = AppState::from_ref(state); + let config = GitHubConfig::from_ref(state); let Some(user) = session.get::(CURRENT_USER).await.ok().flatten() else { return Ok(None); }; @@ -255,14 +256,13 @@ where return Ok(None); } } - let current_user = - match refresh_access_token(&app_state.config.github, refresh_token).await { - Ok(current_user) => current_user, - Err(e) => { - tracing::error!("Failed to refresh access token: {:?}", e); - return Ok(None); - } - }; + let current_user = match refresh_access_token(&config, refresh_token).await { + Ok(current_user) => current_user, + Err(e) => { + tracing::error!("Failed to refresh access token: {:?}", e); + return Ok(None); + } + }; if let Err(e) = session.insert(CURRENT_USER, current_user.clone()).await { tracing::error!("Failed to insert user into session: {}", e); } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml new file mode 100644 index 0000000..c095c93 --- /dev/null +++ b/crates/core/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "decomp-dev-core" +version.workspace = true +edition.workspace = true +publish = false + +[dependencies] +anyhow.workspace = true +objdiff-core.workspace = true +serde.workspace = true +time.workspace = true +url.workspace = true +axum.workspace = true +mime.workspace = true +tracing.workspace = true \ No newline at end of file diff --git a/src/config.rs b/crates/core/src/config.rs similarity index 100% rename from src/config.rs rename to crates/core/src/config.rs diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs new file mode 100644 index 0000000..42b980b --- /dev/null +++ b/crates/core/src/lib.rs @@ -0,0 +1,83 @@ +pub mod config; +pub mod models; +pub mod util; + +use std::{convert::Infallible, net::SocketAddr}; + +use axum::{ + Extension, + extract::{ConnectInfo, FromRequestParts, OriginalUri}, + http::{StatusCode, Uri, header, request::Parts}, + response::{IntoResponse, Response}, +}; + +pub enum AppError { + Status(StatusCode), + Internal(anyhow::Error), +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + match self { + Self::Status(status) if status == StatusCode::NOT_FOUND => { + (status, "Not found!!").into_response() + } + Self::Status(status) => status.into_response(), + Self::Internal(err) => { + tracing::error!("{:?}", err); + (StatusCode::INTERNAL_SERVER_ERROR, format!("Something went wrong: {}", err)) + .into_response() + } + } + } +} + +impl> From for AppError { + fn from(err: E) -> Self { Self::Internal(err.into()) } +} + +/// Extractor for the full URI of the request, including the scheme and authority. +/// Uses the `x-forwarded-proto` and `x-forwarded-host` headers if present. +pub struct FullUri(pub Uri); + +impl FromRequestParts for FullUri +where S: Send + Sync +{ + type Rejection = Infallible; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let uri = Extension::::from_request_parts(parts, state) + .await + .map_or_else(|_| parts.uri.clone(), |Extension(OriginalUri(uri))| uri); + let mut builder = Uri::builder(); + if let Some(scheme) = + parts.headers.get("x-forwarded-proto").and_then(|value| value.to_str().ok()) + { + builder = builder.scheme(scheme); + } else if let Some(scheme) = uri.scheme().cloned() { + builder = builder.scheme(scheme); + } else { + // TODO: native https? + builder = builder.scheme("http"); + } + if let Some(host) = + parts.headers.get("x-forwarded-host").and_then(|value| value.to_str().ok()) + { + builder = builder.authority(host); + } else if let Some(host) = + parts.headers.get(header::HOST).and_then(|value| value.to_str().ok()) + { + builder = builder.authority(host); + } else if let Some(authority) = uri.authority().cloned() { + builder = builder.authority(authority); + } else if let Ok(ConnectInfo(socket_addr)) = + ConnectInfo::::from_request_parts(parts, state).await + { + builder = builder.authority(socket_addr.to_string()); + } + if let Some(path_and_query) = uri.path_and_query().cloned() { + builder = builder.path_and_query(path_and_query); + } + Ok(FullUri(builder.build().unwrap_or(uri))) + } +} diff --git a/src/models.rs b/crates/core/src/models.rs similarity index 87% rename from src/models.rs rename to crates/core/src/models.rs index 39214b6..2a399ff 100644 --- a/src/models.rs +++ b/crates/core/src/models.rs @@ -4,8 +4,6 @@ use objdiff_core::bindings::report::{Measures, Report, ReportCategory, ReportUni use serde::Serialize; use time::UtcDateTime; -use crate::db::UnitKey; - #[derive(Debug, Clone, Eq, PartialEq, Serialize)] pub struct Project { pub id: u64, @@ -64,19 +62,6 @@ pub struct Commit { pub timestamp: UtcDateTime, } -impl From<&octocrab::models::workflows::HeadCommit> for Commit { - fn from(commit: &octocrab::models::workflows::HeadCommit) -> Self { - Self { - sha: commit.id.clone(), - timestamp: UtcDateTime::from_unix_timestamp( - commit.timestamp.to_utc().timestamp_millis(), - ) - .unwrap_or_else(|_| UtcDateTime::now()), - message: (!commit.message.is_empty()).then(|| commit.message.clone()), - } - } -} - #[derive(Debug, Clone)] pub struct ReportFile { pub commit: Commit, @@ -95,6 +80,9 @@ pub struct ReportInner { pub categories: Vec, } +// BLAKE3 hash of the unit data +pub type UnitKey = [u8; 32]; + pub type CachedReport = ReportInner; pub type FullReport = ReportInner>; diff --git a/src/util.rs b/crates/core/src/util.rs similarity index 100% rename from src/util.rs rename to crates/core/src/util.rs diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml new file mode 100644 index 0000000..2343eaf --- /dev/null +++ b/crates/db/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "decomp-dev-db" +version.workspace = true +edition.workspace = true +publish = false + +[dependencies] +anyhow.workspace = true +blake3 = "1.5" +decomp-dev-core = { path = "../core" } +futures-util.workspace = true +hex.workspace = true +moka = { version = "0.12", features = ["future"] } +objdiff-core.workspace = true +prost = "0.13" +sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "time"] } +time.workspace = true +tracing.workspace = true +zstd = "0.13" +serde_json.workspace = true \ No newline at end of file diff --git a/src/db/mod.rs b/crates/db/src/lib.rs similarity index 97% rename from src/db/mod.rs rename to crates/db/src/lib.rs index b2ec3e5..3c9310c 100644 --- a/src/db/mod.rs +++ b/crates/db/src/lib.rs @@ -1,23 +1,22 @@ use core::mem; use std::{borrow::Cow, cell::RefCell, collections::HashMap, sync::Arc}; -use anyhow::{anyhow, bail, Context, Result}; -use futures_util::TryStreamExt; -use moka::future::Cache; -use objdiff_core::bindings::report::{Report, ReportUnit, REPORT_VERSION}; -use prost::Message; -use sqlx::{ - migrate::MigrateDatabase, Connection, Executor, Pool, Row, Sqlite, SqliteConnection, SqlitePool, -}; -use time::{macros::format_description, OffsetDateTime, UtcDateTime}; - -use crate::{ +use anyhow::{Context, Result, anyhow, bail}; +use decomp_dev_core::{ config::DbConfig, models::{ CachedReport, CachedReportFile, Commit, FrogressMapping, FullReport, FullReportFile, - Project, ProjectInfo, + Project, ProjectInfo, UnitKey, }, }; +use futures_util::TryStreamExt; +use moka::future::Cache; +use objdiff_core::bindings::report::{REPORT_VERSION, Report, ReportUnit}; +use prost::Message; +use sqlx::{ + Connection, Executor, Pool, Row, Sqlite, SqliteConnection, SqlitePool, migrate::MigrateDatabase, +}; +use time::{OffsetDateTime, UtcDateTime, macros::format_description}; #[derive(Clone)] pub struct Database { @@ -34,9 +33,6 @@ struct ReportKey { version: String, } -// BLAKE3 hash of the unit data -pub type UnitKey = [u8; 32]; - // Maximum number of bind parameters in a single query (SQLite limit) const BIND_LIMIT: usize = 32766; @@ -49,7 +45,7 @@ impl Database { } let pool = SqlitePool::connect(&config.url).await.context("Failed to connect to database")?; - sqlx::migrate!("./migrations") + sqlx::migrate!("../../migrations") .run(&pool) .await .context("Failed to run database migrations")?; @@ -104,8 +100,8 @@ impl Database { project.default_category, project.default_version, ) - .execute(&mut *tx) - .await?; + .execute(&mut *tx) + .await?; report.migrate()?; let units = mem::take(&mut report.units); let data = compress(&report.encode_to_vec()); @@ -126,10 +122,10 @@ impl Database { data, report.version, ) - .fetch_one(&mut *tx) - .await? - .id; - Self::insert_report_units(&mut *tx, &units, report_id).await?; + .fetch_one(&mut *tx) + .await? + .id; + Self::insert_report_units(&mut tx, &units, report_id).await?; tx.commit().await?; // self.report_cache // .insert( @@ -369,8 +365,8 @@ impl Database { owner, repo ) - .fetch_optional(&mut *conn) - .await? + .fetch_optional(&mut *conn) + .await? { Some(row) => Project { id: row.id as u64, @@ -386,7 +382,7 @@ impl Database { }, None => return Ok(None), }; - self.get_project_info_inner(&mut *conn, project, commit).await + self.get_project_info_inner(&mut conn, project, commit).await } pub async fn get_project_info_by_id( @@ -404,8 +400,8 @@ impl Database { "#, project_id_db ) - .fetch_optional(&mut *conn) - .await? + .fetch_optional(&mut *conn) + .await? { Some(row) => Project { id: project_id, @@ -421,7 +417,7 @@ impl Database { }, None => return Ok(None), }; - self.get_project_info_inner(&mut *conn, project, commit).await + self.get_project_info_inner(&mut conn, project, commit).await } async fn get_project_info_inner( @@ -761,7 +757,7 @@ impl Database { .execute(&mut *tx) .await?; // Insert updated report units - Self::insert_report_units(&mut *tx, &units, report_id).await?; + Self::insert_report_units(&mut tx, &units, report_id).await?; tx.commit().await?; } Ok(()) diff --git a/crates/github/Cargo.toml b/crates/github/Cargo.toml new file mode 100644 index 0000000..e54616e --- /dev/null +++ b/crates/github/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "decomp-dev-github" +version.workspace = true +edition.workspace = true +publish = false + +[dependencies] +anyhow.workspace = true +axum.workspace = true +decomp-dev-core = { path = "../core" } +decomp-dev-db = { path = "../db" } +futures-util.workspace = true +hex = "0.4" +hmac = "0.12" +http = "1.3" +jsonwebtoken = "9.3" +objdiff-core.workspace = true +octocrab.workspace = true +regex = "1.11" +serde.workspace = true +sha2 = "0.10" +time.workspace = true +tokio.workspace = true +tracing.workspace = true +zip = { version = "2.6", default-features = false, features = ["flate2", "deflate-flate2"] } +serde_json.workspace = true \ No newline at end of file diff --git a/crates/github/src/changes.rs b/crates/github/src/changes.rs new file mode 100644 index 0000000..3b48699 --- /dev/null +++ b/crates/github/src/changes.rs @@ -0,0 +1,283 @@ +use std::cmp::Ordering; + +use anyhow::Result; +use decomp_dev_core::models::{Commit, FullReportFile}; +use objdiff_core::bindings::report::{ + ChangeItem, ChangeItemInfo, ChangeUnit, Changes, Report, ReportItem, ReportUnit, +}; + +use crate::ProcessArtifactResult; + +pub fn generate_changes(previous: &Report, current: &Report) -> Result { + let mut changes = Changes { from: previous.measures, to: current.measures, units: vec![] }; + for prev_unit in &previous.units { + let curr_unit = current.units.iter().find(|u| u.name == prev_unit.name); + let sections = process_items(prev_unit, curr_unit, |u| &u.sections); + let functions = process_items(prev_unit, curr_unit, |u| &u.functions); + + let prev_measures = prev_unit.measures; + let curr_measures = curr_unit.and_then(|u| u.measures); + if !functions.is_empty() || prev_measures != curr_measures { + changes.units.push(ChangeUnit { + name: prev_unit.name.clone(), + from: prev_measures, + to: curr_measures, + sections, + functions, + metadata: curr_unit + .as_ref() + .and_then(|u| u.metadata.clone()) + .or_else(|| prev_unit.metadata.clone()), + }); + } + } + for curr_unit in ¤t.units { + if !previous.units.iter().any(|u| u.name == curr_unit.name) { + changes.units.push(ChangeUnit { + name: curr_unit.name.clone(), + from: None, + to: curr_unit.measures, + sections: process_new_items(&curr_unit.sections), + functions: process_new_items(&curr_unit.functions), + metadata: curr_unit.metadata.clone(), + }); + } + } + Ok(changes) +} + +fn process_items &Vec>( + prev_unit: &ReportUnit, + curr_unit: Option<&ReportUnit>, + getter: F, +) -> Vec { + let prev_items = getter(prev_unit); + let mut items = vec![]; + if let Some(curr_unit) = curr_unit { + let curr_items = getter(curr_unit); + for prev_func in prev_items { + let prev_func_info = ChangeItemInfo::from(prev_func); + let prev_func_address = prev_func.metadata.as_ref().and_then(|m| m.virtual_address); + let curr_func = curr_items.iter().find(|f| { + f.name == prev_func.name + || prev_func_address.is_some_and(|a| { + f.metadata.as_ref().and_then(|m| m.virtual_address).is_some_and(|b| a == b) + }) + }); + if let Some(curr_func) = curr_func { + let curr_func_info = ChangeItemInfo::from(curr_func); + if prev_func_info != curr_func_info { + items.push(ChangeItem { + name: curr_func.name.clone(), + from: Some(prev_func_info), + to: Some(curr_func_info), + metadata: curr_func.metadata.clone(), + }); + } + } else { + items.push(ChangeItem { + name: prev_func.name.clone(), + from: Some(prev_func_info), + to: None, + metadata: prev_func.metadata.clone(), + }); + } + } + for curr_func in curr_items { + let curr_func_address = curr_func.metadata.as_ref().and_then(|m| m.virtual_address); + if !prev_items.iter().any(|f| { + f.name == curr_func.name + || curr_func_address.is_some_and(|a| { + f.metadata.as_ref().and_then(|m| m.virtual_address).is_some_and(|b| a == b) + }) + }) { + items.push(ChangeItem { + name: curr_func.name.clone(), + from: None, + to: Some(ChangeItemInfo::from(curr_func)), + metadata: curr_func.metadata.clone(), + }); + } + } + } else { + for prev_func in prev_items { + items.push(ChangeItem { + name: prev_func.name.clone(), + from: Some(ChangeItemInfo::from(prev_func)), + to: None, + metadata: prev_func.metadata.clone(), + }); + } + } + items +} + +fn process_new_items(items: &[ReportItem]) -> Vec { + items + .iter() + .map(|item| ChangeItem { + name: item.name.clone(), + from: None, + to: Some(ChangeItemInfo::from(item)), + metadata: item.metadata.clone(), + }) + .collect() +} + +fn measure_line_matched( + name: &str, + from: u64, + from_percent: f32, + to: u64, + to_percent: f32, +) -> String { + let emoji = if to > from { "📈" } else { "📉" }; + let percent_diff = to_percent - from_percent; + let percent_str = if percent_diff < 0.0 { + format!("{percent_diff:.2}%") + } else { + format!("+{percent_diff:.2}%") + }; + let bytes_diff = to as i64 - from as i64; + let bytes_str = match bytes_diff.cmp(&0) { + Ordering::Less => bytes_diff.to_string(), + Ordering::Equal | Ordering::Greater => format!("+{bytes_diff}"), + }; + format!("{emoji} **{name}**: {to_percent:.2}% ({percent_str}, {bytes_str} bytes)\n") +} + +fn measure_line_bytes(name: &str, from: u64, to: u64) -> String { + let diff = to as i64 - from as i64; + let diff_str = match diff.cmp(&0) { + Ordering::Less => diff.to_string(), + Ordering::Equal | Ordering::Greater => format!("+{diff}"), + }; + format!("**{name}**: {to} bytes ({diff_str} bytes)\n") +} + +fn measure_line_simple(name: &str, from: u64, to: u64) -> String { + let diff = to as i64 - from as i64; + let diff_str = match diff.cmp(&0) { + Ordering::Less => diff.to_string(), + Ordering::Equal | Ordering::Greater => format!("+{diff}"), + }; + format!("**{name}**: {to} ({diff_str})\n") +} + +pub fn generate_comment( + from: &FullReportFile, + to: &ProcessArtifactResult, + to_commit: &Commit, + changes: Changes, +) -> String { + let mut comment = format!( + "### Report for {} ({} - {})\n\n", + to.version, + &from.commit.sha[..7], + &to_commit.sha[..7] + ); + let mut measure_written = false; + let from_measures = from.report.measures; + let to_measures = to.report.measures.unwrap_or_default(); + if from_measures.total_code != to_measures.total_code { + comment.push_str(&measure_line_bytes( + "Total code", + from_measures.total_code, + to_measures.total_code, + )); + measure_written = true; + } + if from_measures.total_functions != to_measures.total_functions { + comment.push_str(&measure_line_simple( + "Total functions", + from_measures.total_functions as u64, + to_measures.total_functions as u64, + )); + measure_written = true; + } + if from_measures.matched_code != to_measures.matched_code { + comment.push_str(&measure_line_matched( + "Matched code", + from_measures.matched_code, + from_measures.matched_code_percent, + to_measures.matched_code, + to_measures.matched_code_percent, + )); + measure_written = true; + } + if from_measures.complete_code != to_measures.complete_code { + comment.push_str(&measure_line_matched( + "Linked code", + from_measures.complete_code, + from_measures.complete_code_percent, + to_measures.complete_code, + to_measures.complete_code_percent, + )); + measure_written = true; + } + if measure_written { + comment.push('\n'); + } + let mut total_changes = 0; + let mut iter = changes.units.into_iter().flat_map(|mut unit| { + let functions = core::mem::take(&mut unit.functions); + functions.into_iter().map(move |f| (unit.clone(), f)) + }); + for (unit, item) in iter.by_ref() { + let (from, to) = match (item.from, item.to) { + (Some(from), Some(to)) => (from, to), + (None, Some(to)) => (ChangeItemInfo::default(), to), + (Some(from), None) => (from, ChangeItemInfo::default()), + (None, None) => continue, + }; + let emoji = if to.fuzzy_match_percent == 100.0 { + "✅" + } else if to.fuzzy_match_percent > from.fuzzy_match_percent { + "📈" + } else { + "📉" + }; + let from_bytes = ((from.fuzzy_match_percent as f64 / 100.0) * from.size as f64) as u64; + let to_bytes = ((to.fuzzy_match_percent as f64 / 100.0) * to.size as f64) as u64; + let bytes_diff = to_bytes as i64 - from_bytes as i64; + let bytes_str = match bytes_diff.cmp(&0) { + Ordering::Less => bytes_diff.to_string(), + Ordering::Equal => "0".to_string(), + Ordering::Greater => format!("+{}", bytes_diff), + }; + let name = + item.metadata.as_ref().and_then(|m| m.demangled_name.as_deref()).unwrap_or(&item.name); + comment.push_str(&format!( + "{emoji} `{} | {}` {} bytes -> {:.2}%\n", + unit.name, name, bytes_str, to.fuzzy_match_percent + )); + total_changes += 1; + if total_changes >= 30 { + break; + } + } + let remaining = iter.count(); + if remaining > 0 { + comment.push_str(&format!("...and {} more items\n", remaining)); + } else if total_changes == 0 { + comment.push_str("No changes\n"); + } + comment +} + +#[allow(unused)] +fn platform_name(platform: &str) -> &str { + match platform { + "gc" => "GameCube", + "wii" => "Wii", + "n64" => "Nintendo 64", + "switch" => "Nintendo Switch", + "3ds" => "Nintendo 3DS", + "nds" => "Nintendo DS", + "gba" => "Game Boy Advance", + "gbc" => "Game Boy Color", + "ps" => "PlayStation", + "ps2" => "PlayStation 2", + _ => platform, + } +} diff --git a/src/github/mod.rs b/crates/github/src/lib.rs similarity index 92% rename from src/github/mod.rs rename to crates/github/src/lib.rs index e2957c3..39fa0a4 100644 --- a/src/github/mod.rs +++ b/crates/github/src/lib.rs @@ -1,5 +1,8 @@ +pub mod changes; +pub mod webhook; + use std::{ - collections::{hash_map::Entry, HashMap}, + collections::{HashMap, hash_map::Entry}, ffi::OsStr, io::{Cursor, Read}, pin::pin, @@ -7,26 +10,26 @@ use std::{ }; use anyhow::{Context, Result}; -use axum::http::StatusCode; +use decomp_dev_core::{ + config::GitHubConfig, + models::{Commit, Project}, +}; +use decomp_dev_db::Database; use futures_util::TryStreamExt; +use http::StatusCode; use objdiff_core::bindings::report::Report; use octocrab::{ - models::{repos::RepoCommitPage, ArtifactId, InstallationId, RunId}, - params::actions::ArchiveFormat, GitHubError, Octocrab, + models::{ArtifactId, InstallationId, RunId, repos::RepoCommitPage, workflows::HeadCommit}, + params::actions::ArchiveFormat, }; use regex::Regex; +use time::UtcDateTime; use tokio::{ sync::{Mutex, Semaphore}, task::JoinSet, }; -use crate::{ - config::GitHubConfig, - models::{Commit, Project}, - AppState, -}; - #[derive(Clone)] pub struct GitHub { pub client: Octocrab, @@ -153,15 +156,13 @@ impl GitHub { } } -pub async fn run(state: &AppState, repo_id: u64, stop_run_id: u64) -> Result<()> { - let mut project_info = state - .db +pub async fn run(github: &GitHub, db: &Database, repo_id: u64, stop_run_id: u64) -> Result<()> { + let mut project_info = db .get_project_info_by_id(repo_id, None) .await .context("Failed to fetch project info")? .with_context(|| format!("Failed to fetch project info for ID {}", repo_id))?; - let repo = state - .github + let repo = github .client .repos_by_id(project_info.project.id) .get() @@ -178,12 +179,8 @@ pub async fn run(state: &AppState, repo_id: u64, stop_run_id: u64) -> Result<()> owner.login, repo.name ); - state - .db - .update_project_owner_repo(project_info.project.id, &owner.login, &repo.name) - .await?; - project_info = state - .db + db.update_project_owner_repo(project_info.project.id, &owner.login, &repo.name).await?; + project_info = db .get_project_info_by_id(repo_id, None) .await .context("Failed to fetch project info")? @@ -192,7 +189,7 @@ pub async fn run(state: &AppState, repo_id: u64, stop_run_id: u64) -> Result<()> let project = &project_info.project; tracing::debug!("Refreshing project {}/{}", project.owner, project.repo); - let client = state.github.client_for(&project.owner).await?; + let client = github.client_for(&project.owner).await?; let workflow_ids = if let Some(workflow_id) = &project.workflow_id { vec![workflow_id.clone()] @@ -234,7 +231,7 @@ pub async fn run(state: &AppState, repo_id: u64, stop_run_id: u64) -> Result<()> .. }) => { - break + break; } Err(e) => { return Err(e) @@ -274,9 +271,9 @@ pub async fn run(state: &AppState, repo_id: u64, stop_run_id: u64) -> Result<()> let sem = sem.clone(); let project = project.clone(); let client = client.clone(); - let db = state.db.clone(); + let db = db.clone(); let run_id = run.id; - let commit = Commit::from(&run.head_commit); + let commit = commit_from_head_commit(&run.head_commit); set.spawn(async move { let _permit = sem.acquire().await.unwrap(); match db.report_exists(project.id, &commit.sha).await { @@ -310,9 +307,7 @@ pub async fn run(state: &AppState, repo_id: u64, stop_run_id: u64) -> Result<()> ); for artifact in artifacts { let start = std::time::Instant::now(); - state - .db - .insert_report(&project, &commit, &artifact.version, *artifact.report) + db.insert_report(project, &commit, &artifact.version, *artifact.report) .await?; let duration = start.elapsed(); tracing::info!( @@ -340,7 +335,7 @@ pub async fn run(state: &AppState, repo_id: u64, stop_run_id: u64) -> Result<()> if found_artifacts { if project.workflow_id.is_none() { - state.db.update_project_workflow_id(project.id, workflow_id).await?; + db.update_project_workflow_id(project.id, workflow_id).await?; } break; } @@ -485,3 +480,12 @@ async fn download_artifact( } Ok(vec![]) } + +pub fn commit_from_head_commit(commit: &HeadCommit) -> Commit { + Commit { + sha: commit.id.clone(), + timestamp: UtcDateTime::from_unix_timestamp(commit.timestamp.to_utc().timestamp_millis()) + .unwrap_or_else(|_| UtcDateTime::now()), + message: (!commit.message.is_empty()).then(|| commit.message.clone()), + } +} diff --git a/src/handlers/github.rs b/crates/github/src/webhook.rs similarity index 55% rename from src/handlers/github.rs rename to crates/github/src/webhook.rs index e4be7b0..7686779 100644 --- a/src/handlers/github.rs +++ b/crates/github/src/webhook.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, fmt::Display, mem::take}; +use std::fmt::Display; use anyhow::{Context, Result}; use axum::{ @@ -7,33 +7,38 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; +use decomp_dev_core::{AppError, config::GitHubConfig}; +use decomp_dev_db::Database; use hmac::{Hmac, Mac}; -use objdiff_core::bindings::report::{ - ChangeItem, ChangeItemInfo, ChangeUnit, Changes, Report, ReportItem, ReportUnit, -}; use octocrab::{ + Octocrab, models::{ pulls::PullRequest, webhook_events::{ + EventInstallation, WebhookEvent, WebhookEventPayload, payload::{ InstallationWebhookEventAction, PullRequestWebhookEventAction, WorkflowRunWebhookEventAction, }, - EventInstallation, WebhookEvent, WebhookEventPayload, }, workflows::{Run, WorkFlow}, }, - Octocrab, }; use sha2::Sha256; use crate::{ - github::{process_workflow_run, ProcessArtifactResult, ProcessWorkflowRunResult}, - handlers::AppError, - models::{Commit, FullReportFile}, - AppState, + GitHub, ProcessWorkflowRunResult, + changes::{generate_changes, generate_comment}, + commit_from_head_commit, process_workflow_run, }; +#[derive(Clone)] +pub struct WebhookState { + pub config: GitHubConfig, + pub db: Database, + pub github: GitHub, +} + #[derive(Debug, Clone, serde::Deserialize)] pub struct RunWithPullRequests { #[serde(flatten)] @@ -41,9 +46,7 @@ pub struct RunWithPullRequests { pub pull_requests: Vec, } -pub async fn webhook( - GitHubEvent { event, state }: GitHubEvent, -) -> Result { +pub async fn webhook(GitHubEvent { event, state }: GitHubEvent) -> Result { let Some(installations) = &state.github.installations else { tracing::warn!("Received webhook event {:?} with no GitHub app config", event.kind); return Ok((StatusCode::OK, "No app config").into_response()); @@ -160,7 +163,7 @@ pub async fn webhook( } async fn handle_workflow_run_completed( - state: &AppState, + state: &WebhookState, client: Octocrab, _workflow: WorkFlow, workflow_run: RunWithPullRequests, @@ -185,7 +188,7 @@ async fn handle_workflow_run_completed( return Ok(()); } - let commit = Commit::from(&workflow_run.head_commit); + let commit = commit_from_head_commit(&workflow_run.head_commit); if workflow_run.event == "push" && match &repository.default_branch { Some(default_branch) => default_branch == &workflow_run.head_branch, @@ -261,7 +264,7 @@ async fn handle_workflow_run_completed( }; let report_file = state.db.upgrade_report(&cached_report).await?; let report = report_file.report.flatten(); - let changes = changes(&report, &artifact.report)?; + let changes = generate_changes(&report, &artifact.report)?; let comment_text = generate_comment(&report_file, artifact, &commit, changes); let existing_comment = existing_comments .items @@ -292,7 +295,7 @@ async fn handle_workflow_run_completed( } async fn handle_pull_request_update( - _state: &AppState, + _state: &WebhookState, _client: Octocrab, _pull_request: PullRequest, ) -> Result<()> { @@ -302,16 +305,16 @@ async fn handle_pull_request_update( } /// Verify and extract GitHub Event Payload. -#[derive(Debug, Clone)] +#[derive(Clone)] #[must_use] -pub struct GitHubEvent { +pub struct GitHubEvent { pub event: WebhookEvent, - pub state: S, + pub state: WebhookState, } -impl FromRequest for GitHubEvent +impl FromRequest for GitHubEvent where - AppState: FromRef, + WebhookState: FromRef, S: Send + Sync + Clone, { type Rejection = Response; @@ -327,8 +330,8 @@ where .and_then(|v| v.to_str().ok()) .ok_or_else(|| err("X-GitHub-Event header missing"))? .to_string(); - let app_state = AppState::from_ref(state); - let body = if let Some(app_config) = &app_state.config.github.app { + let inner = WebhookState::from_ref(state); + let body = if let Some(app_config) = &inner.config.app { let signature_sha256 = req .headers() .get("X-Hub-Signature-256") @@ -352,280 +355,6 @@ where }; let value = WebhookEvent::try_from_header_and_body(&event, &body) .map_err(|_| err("error parsing body"))?; - Ok(GitHubEvent { event: value, state: state.clone() }) - } -} - -fn changes(previous: &Report, current: &Report) -> Result { - let mut changes = Changes { from: previous.measures, to: current.measures, units: vec![] }; - for prev_unit in &previous.units { - let curr_unit = current.units.iter().find(|u| u.name == prev_unit.name); - let sections = process_items(prev_unit, curr_unit, |u| &u.sections); - let functions = process_items(prev_unit, curr_unit, |u| &u.functions); - - let prev_measures = prev_unit.measures; - let curr_measures = curr_unit.and_then(|u| u.measures); - if !functions.is_empty() || prev_measures != curr_measures { - changes.units.push(ChangeUnit { - name: prev_unit.name.clone(), - from: prev_measures, - to: curr_measures, - sections, - functions, - metadata: curr_unit - .as_ref() - .and_then(|u| u.metadata.clone()) - .or_else(|| prev_unit.metadata.clone()), - }); - } - } - for curr_unit in ¤t.units { - if !previous.units.iter().any(|u| u.name == curr_unit.name) { - changes.units.push(ChangeUnit { - name: curr_unit.name.clone(), - from: None, - to: curr_unit.measures, - sections: process_new_items(&curr_unit.sections), - functions: process_new_items(&curr_unit.functions), - metadata: curr_unit.metadata.clone(), - }); - } - } - Ok(changes) -} - -fn process_items &Vec>( - prev_unit: &ReportUnit, - curr_unit: Option<&ReportUnit>, - getter: F, -) -> Vec { - let prev_items = getter(prev_unit); - let mut items = vec![]; - if let Some(curr_unit) = curr_unit { - let curr_items = getter(curr_unit); - for prev_func in prev_items { - let prev_func_info = ChangeItemInfo::from(prev_func); - let prev_func_address = prev_func.metadata.as_ref().and_then(|m| m.virtual_address); - let curr_func = curr_items.iter().find(|f| { - f.name == prev_func.name - || prev_func_address.is_some_and(|a| { - f.metadata.as_ref().and_then(|m| m.virtual_address).is_some_and(|b| a == b) - }) - }); - if let Some(curr_func) = curr_func { - let curr_func_info = ChangeItemInfo::from(curr_func); - if prev_func_info != curr_func_info { - items.push(ChangeItem { - name: curr_func.name.clone(), - from: Some(prev_func_info), - to: Some(curr_func_info), - metadata: curr_func.metadata.clone(), - }); - } - } else { - items.push(ChangeItem { - name: prev_func.name.clone(), - from: Some(prev_func_info), - to: None, - metadata: prev_func.metadata.clone(), - }); - } - } - for curr_func in curr_items { - let curr_func_address = curr_func.metadata.as_ref().and_then(|m| m.virtual_address); - if !prev_items.iter().any(|f| { - f.name == curr_func.name - || curr_func_address.is_some_and(|a| { - f.metadata.as_ref().and_then(|m| m.virtual_address).is_some_and(|b| a == b) - }) - }) { - items.push(ChangeItem { - name: curr_func.name.clone(), - from: None, - to: Some(ChangeItemInfo::from(curr_func)), - metadata: curr_func.metadata.clone(), - }); - } - } - } else { - for prev_func in prev_items { - items.push(ChangeItem { - name: prev_func.name.clone(), - from: Some(ChangeItemInfo::from(prev_func)), - to: None, - metadata: prev_func.metadata.clone(), - }); - } - } - items -} - -fn process_new_items(items: &[ReportItem]) -> Vec { - items - .iter() - .map(|item| ChangeItem { - name: item.name.clone(), - from: None, - to: Some(ChangeItemInfo::from(item)), - metadata: item.metadata.clone(), - }) - .collect() -} - -fn measure_line_matched( - name: &str, - from: u64, - from_percent: f32, - to: u64, - to_percent: f32, -) -> String { - let emoji = if to > from { "📈" } else { "📉" }; - let percent_diff = to_percent - from_percent; - let percent_str = if percent_diff < 0.0 { - format!("{percent_diff:.2}%") - } else { - format!("+{percent_diff:.2}%") - }; - let bytes_diff = to as i64 - from as i64; - let bytes_str = match bytes_diff.cmp(&0) { - Ordering::Less => bytes_diff.to_string(), - Ordering::Equal | Ordering::Greater => format!("+{bytes_diff}"), - }; - format!("{emoji} **{name}**: {to_percent:.2}% ({percent_str}, {bytes_str} bytes)\n") -} - -fn measure_line_bytes(name: &str, from: u64, to: u64) -> String { - let diff = to as i64 - from as i64; - let diff_str = match diff.cmp(&0) { - Ordering::Less => diff.to_string(), - Ordering::Equal | Ordering::Greater => format!("+{diff}"), - }; - format!("**{name}**: {to} bytes ({diff_str} bytes)\n") -} - -fn measure_line_simple(name: &str, from: u64, to: u64) -> String { - let diff = to as i64 - from as i64; - let diff_str = match diff.cmp(&0) { - Ordering::Less => diff.to_string(), - Ordering::Equal | Ordering::Greater => format!("+{diff}"), - }; - format!("**{name}**: {to} ({diff_str})\n") -} - -fn generate_comment( - from: &FullReportFile, - to: &ProcessArtifactResult, - to_commit: &Commit, - changes: Changes, -) -> String { - let mut comment = format!( - "### Report for {} ({} - {})\n\n", - to.version, - &from.commit.sha[..7], - &to_commit.sha[..7] - ); - let mut measure_written = false; - let from_measures = from.report.measures; - let to_measures = to.report.measures.unwrap_or_default(); - if from_measures.total_code != to_measures.total_code { - comment.push_str(&measure_line_bytes( - "Total code", - from_measures.total_code, - to_measures.total_code, - )); - measure_written = true; - } - if from_measures.total_functions != to_measures.total_functions { - comment.push_str(&measure_line_simple( - "Total functions", - from_measures.total_functions as u64, - to_measures.total_functions as u64, - )); - measure_written = true; - } - if from_measures.matched_code != to_measures.matched_code { - comment.push_str(&measure_line_matched( - "Matched code", - from_measures.matched_code, - from_measures.matched_code_percent, - to_measures.matched_code, - to_measures.matched_code_percent, - )); - measure_written = true; - } - if from_measures.complete_code != to_measures.complete_code { - comment.push_str(&measure_line_matched( - "Linked code", - from_measures.complete_code, - from_measures.complete_code_percent, - to_measures.complete_code, - to_measures.complete_code_percent, - )); - measure_written = true; - } - if measure_written { - comment.push('\n'); - } - let mut total_changes = 0; - let mut iter = changes.units.into_iter().flat_map(|mut unit| { - let functions = take(&mut unit.functions); - functions.into_iter().map(move |f| (unit.clone(), f)) - }); - for (unit, item) in iter.by_ref() { - let (from, to) = match (item.from, item.to) { - (Some(from), Some(to)) => (from, to), - (None, Some(to)) => (ChangeItemInfo::default(), to), - (Some(from), None) => (from, ChangeItemInfo::default()), - (None, None) => continue, - }; - let emoji = if to.fuzzy_match_percent == 100.0 { - "✅" - } else if to.fuzzy_match_percent > from.fuzzy_match_percent { - "📈" - } else { - "📉" - }; - let from_bytes = ((from.fuzzy_match_percent as f64 / 100.0) * from.size as f64) as u64; - let to_bytes = ((to.fuzzy_match_percent as f64 / 100.0) * to.size as f64) as u64; - let bytes_diff = to_bytes as i64 - from_bytes as i64; - let bytes_str = match bytes_diff.cmp(&0) { - Ordering::Less => bytes_diff.to_string(), - Ordering::Equal => "0".to_string(), - Ordering::Greater => format!("+{}", bytes_diff), - }; - let name = - item.metadata.as_ref().and_then(|m| m.demangled_name.as_deref()).unwrap_or(&item.name); - comment.push_str(&format!( - "{emoji} `{} | {}` {} bytes -> {:.2}%\n", - unit.name, name, bytes_str, to.fuzzy_match_percent - )); - total_changes += 1; - if total_changes >= 30 { - break; - } - } - let remaining = iter.count(); - if remaining > 0 { - comment.push_str(&format!("...and {} more items\n", remaining)); - } else if total_changes == 0 { - comment.push_str("No changes\n"); - } - comment -} - -#[allow(unused)] -fn platform_name(platform: &str) -> &str { - match platform { - "gc" => "GameCube", - "wii" => "Wii", - "n64" => "Nintendo 64", - "switch" => "Nintendo Switch", - "3ds" => "Nintendo 3DS", - "nds" => "Nintendo DS", - "gba" => "Game Boy Advance", - "gbc" => "Game Boy Color", - "ps" => "PlayStation", - "ps2" => "PlayStation 2", - _ => platform, + Ok(GitHubEvent { event: value, state: inner }) } } diff --git a/crates/images/Cargo.toml b/crates/images/Cargo.toml new file mode 100644 index 0000000..c64b2c3 --- /dev/null +++ b/crates/images/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "decomp-dev-images" +version.workspace = true +edition.workspace = true +publish = false + +[dependencies] +anyhow.workspace = true +resvg = "0.45" +tracing.workspace = true +objdiff-core.workspace = true +image.workspace = true +serde.workspace = true +badge-maker = "0.3" +axum.workspace = true +decomp-dev-core = { path = "../core" } +tokio.workspace = true +mime.workspace = true +streemap = "0.1" +palette = "0.7" diff --git a/src/handlers/badge.rs b/crates/images/src/badge.rs similarity index 98% rename from src/handlers/badge.rs rename to crates/images/src/badge.rs index a73345d..29a2bd8 100644 --- a/src/handlers/badge.rs +++ b/crates/images/src/badge.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use image::ImageFormat; use objdiff_core::bindings::report::Measures; use serde::{Deserialize, Serialize}; diff --git a/src/handlers/assets.rs b/crates/images/src/lib.rs similarity index 55% rename from src/handlers/assets.rs rename to crates/images/src/lib.rs index 9c2e4c7..1d7c8d2 100644 --- a/src/handlers/assets.rs +++ b/crates/images/src/lib.rs @@ -1,11 +1,21 @@ +pub mod badge; +pub mod svg; +pub mod treemap; + +use std::str::FromStr; + use axum::{ extract::Path, - http::{header, StatusCode}, + http::{StatusCode, header}, response::{IntoResponse, Response}, }; +use decomp_dev_core::{AppError, util::join_normalized}; +use mime::Mime; -use super::AppError; -use crate::util::join_normalized; +pub fn image_mime_from_ext(ext: &str) -> Option { + image::ImageFormat::from_extension(ext) + .map(|format| Mime::from_str(format.to_mime_type()).unwrap()) +} pub async fn get_asset(Path(filename): Path) -> Result { let path = join_normalized("assets", &filename); @@ -33,3 +43,16 @@ pub async fn get_asset(Path(filename): Path) -> Result Result { + let path = join_normalized("assets", "og.svg"); + let svg_src = tokio::fs::read_to_string(&path) + .await + .map_err(|_| AppError::Status(StatusCode::NOT_FOUND))?; + let data = svg::render_image(&svg_src, image::ImageFormat::Png)?; + Ok(( + [(header::CONTENT_TYPE, "image/png"), (header::CACHE_CONTROL, "public, max-age=3600")], + data, + ) + .into_response()) +} diff --git a/src/svg.rs b/crates/images/src/svg.rs similarity index 88% rename from src/svg.rs rename to crates/images/src/svg.rs index 49fcb20..2848171 100644 --- a/src/svg.rs +++ b/crates/images/src/svg.rs @@ -3,14 +3,14 @@ use std::{ sync::{Arc, OnceLock}, }; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result, anyhow}; use image::{ - buffer::ConvertBuffer, error::UnsupportedErrorKind, ExtendedColorType, ImageError, ImageFormat, - RgbImage, RgbaImage, + ExtendedColorType, ImageError, ImageFormat, RgbImage, RgbaImage, buffer::ConvertBuffer, + error::UnsupportedErrorKind, }; use resvg::{ tiny_skia::{PixmapMut, Transform}, - usvg::{fontdb, Options, Tree}, + usvg::{Options, Tree, fontdb}, }; use tracing::info; diff --git a/src/handlers/treemap.rs b/crates/images/src/treemap.rs similarity index 58% rename from src/handlers/treemap.rs rename to crates/images/src/treemap.rs index 33aef4a..fdb733b 100644 --- a/src/handlers/treemap.rs +++ b/crates/images/src/treemap.rs @@ -1,11 +1,6 @@ -use anyhow::Result; -use image::ImageFormat; use palette::{Mix, Srgb}; -use serde::Serialize; use streemap::Rect; -use crate::{handlers::report::ReportTemplateUnit, svg, templates::render, AppState}; - pub fn layout_units(items: &mut [T], aspect: f32, size_fn: S, mut set_rect_fn: R) where S: Fn(&T) -> f32, @@ -28,33 +23,6 @@ where }); } -#[derive(Serialize)] -struct TreemapTemplateContext<'a> { - units: &'a [ReportTemplateUnit<'a>], - w: u32, - h: u32, -} - -pub fn render_svg( - units: &[ReportTemplateUnit], - w: u32, - h: u32, - state: &AppState, -) -> Result { - render(&state.templates, "treemap.svg", TreemapTemplateContext { units, w, h }) -} - -pub fn render_image( - units: &[ReportTemplateUnit], - w: u32, - h: u32, - state: &AppState, - format: ImageFormat, -) -> Result> { - let svg = render_svg(units, w, h, state)?; - svg::render_image(&svg, format) -} - fn rgb(r: u8, g: u8, b: u8) -> Srgb { Srgb::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0) } diff --git a/crates/scripts/Cargo.toml b/crates/scripts/Cargo.toml new file mode 100644 index 0000000..95c8e4e --- /dev/null +++ b/crates/scripts/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "decomp-dev-scripts" +version.workspace = true +edition.workspace = true +publish = false + +[dependencies] +anyhow.workspace = true +ariadne = { version = "0.4", features = ["auto-color"] } +lightningcss = "1.0.0-alpha.65" +oxc = { version = "0.31", features = ["codegen", "minifier", "transformer", "semantic"] } +tokio.workspace = true +thiserror = "2.0" +grass = "0.13" +decomp-dev-core = { path = "../core" } +axum.workspace = true +mime.workspace = true \ No newline at end of file diff --git a/crates/scripts/src/css.rs b/crates/scripts/src/css.rs new file mode 100644 index 0000000..133fd0d --- /dev/null +++ b/crates/scripts/src/css.rs @@ -0,0 +1,24 @@ +use std::{ffi::OsStr, path::Path}; + +use anyhow::anyhow; + +pub fn transform(path: &Path) -> Result { + let mut path = path.with_extension(""); + let printer_options = lightningcss::stylesheet::PrinterOptions { + minify: path.extension() == Some(OsStr::new("min")), + ..Default::default() + }; + path = path.with_extension("scss"); + let options = grass::Options::default().load_path("node_modules"); + let mut output = grass::from_path(&path, &options)?; + // Skip lightningcss entirely if we're not minifying + if printer_options.minify { + let options = lightningcss::stylesheet::ParserOptions::default(); + let stylesheet = lightningcss::stylesheet::StyleSheet::parse(&output, options) + .map_err(|e| anyhow!(e.to_string()))?; + let result = stylesheet.to_css(printer_options)?; + drop(stylesheet); + output = result.code; + } + Ok(output) +} diff --git a/src/handlers/js.rs b/crates/scripts/src/js.rs similarity index 60% rename from src/handlers/js.rs rename to crates/scripts/src/js.rs index 68b26af..93ea9be 100644 --- a/src/handlers/js.rs +++ b/crates/scripts/src/js.rs @@ -1,12 +1,7 @@ -use std::{ffi::OsStr, ops::Range}; +use std::ops::Range; -use anyhow::{anyhow, Result}; +use anyhow::anyhow; use ariadne::{ColorGenerator, Label, Report, ReportBuilder, ReportKind, Source}; -use axum::{ - extract::Path, - http::{header, StatusCode}, - response::{IntoResponse, Response}, -}; use oxc::{ allocator::Allocator, codegen::{CodeGenerator, CodegenReturn}, @@ -17,69 +12,24 @@ use oxc::{ span::SourceType, transformer::{EnvOptions, Targets, TransformOptions, Transformer}, }; +use thiserror::Error; -use crate::{handlers::AppError, util::join_normalized}; - -pub async fn get_js(Path(filename): Path) -> Result { - let mut path = join_normalized("js", &filename); - #[derive(Debug, Copy, Clone, Eq, PartialEq)] - enum ResponseType { - Js, - SourceMap, - } - let response_type; - if path.extension() == Some(OsStr::new("js")) { - response_type = ResponseType::Js; - } else if path.extension() == Some(OsStr::new("map")) { - path = path.with_extension(""); - if path.extension() != Some(OsStr::new("js")) { - return Err(AppError::Status(StatusCode::NOT_FOUND)); - } - response_type = ResponseType::SourceMap; - } else { - return Err(AppError::Status(StatusCode::NOT_FOUND)); - } - path = path.with_extension(""); - let minify = path.extension() == Some(OsStr::new("min")); - path = path.with_extension("ts"); - let ret = transform(&path, minify, response_type == ResponseType::SourceMap).await?; - let (data, content_type) = match response_type { - ResponseType::Js => ( - format!("{}\n//# sourceMappingURL={}.map", ret.code, filename), - mime::APPLICATION_JAVASCRIPT_UTF_8.as_ref(), - ), - ResponseType::SourceMap => { - (ret.map.unwrap().to_json_string(), mime::APPLICATION_JSON.as_ref()) - } - }; - Ok(( - [ - (header::CONTENT_TYPE, content_type), - #[cfg(not(debug_assertions))] - (header::CACHE_CONTROL, "public, max-age=3600"), - #[cfg(debug_assertions)] - (header::CACHE_CONTROL, "no-cache"), - ], - data, - ) - .into_response()) +#[derive(Error, Debug)] +pub enum JsError { + #[error("File not found")] + NotFound, + #[error(transparent)] + Internal(#[from] anyhow::Error), } -async fn transform( +pub async fn transform( path: &std::path::Path, minify: bool, source_map: bool, -) -> Result { - let filename = path - .file_name() - .ok_or(AppError::Status(StatusCode::NOT_FOUND))? - .to_string_lossy() - .into_owned(); - let source_text = tokio::fs::read_to_string(&path) - .await - .map_err(|_| AppError::Status(StatusCode::NOT_FOUND))?; - let source_type = - SourceType::from_path(path).map_err(|_| AppError::Status(StatusCode::NOT_FOUND))?; +) -> Result { + let filename = path.file_name().ok_or(JsError::NotFound)?.to_string_lossy().into_owned(); + let source_text = tokio::fs::read_to_string(&path).await.map_err(|_| JsError::NotFound)?; + let source_type = SourceType::from_path(path).map_err(|_| JsError::NotFound)?; let allocator = Allocator::default(); let parsed = Parser::new(&allocator, &source_text, source_type).parse(); handle_errors(parsed.errors, &filename, &source_text)?; @@ -125,7 +75,7 @@ fn handle_errors( errors: Vec, filename: &str, source_text: &str, -) -> Result<(), AppError> { +) -> Result<(), anyhow::Error> { let mut has_error = false; for diagnostic in errors { let mut colors = ColorGenerator::new(); @@ -152,7 +102,7 @@ fn handle_errors( for span in spans { let offset = span.offset(); let mut label = Label::new((filename, offset..offset)).with_color(colors.next()); - if let Some(message) = span.label().as_deref() { + if let Some(message) = span.label() { label = label.with_message(message); } report = report.with_label(label); @@ -168,9 +118,5 @@ fn handle_errors( has_error = true; } } - if has_error { - Err(AppError::Status(StatusCode::INTERNAL_SERVER_ERROR)) - } else { - Ok(()) - } + if has_error { Err(anyhow!("Failed to transform JS")) } else { Ok(()) } } diff --git a/crates/scripts/src/lib.rs b/crates/scripts/src/lib.rs new file mode 100644 index 0000000..9dc032c --- /dev/null +++ b/crates/scripts/src/lib.rs @@ -0,0 +1,80 @@ +pub mod css; +pub mod js; + +use std::ffi::OsStr; + +use anyhow::anyhow; +use axum::{ + extract::Path, + http::{StatusCode, header}, + response::{IntoResponse, Response}, +}; +use decomp_dev_core::{AppError, util::join_normalized}; + +pub async fn get_css(Path(filename): Path) -> Result { + let path = join_normalized("css", &filename); + if path.extension() != Some(OsStr::new("css")) { + return Err(AppError::Status(StatusCode::NOT_FOUND)); + } + let output = css::transform(&path).map_err(|e| AppError::Internal(anyhow!(e.to_string())))?; + Ok(( + [ + (header::CONTENT_TYPE, mime::TEXT_CSS_UTF_8.as_ref()), + #[cfg(not(debug_assertions))] + (header::CACHE_CONTROL, "public, max-age=3600"), + #[cfg(debug_assertions)] + (header::CACHE_CONTROL, "no-cache"), + ], + output, + ) + .into_response()) +} + +pub async fn get_js(Path(filename): Path) -> Result { + let mut path = join_normalized("js", &filename); + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + enum ResponseType { + Js, + SourceMap, + } + let response_type; + if path.extension() == Some(OsStr::new("js")) { + response_type = ResponseType::Js; + } else if path.extension() == Some(OsStr::new("map")) { + path = path.with_extension(""); + if path.extension() != Some(OsStr::new("js")) { + return Err(AppError::Status(StatusCode::NOT_FOUND)); + } + response_type = ResponseType::SourceMap; + } else { + return Err(AppError::Status(StatusCode::NOT_FOUND)); + } + path = path.with_extension(""); + let minify = path.extension() == Some(OsStr::new("min")); + path = path.with_extension("ts"); + let ret = match js::transform(&path, minify, response_type == ResponseType::SourceMap).await { + Ok(ret) => ret, + Err(js::JsError::NotFound) => return Err(AppError::Status(StatusCode::NOT_FOUND)), + Err(js::JsError::Internal(e)) => return Err(AppError::Internal(e)), + }; + let (data, content_type) = match response_type { + ResponseType::Js => ( + format!("{}\n//# sourceMappingURL={}.map", ret.code, filename), + mime::APPLICATION_JAVASCRIPT_UTF_8.as_ref(), + ), + ResponseType::SourceMap => { + (ret.map.unwrap().to_json_string(), mime::APPLICATION_JSON.as_ref()) + } + }; + Ok(( + [ + (header::CONTENT_TYPE, content_type), + #[cfg(not(debug_assertions))] + (header::CACHE_CONTROL, "public, max-age=3600"), + #[cfg(debug_assertions)] + (header::CACHE_CONTROL, "no-cache"), + ], + data, + ) + .into_response()) +} diff --git a/crates/web/Cargo.toml b/crates/web/Cargo.toml new file mode 100644 index 0000000..df45758 --- /dev/null +++ b/crates/web/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "decomp-dev-web" +version.workspace = true +edition.workspace = true +publish = false + +[dependencies] +anyhow.workspace = true +axum.workspace = true +decomp-dev-auth = { path = "../auth" } +decomp-dev-core = { path = "../core" } +decomp-dev-db = { path = "../db" } +decomp-dev-github = { path = "../github" } +decomp-dev-images = { path = "../images" } +decomp-dev-scripts = { path = "../scripts" } +itertools = "0.14" +maud = { version = "0.27", features = ["axum"] } +mime.workspace = true +objdiff-core.workspace = true +reqwest.workspace = true +serde.workspace = true +serde_json.workspace = true +serde_yaml = "0.9" +time.workspace = true +timeago = { version = "0.4.2", default-features = false } +tokio-cron-scheduler = "0.13" +tokio.workspace = true +tower = { version = "0.5", features = ["full"] } +tower-http = { version = "0.6", features = ["full"] } +tower-livereload = "0.9" +tower-sessions-sqlx-store = { version = "0.15", features = ["sqlite"] } +tower-sessions.workspace = true +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing.workspace = true +url.workspace = true + +# image +image.workspace = true + +# proto +bytes = "1.7" +prost = "0.13" diff --git a/src/cron.rs b/crates/web/src/cron.rs similarity index 91% rename from src/cron.rs rename to crates/web/src/cron.rs index 34737fd..0c84e43 100644 --- a/src/cron.rs +++ b/crates/web/src/cron.rs @@ -1,9 +1,9 @@ use anyhow::Result; use tokio_cron_scheduler::{Job, JobScheduler}; -use tower_sessions::{ExpiredDeletion}; +use tower_sessions::ExpiredDeletion; use tracing::log; -use crate::{github, AppState}; +use crate::AppState; pub type Scheduler = JobScheduler; @@ -59,7 +59,9 @@ pub async fn refresh_projects(state: &AppState) -> Result<()> { continue; } } - if let Err(e) = github::run(state, project_info.project.id, 0).await { + if let Err(e) = + decomp_dev_github::run(&state.github, &state.db, project_info.project.id, 0).await + { log::error!( "Failed to refresh {}/{}: {:?}", project_info.project.owner, diff --git a/src/frogress/mod.rs b/crates/web/src/frogress/mod.rs similarity index 98% rename from src/frogress/mod.rs rename to crates/web/src/frogress/mod.rs index aa44e69..257265f 100644 --- a/src/frogress/mod.rs +++ b/crates/web/src/frogress/mod.rs @@ -1,13 +1,14 @@ #![allow(unused)] use std::collections::HashMap; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{Context, Result, anyhow, bail}; +use decomp_dev_core::models::Commit; use itertools::Itertools; use objdiff_core::bindings::report::{Measures, Report, ReportCategory}; use time::UtcDateTime; use tracing::log; -use crate::{models::Commit, AppState}; +use crate::AppState; type FrogressCategory = HashMap; type FrogressVersion = HashMap; diff --git a/crates/web/src/handlers/common.rs b/crates/web/src/handlers/common.rs new file mode 100644 index 0000000..29aa08f --- /dev/null +++ b/crates/web/src/handlers/common.rs @@ -0,0 +1,194 @@ +use std::time::{Duration, Instant}; + +use decomp_dev_auth::CurrentUser; +use maud::{Markup, PreEscaped, html}; +use objdiff_core::bindings::report::Measures; +use time::{UtcDateTime, macros::format_description}; + +pub fn timeago(value: UtcDateTime) -> String { + let Ok(duration) = Duration::try_from(UtcDateTime::now() - value) else { + return "[out of range]".to_string(); + }; + timeago::Formatter::new().convert(duration) +} + +pub fn date(value: UtcDateTime) -> String { + value.format(format_description!( + "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour sign:mandatory]:[offset_minute]" + )).unwrap_or_else(|_| "[invalid]".to_string()) +} + +pub fn header() -> Markup { + html! { + meta name="viewport" content="width=device-width, initial-scale=1.0"; + meta name="color-scheme" content="dark light"; + meta name="darkreader-lock"; + link rel="stylesheet" href="/css/main.min.css"; + script src="/js/main.min.js" defer; + script { + r#"let theme = null; +try { + theme = localStorage.getItem('theme'); +} catch (_) { +} +if (theme) { + document.documentElement.setAttribute('data-theme', theme); +}"# + } + } +} + +pub fn footer(start: Instant, current_user: Option<&CurrentUser>) -> Markup { + let elapsed = start.elapsed(); + html! { + footer { + span class="section" { + small class="muted" { "Generated in " (elapsed.as_millis()) "ms" } + " | " + small class="muted" { + a href="https://github.com/encounter/decomp.dev" { "Code" } + " by " + a href="https://github.com/encounter" { "@encounter" } + } + } + @if let Some(user) = current_user { + span class="section" { + small class="muted" { + "Logged in as " + a href=(user.profile.html_url) { "@" (user.profile.login) } + } + form action="/logout" method="post" style="display: inline" { + input type="submit" class="button outline secondary" value="Logout"; + } + } + } + } + } +} + +pub fn nav_links() -> Markup { + html! { + ul { + li { + a href="https://ghidra.decomp.dev" { "Ghidra" } + } + li { + a href="https://wiki.decomp.dev" { "Wiki" } + } + li { + a #theme-toggle href="#" title="Toggle theme" { + (PreEscaped(r#" + +"#)) + } + } + } + } +} + +/// Format a size in bytes to a human-readable string. +/// Uses SI (kilo = 1000) units, formatted to two decimal places. +pub fn size(value: u64) -> String { + let units = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + let mut value = value as f64; + let mut unit = 0; + while value >= 1000.0 && unit < units.len() - 1 { + value /= 1000.0; + unit += 1; + } + format!("{:.2} {}", value, units[unit]) +} + +pub fn code_progress_sections(measures: &Measures) -> Markup { + if measures.total_code == 0 { + return Markup::default(); + } + let mut out = Markup::default(); + let mut add_section = |class: &str, percent: f32, tooltip: String| { + let rendered = html! { + div class=(class) style=(format!("width: {}%", percent)) + data-tooltip=(tooltip) {} + }; + out.0.push_str(&rendered.0); + }; + let mut current_percent = 0.0; + if measures.complete_code_percent > 0.0 { + add_section( + "progress-section", + measures.complete_code_percent - current_percent, + format!( + "{:.2}% fully linked ({})", + measures.complete_code_percent, + size(measures.complete_code) + ), + ); + current_percent = measures.complete_code_percent; + } + if measures.matched_code_percent > 0.0 && measures.matched_code_percent > current_percent { + add_section( + if current_percent > 0.0 { "progress-section striped" } else { "progress-section" }, + measures.matched_code_percent - current_percent, + format!( + "{:.2}% perfect match ({})", + measures.matched_code_percent, + size(measures.matched_code) + ), + ); + current_percent = measures.matched_code_percent; + } + if measures.fuzzy_match_percent > 0.0 && measures.fuzzy_match_percent > current_percent { + add_section( + "progress-section striped fuzzy", + measures.fuzzy_match_percent - current_percent, + format!("{:.2}% fuzzy match", measures.fuzzy_match_percent), + ); + } + html! { + div class="progress-root code" { + (out) + } + } +} + +pub fn data_progress_sections(measures: &Measures) -> Markup { + if measures.total_data == 0 { + return Markup::default(); + } + let mut out = Markup::default(); + let mut add_section = |class: &str, percent: f32, tooltip: String| { + let rendered = html! { + div class=(class) style=(format!("width: {}%", percent)) + data-tooltip=(tooltip) {} + }; + out.0.push_str(&rendered.0); + }; + let mut current_percent = 0.0; + if measures.complete_data_percent > 0.0 { + add_section( + "progress-section", + measures.complete_data_percent - current_percent, + format!( + "{:.2}% fully linked ({})", + measures.complete_data_percent, + size(measures.complete_data) + ), + ); + current_percent = measures.complete_data_percent; + } + if measures.matched_data_percent > 0.0 && measures.matched_data_percent > current_percent { + add_section( + if current_percent > 0.0 { "progress-section striped" } else { "progress-section" }, + measures.matched_data_percent - current_percent, + format!( + "{:.2}% perfect match ({})", + measures.matched_data_percent, + size(measures.matched_data) + ), + ); + } + html! { + div class="progress-root data" { + (out) + } + } +} diff --git a/crates/web/src/handlers/mod.rs b/crates/web/src/handlers/mod.rs new file mode 100644 index 0000000..385f26a --- /dev/null +++ b/crates/web/src/handlers/mod.rs @@ -0,0 +1,66 @@ +use std::str::FromStr; + +use axum::{ + Router, + http::{HeaderMap, header}, + routing::{get, post}, +}; +use decomp_dev_images::image_mime_from_ext; +use mime::Mime; + +use crate::AppState; + +mod common; +mod project; +mod report; +mod treemap; + +pub fn build_router() -> Router { + Router::new() + .route("/api/github/webhook", post(decomp_dev_github::webhook::webhook)) + .route("/api/github/oauth", get(decomp_dev_auth::oauth)) + .route("/login", get(decomp_dev_auth::login)) + .route("/logout", post(decomp_dev_auth::logout)) + .route("/css/{*filename}", get(decomp_dev_scripts::get_css)) + .route("/js/{*filename}", get(decomp_dev_scripts::get_js)) + .route("/assets/{*filename}", get(decomp_dev_images::get_asset)) + .route("/og.png", get(decomp_dev_images::get_og)) + .route("/", get(project::get_projects)) + .route("/{owner}/{repo}", get(report::get_report)) + .route("/{owner}/{repo}/{version}", get(report::get_report)) + .route("/{owner}/{repo}/{version}/{commit}", get(report::get_report)) +} + +pub fn parse_accept(headers: &HeaderMap, ext: Option<&str>) -> Vec { + // Explicit extension takes precedence + if let Some(ext) = ext { + return match ext.to_ascii_lowercase().as_str() { + "json" => vec![mime::APPLICATION_JSON], + "binpb" | "proto" => vec![Mime::from_str("application/x-protobuf").unwrap()], + "svg" => vec![mime::IMAGE_SVG], + _ => { + if let Some(mime) = image_mime_from_ext(ext) { + vec![mime] + } else { + // An unknown extension should be NOT_ACCEPTABLE, not */*. + vec![] + } + } + }; + } + // Otherwise, parse the Accept header + let result = headers + .get(header::ACCEPT) + .and_then(|value| value.to_str().ok()) + .iter() + .flat_map(|s| s.split(',')) + .map(|s| s.trim()) + .filter_map(|s| Mime::from_str(s).ok()) + .collect::>(); + if result.is_empty() { + // If no Accept header is present, use */* + vec![mime::STAR_STAR] + } else { + result + } +} diff --git a/crates/web/src/handlers/project.rs b/crates/web/src/handlers/project.rs new file mode 100644 index 0000000..fc97ef1 --- /dev/null +++ b/crates/web/src/handlers/project.rs @@ -0,0 +1,278 @@ +use std::{sync::Arc, time::Instant}; + +use anyhow::{Context, anyhow}; +use axum::{ + extract::{Query, State}, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use decomp_dev_auth::CurrentUser; +use decomp_dev_core::{ + AppError, FullUri, + models::{Commit, Project}, + util::UrlExt, +}; +use maud::{DOCTYPE, Markup, html}; +use objdiff_core::bindings::report::Measures; +use serde::{Deserialize, Serialize}; +use tokio::{sync::Semaphore, task::JoinSet}; +use url::Url; + +use crate::{ + AppState, + handlers::common::{code_progress_sections, date, footer, header, nav_links, size, timeago}, +}; + +#[derive(Serialize)] +struct ProjectInfoContext { + project: Project, + commit: Commit, + measures: Measures, +} + +#[derive(Deserialize)] +pub struct ProjectsQuery { + sort: Option, +} + +#[derive(Serialize, Copy, Clone)] +struct SortOption { + key: &'static str, + name: &'static str, +} + +const SORT_OPTIONS: &[SortOption] = &[ + SortOption { key: "updated", name: "Last updated" }, + SortOption { key: "name", name: "Name" }, + SortOption { key: "matched_code_percent", name: "Matched Code (Percent)" }, + SortOption { key: "matched_code", name: "Matched Code" }, + SortOption { key: "total_code", name: "Total Code" }, +]; + +#[derive(Serialize, Clone)] +pub struct ProgressSection { + pub class: String, + pub percent: f32, + pub tooltip: String, +} + +pub async fn get_projects( + State(state): State, + Query(query): Query, + FullUri(uri): FullUri, + current_user: Option, +) -> Result { + let start = Instant::now(); + let projects = state.db.get_projects().await?; + let mut out = projects + .iter() + .filter_map(|p| { + let commit = p.commit.as_ref()?; + Some(ProjectInfoContext { + project: p.project.clone(), + commit: commit.clone(), + measures: Default::default(), + }) + }) + .collect::>(); + + // Fetch latest report for each + let sem = Arc::new(Semaphore::new(10)); + let mut join_set = JoinSet::new(); + for info in projects { + let sem = sem.clone(); + let state = state.clone(); + join_set.spawn(async move { + let _permit = sem.acquire().await; + let Some(version) = info.default_version() else { + return (info, Err(anyhow!("No report version found"))); + }; + let commit = info.commit.as_ref().unwrap(); + let report = state + .db + .get_report(&info.project.owner, &info.project.repo, &commit.sha, version) + .await + .with_context(|| { + format!( + "Failed to fetch report for {}/{} sha {} version {}", + info.project.owner, info.project.repo, commit.sha, version + ) + }); + (info, report) + }); + } + while let Some(result) = join_set.join_next().await { + match result { + Ok((info, Ok(Some(file)))) => { + if let Some(c) = out.iter_mut().find(|i| i.project.id == info.project.id) { + c.measures = *file.report.measures(info.project.default_category.as_deref()); + } + } + Ok((info, Ok(None))) => { + tracing::warn!("No report found for {}", info.project.id); + } + Ok((info, Err(e))) => { + tracing::error!("Failed to fetch report for {}: {:?}", info.project.id, e); + } + Err(e) => { + tracing::error!("Failed to fetch report: {:?}", e); + } + } + } + + let current_sort_key = query.sort.as_deref().unwrap_or("updated"); + let current_sort = SORT_OPTIONS + .iter() + .find(|s| s.key.eq_ignore_ascii_case(current_sort_key)) + .copied() + .ok_or(AppError::Status(StatusCode::BAD_REQUEST))?; + match current_sort.key { + "name" => out.sort_by(|a, b| a.project.name().cmp(&b.project.name())), + "updated" => out.sort_by(|a, b| b.commit.timestamp.cmp(&a.commit.timestamp)), + "matched_code_percent" => out.sort_by(|a, b| { + b.measures + .matched_code_percent + .partial_cmp(&a.measures.matched_code_percent) + .unwrap_or(std::cmp::Ordering::Equal) + }), + "matched_code" => out.sort_by(|a, b| { + b.measures + .matched_code + .partial_cmp(&a.measures.matched_code) + .unwrap_or(std::cmp::Ordering::Equal) + }), + "total_code" => out.sort_by(|a, b| { + b.measures + .total_code + .partial_cmp(&a.measures.total_code) + .unwrap_or(std::cmp::Ordering::Equal) + }), + _ => return Err(AppError::Status(StatusCode::BAD_REQUEST)), + } + + let request_url = Url::parse(&uri.to_string()).context("Failed to parse URI")?; + let canonical_url = request_url.with_path(""); + + let rendered = html! { + (DOCTYPE) + html { + head lang="en" { + meta charset="utf-8"; + title { "Projects • decomp.dev" } + (header()) + meta name="description" content="Decompilation progress reports"; + meta property="og:title" content="Decompilation progress reports"; + meta property="og:description" content="Progress reports for matching decompilation projects"; + meta property="og:image" content=(canonical_url.with_path("/og.png")); + meta property="og:url" content=(canonical_url); + } + body { + header { + nav { + ul { + li { + a href="https://decomp.dev" { strong { "decomp.dev" } } + } + li { + a href="/" { "Projects" } + } + li class="md" { + details class="dropdown" { + summary { (current_sort.name) } + ul { + @for option in SORT_OPTIONS { + li { + a href=(request_url.query_param("sort", Some(option.key))) { + (option.name) + } + } + } + } + } + } + } + (nav_links()) + } + div class="title-group" { + h3 { "Progress Reports" } + blockquote { + "Matching decompilation projects attempt to write source code (C, C++)" + " that compiles to the same binary as the original." + " All source code is written from scratch." + footer { + a href="https://wiki.decomp.dev/" { "Learn more" } + } + } + } + } + main { + details class="dropdown sm" { + summary { (current_sort.name) } + ul { + @for option in SORT_OPTIONS { + li { + a href=(request_url.query_param("sort", Some(option.key))) { + (option.name) + } + } + } + } + } + @for project in out { + (project_fragment(project, current_sort, &canonical_url)) + } + } + (footer(start, current_user.as_ref())) + } + } + }; + Ok(rendered.into_response()) +} + +fn project_fragment( + info: ProjectInfoContext, + current_sort: SortOption, + canonical_url: &Url, +) -> Markup { + let project_path = + canonical_url.with_path(&format!("/{}/{}", info.project.owner, info.project.repo)); + let commit_url = format!( + "https://github.com/{}/{}/commit/{}", + info.project.owner, info.project.repo, info.commit.sha + ); + html! { + article class="project" { + div class="project-header" { + h3 class="project-title" { + a href=(project_path) { (info.project.name()) } + } + @if let Some(platform) = &info.project.platform { + img class="platform-icon" src=(format!("/assets/platforms/{}.svg", platform)) + alt=(platform) width="24" height="24"; + } + } + h6 { + @if current_sort.key == "total_code" || current_sort.key == "matched_code" { + (size(info.measures.matched_code)) + " matched code | " + (size(info.measures.total_code)) + " total code" + } @else { + (format!("{:.2}%", info.measures.matched_code_percent)) + " decompiled" + @if info.measures.complete_code_percent > 0.0 { + " | " + (format!("{:.2}%", info.measures.complete_code_percent)) + " fully linked" + } + } + } + (code_progress_sections(&info.measures)) + small class="muted" { + span title=(date(info.commit.timestamp)) { "Updated " (timeago(info.commit.timestamp)) } + " in commit " + a href=(commit_url) target="_blank" { (info.commit.sha[..7]) } + } + } + } +} diff --git a/src/handlers/report.rs b/crates/web/src/handlers/report.rs similarity index 65% rename from src/handlers/report.rs rename to crates/web/src/handlers/report.rs index 9c9f7e0..5e6d466 100644 --- a/src/handlers/report.rs +++ b/crates/web/src/handlers/report.rs @@ -2,29 +2,36 @@ use std::{borrow::Cow, iter, time::Instant}; use anyhow::{Context, Result}; use axum::{ - extract::{Path, Query, State}, - http::{header, HeaderMap, StatusCode, Uri}, - response::{Html, IntoResponse, Response}, Json, + extract::{Path, Query, State}, + http::{HeaderMap, StatusCode, Uri, header}, + response::{IntoResponse, Response}, +}; +use decomp_dev_auth::CurrentUser; +use decomp_dev_core::{ + AppError, FullUri, + models::{FullReportFile, ProjectInfo}, + util::UrlExt, +}; +use decomp_dev_images::{ + badge, + treemap::{layout_units, unit_color}, }; use image::ImageFormat; +use maud::{DOCTYPE, Markup, PreEscaped, html}; use mime::Mime; use objdiff_core::bindings::report::{Measures, ReportCategory, ReportUnit}; -use octocrab::models::Author; use serde::{Deserialize, Serialize}; use time::format_description::well_known::Rfc3339; use url::Url; -use super::{badge, parse_accept, treemap, AppError, FullUri, Protobuf, PROTOBUF}; +use super::{parse_accept, treemap}; use crate::{ - handlers::{ - auth::CurrentUser, - project::{code_progress_sections, data_progress_sections, ProgressSection}, - }, - models::{FullReportFile, Project, ProjectInfo}, - templates::render, - util::UrlExt, AppState, + handlers::common::{ + code_progress_sections, data_progress_sections, footer, header, nav_links, size, + }, + proto::{PROTOBUF, Protobuf}, }; #[derive(Deserialize)] @@ -56,46 +63,16 @@ impl ReportQuery { } } -#[derive(Serialize)] -struct ReportTemplateContext<'a> { - project: &'a Project, - project_name: &'a str, - project_short_name: &'a str, - project_url: &'a str, - project_path: &'a str, - commit: &'a str, - version: &'a str, - measures: TemplateMeasures, - units: &'a [ReportTemplateUnit<'a>], - versions: &'a [ReportTemplateVersion<'a>], - prev_commit_path: Option<&'a str>, - next_commit_path: Option<&'a str>, - latest_commit_path: Option<&'a str>, - categories: &'a [ReportCategoryItem<'a>], - current_category: &'a ReportCategoryItem<'a>, - canonical_path: &'a str, - canonical_url: &'a str, - image_url: &'a str, - current_unit: Option<&'a str>, - units_path: &'a str, - commit_message: Option<&'a str>, - commit_url: &'a str, - source_file_url: Option<&'a str>, - code_progress: Vec, - data_progress: Vec, - current_user: Option, -} - #[derive(Serialize)] pub struct ReportTemplateUnit<'a> { - name: &'a str, - total_code: u64, - fuzzy_match_percent: f32, - color: String, - x: f32, - y: f32, - w: f32, - h: f32, + pub name: &'a str, + pub total_code: u64, + pub fuzzy_match_percent: f32, + pub color: String, + pub x: f32, + pub y: f32, + pub w: f32, + pub h: f32, } #[derive(Serialize, Clone)] @@ -274,10 +251,8 @@ async fn mode_report( if (mime.type_() == mime::STAR && mime.subtype() == mime::STAR) || (mime.type_() == mime::TEXT && mime.subtype() == mime::HTML) { - let mut rendered = render_template(scope, state, uri, current_user).await?; - let elapsed = start.elapsed(); - rendered = rendered.replace("[[time]]", &format!("{}ms", elapsed.as_millis())); - return Ok(Html(rendered).into_response()); + let rendered = render_template(scope, state, uri, current_user, start).await?; + return Ok(rendered.into_response()); } else if mime.type_() == mime::APPLICATION && mime.subtype() == mime::JSON { let flattened = scope.report.report.flatten(); return Ok(Json(flattened).into_response()); @@ -286,7 +261,7 @@ async fn mode_report( return Ok(Protobuf(&flattened).into_response()); } else if mime.type_() == mime::IMAGE && mime.subtype() == mime::SVG { let (w, h) = query.size(); - let svg = treemap::render_svg(&scope.units, w, h, state)?; + let svg = treemap::render_svg(&scope.units, w, h); return Ok(([(header::CONTENT_TYPE, mime::IMAGE_SVG.as_ref())], svg).into_response()); } else if mime.type_() == mime::IMAGE { let format = if mime.subtype() == mime::STAR { @@ -297,7 +272,7 @@ async fn mode_report( .ok_or_else(|| AppError::Status(StatusCode::NOT_ACCEPTABLE))? }; let (w, h) = query.size(); - let data = treemap::render_image(&scope.units, w, h, state, format)?; + let data = treemap::render_image(&scope.units, w, h, format)?; return Ok(([(header::CONTENT_TYPE, format.to_mime_type())], data).into_response()); } } @@ -368,9 +343,8 @@ async fn mode_history( state.db.fetch_all_reports(&scope.project_info.project, &scope.report.version).await?; let mut result = Vec::with_capacity(report_measures.len()); for report in report_measures { - let mut measures = Some(Cow::Borrowed( - report.report.measures(scope.project_info.project.default_category.as_deref()), - )); + let mut measures = + Some(*report.report.measures(scope.project_info.project.default_category.as_deref())); if let Some(unit_name) = query.unit.as_ref() { let full_report = state.db.upgrade_report(&report).await?; measures = full_report @@ -379,7 +353,7 @@ async fn mode_history( .iter() .find(|u| &u.name == unit_name) .and_then(|c| c.measures.as_ref()) - .map(|m| Cow::Owned(m.clone())); + .copied(); } else if let Some(category_id) = query.category.as_ref() { measures = report .report @@ -387,9 +361,9 @@ async fn mode_history( .iter() .find(|c| &c.id == category_id) .and_then(|c| c.measures.as_ref()) - .map(|m| Cow::Borrowed(m)); + .copied(); } - let Some(measures) = measures else { + let Some(measures) = &measures else { continue; }; result.push(ReportHistoryEntry { @@ -399,7 +373,7 @@ async fn mode_history( .format(&Rfc3339) .unwrap_or_else(|_| "[invalid]".to_string()), commit_sha: report.commit.sha, - measures: TemplateMeasures::from(measures.as_ref()), + measures: TemplateMeasures::from(measures), }); } for mime in acceptable { @@ -469,61 +443,62 @@ fn apply_scope<'a>( current_unit = Some(unit.as_ref()); } let (w, h) = query.size(); - let mut units = - if let Some(unit) = current_unit { - unit.functions - .iter() - .filter_map(|f| { - if f.size == 0 { + let mut units = if let Some(unit) = current_unit { + unit.functions + .iter() + .filter_map(|f| { + if f.size == 0 { + return None; + } + Some(ReportTemplateUnit { + name: f + .metadata + .as_ref() + .and_then(|m| m.demangled_name.as_deref()) + .unwrap_or(&f.name), + total_code: f.size, + fuzzy_match_percent: f.fuzzy_match_percent, + color: unit_color(f.fuzzy_match_percent), + x: 0.0, + y: 0.0, + w: 0.0, + h: 0.0, + }) + }) + .collect::>() + } else { + report + .report + .units + .iter() + .filter_map(|unit| { + if let Some(category_id) = &category_id_filter { + if !unit + .metadata + .as_ref() + .is_some_and(|m| m.progress_categories.iter().any(|c| c == category_id)) + { return None; } - Some(ReportTemplateUnit { - name: f - .metadata - .as_ref() - .and_then(|m| m.demangled_name.as_deref()) - .unwrap_or(&f.name), - total_code: f.size, - fuzzy_match_percent: f.fuzzy_match_percent, - color: treemap::unit_color(f.fuzzy_match_percent), - x: 0.0, - y: 0.0, - w: 0.0, - h: 0.0, - }) + } + let measures = unit.measures.as_ref()?; + if measures.total_code == 0 { + return None; + } + Some(ReportTemplateUnit { + name: &unit.name, + total_code: measures.total_code, + fuzzy_match_percent: measures.fuzzy_match_percent, + color: unit_color(measures.fuzzy_match_percent), + x: 0.0, + y: 0.0, + w: 0.0, + h: 0.0, }) - .collect::>() - } else { - report - .report - .units - .iter() - .filter_map(|unit| { - if let Some(category_id) = &category_id_filter { - if !unit.metadata.as_ref().map_or(false, |m| { - m.progress_categories.iter().any(|c| c == category_id) - }) { - return None; - } - } - let measures = unit.measures.as_ref()?; - if measures.total_code == 0 { - return None; - } - Some(ReportTemplateUnit { - name: &unit.name, - total_code: measures.total_code, - fuzzy_match_percent: measures.fuzzy_match_percent, - color: treemap::unit_color(measures.fuzzy_match_percent), - x: 0.0, - y: 0.0, - w: 0.0, - h: 0.0, - }) - }) - .collect::>() - }; - treemap::layout_units( + }) + .collect::>() + }; + layout_units( &mut units, w as f32 / h as f32, |i| i.total_code as f32, @@ -546,7 +521,8 @@ async fn render_template( state: &AppState, uri: Uri, current_user: Option, -) -> Result { + start: Instant, +) -> Result { let Scope { report, project_info, measures, current_category, current_unit, units, label } = scope; @@ -659,32 +635,157 @@ async fn render_template( Cow::Borrowed(project_info.project.short_name()) }; - render(&state.templates, "report.html", ReportTemplateContext { - project: &project_info.project, - project_name: project_name.as_ref(), - project_short_name: project_short_name.as_ref(), - project_url: &project_info.project.repo_url(), - project_path: &project_base_path, - commit: &report.commit.sha, - version: &report.version, - measures: TemplateMeasures::from(*measures), - units, - versions: &versions, - prev_commit_path: prev_commit_path.as_deref(), - next_commit_path: next_commit_path.as_deref(), - latest_commit_path: latest_commit_path.as_deref(), - categories: &categories, - current_category: ¤t_category, - canonical_path: canonical_url.path_and_query(), - canonical_url: canonical_url.as_ref(), - image_url: image_url.as_ref(), - current_unit: current_unit.map(|u| u.name.as_str()), - units_path: &units_path, - commit_message, - commit_url: &commit_url, - source_file_url: source_file_url.as_deref(), - code_progress: code_progress_sections(measures), - data_progress: data_progress_sections(measures), - current_user: current_user.map(|u| u.profile), + Ok(html! { + (DOCTYPE) + html { + head lang="en" { + meta charset="utf-8"; + title { (project_short_name) " • Progress Report" } + (header()) + meta name="description" content=(format!("Decompilation progress report for {project_name}")); + meta property="og:title" content=(format!("{project_short_name} is {:.2}% decompiled", measures.matched_code_percent)); + meta property="og:description" content=(format!("Decompilation progress report for {project_name}")); + meta property="og:image" content=(image_url); + meta property="og:url" content=(canonical_url); + script src="/js/treemap.min.js" {} + } + body { + header { + nav { + ul { + li { + a href="https://decomp.dev" { strong { "decomp.dev" } } + } + li { + a href="/" { "Projects" } + } + li { + a href=(project_base_path) { (project_short_name) } + } + li class="md" { + details class="dropdown" { + summary { (report.version) } + ul { + @for version in &versions { + li { + a href=(version.path) { (version.id) } + } + } + } + } + } + } + (nav_links()) + } + } + main { + h3 { (format!("{project_short_name} is {:.2}% decompiled", measures.matched_code_percent)) } + @if current_unit.is_none() && measures.complete_code_percent > 0.0 { + h4 class="muted" { (format!("{:.2}% fully linked", measures.complete_code_percent)) } + } + @if let Some(source_file_url) = source_file_url { + h4 class="muted" { + a href=(source_file_url) target="_blank" { "View source file" } + } + } + details class="dropdown sm" { + summary { (report.version) } + ul { + @for version in &versions { + li { + a href=(version.path) { (version.id) } + } + } + } + } + @if measures.total_code > 0 { + h6 class="report-header" { + "Code " + small class="muted" { "(" (size(measures.total_code)) ")" } + } + (code_progress_sections(&measures)) + } + @if measures.total_data > 0 { + h6 class="report-header" { + "Data " + small class="muted" { "(" (size(measures.total_data)) ")" } + } + (data_progress_sections(&measures)) + } + h6 class="report-header" { "Commit" } + div { + @if let Some(message) = commit_message { + pre { + a href=(commit_url) target="_blank" { + (report.commit.sha[..7]) + } + " | " + (message) + } + } + div role="group" { + @if let Some(prev_commit_path) = prev_commit_path { + a role="button" class="outline secondary" href=(prev_commit_path) { + "Previous" + } + } @else { + button disabled class="outline secondary" { + "Previous" + } + } + @if let Some(next_commit_path) = next_commit_path { + a role="button" class="outline secondary" href=(next_commit_path) { + "Next" + } + } @else { + button disabled class="outline secondary" { + "Next" + } + } + @if let Some(latest_commit_path) = latest_commit_path { + a role="button" class="primary" href=(latest_commit_path) { + "Latest" + } + } @else { + button disabled class="primary" { + "Latest" + } + } + } + } + @if current_unit.is_some() { + h6 class="report-header" { "Functions" } + div role="group" { + a role="button" href=(units_path) { "Back to units" } + } + } @else { + h6 class="report-header" { "Units" } + @if categories.len() > 1 { + details class="dropdown" { + summary { (current_category.name) } + ul { + @for category in &categories { + li { + a href=(category.path) { (category.name) } + } + } + } + } + } + } + script { + (PreEscaped(r#"document.write('');drawTreemap("treemap","#)) + (current_unit.is_none()) + "," + (PreEscaped(serde_json::to_string(&units)?)) + ");" + } + noscript { + img #treemap src=(image_url) alt="Progress graph"; + } + } + } + (footer(start, current_user.as_ref())) + } }) } diff --git a/crates/web/src/handlers/treemap.rs b/crates/web/src/handlers/treemap.rs new file mode 100644 index 0000000..874bb51 --- /dev/null +++ b/crates/web/src/handlers/treemap.rs @@ -0,0 +1,34 @@ +use anyhow::Result; +use decomp_dev_images::svg; +use image::ImageFormat; +use maud::{PreEscaped, html}; + +use crate::handlers::report::ReportTemplateUnit; + +pub fn render_svg(units: &[ReportTemplateUnit], w: u32, h: u32) -> String { + html! { + (PreEscaped("")) + svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox=(format!("0 0 {w} {h}")) { + style { ".unit { stroke: #000; stroke-width: 1; }" } + @for unit in units { + rect class="unit" + width=(format!("{}%", unit.w * 100.0)) + height=(format!("{}%", unit.h * 100.0)) + x=(format!("{}%", unit.x * 100.0)) + y=(format!("{}%", unit.y * 100.0)) + fill=(unit.color) {} + } + } + } + .into_string() +} + +pub fn render_image( + units: &[ReportTemplateUnit], + w: u32, + h: u32, + format: ImageFormat, +) -> Result> { + let svg = render_svg(units, w, h); + svg::render_image(&svg, format) +} diff --git a/src/main.rs b/crates/web/src/main.rs similarity index 79% rename from src/main.rs rename to crates/web/src/main.rs index a970f3e..6d24a04 100644 --- a/src/main.rs +++ b/crates/web/src/main.rs @@ -1,13 +1,7 @@ -mod config; mod cron; -mod db; mod frogress; -mod github; mod handlers; -mod models; -mod svg; -mod templates; -mod util; +mod proto; use std::{ fs::File, @@ -18,32 +12,46 @@ use std::{ }; use axum::{ - http::{header, Method}, Router, + extract::FromRef, + http::{Method, header}, }; +use decomp_dev_core::config::{Config, GitHubConfig}; +use decomp_dev_db::Database; +use decomp_dev_github::{GitHub, webhook::WebhookState}; use tokio::{net::TcpListener, signal}; use tower::ServiceBuilder; use tower_http::{ - cors, + ServiceBuilderExt, cors, cors::CorsLayer, timeout::TimeoutLayer, trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}, - ServiceBuilderExt, }; use tower_sessions::{Expiry, SessionManagerLayer, SessionStore}; use tower_sessions_sqlx_store::SqliteStore; -use tracing_subscriber::{filter::LevelFilter, EnvFilter}; +use tracing_subscriber::{EnvFilter, filter::LevelFilter}; -use crate::{ - config::Config, db::Database, github::GitHub, handlers::build_router, templates::Templates, -}; +use crate::handlers::build_router; -#[derive(Clone)] +#[derive(Clone, FromRef)] struct AppState { config: Config, db: Database, github: GitHub, - templates: Templates, +} + +impl FromRef for WebhookState { + fn from_ref(state: &AppState) -> Self { + Self { + config: state.config.github.clone(), + db: state.db.clone(), + github: state.github.clone(), + } + } +} + +impl FromRef for GitHubConfig { + fn from_ref(state: &AppState) -> Self { state.config.github.clone() } } #[tokio::main] @@ -63,8 +71,7 @@ async fn main() { }; let db = Database::new(&config.db).await.expect("Failed to open database"); let github = GitHub::new(&config.github).await.expect("Failed to create GitHub client"); - let templates = templates::create("templates"); - let state = AppState { config, db: db.clone(), github, templates }; + let state = AppState { config, db: db.clone(), github }; // Create session store let session_store = SqliteStore::new(db.pool.clone()); @@ -113,7 +120,10 @@ fn app(state: AppState, session_store: impl SessionStore + Clone) -> Router { .with_expiry(Expiry::OnInactivity(time::Duration::days(1))), ) .compression(); - build_router().layer(middleware).with_state(state) + let router = build_router(); + #[cfg(debug_assertions)] + let router = router.layer(tower_livereload::LiveReloadLayer::new()); + router.layer(middleware).with_state(state) } async fn shutdown_signal() { diff --git a/crates/web/src/proto.rs b/crates/web/src/proto.rs new file mode 100644 index 0000000..69b51dd --- /dev/null +++ b/crates/web/src/proto.rs @@ -0,0 +1,20 @@ +use axum::{ + http::header, + response::{IntoResponse, Response}, +}; +use bytes::BytesMut; +use prost::Message; + +pub struct Protobuf<'a, T>(pub &'a T) +where T: Message; + +pub const APPLICATION_PROTOBUF: &str = "application/x-protobuf"; +pub const PROTOBUF: &str = "x-protobuf"; + +impl IntoResponse for Protobuf<'_, T> { + fn into_response(self) -> Response { + let mut bytes = BytesMut::with_capacity(self.0.encoded_len()); + self.0.encode(&mut bytes).unwrap(); + ([(header::CONTENT_TYPE, APPLICATION_PROTOBUF)], bytes.freeze()).into_response() + } +} diff --git a/src/handlers/css.rs b/src/handlers/css.rs deleted file mode 100644 index 367e6d3..0000000 --- a/src/handlers/css.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::ffi::OsStr; - -use anyhow::anyhow; -use axum::{ - extract::Path, - http::{header, StatusCode}, - response::{IntoResponse, Response}, -}; - -use super::AppError; -use crate::util::join_normalized; - -pub async fn get_css(Path(filename): Path) -> Result { - let mut path = join_normalized("css", &filename); - if path.extension() != Some(OsStr::new("css")) { - return Err(AppError::Status(StatusCode::NOT_FOUND)); - } - path = path.with_extension(""); - let printer_options = lightningcss::stylesheet::PrinterOptions { - minify: path.extension() == Some(OsStr::new("min")), - ..Default::default() - }; - path = path.with_extension("scss"); - let options = grass::Options::default().load_path("node_modules"); - let mut output = grass::from_path(&path, &options)?; - // Skip lightningcss entirely if we're not minifying - if printer_options.minify { - let options = lightningcss::stylesheet::ParserOptions::default(); - let stylesheet = lightningcss::stylesheet::StyleSheet::parse(&output, options) - .map_err(|e| anyhow!(e.to_string()))?; - let result = stylesheet.to_css(printer_options)?; - drop(stylesheet); - output = result.code; - } - Ok(( - [ - (header::CONTENT_TYPE, mime::TEXT_CSS_UTF_8.as_ref()), - #[cfg(not(debug_assertions))] - (header::CACHE_CONTROL, "public, max-age=3600"), - #[cfg(debug_assertions)] - (header::CACHE_CONTROL, "no-cache"), - ], - output, - ) - .into_response()) -} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs deleted file mode 100644 index 34e8da9..0000000 --- a/src/handlers/mod.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::{convert::Infallible, net::SocketAddr, str::FromStr}; - -use axum::{ - extract::{ConnectInfo, FromRequestParts, OriginalUri}, - http::{header, request::Parts, HeaderMap, StatusCode, Uri}, - response::{IntoResponse, Response}, - routing::{get, post}, - Extension, Router, -}; -use bytes::BytesMut; -use mime::Mime; -use prost::Message; - -use crate::AppState; - -mod assets; -mod auth; -mod badge; -mod css; -mod github; -mod js; -mod og; -mod project; -mod report; -mod treemap; - -pub fn build_router() -> Router { - Router::new() - .route("/api/github/webhook", post(github::webhook)) - .route("/api/github/oauth", get(auth::oauth)) - .route("/login", get(auth::login)) - .route("/logout", post(auth::logout)) - .route("/css/{*filename}", get(css::get_css)) - .route("/js/{*filename}", get(js::get_js)) - .route("/assets/{*filename}", get(assets::get_asset)) - .route("/og.png", get(og::get_og)) - .route("/", get(project::get_projects)) - .route("/{owner}/{repo}", get(report::get_report)) - .route("/{owner}/{repo}/{version}", get(report::get_report)) - .route("/{owner}/{repo}/{version}/{commit}", get(report::get_report)) -} - -pub enum AppError { - Status(StatusCode), - Internal(anyhow::Error), -} - -impl IntoResponse for AppError { - fn into_response(self) -> Response { - match self { - Self::Status(status) if status == StatusCode::NOT_FOUND => { - (status, "Not found").into_response() - } - Self::Status(status) => status.into_response(), - Self::Internal(err) => { - tracing::error!("{:?}", err); - (StatusCode::INTERNAL_SERVER_ERROR, format!("Something went wrong: {}", err)) - .into_response() - } - } - } -} - -impl> From for AppError { - fn from(err: E) -> Self { Self::Internal(err.into()) } -} - -pub fn parse_accept(headers: &HeaderMap, ext: Option<&str>) -> Vec { - // Explicit extension takes precedence - if let Some(ext) = ext { - return match ext.to_ascii_lowercase().as_str() { - "json" => vec![mime::APPLICATION_JSON], - "binpb" | "proto" => vec![Mime::from_str("application/x-protobuf").unwrap()], - "svg" => vec![mime::IMAGE_SVG], - _ => { - if let Some(format) = image::ImageFormat::from_extension(ext) { - vec![Mime::from_str(format.to_mime_type()).unwrap()] - } else { - // An unknown extension should be NOT_ACCEPTABLE, not */*. - vec![] - } - } - }; - } - // Otherwise, parse the Accept header - let result = headers - .get(header::ACCEPT) - .and_then(|value| value.to_str().ok()) - .iter() - .flat_map(|s| s.split(',')) - .map(|s| s.trim()) - .filter_map(|s| Mime::from_str(s).ok()) - .collect::>(); - if result.is_empty() { - // If no Accept header is present, use */* - vec![mime::STAR_STAR] - } else { - result - } -} - -pub struct Protobuf<'a, T>(pub &'a T) -where T: Message; - -pub const APPLICATION_PROTOBUF: &str = "application/x-protobuf"; -pub const PROTOBUF: &str = "x-protobuf"; - -impl IntoResponse for Protobuf<'_, T> { - fn into_response(self) -> Response { - let mut bytes = BytesMut::with_capacity(self.0.encoded_len()); - self.0.encode(&mut bytes).unwrap(); - ([(header::CONTENT_TYPE, APPLICATION_PROTOBUF)], bytes.freeze()).into_response() - } -} - -/// Extractor for the full URI of the request, including the scheme and authority. -/// Uses the `x-forwarded-proto` and `x-forwarded-host` headers if present. -pub struct FullUri(pub Uri); - -impl FromRequestParts for FullUri -where S: Send + Sync -{ - type Rejection = Infallible; - - async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - let uri = Extension::::from_request_parts(parts, state) - .await - .map_or_else(|_| parts.uri.clone(), |Extension(OriginalUri(uri))| uri); - let mut builder = Uri::builder(); - if let Some(scheme) = - parts.headers.get("x-forwarded-proto").and_then(|value| value.to_str().ok()) - { - builder = builder.scheme(scheme); - } else if let Some(scheme) = uri.scheme().cloned() { - builder = builder.scheme(scheme); - } else { - // TODO: native https? - builder = builder.scheme("http"); - } - if let Some(host) = - parts.headers.get("x-forwarded-host").and_then(|value| value.to_str().ok()) - { - builder = builder.authority(host); - } else if let Some(host) = - parts.headers.get(header::HOST).and_then(|value| value.to_str().ok()) - { - builder = builder.authority(host); - } else if let Some(authority) = uri.authority().cloned() { - builder = builder.authority(authority); - } else if let Ok(ConnectInfo(socket_addr)) = - ConnectInfo::::from_request_parts(parts, state).await - { - builder = builder.authority(socket_addr.to_string()); - } - if let Some(path_and_query) = uri.path_and_query().cloned() { - builder = builder.path_and_query(path_and_query); - } - Ok(FullUri(builder.build().unwrap_or(uri))) - } -} diff --git a/src/handlers/og.rs b/src/handlers/og.rs deleted file mode 100644 index 00851fe..0000000 --- a/src/handlers/og.rs +++ /dev/null @@ -1,19 +0,0 @@ -use axum::{ - http::{header, StatusCode}, - response::{IntoResponse, Response}, -}; - -use crate::{handlers::AppError, svg, util::join_normalized}; - -pub async fn get_og() -> Result { - let path = join_normalized("templates", "og.svg"); - let svg_src = tokio::fs::read_to_string(&path) - .await - .map_err(|_| AppError::Status(StatusCode::NOT_FOUND))?; - let data = svg::render_image(&svg_src, image::ImageFormat::Png)?; - Ok(( - [(header::CONTENT_TYPE, "image/png"), (header::CACHE_CONTROL, "public, max-age=3600")], - data, - ) - .into_response()) -} diff --git a/src/handlers/project.rs b/src/handlers/project.rs deleted file mode 100644 index c9e9318..0000000 --- a/src/handlers/project.rs +++ /dev/null @@ -1,272 +0,0 @@ -use std::{sync::Arc, time::Instant}; - -use anyhow::{anyhow, Context}; -use axum::{ - extract::{Query, State}, - http::StatusCode, - response::{Html, IntoResponse, Response}, -}; -use objdiff_core::bindings::report::Measures; -use octocrab::models::Author; -use serde::{Deserialize, Serialize}; -use time::UtcDateTime; -use tokio::{sync::Semaphore, task::JoinSet}; -use url::Url; - -use super::{AppError, FullUri}; -use crate::{ - handlers::{auth::CurrentUser, report::TemplateMeasures}, - templates::{render, size}, - util::UrlExt, - AppState, -}; - -#[derive(Serialize)] -struct ProjectsTemplateContext<'a> { - projects: Vec, - sort_options: &'static [SortOption], - current_sort: SortOption, - canonical_url: &'a str, - image_url: &'a str, - current_user: Option, -} - -#[derive(Serialize)] -struct ProjectInfoContext { - id: u64, - path: String, - owner: String, - repo: String, - name: String, - short_name: String, - commit: String, - timestamp: UtcDateTime, - measures: TemplateMeasures, - platform: Option, - code_progress: Vec, -} - -#[derive(Deserialize)] -pub struct ProjectsQuery { - sort: Option, -} - -#[derive(Serialize, Copy, Clone)] -struct SortOption { - key: &'static str, - name: &'static str, -} - -const SORT_OPTIONS: &[SortOption] = &[ - SortOption { key: "updated", name: "Last updated" }, - SortOption { key: "name", name: "Name" }, - SortOption { key: "matched_code_percent", name: "Matched Code (Percent)" }, - SortOption { key: "matched_code", name: "Matched Code" }, - SortOption { key: "total_code", name: "Total Code" }, -]; - -#[derive(Serialize, Clone)] -pub struct ProgressSection { - pub class: String, - pub percent: f32, - pub tooltip: String, -} - -pub async fn get_projects( - State(state): State, - Query(query): Query, - FullUri(uri): FullUri, - current_user: Option, -) -> Result { - let start = Instant::now(); - let projects = state.db.get_projects().await?; - let mut out = projects - .iter() - .filter_map(|p| { - let commit = p.commit.as_ref()?; - Some(ProjectInfoContext { - id: p.project.id, - path: format!("/{}/{}", p.project.owner, p.project.repo), - owner: p.project.owner.clone(), - repo: p.project.repo.clone(), - name: p.project.name().into_owned(), - short_name: p.project.short_name().to_owned(), - commit: commit.sha.clone(), - timestamp: commit.timestamp, - measures: Default::default(), - platform: p.project.platform.clone(), - code_progress: vec![], - }) - }) - .collect::>(); - - // Fetch latest report for each - let sem = Arc::new(Semaphore::new(10)); - let mut join_set = JoinSet::new(); - for info in projects { - let sem = sem.clone(); - let state = state.clone(); - join_set.spawn(async move { - let _permit = sem.acquire().await; - let Some(version) = info.default_version() else { - return (info, Err(anyhow!("No report version found"))); - }; - let commit = info.commit.as_ref().unwrap(); - let report = state - .db - .get_report(&info.project.owner, &info.project.repo, &commit.sha, version) - .await - .with_context(|| { - format!( - "Failed to fetch report for {}/{} sha {} version {}", - info.project.owner, info.project.repo, commit.sha, version - ) - }); - (info, report) - }); - } - while let Some(result) = join_set.join_next().await { - match result { - Ok((info, Ok(Some(file)))) => { - if let Some(c) = out.iter_mut().find(|i| i.id == info.project.id) { - let measures = file.report.measures(info.project.default_category.as_deref()); - c.measures = TemplateMeasures::from(measures); - c.code_progress = code_progress_sections(measures); - } - } - Ok((info, Ok(None))) => { - tracing::warn!("No report found for {}", info.project.id); - } - Ok((info, Err(e))) => { - tracing::error!("Failed to fetch report for {}: {:?}", info.project.id, e); - } - Err(e) => { - tracing::error!("Failed to fetch report: {:?}", e); - } - } - } - - let current_sort_key = query.sort.as_deref().unwrap_or("updated"); - let current_sort = SORT_OPTIONS - .iter() - .find(|s| s.key.eq_ignore_ascii_case(current_sort_key)) - .copied() - .ok_or(AppError::Status(StatusCode::BAD_REQUEST))?; - match current_sort.key { - "name" => out.sort_by(|a, b| a.name.cmp(&b.name)), - "updated" => out.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)), - "matched_code_percent" => out.sort_by(|a, b| { - b.measures - .matched_code_percent - .partial_cmp(&a.measures.matched_code_percent) - .unwrap_or(std::cmp::Ordering::Equal) - }), - "matched_code" => out.sort_by(|a, b| { - b.measures - .matched_code - .partial_cmp(&a.measures.matched_code) - .unwrap_or(std::cmp::Ordering::Equal) - }), - "total_code" => out.sort_by(|a, b| { - b.measures - .total_code - .partial_cmp(&a.measures.total_code) - .unwrap_or(std::cmp::Ordering::Equal) - }), - _ => return Err(AppError::Status(StatusCode::BAD_REQUEST)), - } - - let request_url = Url::parse(&uri.to_string()).context("Failed to parse URI")?; - let canonical_url = request_url.with_path(""); - let image_url = canonical_url.with_path("/og.png"); - - let mut rendered = render(&state.templates, "projects.html", ProjectsTemplateContext { - projects: out, - sort_options: SORT_OPTIONS, - current_sort, - canonical_url: canonical_url.as_str(), - image_url: image_url.as_str(), - current_user: current_user.map(|u| u.profile), - })?; - let elapsed = start.elapsed(); - rendered = rendered.replace("[[time]]", &format!("{}ms", elapsed.as_millis())); - Ok(Html(rendered).into_response()) -} - -pub fn code_progress_sections(measures: &Measures) -> Vec { - let mut out = vec![]; - if measures.total_code == 0 { - return out; - } - let mut add_section = |class: &str, percent: f32, tooltip: String| { - out.push(ProgressSection { class: class.to_owned(), percent, tooltip }); - }; - let mut current_percent = 0.0; - if measures.complete_code_percent > 0.0 { - add_section( - "progress-section", - measures.complete_code_percent - current_percent, - format!( - "{:.2}% fully linked ({})", - measures.complete_code_percent, - size(measures.complete_code) - ), - ); - current_percent = measures.complete_code_percent; - } - if measures.matched_code_percent > 0.0 && measures.matched_code_percent > current_percent { - add_section( - if current_percent > 0.0 { "progress-section striped" } else { "progress-section" }, - measures.matched_code_percent - current_percent, - format!( - "{:.2}% perfect match ({})", - measures.matched_code_percent, - size(measures.matched_code) - ), - ); - current_percent = measures.matched_code_percent; - } - if measures.fuzzy_match_percent > 0.0 && measures.fuzzy_match_percent > current_percent { - add_section( - "progress-section striped fuzzy", - measures.fuzzy_match_percent - current_percent, - format!("{:.2}% fuzzy match", measures.fuzzy_match_percent), - ); - } - out -} - -pub fn data_progress_sections(measures: &Measures) -> Vec { - let mut out = vec![]; - if measures.total_data == 0 { - return out; - } - let mut add_section = |class: &str, percent: f32, tooltip: String| { - out.push(ProgressSection { class: class.to_owned(), percent, tooltip }); - }; - let mut current_percent = 0.0; - if measures.complete_data_percent > 0.0 { - add_section( - "progress-section", - measures.complete_data_percent - current_percent, - format!( - "{:.2}% fully linked ({})", - measures.complete_data_percent, - size(measures.complete_data) - ), - ); - current_percent = measures.complete_data_percent; - } - if measures.matched_data_percent > 0.0 && measures.matched_data_percent > current_percent { - add_section( - if current_percent > 0.0 { "progress-section striped" } else { "progress-section" }, - measures.matched_data_percent - current_percent, - format!( - "{:.2}% perfect match ({})", - measures.matched_data_percent, - size(measures.matched_data) - ), - ); - } - out -} diff --git a/src/templates.rs b/src/templates.rs deleted file mode 100644 index c7ee647..0000000 --- a/src/templates.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::{borrow::Cow, sync::Arc, time::Duration}; - -use anyhow::{Context, Result}; -use minijinja::{path_loader, Environment}; -use minijinja_autoreload::AutoReloader; -use time::{format_description, macros::format_description, UtcDateTime}; - -pub type Templates = Arc; - -pub fn create(template_path: impl Into) -> Templates { - let template_path = template_path.into(); - Arc::new(AutoReloader::new(move |notifier| { - let mut env = Environment::new(); - let template_path = template_path.as_str(); - notifier.watch_path(template_path, true); - env.set_loader(path_loader(template_path)); - env.set_trim_blocks(true); - env.set_lstrip_blocks(true); - env.add_filter("date", date); - env.add_filter("timeago", timeago); - env.add_filter("size", size); - Ok(env) - })) -} - -pub fn render(templates: &Templates, template_name: &str, context: S) -> Result -where S: serde::Serialize { - let env = templates.acquire_env().context("Failed to get template environment")?; - let template = env.get_template(template_name).context("Failed to get template")?; - template.render(context).context("Failed to render template") -} - -fn timeago(value: String) -> String { - let Ok(value) = serde_json::from_str::(&value) else { - return format!("[invalid {}]", value); - }; - let Ok(duration) = Duration::try_from(UtcDateTime::now() - value) else { - return "[out of range]".to_string(); - }; - timeago::Formatter::new().convert(duration) -} - -fn date(value: String, format: Option) -> String { - let Ok(value) = serde_json::from_str::(&value) else { - return format!("[invalid {}]", value); - }; - let format = if let Some(format) = format.as_deref() { - match format_description::parse_borrowed::<2>(format) { - Ok(format) => Cow::Owned(format), - Err(_) => return format!("[invalid format {}]", format), - } - } else { - Cow::Borrowed(format_description!("[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour sign:mandatory]:[offset_minute]")) - }; - value.format(&format).unwrap_or_else(|_| "[invalid]".to_string()) -} - -/// Format a size in bytes to a human-readable string. -/// Uses SI (kilo = 1000) units, formatted to two decimal places. -pub fn size(value: u64) -> String { - let units = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - let mut value = value as f64; - let mut unit = 0; - while value >= 1000.0 && unit < units.len() - 1 { - value /= 1000.0; - unit += 1; - } - format!("{:.2} {}", value, units[unit]) -} diff --git a/templates/fragments/footer.html b/templates/fragments/footer.html deleted file mode 100644 index f1d3e31..0000000 --- a/templates/fragments/footer.html +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/templates/fragments/header.html b/templates/fragments/header.html deleted file mode 100644 index d7c0f60..0000000 --- a/templates/fragments/header.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - diff --git a/templates/fragments/nav-links.html b/templates/fragments/nav-links.html deleted file mode 100644 index 48fa74e..0000000 --- a/templates/fragments/nav-links.html +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/templates/oldgraphs.html b/templates/oldgraphs.html deleted file mode 100644 index ad7e84e..0000000 --- a/templates/oldgraphs.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - \ No newline at end of file diff --git a/templates/projects.html b/templates/projects.html deleted file mode 100644 index 960ed12..0000000 --- a/templates/projects.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - Projects • decomp.dev - {% include 'fragments/header.html' %} - - - - - - - -
- -
-

Progress Reports

-
- Matching decompilation projects attempt to write source code (C, C++) that compiles to the same binary as the original. - All source code is written from scratch. - -
-
-
-
- - {% for project in projects %} - {% set measures = project.measures %} -
-
-

- - {{ project.name }} - -

- {% if project.platform %} - {{ project.platform }} - {% endif %} -
-
- {% if current_sort.key == 'total_code' or current_sort.key == 'matched_code' %} - {{ measures.matched_code | size }} matched code - | {{ measures.total_code | size }} total code - {% else %} - {{ measures.matched_code_percent | round(2) }}% decompiled - {% if measures.complete_code_percent > 0 %} - | {{ measures.complete_code_percent | round(2) }}% fully linked - {% endif %} - {% endif %} -
-
- {% for section in project.code_progress %} -
- {% endfor %} -
- - Updated {{ project.timestamp | timeago }} - in commit {{ project.commit[:7] }} - -
- {% endfor %} -
-{% include 'fragments/footer.html' %} - - diff --git a/templates/report.html b/templates/report.html deleted file mode 100644 index ce691e0..0000000 --- a/templates/report.html +++ /dev/null @@ -1,126 +0,0 @@ - - - - - {{ project_short_name }} • Progress Report - {% include 'fragments/header.html' %} - - - - - - - - -
- -
-
-

{{ project_short_name }} is {{ measures.matched_code_percent | round(2) }}% decompiled

- {% if not current_unit and measures.complete_code_percent > 0 %} -

{{ measures.complete_code_percent | round(2) }}% fully linked

- {% endif %} - {% if source_file_url %} -

View source file

- {% endif %} - -
Code ({{ measures.total_code | size }})
-
- {% for section in code_progress %} -
- {% endfor %} -
- {% if data_progress %} -
Data ({{ measures.total_data | size }})
-
- {% for section in data_progress %} -
- {% endfor %} -
- {% endif %} -
Commit
-
- {% if commit_message %} -
{{ commit[:7] }} | {{ commit_message }}
- {% endif %} -
- {% if prev_commit_path %} - Previous - {% else %} - - {% endif %} - {% if next_commit_path %} - Next - {% else %} - - {% endif %} - {% if latest_commit_path %} - Latest - {% else %} - - {% endif %} -
-
- {% if current_unit %} -
Functions
- - {% else %} -
Units
- {% if categories | length > 1 %} - - {% endif %} - {% endif %} - - -
-{% include 'fragments/footer.html' %} - - diff --git a/templates/treemap.svg b/templates/treemap.svg deleted file mode 100644 index f4de12e..0000000 --- a/templates/treemap.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - {% for unit in units %} - - {% endfor %} -