diff --git a/Cargo.lock b/Cargo.lock index 9134f0604..e27c21833 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,7 +49,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", ] @@ -135,9 +135,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arcstr" @@ -193,7 +193,7 @@ dependencies = [ "proc-macro2", "quote", "rustc-hash", - "syn", + "syn 2.0.119", ] [[package]] @@ -394,7 +394,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -405,13 +405,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.2", ] [[package]] @@ -628,9 +628,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.67" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" +checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" dependencies = [ "find-msvc-tools", "shlex", @@ -706,9 +706,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.2" +version = "4.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" +checksum = "0fb99565819980999fb7b4a1796046a5c949e6d4ff132cf5fadf5a641e20d776" dependencies = [ "clap_builder", "clap_derive", @@ -748,14 +748,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "32f2392eae7f16557a3d727ef3a12e57b2b2ca6f98566a5f4fb41ffe305df077" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -959,7 +959,7 @@ dependencies = [ "subtle", "swagger-ui-redist", "tempfile", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", "tokio", "toml", @@ -999,7 +999,9 @@ dependencies = [ "proc-macro2", "quote", "rand 0.10.2", - "syn", + "serde", + "serde_json", + "syn 2.0.119", "tempfile", "tracing", "tracing-subscriber", @@ -1015,7 +1017,7 @@ dependencies = [ "cot-cli", "glob", "libtest-mimic", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] @@ -1026,7 +1028,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "tracing", ] @@ -1057,7 +1059,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "sync_wrapper", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tower", "tower-sessions", @@ -1076,7 +1078,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.119", "trybuild", ] @@ -1248,7 +1250,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.119", ] [[package]] @@ -1261,7 +1263,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.119", ] [[package]] @@ -1272,7 +1274,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1283,7 +1285,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core 0.23.0", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1343,7 +1345,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1353,7 +1355,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn", + "syn 2.0.119", ] [[package]] @@ -1375,7 +1377,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.119", "unicode-xid", ] @@ -1421,7 +1423,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1451,7 +1453,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1522,7 +1524,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1676,9 +1678,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "find-msvc-tools" @@ -1771,9 +1773,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" dependencies = [ "futures-channel", "futures-core", @@ -1785,9 +1787,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -1795,15 +1797,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -1823,9 +1825,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-lite" @@ -1842,32 +1844,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-core", "futures-io", @@ -1934,9 +1936,9 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "gloo-timers" @@ -2128,9 +2130,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", @@ -2341,7 +2343,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2476,7 +2478,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.18", + "thiserror 2.0.19", "walkdir", "windows-link", ] @@ -2491,7 +2493,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn", + "syn 2.0.119", ] [[package]] @@ -2510,7 +2512,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2578,9 +2580,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.186" +version = "0.2.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "22053b6a34f84abc97f9129e61334f40174659a1b9bd18c970b83db6a9a6348b" [[package]] name = "libm" @@ -2737,7 +2739,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2866,7 +2868,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3035,7 +3037,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3194,7 +3196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.119", ] [[package]] @@ -3208,18 +3210,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -3360,22 +3362,22 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.2", ] [[package]] @@ -3489,7 +3491,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3546,7 +3548,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3619,7 +3621,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 2.0.119", ] [[package]] @@ -3693,9 +3695,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -3703,22 +3705,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.2", ] [[package]] @@ -3729,7 +3731,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3745,9 +3747,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -3949,7 +3951,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tokio-stream", "tracing", @@ -3966,7 +3968,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn", + "syn 2.0.119", ] [[package]] @@ -3989,7 +3991,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn", + "syn 2.0.119", "tokio", "url", ] @@ -4017,7 +4019,7 @@ dependencies = [ "sha1", "sha2 0.11.0", "sqlx-core", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", ] @@ -4052,7 +4054,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "whoami", ] @@ -4077,7 +4079,7 @@ dependencies = [ "percent-encoding", "serde", "sqlx-core", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "url", ] @@ -4141,6 +4143,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -4158,14 +4171,14 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "target-triple" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" +checksum = "c3a6bfce3d99adfa72d24750a61f782f3036a81e7f86d8841ee1326deaebd171" [[package]] name = "tempfile" @@ -4177,7 +4190,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4216,11 +4229,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.19", ] [[package]] @@ -4231,18 +4244,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.2", ] [[package]] @@ -4256,9 +4269,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.53" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" dependencies = [ "deranged", "num-conv", @@ -4276,9 +4289,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -4330,9 +4343,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.53.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -4346,13 +4359,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4388,13 +4401,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] @@ -4557,7 +4571,7 @@ dependencies = [ "rand 0.9.5", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", "tokio", "tracing", @@ -4595,7 +4609,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4655,7 +4669,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad06847b7afb65c7866a36664b75c40b895e318cea4f71299f013fb22965329d" dependencies = [ "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4776,9 +4790,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-bag" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd4ec1eb1d240636e354a30110a1dfcb37047169a4d9bd6d9d3469df574b5c4" +checksum = "ef73bfbaf3216cb59c205d7176bee1194e0d84348979da31f4a71fefe3c2054e" [[package]] name = "vcpkg" @@ -4886,7 +4900,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -4931,9 +4945,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" dependencies = [ "rustls-pki-types", ] @@ -4966,7 +4980,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4996,7 +5010,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -5007,7 +5021,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -5145,9 +5159,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "xxhash-rust" -version = "0.8.17" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985eec839aaf2a1270af8f4ebcf63cf9401cfd90f0902f97c28d9f104ffbde72" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" [[package]] name = "yoke" @@ -5180,7 +5194,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -5192,28 +5206,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -5233,7 +5247,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -5284,7 +5298,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -5295,7 +5309,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] diff --git a/cot-cli/Cargo.toml b/cot-cli/Cargo.toml index 0dd89af59..3509b0825 100644 --- a/cot-cli/Cargo.toml +++ b/cot-cli/Cargo.toml @@ -42,6 +42,8 @@ quote.workspace = true syn.workspace = true tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter"] } +serde = { workspace = true, features = ["derive"] } +serde_json = {workspace = true} [dev-dependencies] cot-cli = { path = ".", features = ["test_utils"] } diff --git a/cot-cli/src/args.rs b/cot-cli/src/args.rs index 1e35ceec8..3deb7fb44 100644 --- a/cot-cli/src/args.rs +++ b/cot-cli/src/args.rs @@ -1,8 +1,15 @@ +use std::ffi::OsString; use std::path::PathBuf; use clap::{Args, Parser, Subcommand}; use clap_verbosity_flag::Verbosity; +pub const PACKAGE_LONG_FLAG: &str = "--package"; +pub const PACKAGE_SHORT_FLAG: &str = "-p"; +pub const RELEASE_FLAG: &str = "--release"; +pub const HELP_LONG_FLAG: &str = "--help"; +pub const HELP_SHORT_FLAG: &str = "-h"; + #[derive(Debug, Parser)] #[command( name = "cot", @@ -11,6 +18,13 @@ use clap_verbosity_flag::Verbosity; long_about = None )] pub struct Cli { + /// Use target/release instead of target/debug when looking for the project + /// binary + #[arg(long, global = true)] + release: bool, + /// Package to use, in case you're running this in a workspace + #[arg(short = 'p', long, global = true, value_name = "PACKAGE")] + pub package: Option, #[command(flatten)] pub verbose: Verbosity, #[command(subcommand)] @@ -29,6 +43,9 @@ pub enum Commands { /// Manage Cot CLI #[command(subcommand)] Cli(CliCommands), + + #[command(external_subcommand)] + External(Vec), } #[derive(Debug, Args)] @@ -119,3 +136,71 @@ pub struct CompletionsArgs { /// Shell to generate completions for pub shell: clap_complete::Shell, } + +/// Pulls `-p ` / `--package ` / `--package=` out of raw +/// argv, before clap has parsed anything. Needed because `project::load` +/// must run before `Cli::parse()` for the `--help` interception path. +#[must_use] +pub fn extract_package_arg(raw: &[String]) -> Option { + let mut iter = raw.iter(); + while let Some(arg) = iter.next() { + if let Some(value) = arg.strip_prefix(&format!("{PACKAGE_LONG_FLAG}=")) { + return Some(value.to_string()); + } + if arg == PACKAGE_LONG_FLAG || arg == PACKAGE_SHORT_FLAG { + return iter.next().cloned(); + } + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + + fn args(raw: &[&str]) -> Vec { + raw.iter().map(|arg| (*arg).to_string()).collect() + } + + #[test] + fn extract_package_arg_long_with_separate_value() { + let raw = args(&["cot", "--release", "--package", "blog", "check"]); + + assert_eq!(extract_package_arg(&raw), Some("blog".to_string())); + } + + #[test] + fn extract_package_arg_long_with_equals_value() { + let raw = args(&["cot", "--package=blog", "check"]); + + assert_eq!(extract_package_arg(&raw), Some("blog".to_string())); + } + + #[test] + fn extract_package_arg_short_with_value() { + let raw = args(&["cot", "-p", "blog", "check"]); + + assert_eq!(extract_package_arg(&raw), Some("blog".to_string())); + } + + #[test] + fn extract_package_arg_returns_first_package_flag() { + let raw = args(&["cot", "-p", "first", "--package", "second", "check"]); + + assert_eq!(extract_package_arg(&raw), Some("first".to_string())); + } + + #[test] + fn extract_package_arg_missing_value_returns_none() { + let raw = args(&["cot", "check", "-p"]); + + assert_eq!(extract_package_arg(&raw), None); + } + + #[test] + fn extract_package_arg_absent_returns_none() { + let raw = args(&["cot", "--release", "check"]); + + assert_eq!(extract_package_arg(&raw), None); + } +} diff --git a/cot-cli/src/handlers.rs b/cot-cli/src/handlers.rs index 23b34fb90..2ee7ad303 100644 --- a/cot-cli/src/handlers.rs +++ b/cot-cli/src/handlers.rs @@ -1,7 +1,11 @@ +use std::ffi::OsString; +#[cfg(unix)] +use std::os::unix::process::CommandExt; use std::path::PathBuf; use anyhow::Context; use clap::CommandFactory; +use cot::metadata::CommandMeta; use crate::args::{ Cli, CompletionsArgs, ManpagesArgs, MigrationListArgs, MigrationMakeArgs, MigrationNewArgs, @@ -11,6 +15,7 @@ use crate::migration_generator::{ MigrationGeneratorOptions, create_new_migration, list_migrations, make_migrations, }; use crate::new_project::{CotSource, new_project}; +use crate::project::ProjectBinary; pub fn handle_new_project( ProjectNewArgs { path, name, source }: ProjectNewArgs, @@ -95,6 +100,90 @@ pub fn handle_cli_completions(CompletionsArgs { shell }: CompletionsArgs) -> any Ok(()) } +pub fn handle_external( + args: &[OsString], + project: Option, + _release: bool, +) -> anyhow::Result<()> { + let subcmd = args[0].to_string_lossy(); + + let Some(proj) = project else { + anyhow::bail!( + "Unknown command `{subcmd}` and no project binary was found in target/.\n\ + Hint: run `cargo build` first, or `cargo build --release`." + ); + }; + + let known = proj + .metadata + .commands + .iter() + .any(|c| c.name == subcmd.as_ref() || c.aliases.iter().any(|a| a == subcmd.as_ref())); + + if !known { + anyhow::bail!( + "Unknown command `{subcmd}`.\n\ + Run `cot --help` to see all available commands." + ); + } + + exec(&proj, args) +} + +fn exec(proj: &ProjectBinary, args: &[OsString]) -> anyhow::Result<()> { + #[cfg(unix)] + { + let err = std::process::Command::new(&proj.path).args(args).exec(); + anyhow::bail!("Failed to exec {}: {err}", proj.path.display()); + } + + #[cfg(not(unix))] + { + let status = std::process::Command::new(&proj.path).args(args).status()?; + std::process::exit(status.code().unwrap_or(1)); + } +} + +/// Build a fresh [`clap::Command`] and inject the project's subcommands into +/// it before printing. +pub fn handle_combined_help(project: Option<&ProjectBinary>) -> anyhow::Result<()> { + let mut cmd = combined_help_command(project); + + cmd.print_long_help()?; + println!(); + Ok(()) +} + +fn combined_help_command(project: Option<&ProjectBinary>) -> clap::Command { + let mut cmd = Cli::command(); + + if let Some(proj) = project { + for meta_cmd in &proj.metadata.commands { + cmd = cmd.subcommand(build_clap_subcommand(meta_cmd)); + } + } + + cmd +} + +fn build_clap_subcommand(meta: &CommandMeta) -> clap::Command { + let mut cmd = clap::Command::new(&meta.name); + + if let Some(about) = &meta.about { + cmd = cmd.about(about.clone()); + } + + for alias in &meta.aliases { + cmd = cmd.visible_alias(alias.clone()); + } + + for sub in &meta.subcommands { + cmd = cmd.subcommand(build_clap_subcommand(sub)); + } + + cmd +} + fn generate_completions(shell: clap_complete::Shell, writer: &mut impl std::io::Write) { clap_complete::generate(shell, &mut Cli::command(), "cot", writer); } @@ -180,4 +269,98 @@ mod tests { assert!(!output.is_empty()); } + + #[test] + fn external_command_without_project_reports_build_hint() { + let result = handle_external(&[OsString::from("serve")], None, false); + + assert!(result.is_err()); + let message = result.unwrap_err().to_string(); + assert!(message.contains("Unknown command `serve`")); + assert!(message.contains("run `cargo build` first")); + } + + #[test] + fn external_command_unknown_to_project_reports_unknown_command() { + let project = ProjectBinary { + path: PathBuf::from("target/debug/example"), + metadata: cot::metadata::ProjectMetadata { + binary_name: "example".to_string(), + commands: vec![CommandMeta { + name: "check".to_string(), + about: None, + aliases: vec![], + subcommands: vec![], + }], + }, + }; + + let result = handle_external(&[OsString::from("foo")], Some(project), false); + + assert!(result.is_err()); + let message = result.unwrap_err().to_string(); + assert!(message.contains("Unknown command `foo`")); + assert!(message.contains("cot --help")); + } + + #[test] + fn build_clap_subcommand_preserves_about_aliases_and_nested_subcommands() { + let meta = CommandMeta { + name: "migration".to_string(), + about: Some("Migration commands".to_string()), + aliases: vec!["database".to_string()], + subcommands: vec![CommandMeta { + name: "rollback".to_string(), + about: Some("Rollback migrations".to_string()), + aliases: vec!["rbk".to_string()], + subcommands: vec![], + }], + }; + + let cmd = build_clap_subcommand(&meta); + + assert_eq!(cmd.get_name(), "migration"); + assert_eq!(cmd.get_about().unwrap().to_string(), "Migration commands"); + assert!(cmd.get_all_aliases().any(|alias| alias == "database")); + let nested = cmd + .get_subcommands() + .find(|subcommand| subcommand.get_name() == "rollback") + .unwrap(); + assert_eq!( + nested.get_about().unwrap().to_string(), + "Rollback migrations" + ); + assert!(nested.get_all_aliases().any(|alias| alias == "rbk")); + } + + #[test] + fn combined_help_command_includes_project_commands_and_builtin_commands() { + let project = ProjectBinary { + path: PathBuf::from("target/debug/example"), + metadata: cot::metadata::ProjectMetadata { + binary_name: "example".to_string(), + commands: vec![CommandMeta { + name: "health".to_string(), + about: Some("Check the server health".to_string()), + aliases: vec![], + subcommands: vec![], + }], + }, + }; + + let cmd = combined_help_command(Some(&project)); + + assert!( + cmd.get_subcommands() + .any(|subcommand| subcommand.get_name() == "new") + ); + let health = cmd + .get_subcommands() + .find(|subcommand| subcommand.get_name() == "health") + .unwrap(); + assert_eq!( + health.get_about().unwrap().to_string(), + "Check the server health" + ); + } } diff --git a/cot-cli/src/lib.rs b/cot-cli/src/lib.rs index 5c23e7383..c76021490 100644 --- a/cot-cli/src/lib.rs +++ b/cot-cli/src/lib.rs @@ -4,6 +4,7 @@ pub mod args; pub mod handlers; pub mod migration_generator; pub mod new_project; +pub mod project; #[cfg(feature = "test_utils")] pub mod test_utils; mod utils; diff --git a/cot-cli/src/main.rs b/cot-cli/src/main.rs index c80f98271..73aa1006a 100644 --- a/cot-cli/src/main.rs +++ b/cot-cli/src/main.rs @@ -1,11 +1,50 @@ #![allow(unreachable_pub)] // triggers false positives because we have both a binary and library use clap::Parser; -use cot_cli::args::{Cli, CliCommands, Commands, MigrationCommands}; -use cot_cli::handlers; +use cot_cli::args::{ + Cli, CliCommands, Commands, HELP_LONG_FLAG, HELP_SHORT_FLAG, MigrationCommands, + PACKAGE_LONG_FLAG, PACKAGE_SHORT_FLAG, RELEASE_FLAG, extract_package_arg, +}; +use cot_cli::{handlers, project}; use tracing_subscriber::util::SubscriberInitExt; +fn is_top_level_help(args: &[String]) -> bool { + if !args + .iter() + .any(|a| a == HELP_LONG_FLAG || a == HELP_SHORT_FLAG) + { + return false; + } + + let mut rest = args.iter().skip(1).peekable(); + while let Some(arg) = rest.next() { + match arg.as_str() { + HELP_LONG_FLAG | HELP_SHORT_FLAG | RELEASE_FLAG => {} + PACKAGE_SHORT_FLAG | PACKAGE_LONG_FLAG => { + let Some(value) = rest.next() else { + return false; + }; + if value.starts_with('-') { + return false; + } + } + _ => return false, + } + } + true +} + fn main() -> anyhow::Result<()> { + let raw: Vec = std::env::args().collect(); + let release = raw.iter().any(|a| a == RELEASE_FLAG); + let package = extract_package_arg(&raw); + + if is_top_level_help(&raw) { + let project = project::load(&std::env::current_dir()?, release, package.as_deref())?; + handlers::handle_combined_help(project.as_ref())?; + return Ok(()); + } + let cli = Cli::parse(); tracing_subscriber::fmt() @@ -27,5 +66,52 @@ fn main() -> anyhow::Result<()> { MigrationCommands::Make(args) => handlers::handle_migration_make(args), MigrationCommands::New(args) => handlers::handle_migration_new(args), }, + Commands::External(args) => { + let project = project::load(&std::env::current_dir()?, release, package.as_deref())?; + handlers::handle_external(&args, project, release) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn args(raw: &[&str]) -> Vec { + raw.iter().map(|arg| (*arg).to_string()).collect() + } + + #[test] + fn top_level_help_accepts_only_global_flags() { + assert!(is_top_level_help(&args(&["cot", "--help"]))); + assert!(is_top_level_help(&args(&["cot", "-h"]))); + assert!(is_top_level_help(&args(&[ + "cot", + "--release", + "-p", + "blog", + "--help" + ]))); + assert!(is_top_level_help(&args(&[ + "cot", + "--package", + "blog", + "-h", + "--release" + ]))); + } + + #[test] + fn top_level_help_rejects_subcommands_and_non_help_invocations() { + assert!(!is_top_level_help(&args(&["cot"]))); + assert!(!is_top_level_help(&args(&["cot", "migration", "--help"]))); + assert!(!is_top_level_help(&args(&["cot", "serve", "-h"]))); + assert!(!is_top_level_help(&args(&["cot", "--version"]))); + } + + #[test] + fn top_level_help_treats_missing_package_value_as_not_top_level_help() { + assert!(!is_top_level_help(&args(&["cot", "-p", "--help"]))); + assert!(!is_top_level_help(&args(&["cot", "--package", "-h"]))); } } diff --git a/cot-cli/src/project.rs b/cot-cli/src/project.rs new file mode 100644 index 000000000..56c83f016 --- /dev/null +++ b/cot-cli/src/project.rs @@ -0,0 +1,647 @@ +use std::fmt::Write; +use std::path::{Path, PathBuf}; +use std::time::SystemTime; + +use anyhow::{Context, bail}; +use cargo_toml::Manifest; +use cot::metadata::{METADATA_FLAG, ProjectMetadata}; +use serde::{Deserialize, Serialize}; + +use crate::utils::{CargoTomlManager, PackageManager, WorkspaceManager}; + +const RELEASE_PROFILE: &str = "release"; +const DEBUG_PROFILE: &str = "debug"; + +#[derive(Serialize, Deserialize)] +struct Cache { + binary_mtime_secs: u64, + metadata: ProjectMetadata, +} + +const COT_DIR_NAME: &str = ".cot"; +const CACHE_FILE_NAME: &str = "command-cache.json"; + +fn command_cache_path(project_dir: &Path) -> PathBuf { + project_dir.join(COT_DIR_NAME).join(CACHE_FILE_NAME) +} + +#[derive(Debug)] +pub struct ProjectBinary { + pub path: PathBuf, + pub metadata: ProjectMetadata, +} + +/// Find and load the project binary and its metadata. +/// +/// `package` corresponds to `cot -p ...` or `--package `. +/// It's required when run from a workspace root +/// (or any directory that doesn't unambiguously belong to one package) and +/// the workspace has more than one member. +pub fn load( + path: &Path, + release: bool, + package: Option<&str>, +) -> anyhow::Result> { + let Some(manager) = CargoTomlManager::from_path(path)? else { + return Ok(None); + }; + + let (package_manager, target_dir_root): (&PackageManager, PathBuf) = match &manager { + CargoTomlManager::Package(pm) => { + let dir = pm.get_package_path().to_path_buf(); + (pm, dir) + } + CargoTomlManager::Workspace(wm) => { + let pm = resolve_workspace_package(wm, package)?; + (pm, wm.get_workspace_root().to_path_buf()) + } + }; + + let project_dir = package_manager.get_package_path(); + let binary_name = resolve_binary_name(package_manager)?; + let target_dir = resolve_target_dir(&target_dir_root); + let profile = if release { + RELEASE_PROFILE + } else { + DEBUG_PROFILE + }; + + #[cfg(target_os = "windows")] + let binary_name = format!("{binary_name}.exe"); + + let binary_path = target_dir.join(profile).join(binary_name); + + if !binary_path.exists() { + return Ok(None); + } + + // Guard against the `cot` CLI resolving to itself. This can happen when + // running from within the `cot-cli` package or a workspace package whose + // binary is the current executable. Querying it for `--metadata` would + // either recurse or fail: only cot application binaries implement that + // flag, not the CLI proxy. + if is_current_executable(&binary_path) { + return Ok(None); + } + + let cache_path = command_cache_path(project_dir); + let metadata = load_or_refresh_metadata(&binary_path, &cache_path).context(format!( + "unable to load metadata from binary `{}`", + binary_path.display() + ))?; + + Ok(Some(ProjectBinary { + path: binary_path, + metadata, + })) +} + +fn is_current_executable(binary_path: &Path) -> bool { + let Ok(current_exe) = std::env::current_exe() else { + return false; + }; + + let Ok(binary_path) = binary_path.canonicalize() else { + return false; + }; + let Ok(current_exe) = current_exe.canonicalize() else { + return false; + }; + + binary_path == current_exe +} + +fn resolve_workspace_package<'a>( + wm: &'a WorkspaceManager, + package: Option<&str>, +) -> anyhow::Result<&'a PackageManager> { + if let Some(name) = package { + return wm.get_package_manager(name).with_context(|| { + format!( + "package `{name}` not found in workspace.\nAvailable packages: {}", + available_packages(wm) + ) + }); + } + + if let Some(pm) = wm.get_current_package_manager() { + return Ok(pm); + } + + bail!( + "multiple packages found in the workspace; specify which one to use with `-p `.\n\n\ + Available packages: {}", + available_packages(wm) + ) +} + +fn available_packages(wm: &WorkspaceManager) -> String { + wm.get_packages() + .iter() + .map(|p| p.get_package_name()) + .collect::>() + .join(", ") +} + +/// Resolve the binary name for a package: +/// +/// 1. If the package has a `[package.metadata.cot.binary]` entry (typically as +/// a result of disambiguating multiple binaries), use that. +/// 2. If the package has a single `[[bin]]` explicitly in `Cargo.toml`, use +/// that. +/// 3. Otherwise, use the package name. +fn resolve_binary_name(package_manager: &PackageManager) -> anyhow::Result { + let manifest: &Manifest = package_manager.get_manifest(); + + if let Some(package) = &manifest.package + && let Some(metadata) = &package.metadata + && let Some(name) = metadata + .get("cot") + .and_then(|c| c.get("binary")) + .and_then(|b| b.as_str()) + { + return Ok(name.to_string()); + } + + let named_bins: Vec<&str> = manifest + .bin + .iter() + .filter_map(|b| b.name.as_deref()) + .collect(); + + match named_bins.len() { + 0 => {} + 1 => return Ok(named_bins[0].to_string()), + _ => bail!( + "package `{}` has multiple [[bin]] targets.\n\ + Specify which one `cot` should use by adding to its Cargo.toml:\n\ + \n\ + [package.metadata.cot]\n\ + binary = \"your-binary-name\"", + package_manager.get_package_name(), + ), + } + + manifest + .package + .as_ref() + .map(|p| p.name.clone()) + .context("Cargo.toml has no [package] section and no [[bin]] targets") +} + +fn resolve_target_dir(start_dir: &Path) -> PathBuf { + let mut dir = start_dir; + loop { + let candidate = dir.join("target"); + if candidate.exists() { + return candidate; + } + match dir.parent() { + Some(parent) => dir = parent, + None => break, + } + } + start_dir.join("target") +} + +fn load_or_refresh_metadata( + binary_path: &Path, + cache_path: &Path, +) -> anyhow::Result { + let current_mtime_secs = mtime_secs(binary_path)?; + + if let Ok(bytes) = std::fs::read(cache_path) + && let Ok(cache) = serde_json::from_slice::(&bytes) + && cache.binary_mtime_secs == current_mtime_secs + { + return Ok(cache.metadata); + } + + let output = std::process::Command::new(binary_path) + .arg(METADATA_FLAG) + .output() + .with_context(|| format!("Failed to spawn {}", binary_path.display()))?; + + if !output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + let mut msg = format!( + "Binary `{}` exited with status {} when queried for metadata.", + binary_path.display(), + output.status, + ); + + if !stderr.trim().is_empty() { + let _ = write!(msg, "\n\nstderr:\n{}", stderr.trim()); + } + + if !stdout.trim().is_empty() { + let _ = write!(msg, "\n\nstdout:\n{}", stdout.trim()); + } + bail!(msg); + } + + let metadata: ProjectMetadata = serde_json::from_slice(&output.stdout).with_context(|| { + let raw = String::from_utf8_lossy(&output.stdout); + format!( + "Binary `{}` returned invalid JSON for {METADATA_FLAG}.\n\nGot:\n{}", + binary_path.display(), + raw.trim(), + ) + })?; + + write_cache( + cache_path, + &Cache { + binary_mtime_secs: current_mtime_secs, + metadata: metadata.clone(), + }, + )?; + + Ok(metadata) +} + +fn mtime_secs(path: &Path) -> anyhow::Result { + let metadata = path.metadata()?; + Ok(metadata + .modified()? + .duration_since(SystemTime::UNIX_EPOCH)? + .as_secs()) +} + +fn write_cache(cache_path: &Path, cache: &Cache) -> anyhow::Result<()> { + if let Some(parent) = cache_path.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write(cache_path, serde_json::to_string(cache)?)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::fs; + #[cfg(unix)] + use std::os::unix::fs::PermissionsExt; + + use cot::metadata::CommandMeta; + use tempfile::TempDir; + + use super::*; + + fn write_package_manifest(package_dir: &Path, package_name: &str, extra: &str) { + fs::create_dir_all(package_dir).unwrap(); + fs::write( + package_dir.join("Cargo.toml"), + format!( + r#"[package] +name = "{package_name}" +version = "0.1.0" +edition = "2024" + +{extra}"# + ), + ) + .unwrap(); + } + + fn write_workspace_manifest(workspace_dir: &Path, members: &[&str]) { + fs::write( + workspace_dir.join("Cargo.toml"), + format!( + "[workspace]\nresolver = \"3\"\nmembers = [{}]\n", + members + .iter() + .map(|member| format!("\"{member}\"")) + .collect::>() + .join(", ") + ), + ) + .unwrap(); + } + + fn command(name: &str) -> CommandMeta { + CommandMeta { + name: name.to_string(), + about: None, + aliases: vec![], + subcommands: vec![], + } + } + + fn metadata(binary_name: &str, command_names: &[&str]) -> ProjectMetadata { + ProjectMetadata { + binary_name: binary_name.to_string(), + commands: command_names.iter().map(|name| command(name)).collect(), + } + } + + #[cfg(unix)] + fn write_metadata_script(path: &Path, metadata: &ProjectMetadata) { + let json = serde_json::to_string(metadata).unwrap(); + write_shell_script(path, &format!("printf '%s\\n' '{json}'\n")); + } + + #[cfg(unix)] + fn write_shell_script(path: &Path, body: &str) { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).unwrap(); + } + fs::write(path, format!("#!/bin/sh\n{body}")).unwrap(); + let mut permissions = fs::metadata(path).unwrap().permissions(); + permissions.set_mode(0o755); + fs::set_permissions(path, permissions).unwrap(); + } + + #[test] + fn load_returns_none_without_cargo_manifest() { + let temp_dir = TempDir::new().unwrap(); + + let result = load(temp_dir.path(), false, None).unwrap(); + + assert!(result.is_none()); + } + + #[test] + fn load_errors_when_start_path_does_not_exist() { + let temp_dir = TempDir::new().unwrap(); + + let result = load(&temp_dir.path().join("missing"), false, None); + + assert!(result.is_err()); + assert!( + result + .unwrap_err() + .to_string() + .contains("path does not exist") + ); + } + + #[test] + fn load_returns_none_when_expected_binary_is_missing() { + let temp_dir = TempDir::new().unwrap(); + write_package_manifest(temp_dir.path(), "demo", ""); + + let result = load(temp_dir.path(), false, None).unwrap(); + + assert!(result.is_none()); + } + + #[test] + #[cfg(unix)] + fn load_reads_debug_binary_metadata_and_writes_cache() { + let temp_dir = TempDir::new().unwrap(); + write_package_manifest(temp_dir.path(), "demo", ""); + let binary_path = temp_dir.path().join("target/debug/demo"); + write_metadata_script(&binary_path, &metadata("demo", &["serve"])); + + let project = load(temp_dir.path(), false, None).unwrap().unwrap(); + + assert_eq!(project.path, binary_path); + assert_eq!(project.metadata.binary_name, "demo"); + assert_eq!(project.metadata.commands[0].name, "serve"); + assert!(command_cache_path(temp_dir.path()).exists()); + } + + #[test] + #[cfg(unix)] + fn load_uses_release_profile_when_requested() { + let temp_dir = TempDir::new().unwrap(); + write_package_manifest(temp_dir.path(), "demo", ""); + let binary_path = temp_dir.path().join("target/release/demo"); + write_metadata_script(&binary_path, &metadata("demo", &["serve"])); + + let project = load(temp_dir.path(), true, None).unwrap().unwrap(); + + assert_eq!(project.path, binary_path); + } + + #[test] + #[cfg(unix)] + fn load_uses_single_named_bin_target() { + let temp_dir = TempDir::new().unwrap(); + write_package_manifest( + temp_dir.path(), + "demo", + r#"[[bin]] +name = "server" +path = "src/server.rs" +"#, + ); + let binary_path = temp_dir.path().join("target/debug/server"); + write_metadata_script(&binary_path, &metadata("server", &["serve"])); + + let project = load(temp_dir.path(), false, None).unwrap().unwrap(); + + assert_eq!(project.path, binary_path); + assert_eq!(project.metadata.binary_name, "server"); + } + + #[test] + #[cfg(unix)] + fn load_uses_metadata_binary_override_before_bin_targets() { + let temp_dir = TempDir::new().unwrap(); + write_package_manifest( + temp_dir.path(), + "demo", + r#"[package.metadata.cot] +binary = "api" + +[[bin]] +name = "api" +path = "src/api.rs" + +[[bin]] +name = "worker" +path = "src/worker.rs" +"#, + ); + let binary_path = temp_dir.path().join("target/debug/api"); + write_metadata_script(&binary_path, &metadata("api", &["serve"])); + + let project = load(temp_dir.path(), false, None).unwrap().unwrap(); + + assert_eq!(project.path, binary_path); + assert_eq!(project.metadata.binary_name, "api"); + } + + #[test] + fn load_errors_on_multiple_bin_targets_without_override() { + let temp_dir = TempDir::new().unwrap(); + write_package_manifest( + temp_dir.path(), + "demo", + r#"[[bin]] +name = "api" +path = "src/api.rs" + +[[bin]] +name = "worker" +path = "src/worker.rs" +"#, + ); + + let result = load(temp_dir.path(), false, None); + + assert!(result.is_err()); + let message = result.unwrap_err().to_string(); + assert!(message.contains("multiple [[bin]] targets")); + assert!(message.contains("[package.metadata.cot]")); + } + + #[test] + fn workspace_root_requires_package_when_ambiguous() { + let temp_dir = TempDir::new().unwrap(); + write_workspace_manifest(temp_dir.path(), &["api", "web"]); + write_package_manifest(&temp_dir.path().join("api"), "api", ""); + write_package_manifest(&temp_dir.path().join("web"), "web", ""); + + let result = load(temp_dir.path(), false, None); + + assert!(result.is_err()); + let message = result.unwrap_err().to_string(); + assert!(message.contains("multiple packages found")); + assert!(message.contains("api")); + assert!(message.contains("web")); + } + + #[test] + fn workspace_package_flag_must_match_member() { + let temp_dir = TempDir::new().unwrap(); + write_workspace_manifest(temp_dir.path(), &["api", "web"]); + write_package_manifest(&temp_dir.path().join("api"), "api", ""); + write_package_manifest(&temp_dir.path().join("web"), "web", ""); + + let result = load(temp_dir.path(), false, Some("missing")); + + assert!(result.is_err()); + let message = result.unwrap_err().to_string(); + assert!(message.contains("package `missing` not found")); + assert!(message.contains("api")); + assert!(message.contains("web")); + } + + #[test] + #[cfg(unix)] + fn workspace_root_uses_selected_package_and_workspace_target_dir() { + let temp_dir = TempDir::new().unwrap(); + write_workspace_manifest(temp_dir.path(), &["api", "web"]); + write_package_manifest(&temp_dir.path().join("api"), "api", ""); + write_package_manifest(&temp_dir.path().join("web"), "web", ""); + let binary_path = temp_dir.path().join("target/debug/api"); + write_metadata_script(&binary_path, &metadata("api", &["check"])); + + let project = load(temp_dir.path(), false, Some("api")).unwrap().unwrap(); + + assert_eq!(project.path, binary_path); + assert!(temp_dir.path().join("api").exists()); + } + + #[test] + #[cfg(unix)] + fn workspace_member_directory_uses_current_package_without_flag() { + let temp_dir = TempDir::new().unwrap(); + write_workspace_manifest(temp_dir.path(), &["api", "web"]); + write_package_manifest(&temp_dir.path().join("api"), "api", ""); + write_package_manifest(&temp_dir.path().join("web"), "web", ""); + let binary_path = temp_dir.path().join("target/debug/web"); + write_metadata_script(&binary_path, &metadata("web", &["check"])); + + let project = load(&temp_dir.path().join("web"), false, None) + .unwrap() + .unwrap(); + + assert_eq!(project.path, binary_path); + assert_eq!(project.metadata.binary_name, "web"); + } + + #[test] + #[cfg(unix)] + fn load_reuses_valid_cache_without_spawning_binary() { + let temp_dir = TempDir::new().unwrap(); + write_package_manifest(temp_dir.path(), "demo", ""); + let binary_path = temp_dir.path().join("target/debug/demo"); + write_shell_script( + &binary_path, + "echo 'binary should not be queried' >&2\nexit 42\n", + ); + let cache = Cache { + binary_mtime_secs: mtime_secs(&binary_path).unwrap(), + metadata: metadata("demo", &["cached"]), + }; + write_cache(&command_cache_path(temp_dir.path()), &cache).unwrap(); + + let project = load(temp_dir.path(), false, None).unwrap().unwrap(); + + assert_eq!(project.metadata.commands[0].name, "cached"); + } + + #[test] + #[cfg(unix)] + fn load_refreshes_stale_cache() { + let temp_dir = TempDir::new().unwrap(); + write_package_manifest(temp_dir.path(), "demo", ""); + let binary_path = temp_dir.path().join("target/debug/demo"); + write_metadata_script(&binary_path, &metadata("demo", &["fresh"])); + let cache = Cache { + binary_mtime_secs: 0, + metadata: metadata("demo", &["stale"]), + }; + write_cache(&command_cache_path(temp_dir.path()), &cache).unwrap(); + + let project = load(temp_dir.path(), false, None).unwrap().unwrap(); + + assert_eq!(project.metadata.commands[0].name, "fresh"); + } + + #[test] + #[cfg(unix)] + fn load_reports_metadata_command_failure_with_output() { + let temp_dir = TempDir::new().unwrap(); + write_package_manifest(temp_dir.path(), "demo", ""); + let binary_path = temp_dir.path().join("target/debug/demo"); + write_shell_script( + &binary_path, + "echo stdout message\necho stderr message >&2\nexit 42\n", + ); + + let result = load(temp_dir.path(), false, None); + + assert!(result.is_err()); + let message = format!("{:#}", result.unwrap_err()); + assert!(message.contains("unable to load metadata")); + assert!(message.contains("exited with status")); + assert!(message.contains("stdout message")); + assert!(message.contains("stderr message")); + } + + #[test] + #[cfg(unix)] + fn load_reports_invalid_metadata_json() { + let temp_dir = TempDir::new().unwrap(); + write_package_manifest(temp_dir.path(), "demo", ""); + let binary_path = temp_dir.path().join("target/debug/demo"); + write_shell_script(&binary_path, "echo 'not json'\n"); + + let result = load(temp_dir.path(), false, None); + + assert!(result.is_err()); + let message = format!("{:#}", result.unwrap_err()); + assert!(message.contains(METADATA_FLAG)); + assert!(message.contains("not json")); + } + + #[test] + fn current_executable_matches_current_process() { + let current_exe = std::env::current_exe().unwrap(); + + assert!(is_current_executable(¤t_exe)); + } + + #[test] + fn current_executable_does_not_match_missing_path() { + let missing = std::env::temp_dir().join("cot-cli-missing-test-binary"); + + assert!(!is_current_executable(&missing)); + } +} diff --git a/cot-cli/src/project_template/.gitignore b/cot-cli/src/project_template/.gitignore index a611abcd7..6e99f71d2 100644 --- a/cot-cli/src/project_template/.gitignore +++ b/cot-cli/src/project_template/.gitignore @@ -3,6 +3,9 @@ debug/ target/ +# Cot related auto generated files +.cot/ + # These are backup files generated by rustfmt **/*.rs.bk diff --git a/cot-cli/src/utils.rs b/cot-cli/src/utils.rs index 6ec4cbcf8..e0a42abd1 100644 --- a/cot-cli/src/utils.rs +++ b/cot-cli/src/utils.rs @@ -258,6 +258,10 @@ impl WorkspaceManager { self.package_manifests.get(package_name) } + pub(crate) fn get_workspace_root(&self) -> &Path { + self.workspace_root.as_path() + } + #[cfg(test)] pub(crate) fn get_package_manager_by_path( &self, @@ -295,7 +299,6 @@ impl PackageManager { path.to_owned() } - #[cfg(test)] pub(crate) fn get_manifest(&self) -> &Manifest { &self.manifest } diff --git a/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_bash.snap b/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_bash.snap index 7b57c92dd..a407b0db0 100644 --- a/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_bash.snap +++ b/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_bash.snap @@ -116,12 +116,20 @@ _cot() { case "${cmd}" in cot) - opts="-v -q -h -V --verbose --quiet --help --version new migration cli help" + opts="-p -v -q -h -V --release --package --verbose --quiet --help --version new migration cli help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --package) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -p) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -130,12 +138,20 @@ _cot() { return 0 ;; cot__subcmd__cli) - opts="-v -q -h --verbose --quiet --help manpages completions help" + opts="-p -v -q -h --release --package --verbose --quiet --help manpages completions help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --package) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -p) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -144,12 +160,20 @@ _cot() { return 0 ;; cot__subcmd__cli__subcmd__completions) - opts="-v -q -h --verbose --quiet --help bash elvish fish powershell zsh" + opts="-p -v -q -h --release --package --verbose --quiet --help bash elvish fish powershell zsh" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --package) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -p) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -214,7 +238,7 @@ _cot() { return 0 ;; cot__subcmd__cli__subcmd__manpages) - opts="-o -c -v -q -h --output-dir --create --verbose --quiet --help" + opts="-o -c -p -v -q -h --output-dir --create --release --package --verbose --quiet --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -228,6 +252,14 @@ _cot() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --package) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -p) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -376,12 +408,20 @@ _cot() { return 0 ;; cot__subcmd__migration) - opts="-v -q -h --verbose --quiet --help list make new help" + opts="-p -v -q -h --release --package --verbose --quiet --help list make new help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --package) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -p) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -460,12 +500,20 @@ _cot() { return 0 ;; cot__subcmd__migration__subcmd__list) - opts="-v -q -h --verbose --quiet --help" + opts="-p -v -q -h --release --package --verbose --quiet --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --package) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -p) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -474,7 +522,7 @@ _cot() { return 0 ;; cot__subcmd__migration__subcmd__make) - opts="-v -q -h --app-name --output-dir --verbose --quiet --help" + opts="-p -v -q -h --app-name --output-dir --release --package --verbose --quiet --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -488,6 +536,14 @@ _cot() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --package) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -p) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -496,7 +552,7 @@ _cot() { return 0 ;; cot__subcmd__migration__subcmd__new) - opts="-v -q -h --app-name --verbose --quiet --help" + opts="-p -v -q -h --app-name --release --package --verbose --quiet --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -506,6 +562,14 @@ _cot() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --package) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -p) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -514,7 +578,7 @@ _cot() { return 0 ;; cot__subcmd__new) - opts="-v -q -h --name --use-git --cot-path --verbose --quiet --help" + opts="-p -v -q -h --name --use-git --cot-path --release --package --verbose --quiet --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -528,6 +592,14 @@ _cot() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --package) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -p) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; diff --git a/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_elvish.snap b/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_elvish.snap index 66217fe41..1bb6e0952 100644 --- a/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_elvish.snap +++ b/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_elvish.snap @@ -30,6 +30,9 @@ set edit:completion:arg-completer[cot] = {|@words| } var completions = [ &'cot'= { + cand -p 'Package to use, in case you''re running this in a workspace' + cand --package 'Package to use, in case you''re running this in a workspace' + cand --release 'Use target/release instead of target/debug when looking for the project binary' cand -v 'Increase logging verbosity' cand --verbose 'Increase logging verbosity' cand -q 'Decrease logging verbosity' @@ -46,7 +49,10 @@ set edit:completion:arg-completer[cot] = {|@words| &'cot;new'= { cand --name 'Set the resulting crate name [default: the directory name]' cand --cot-path 'Use `cot` from the specified path instead of a published crate' + cand -p 'Package to use, in case you''re running this in a workspace' + cand --package 'Package to use, in case you''re running this in a workspace' cand --use-git 'Use the latest `cot` version from git instead of a published crate' + cand --release 'Use target/release instead of target/debug when looking for the project binary' cand -v 'Increase logging verbosity' cand --verbose 'Increase logging verbosity' cand -q 'Decrease logging verbosity' @@ -55,6 +61,9 @@ set edit:completion:arg-completer[cot] = {|@words| cand --help 'Print help' } &'cot;migration'= { + cand -p 'Package to use, in case you''re running this in a workspace' + cand --package 'Package to use, in case you''re running this in a workspace' + cand --release 'Use target/release instead of target/debug when looking for the project binary' cand -v 'Increase logging verbosity' cand --verbose 'Increase logging verbosity' cand -q 'Decrease logging verbosity' @@ -67,6 +76,9 @@ set edit:completion:arg-completer[cot] = {|@words| cand help 'Print this message or the help of the given subcommand(s)' } &'cot;migration;list'= { + cand -p 'Package to use, in case you''re running this in a workspace' + cand --package 'Package to use, in case you''re running this in a workspace' + cand --release 'Use target/release instead of target/debug when looking for the project binary' cand -v 'Increase logging verbosity' cand --verbose 'Increase logging verbosity' cand -q 'Decrease logging verbosity' @@ -77,6 +89,9 @@ set edit:completion:arg-completer[cot] = {|@words| &'cot;migration;make'= { cand --app-name 'Name of the app to use in the migration [default: crate name]' cand --output-dir 'Directory to write the migrations to [default: the migrations/ directory in the crate''s src/ directory]' + cand -p 'Package to use, in case you''re running this in a workspace' + cand --package 'Package to use, in case you''re running this in a workspace' + cand --release 'Use target/release instead of target/debug when looking for the project binary' cand -v 'Increase logging verbosity' cand --verbose 'Increase logging verbosity' cand -q 'Decrease logging verbosity' @@ -86,6 +101,9 @@ set edit:completion:arg-completer[cot] = {|@words| } &'cot;migration;new'= { cand --app-name 'Name of the app to use in the migration (default: crate name)' + cand -p 'Package to use, in case you''re running this in a workspace' + cand --package 'Package to use, in case you''re running this in a workspace' + cand --release 'Use target/release instead of target/debug when looking for the project binary' cand -v 'Increase logging verbosity' cand --verbose 'Increase logging verbosity' cand -q 'Decrease logging verbosity' @@ -108,6 +126,9 @@ set edit:completion:arg-completer[cot] = {|@words| &'cot;migration;help;help'= { } &'cot;cli'= { + cand -p 'Package to use, in case you''re running this in a workspace' + cand --package 'Package to use, in case you''re running this in a workspace' + cand --release 'Use target/release instead of target/debug when looking for the project binary' cand -v 'Increase logging verbosity' cand --verbose 'Increase logging verbosity' cand -q 'Decrease logging verbosity' @@ -121,8 +142,11 @@ set edit:completion:arg-completer[cot] = {|@words| &'cot;cli;manpages'= { cand -o 'Directory to write the manpages to [default: current directory]' cand --output-dir 'Directory to write the manpages to [default: current directory]' + cand -p 'Package to use, in case you''re running this in a workspace' + cand --package 'Package to use, in case you''re running this in a workspace' cand -c 'Create the directory if it doesn''t exist' cand --create 'Create the directory if it doesn''t exist' + cand --release 'Use target/release instead of target/debug when looking for the project binary' cand -v 'Increase logging verbosity' cand --verbose 'Increase logging verbosity' cand -q 'Decrease logging verbosity' @@ -131,6 +155,9 @@ set edit:completion:arg-completer[cot] = {|@words| cand --help 'Print help' } &'cot;cli;completions'= { + cand -p 'Package to use, in case you''re running this in a workspace' + cand --package 'Package to use, in case you''re running this in a workspace' + cand --release 'Use target/release instead of target/debug when looking for the project binary' cand -v 'Increase logging verbosity' cand --verbose 'Increase logging verbosity' cand -q 'Decrease logging verbosity' diff --git a/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_fish.snap b/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_fish.snap index bfd28c5cf..1d6cc2ba2 100644 --- a/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_fish.snap +++ b/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_fish.snap @@ -12,7 +12,7 @@ exit_code: 0 ----- stdout ----- # Print an optspec for argparse to handle cmd's options that are independent of any subcommand. function __fish_cot_global_optspecs - string join \n v/verbose q/quiet h/help V/version + string join \n release p/package= v/verbose q/quiet h/help V/version end function __fish_cot_needs_command @@ -36,6 +36,8 @@ function __fish_cot_using_subcommand contains -- $cmd[1] $argv end +complete -c cot -n "__fish_cot_needs_command" -s p -l package -d 'Package to use, in case you\'re running this in a workspace' -r +complete -c cot -n "__fish_cot_needs_command" -l release -d 'Use target/release instead of target/debug when looking for the project binary' complete -c cot -n "__fish_cot_needs_command" -s v -l verbose -d 'Increase logging verbosity' complete -c cot -n "__fish_cot_needs_command" -s q -l quiet -d 'Decrease logging verbosity' complete -c cot -n "__fish_cot_needs_command" -s h -l help -d 'Print help' @@ -46,10 +48,14 @@ complete -c cot -n "__fish_cot_needs_command" -f -a "cli" -d 'Manage Cot CLI' complete -c cot -n "__fish_cot_needs_command" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' complete -c cot -n "__fish_cot_using_subcommand new" -l name -d 'Set the resulting crate name [default: the directory name]' -r complete -c cot -n "__fish_cot_using_subcommand new" -l cot-path -d 'Use `cot` from the specified path instead of a published crate' -r -F +complete -c cot -n "__fish_cot_using_subcommand new" -s p -l package -d 'Package to use, in case you\'re running this in a workspace' -r complete -c cot -n "__fish_cot_using_subcommand new" -l use-git -d 'Use the latest `cot` version from git instead of a published crate' +complete -c cot -n "__fish_cot_using_subcommand new" -l release -d 'Use target/release instead of target/debug when looking for the project binary' complete -c cot -n "__fish_cot_using_subcommand new" -s v -l verbose -d 'Increase logging verbosity' complete -c cot -n "__fish_cot_using_subcommand new" -s q -l quiet -d 'Decrease logging verbosity' complete -c cot -n "__fish_cot_using_subcommand new" -s h -l help -d 'Print help' +complete -c cot -n "__fish_cot_using_subcommand migration; and not __fish_seen_subcommand_from list make new help" -s p -l package -d 'Package to use, in case you\'re running this in a workspace' -r +complete -c cot -n "__fish_cot_using_subcommand migration; and not __fish_seen_subcommand_from list make new help" -l release -d 'Use target/release instead of target/debug when looking for the project binary' complete -c cot -n "__fish_cot_using_subcommand migration; and not __fish_seen_subcommand_from list make new help" -s v -l verbose -d 'Increase logging verbosity' complete -c cot -n "__fish_cot_using_subcommand migration; and not __fish_seen_subcommand_from list make new help" -s q -l quiet -d 'Decrease logging verbosity' complete -c cot -n "__fish_cot_using_subcommand migration; and not __fish_seen_subcommand_from list make new help" -s h -l help -d 'Print help' @@ -57,15 +63,21 @@ complete -c cot -n "__fish_cot_using_subcommand migration; and not __fish_seen_s complete -c cot -n "__fish_cot_using_subcommand migration; and not __fish_seen_subcommand_from list make new help" -f -a "make" -d 'Generate migrations for a Cot project' complete -c cot -n "__fish_cot_using_subcommand migration; and not __fish_seen_subcommand_from list make new help" -f -a "new" -d 'Create a new empty migration' complete -c cot -n "__fish_cot_using_subcommand migration; and not __fish_seen_subcommand_from list make new help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from list" -s p -l package -d 'Package to use, in case you\'re running this in a workspace' -r +complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from list" -l release -d 'Use target/release instead of target/debug when looking for the project binary' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from list" -s v -l verbose -d 'Increase logging verbosity' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from list" -s q -l quiet -d 'Decrease logging verbosity' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from make" -l app-name -d 'Name of the app to use in the migration [default: crate name]' -r complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from make" -l output-dir -d 'Directory to write the migrations to [default: the migrations/ directory in the crate\'s src/ directory]' -r -F +complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from make" -s p -l package -d 'Package to use, in case you\'re running this in a workspace' -r +complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from make" -l release -d 'Use target/release instead of target/debug when looking for the project binary' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from make" -s v -l verbose -d 'Increase logging verbosity' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from make" -s q -l quiet -d 'Decrease logging verbosity' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from make" -s h -l help -d 'Print help' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from new" -l app-name -d 'Name of the app to use in the migration (default: crate name)' -r +complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from new" -s p -l package -d 'Package to use, in case you\'re running this in a workspace' -r +complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from new" -l release -d 'Use target/release instead of target/debug when looking for the project binary' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from new" -s v -l verbose -d 'Increase logging verbosity' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from new" -s q -l quiet -d 'Decrease logging verbosity' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from new" -s h -l help -d 'Print help' @@ -73,6 +85,8 @@ complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subco complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from help" -f -a "make" -d 'Generate migrations for a Cot project' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from help" -f -a "new" -d 'Create a new empty migration' complete -c cot -n "__fish_cot_using_subcommand migration; and __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c cot -n "__fish_cot_using_subcommand cli; and not __fish_seen_subcommand_from manpages completions help" -s p -l package -d 'Package to use, in case you\'re running this in a workspace' -r +complete -c cot -n "__fish_cot_using_subcommand cli; and not __fish_seen_subcommand_from manpages completions help" -l release -d 'Use target/release instead of target/debug when looking for the project binary' complete -c cot -n "__fish_cot_using_subcommand cli; and not __fish_seen_subcommand_from manpages completions help" -s v -l verbose -d 'Increase logging verbosity' complete -c cot -n "__fish_cot_using_subcommand cli; and not __fish_seen_subcommand_from manpages completions help" -s q -l quiet -d 'Decrease logging verbosity' complete -c cot -n "__fish_cot_using_subcommand cli; and not __fish_seen_subcommand_from manpages completions help" -s h -l help -d 'Print help' @@ -80,10 +94,14 @@ complete -c cot -n "__fish_cot_using_subcommand cli; and not __fish_seen_subcomm complete -c cot -n "__fish_cot_using_subcommand cli; and not __fish_seen_subcommand_from manpages completions help" -f -a "completions" -d 'Generate completions for the Cot CLI' complete -c cot -n "__fish_cot_using_subcommand cli; and not __fish_seen_subcommand_from manpages completions help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' complete -c cot -n "__fish_cot_using_subcommand cli; and __fish_seen_subcommand_from manpages" -s o -l output-dir -d 'Directory to write the manpages to [default: current directory]' -r -F +complete -c cot -n "__fish_cot_using_subcommand cli; and __fish_seen_subcommand_from manpages" -s p -l package -d 'Package to use, in case you\'re running this in a workspace' -r complete -c cot -n "__fish_cot_using_subcommand cli; and __fish_seen_subcommand_from manpages" -s c -l create -d 'Create the directory if it doesn\'t exist' +complete -c cot -n "__fish_cot_using_subcommand cli; and __fish_seen_subcommand_from manpages" -l release -d 'Use target/release instead of target/debug when looking for the project binary' complete -c cot -n "__fish_cot_using_subcommand cli; and __fish_seen_subcommand_from manpages" -s v -l verbose -d 'Increase logging verbosity' complete -c cot -n "__fish_cot_using_subcommand cli; and __fish_seen_subcommand_from manpages" -s q -l quiet -d 'Decrease logging verbosity' complete -c cot -n "__fish_cot_using_subcommand cli; and __fish_seen_subcommand_from manpages" -s h -l help -d 'Print help' +complete -c cot -n "__fish_cot_using_subcommand cli; and __fish_seen_subcommand_from completions" -s p -l package -d 'Package to use, in case you\'re running this in a workspace' -r +complete -c cot -n "__fish_cot_using_subcommand cli; and __fish_seen_subcommand_from completions" -l release -d 'Use target/release instead of target/debug when looking for the project binary' complete -c cot -n "__fish_cot_using_subcommand cli; and __fish_seen_subcommand_from completions" -s v -l verbose -d 'Increase logging verbosity' complete -c cot -n "__fish_cot_using_subcommand cli; and __fish_seen_subcommand_from completions" -s q -l quiet -d 'Decrease logging verbosity' complete -c cot -n "__fish_cot_using_subcommand cli; and __fish_seen_subcommand_from completions" -s h -l help -d 'Print help' diff --git a/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_powershell.snap b/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_powershell.snap index fc9bf6565..5a5803e53 100644 --- a/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_powershell.snap +++ b/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_powershell.snap @@ -33,6 +33,9 @@ Register-ArgumentCompleter -Native -CommandName 'cot' -ScriptBlock { $completions = @(switch ($command) { 'cot' { + [CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--package', '--package', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--release', '--release', [CompletionResultType]::ParameterName, 'Use target/release instead of target/debug when looking for the project binary') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Decrease logging verbosity') @@ -50,7 +53,10 @@ Register-ArgumentCompleter -Native -CommandName 'cot' -ScriptBlock { 'cot;new' { [CompletionResult]::new('--name', '--name', [CompletionResultType]::ParameterName, 'Set the resulting crate name [default: the directory name]') [CompletionResult]::new('--cot-path', '--cot-path', [CompletionResultType]::ParameterName, 'Use `cot` from the specified path instead of a published crate') + [CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--package', '--package', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') [CompletionResult]::new('--use-git', '--use-git', [CompletionResultType]::ParameterName, 'Use the latest `cot` version from git instead of a published crate') + [CompletionResult]::new('--release', '--release', [CompletionResultType]::ParameterName, 'Use target/release instead of target/debug when looking for the project binary') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Decrease logging verbosity') @@ -60,6 +66,9 @@ Register-ArgumentCompleter -Native -CommandName 'cot' -ScriptBlock { break } 'cot;migration' { + [CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--package', '--package', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--release', '--release', [CompletionResultType]::ParameterName, 'Use target/release instead of target/debug when looking for the project binary') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Decrease logging verbosity') @@ -73,6 +82,9 @@ Register-ArgumentCompleter -Native -CommandName 'cot' -ScriptBlock { break } 'cot;migration;list' { + [CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--package', '--package', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--release', '--release', [CompletionResultType]::ParameterName, 'Use target/release instead of target/debug when looking for the project binary') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Decrease logging verbosity') @@ -84,6 +96,9 @@ Register-ArgumentCompleter -Native -CommandName 'cot' -ScriptBlock { 'cot;migration;make' { [CompletionResult]::new('--app-name', '--app-name', [CompletionResultType]::ParameterName, 'Name of the app to use in the migration [default: crate name]') [CompletionResult]::new('--output-dir', '--output-dir', [CompletionResultType]::ParameterName, 'Directory to write the migrations to [default: the migrations/ directory in the crate''s src/ directory]') + [CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--package', '--package', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--release', '--release', [CompletionResultType]::ParameterName, 'Use target/release instead of target/debug when looking for the project binary') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Decrease logging verbosity') @@ -94,6 +109,9 @@ Register-ArgumentCompleter -Native -CommandName 'cot' -ScriptBlock { } 'cot;migration;new' { [CompletionResult]::new('--app-name', '--app-name', [CompletionResultType]::ParameterName, 'Name of the app to use in the migration (default: crate name)') + [CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--package', '--package', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--release', '--release', [CompletionResultType]::ParameterName, 'Use target/release instead of target/debug when looking for the project binary') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Decrease logging verbosity') @@ -122,6 +140,9 @@ Register-ArgumentCompleter -Native -CommandName 'cot' -ScriptBlock { break } 'cot;cli' { + [CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--package', '--package', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--release', '--release', [CompletionResultType]::ParameterName, 'Use target/release instead of target/debug when looking for the project binary') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Decrease logging verbosity') @@ -136,8 +157,11 @@ Register-ArgumentCompleter -Native -CommandName 'cot' -ScriptBlock { 'cot;cli;manpages' { [CompletionResult]::new('-o', '-o', [CompletionResultType]::ParameterName, 'Directory to write the manpages to [default: current directory]') [CompletionResult]::new('--output-dir', '--output-dir', [CompletionResultType]::ParameterName, 'Directory to write the manpages to [default: current directory]') + [CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--package', '--package', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') [CompletionResult]::new('-c', '-c', [CompletionResultType]::ParameterName, 'Create the directory if it doesn''t exist') [CompletionResult]::new('--create', '--create', [CompletionResultType]::ParameterName, 'Create the directory if it doesn''t exist') + [CompletionResult]::new('--release', '--release', [CompletionResultType]::ParameterName, 'Use target/release instead of target/debug when looking for the project binary') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Decrease logging verbosity') @@ -147,6 +171,9 @@ Register-ArgumentCompleter -Native -CommandName 'cot' -ScriptBlock { break } 'cot;cli;completions' { + [CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--package', '--package', [CompletionResultType]::ParameterName, 'Package to use, in case you''re running this in a workspace') + [CompletionResult]::new('--release', '--release', [CompletionResultType]::ParameterName, 'Use target/release instead of target/debug when looking for the project binary') [CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'Increase logging verbosity') [CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Decrease logging verbosity') diff --git a/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_zsh.snap b/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_zsh.snap index 1332d6345..9dd5f2a37 100644 --- a/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_zsh.snap +++ b/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__completions_zsh.snap @@ -27,6 +27,9 @@ _cot() { local context curcontext="$curcontext" state line _arguments "${_arguments_options[@]}" : \ +'-p+[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--package=[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--release[Use target/release instead of target/debug when looking for the project binary]' \ '*-v[Increase logging verbosity]' \ '*--verbose[Increase logging verbosity]' \ '(-v --verbose)*-q[Decrease logging verbosity]' \ @@ -48,7 +51,10 @@ _cot() { _arguments "${_arguments_options[@]}" : \ '--name=[Set the resulting crate name \[default\: the directory name\]]:NAME:_default' \ '--cot-path=[Use \`cot\` from the specified path instead of a published crate]:COT_PATH:_files' \ +'-p+[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--package=[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ '--use-git[Use the latest \`cot\` version from git instead of a published crate]' \ +'--release[Use target/release instead of target/debug when looking for the project binary]' \ '*-v[Increase logging verbosity]' \ '*--verbose[Increase logging verbosity]' \ '(-v --verbose)*-q[Decrease logging verbosity]' \ @@ -60,6 +66,9 @@ _arguments "${_arguments_options[@]}" : \ ;; (migration) _arguments "${_arguments_options[@]}" : \ +'-p+[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--package=[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--release[Use target/release instead of target/debug when looking for the project binary]' \ '*-v[Increase logging verbosity]' \ '*--verbose[Increase logging verbosity]' \ '(-v --verbose)*-q[Decrease logging verbosity]' \ @@ -78,6 +87,9 @@ _arguments "${_arguments_options[@]}" : \ case $line[1] in (list) _arguments "${_arguments_options[@]}" : \ +'-p+[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--package=[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--release[Use target/release instead of target/debug when looking for the project binary]' \ '*-v[Increase logging verbosity]' \ '*--verbose[Increase logging verbosity]' \ '(-v --verbose)*-q[Decrease logging verbosity]' \ @@ -91,6 +103,9 @@ _arguments "${_arguments_options[@]}" : \ _arguments "${_arguments_options[@]}" : \ '--app-name=[Name of the app to use in the migration \[default\: crate name\]]:APP_NAME:_default' \ '--output-dir=[Directory to write the migrations to \[default\: the migrations/ directory in the crate'\''s src/ directory\]]:OUTPUT_DIR:_files' \ +'-p+[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--package=[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--release[Use target/release instead of target/debug when looking for the project binary]' \ '*-v[Increase logging verbosity]' \ '*--verbose[Increase logging verbosity]' \ '(-v --verbose)*-q[Decrease logging verbosity]' \ @@ -103,6 +118,9 @@ _arguments "${_arguments_options[@]}" : \ (new) _arguments "${_arguments_options[@]}" : \ '--app-name=[Name of the app to use in the migration (default\: crate name)]:APP_NAME:_default' \ +'-p+[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--package=[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--release[Use target/release instead of target/debug when looking for the project binary]' \ '*-v[Increase logging verbosity]' \ '*--verbose[Increase logging verbosity]' \ '(-v --verbose)*-q[Decrease logging verbosity]' \ @@ -151,6 +169,9 @@ esac ;; (cli) _arguments "${_arguments_options[@]}" : \ +'-p+[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--package=[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--release[Use target/release instead of target/debug when looking for the project binary]' \ '*-v[Increase logging verbosity]' \ '*--verbose[Increase logging verbosity]' \ '(-v --verbose)*-q[Decrease logging verbosity]' \ @@ -171,8 +192,11 @@ _arguments "${_arguments_options[@]}" : \ _arguments "${_arguments_options[@]}" : \ '-o+[Directory to write the manpages to \[default\: current directory\]]:OUTPUT_DIR:_files' \ '--output-dir=[Directory to write the manpages to \[default\: current directory\]]:OUTPUT_DIR:_files' \ +'-p+[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--package=[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ '-c[Create the directory if it doesn'\''t exist]' \ '--create[Create the directory if it doesn'\''t exist]' \ +'--release[Use target/release instead of target/debug when looking for the project binary]' \ '*-v[Increase logging verbosity]' \ '*--verbose[Increase logging verbosity]' \ '(-v --verbose)*-q[Decrease logging verbosity]' \ @@ -183,6 +207,9 @@ _arguments "${_arguments_options[@]}" : \ ;; (completions) _arguments "${_arguments_options[@]}" : \ +'-p+[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--package=[Package to use, in case you'\''re running this in a workspace]:PACKAGE:_default' \ +'--release[Use target/release instead of target/debug when looking for the project binary]' \ '*-v[Increase logging verbosity]' \ '*--verbose[Increase logging verbosity]' \ '(-v --verbose)*-q[Decrease logging verbosity]' \ diff --git a/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__no_args.snap b/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__no_args.snap index 9fc482ef1..43d02d9c6 100644 --- a/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__no_args.snap +++ b/cot-cli/tests/snapshot_testing/cli/snapshots/cli__snapshot_testing__cli__no_args.snap @@ -20,6 +20,9 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -v, --verbose... Increase logging verbosity - -q, --quiet... Decrease logging verbosity - -h, --help Print help + --release Use target/release instead of target/debug when looking for the project + binary + -p, --package Package to use, in case you're running this in a workspace + -v, --verbose... Increase logging verbosity + -q, --quiet... Decrease logging verbosity + -h, --help Print help diff --git a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help.snap b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help.snap index 710c81f20..9ac2a4d0e 100644 --- a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help.snap +++ b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help.snap @@ -19,9 +19,12 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -v, --verbose... Increase logging verbosity - -q, --quiet... Decrease logging verbosity - -h, --help Print help - -V, --version Print version + --release Use target/release instead of target/debug when looking for the project + binary + -p, --package Package to use, in case you're running this in a workspace + -v, --verbose... Increase logging verbosity + -q, --quiet... Decrease logging verbosity + -h, --help Print help + -V, --version Print version ----- stderr ----- diff --git a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_cli_completions.snap b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_cli_completions.snap index 8e362c49a..94e49a54f 100644 --- a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_cli_completions.snap +++ b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_cli_completions.snap @@ -18,8 +18,11 @@ Arguments: Shell to generate completions for [possible values: bash, elvish, fish, powershell, zsh] Options: - -v, --verbose... Increase logging verbosity - -q, --quiet... Decrease logging verbosity - -h, --help Print help + --release Use target/release instead of target/debug when looking for the project + binary + -p, --package Package to use, in case you're running this in a workspace + -v, --verbose... Increase logging verbosity + -q, --quiet... Decrease logging verbosity + -h, --help Print help ----- stderr ----- diff --git a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_cli_manpages.snap b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_cli_manpages.snap index 8229dced9..1422eb943 100644 --- a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_cli_manpages.snap +++ b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_cli_manpages.snap @@ -16,8 +16,11 @@ Usage: cot cli manpages [OPTIONS] Options: -o, --output-dir Directory to write the manpages to [default: current directory] - -v, --verbose... Increase logging verbosity + --release Use target/release instead of target/debug when looking for the + project binary -c, --create Create the directory if it doesn't exist + -p, --package Package to use, in case you're running this in a workspace + -v, --verbose... Increase logging verbosity -q, --quiet... Decrease logging verbosity -h, --help Print help diff --git a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_migration.snap b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_migration.snap index eeceba2ac..b98d57787 100644 --- a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_migration.snap +++ b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_migration.snap @@ -20,8 +20,11 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -v, --verbose... Increase logging verbosity - -q, --quiet... Decrease logging verbosity - -h, --help Print help + --release Use target/release instead of target/debug when looking for the project + binary + -p, --package Package to use, in case you're running this in a workspace + -v, --verbose... Increase logging verbosity + -q, --quiet... Decrease logging verbosity + -h, --help Print help ----- stderr ----- diff --git a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_migration_list.snap b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_migration_list.snap index f87813e2e..4ae1b261c 100644 --- a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_migration_list.snap +++ b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_migration_list.snap @@ -18,8 +18,11 @@ Arguments: [PATH] Path to the crate directory to list migrations for [default: current directory] Options: - -v, --verbose... Increase logging verbosity - -q, --quiet... Decrease logging verbosity - -h, --help Print help + --release Use target/release instead of target/debug when looking for the project + binary + -p, --package Package to use, in case you're running this in a workspace + -v, --verbose... Increase logging verbosity + -q, --quiet... Decrease logging verbosity + -h, --help Print help ----- stderr ----- diff --git a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_migration_make.snap b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_migration_make.snap index 90eb57e0e..224fbd85b 100644 --- a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_migration_make.snap +++ b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_migration_make.snap @@ -19,9 +19,12 @@ Arguments: Options: --app-name Name of the app to use in the migration [default: crate name] - -v, --verbose... Increase logging verbosity + --release Use target/release instead of target/debug when looking for the + project binary --output-dir Directory to write the migrations to [default: the migrations/ directory in the crate's src/ directory] + -p, --package Package to use, in case you're running this in a workspace + -v, --verbose... Increase logging verbosity -q, --quiet... Decrease logging verbosity -h, --help Print help diff --git a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_new.snap b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_new.snap index 4e4d857e8..c526649d7 100644 --- a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_new.snap +++ b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__help_new.snap @@ -18,10 +18,13 @@ Arguments: Options: --name Set the resulting crate name [default: the directory name] - -v, --verbose... Increase logging verbosity - -q, --quiet... Decrease logging verbosity + --release Use target/release instead of target/debug when looking for the project + binary + -p, --package Package to use, in case you're running this in a workspace --use-git Use the latest `cot` version from git instead of a published crate --cot-path Use `cot` from the specified path instead of a published crate + -v, --verbose... Increase logging verbosity + -q, --quiet... Decrease logging verbosity -h, --help Print help ----- stderr ----- diff --git a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__long_help.snap b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__long_help.snap index 0f46b54d1..972d94aad 100644 --- a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__long_help.snap +++ b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__long_help.snap @@ -19,9 +19,23 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -v, --verbose... Increase logging verbosity - -q, --quiet... Decrease logging verbosity - -h, --help Print help - -V, --version Print version + --release + Use target/release instead of target/debug when looking for the project binary + + -p, --package + Package to use, in case you're running this in a workspace + + -v, --verbose... + Increase logging verbosity + + -q, --quiet... + Decrease logging verbosity + + -h, --help + Print help + + -V, --version + Print version + ----- stderr ----- diff --git a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__no_args.snap b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__no_args.snap index 6cd3549f4..c192bef20 100644 --- a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__no_args.snap +++ b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__no_args.snap @@ -20,7 +20,10 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -v, --verbose... Increase logging verbosity - -q, --quiet... Decrease logging verbosity - -h, --help Print help - -V, --version Print version + --release Use target/release instead of target/debug when looking for the project + binary + -p, --package Package to use, in case you're running this in a workspace + -v, --verbose... Increase logging verbosity + -q, --quiet... Decrease logging verbosity + -h, --help Print help + -V, --version Print version diff --git a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__short_help.snap b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__short_help.snap index d2cc816d2..1e24032b2 100644 --- a/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__short_help.snap +++ b/cot-cli/tests/snapshot_testing/help/snapshots/cli__snapshot_testing__help__short_help.snap @@ -19,9 +19,23 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -v, --verbose... Increase logging verbosity - -q, --quiet... Decrease logging verbosity - -h, --help Print help - -V, --version Print version + --release + Use target/release instead of target/debug when looking for the project binary + + -p, --package + Package to use, in case you're running this in a workspace + + -v, --verbose... + Increase logging verbosity + + -q, --quiet... + Decrease logging verbosity + + -h, --help + Print help + + -V, --version + Print version + ----- stderr ----- diff --git a/cot/Cargo.toml b/cot/Cargo.toml index 8f50d1584..8603f9909 100644 --- a/cot/Cargo.toml +++ b/cot/Cargo.toml @@ -54,7 +54,7 @@ schemars = { workspace = true, optional = true, features = ["derive"] } sea-query = { workspace = true, optional = true } sea-query-sqlx = { workspace = true, features = ["with-chrono"], optional = true } serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true, optional = true } +serde_json.workspace = true sqlx = { workspace = true, features = ["runtime-tokio", "chrono"], optional = true } subtle = { workspace = true, features = ["std"] } swagger-ui-redist = { workspace = true, optional = true } @@ -111,7 +111,7 @@ sqlite = ["db", "sea-query/backend-sqlite", "sea-query-sqlx/sqlx-sqlite", "sqlx/ postgres = ["db", "sea-query/backend-postgres", "sea-query-sqlx/sqlx-postgres", "sqlx/postgres"] mysql = ["db", "sea-query/backend-mysql", "sea-query-sqlx/sqlx-mysql", "sqlx/mysql"] redis = ["cache", "dep:deadpool-redis", "dep:redis", "json"] -json = ["dep:serde_json", "cot_core/json"] +json = ["cot_core/json"] openapi = ["json", "cot_core/schemars", "dep:aide", "dep:schemars"] swagger-ui = ["openapi", "dep:swagger-ui-redist"] live-reload = ["dep:tower-livereload"] diff --git a/cot/src/cli.rs b/cot/src/cli.rs index 97652a2e1..ca1836916 100644 --- a/cot/src/cli.rs +++ b/cot/src/cli.rs @@ -175,6 +175,10 @@ impl Cli { self.tasks.insert(Some(name), Box::new(task)); } + pub(crate) fn command(&self) -> &Command { + &self.command + } + #[must_use] pub(crate) fn common_options(&mut self) -> CommonOptions { let matches = self.command.get_matches_mut(); diff --git a/cot/src/lib.rs b/cot/src/lib.rs index 1479cf2df..1d5f10e49 100644 --- a/cot/src/lib.rs +++ b/cot/src/lib.rs @@ -69,6 +69,7 @@ pub mod config; #[cfg(feature = "email")] pub mod email; mod error_page; +pub mod metadata; pub mod middleware; #[cfg(feature = "openapi")] pub mod openapi; diff --git a/cot/src/metadata.rs b/cot/src/metadata.rs new file mode 100644 index 000000000..c4d2498ac --- /dev/null +++ b/cot/src/metadata.rs @@ -0,0 +1,104 @@ +//! Metadata exported by Cot project binaries for the proxying `cot` CLI. + +use clap::Command; +use serde::{Deserialize, Serialize}; + +/// Flag used to ask a Cot project binary to print its CLI metadata as JSON. +pub const METADATA_FLAG: &str = "--metadata"; + +/// Metadata describing the commands exposed by a Cot project binary. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ProjectMetadata { + /// Name of the project binary that produced the metadata. + pub binary_name: String, + /// Top-level commands exposed by the project binary. + pub commands: Vec, +} + +/// Metadata for a single CLI command. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct CommandMeta { + /// Command name. + pub name: String, + /// Optional command description. + pub about: Option, + /// Visible aliases accepted by the command. + pub aliases: Vec, + /// Nested subcommands exposed by this command. + pub subcommands: Vec, +} + +/// Extract proxyable command metadata from a clap command definition. +pub fn extract(cmd: &Command) -> ProjectMetadata { + ProjectMetadata { + binary_name: cmd.get_name().to_string(), + commands: cmd + .get_subcommands() + .filter(|subcmd| !subcmd.is_hide_set()) + .map(extract_command) + .collect(), + } +} + +fn extract_command(cmd: &Command) -> CommandMeta { + CommandMeta { + name: cmd.get_name().to_string(), + about: cmd.get_about().map(ToString::to_string), + aliases: cmd.get_all_aliases().map(ToString::to_string).collect(), + subcommands: cmd + .get_subcommands() + .filter(|subcmd| !subcmd.is_hide_set()) + .map(extract_command) + .collect(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extract() { + let command = Command::new("demo") + .subcommand(Command::new("serve").about("Serve requests")) + .subcommand(Command::new("secret").hide(true)); + + let metadata = extract(&command); + + assert_eq!(metadata.binary_name, "demo"); + assert_eq!(metadata.commands.len(), 1); + assert_eq!(metadata.commands[0].name, "serve"); + assert_eq!( + metadata.commands[0].about.as_deref(), + Some("Serve requests") + ); + } + + #[test] + fn test_extract_command_with_visible_aliases() { + let command = Command::new("demo").subcommand( + Command::new("database") + .visible_alias("db") + .subcommand(Command::new("migrate").visible_alias("mig")) + .subcommand(Command::new("internal").hide(true)), + ); + + let metadata = extract(&command); + let database = &metadata.commands[0]; + + assert_eq!(database.name, "database"); + assert_eq!(database.aliases, vec!["db"]); + assert_eq!(database.subcommands.len(), 1); + assert_eq!(database.subcommands[0].name, "migrate"); + assert_eq!(database.subcommands[0].aliases, vec!["mig"]); + } + + #[test] + fn test_extract_command_with_no_about() { + let command = Command::new("demo").subcommand(Command::new("plain")); + + let metadata = extract(&command); + + assert_eq!(metadata.commands[0].about, None); + } +} diff --git a/cot/src/project.rs b/cot/src/project.rs index e71f57597..860f64b16 100644 --- a/cot/src/project.rs +++ b/cot/src/project.rs @@ -939,6 +939,12 @@ impl Bootstrapper { cli.set_metadata(self.project.cli_metadata()); self.project.register_tasks(&mut cli); + if std::env::args().any(|arg| arg == cot::metadata::METADATA_FLAG) { + let meta = cot::metadata::extract(cli.command()); + println!("{}", serde_json::to_string_pretty(&meta).unwrap()); + std::process::exit(0); + } + let common_options = cli.common_options(); let self_with_context = self.with_config_name(common_options.config())?;