diff --git a/.gitignore b/.gitignore index 00cabc7..d3334d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target +AGENTS.md test_servers/ fyrer.yml diff --git a/Cargo.lock b/Cargo.lock index 054c224..b81281b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,97 +3,10 @@ version = 4 [[package]] -name = "aho-corasick" -version = "1.1.3" +name = "anyhow" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "bstr" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "clearscreen" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a8ab73a1c02b0c15597b22e09c7dc36e63b2f601f9d1e83ac0c3decd38b1ae" -dependencies = [ - "nix", - "terminfo", - "thiserror", - "which", - "windows-sys 0.59.0", -] - -[[package]] -name = "colored" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" -dependencies = [ - "lazy_static", - "windows-sys 0.59.0", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "env_home" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "equivalent" @@ -102,410 +15,137 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "filetime" -version = "0.2.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +name = "fyrer" +version = "0.3.0" dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.60.2", + "anyhow", + "fyrer-core", + "fyrer-error", + "fyrer-graph", ] [[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +name = "fyrer-cache" +version = "0.3.0" dependencies = [ - "libc", + "fyrer-types", ] [[package]] -name = "fyrer" -version = "0.2.2" +name = "fyrer-core" +version = "0.3.0" dependencies = [ - "clearscreen", - "colored", - "globset", - "notify", + "fyrer-error", "serde", - "serde_derive", "serde_yaml", - "tokio", -] - -[[package]] -name = "globset" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" - -[[package]] -name = "indexmap" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "inotify" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" -dependencies = [ - "bitflags 1.3.2", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "kqueue" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.177" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" - -[[package]] -name = "libredox" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" -dependencies = [ - "bitflags 2.10.0", - "libc", - "redox_syscall", -] - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.48.0", + "thiserror", ] [[package]] -name = "mio" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +name = "fyrer-dev" +version = "0.3.0" dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", + "fyrer-types", ] [[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +name = "fyrer-env" +version = "0.3.0" dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases", - "libc", + "fyrer-types", ] [[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +name = "fyrer-error" +version = "0.3.0" dependencies = [ - "memchr", - "minimal-lexical", + "serde", + "serde_yaml", + "thiserror", ] [[package]] -name = "notify" -version = "6.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +name = "fyrer-executor" +version = "0.3.0" dependencies = [ - "bitflags 2.10.0", - "crossbeam-channel", - "filetime", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "log", - "mio 0.8.11", - "walkdir", - "windows-sys 0.48.0", + "anyhow", + "fyrer-types", ] [[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +name = "fyrer-graph" +version = "0.3.0" dependencies = [ - "lock_api", - "parking_lot_core", + "anyhow", + "fyrer-core", + "fyrer-error", + "fyrer-types", ] [[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +name = "fyrer-scheduler" +version = "0.3.0" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", + "fyrer-graph", + "fyrer-types", ] [[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +name = "fyrer-types" +version = "0.3.0" dependencies = [ - "phf_shared", + "serde", ] [[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator", - "phf_shared", -] +name = "fyrer-utils" +version = "0.3.0" [[package]] -name = "phf_generator" -version = "0.11.3" +name = "hashbrown" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand", -] +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] -name = "phf_shared" -version = "0.11.3" +name = "indexmap" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ - "siphasher", + "equivalent", + "hashbrown", ] [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "itoa" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.10.0", -] - -[[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "rustix" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" -dependencies = [ - "bitflags 2.10.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - [[package]] name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "serde" @@ -550,102 +190,31 @@ dependencies = [ "unsafe-libyaml", ] -[[package]] -name = "signal-hook-registry" -version = "1.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" -dependencies = [ - "libc", -] - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - [[package]] name = "syn" -version = "2.0.107" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "terminfo" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" -dependencies = [ - "fnv", - "nom", - "phf", - "phf_codegen", -] - [[package]] name = "thiserror" -version = "2.0.17" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio" -version = "1.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" -dependencies = [ - "bytes", - "libc", - "mio 1.1.0", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.0" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -654,282 +223,12 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.20" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "which" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" -dependencies = [ - "env_home", - "rustix", - "winsafe", -] - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winsafe" -version = "0.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" diff --git a/Cargo.toml b/Cargo.toml index c68f7f5..4d91950 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,18 @@ -[package] -name = "fyrer" -version = "0.2.2" +[workspace] +resolver = "3" +members = ["crates/*"] +default-members = ["crates/fyrer"] + +[workspace.package] +version = "0.3.0" edition = "2024" -authors = ["Calc hello@vinm.me"] -description = "A lightweight tool to run multiple dev servers concurrently" -readme = "README.md" +authors = ["Calc "] license = "MIT" repository = "https://github.com/07calc/fyrer" -keywords = ["dev", "server", "concurrent", "tool"] -categories = ["development-tools"] -exclude = ["/target/*", "*.rs.bk", "*.log", "examples/*"] -[dependencies] -clearscreen = "4.0.2" -colored = "2.0" +[workspace.dependencies] +anyhow = "1.0" +clap = { version = "4.5", features = ["derive"] } serde = { version = "1.0", features = ["derive"] } -serde_derive = "1.0.228" -serde_yaml = "0.9.34" -tokio = { version = "1.0", features = ["full"] } -notify = "6.1" -globset = "0.4.18" +serde_yaml = "0.9" +thiserror = "1.0" diff --git a/crates/cache/Cargo.toml b/crates/cache/Cargo.toml new file mode 100644 index 0000000..15709c2 --- /dev/null +++ b/crates/cache/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "fyrer-cache" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +fyrer-types = { path = "../types" } diff --git a/crates/cache/src/lib.rs b/crates/cache/src/lib.rs new file mode 100644 index 0000000..0771bcc --- /dev/null +++ b/crates/cache/src/lib.rs @@ -0,0 +1,3 @@ +pub fn is_cacheable(is_persistent: bool, is_utility: bool) -> bool { + !(is_persistent || is_utility) +} diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml new file mode 100644 index 0000000..d5fc5d9 --- /dev/null +++ b/crates/core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "fyrer-core" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +thiserror.workspace = true +serde.workspace = true +serde_yaml.workspace = true +fyrer-error = { path = "../error/" } diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs new file mode 100644 index 0000000..4c316b3 --- /dev/null +++ b/crates/core/src/config.rs @@ -0,0 +1,519 @@ +use fyrer_error::{FyrerError, FyrerResult, config::ConfigError}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{HashMap, HashSet}, + path::PathBuf, +}; + +use crate::tasks::{Task, TaskId, TaskMap}; + +pub type EnvMap = HashMap; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FyrerConfig { + pub version: u32, + #[serde(default = "default_env_map")] + pub env: EnvMap, + pub projects: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ProjectConfig { + pub name: String, + pub root: PathBuf, + #[serde(default = "default_env_map")] + pub env: EnvMap, + pub env_path: String, + #[serde(default = "default_tasks")] + pub tasks: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TaskConfig { + #[serde(default = "default_cmd")] + pub cmd: String, + #[serde(default = "default_vec_string")] + pub depends_on: Vec, + #[serde(default = "default_bool")] + pub persistent: bool, + #[serde(default = "default_vec_string")] + pub inputs: Vec, + #[serde(default = "default_vec_string")] + pub outputs: Vec, + #[serde(default = "default_vec_string")] + pub ignore: Vec, + #[serde(default = "default_bool")] + pub cache: bool, + #[serde(default = "default_restart")] + pub restart: RestartConfig, + #[serde(default = "default_env_map")] + pub env: EnvMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RestartConfig { + pub strategy: RestartStrategy, + pub delay: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum RestartStrategy { + FileChange, + OnFailure, + Never, +} + +impl FyrerConfig { + pub fn new_from_path(path: &str) -> FyrerResult { + let content = std::fs::read_to_string(path).map_err(|e| { + FyrerError::Config(ConfigError::ReadFile { + path: path.to_string(), + source: e, + }) + })?; + + let config: FyrerConfig = serde_yaml::from_str(content.as_str()) + .map_err(|e| FyrerError::Config(ConfigError::ParseYaml(e)))?; + config + .validate() + .map_err(|e| FyrerError::Config(ConfigError::InvalidConfig(e.to_string())))?; + Ok(config) + } + + pub fn new_from_str(content: &str) -> FyrerResult { + let config: FyrerConfig = serde_yaml::from_str(content) + .map_err(|e| FyrerError::Config(ConfigError::ParseYaml(e)))?; + config + .validate() + .map_err(|e| FyrerError::Config(ConfigError::InvalidConfig(e.to_string())))?; + Ok(config) + } + + fn validate(&self) -> FyrerResult<()> { + self.validate_version()?; + self.validate_projects()?; + self.validate_tasks()?; + Ok(()) + } + + fn validate_version(&self) -> FyrerResult<()> { + if self.version != 1 { + return Err(FyrerError::Config(ConfigError::InvalidConfig(format!( + "unsupported config version: {}", + self.version + )))); + } + Ok(()) + } + + fn validate_projects(&self) -> FyrerResult<()> { + let mut project_names = HashSet::new(); + for project in &self.projects { + // let mut task_names: HashSet = HashSet::new(); + if !project_names.insert(&project.name) { + return Err(FyrerError::Config(ConfigError::InvalidConfig(format!( + "duplicate project name: {}", + project.name + )))); + } + if project.root.as_os_str().is_empty() { + return Err(FyrerError::Config(ConfigError::InvalidConfig(format!( + "project '{}' has empty root path", + project.name + )))); + } + if project.root.is_absolute() { + return Err(FyrerError::Config(ConfigError::InvalidConfig(format!( + "project '{}' has absolute root path '{}'", + project.name, + project.root.display() + )))); + } + if project.env_path.is_empty() { + return Err(FyrerError::Config(ConfigError::InvalidConfig(format!( + "project '{}' has empty env_path", + project.name + )))); + } + + //TODO: currently we are making a map of tasks, so its not possible to detect duplicate + //task names at the yaml parsing stage. We need to add custom validation to check for + //duplicate task names and return an error if found. This is a known issue that we will + //address in a future update. + // + // for task_name in project.tasks.keys() { + // if !task_names.insert(task_name) { + // return Err(FyrerError::Config(ConfigError::InvalidConfig(format!( + // "duplicate task name '{}' in project '{}'", + // task_name, project.name + // )))); + // } + // } + } + Ok(()) + } + + fn validate_tasks(&self) -> FyrerResult<()> { + for project in &self.projects { + for (task_name, task) in &project.tasks { + if task.cmd.is_empty() { + return Err(FyrerError::Config(ConfigError::InvalidConfig(format!( + "task '{}' in project '{}' has empty cmd", + task_name, project.name + )))); + } + + if task.cache && task.outputs.is_empty() { + return Err(FyrerError::Config(ConfigError::InvalidConfig(format!( + "task '{}' in project '{}' has cache enabled but no outputs defined", + task_name, project.name + )))); + } + + if task.cache && task.persistent { + return Err(FyrerError::Config(ConfigError::InvalidConfig(format!( + "task '{}' in project '{}' cannot be both cacheable and persistent", + task_name, project.name + )))); + } + + if task.restart.strategy == RestartStrategy::FileChange && task.inputs.is_empty() { + return Err(FyrerError::Config(ConfigError::InvalidConfig(format!( + "task '{}' in project '{}' has file change restart strategy but no inputs defined", + task_name, project.name + )))); + } + } + } + Ok(()) + } + + pub fn create_task_map(&self) -> TaskMap { + let mut task_map = HashMap::new(); + for project in &self.projects { + for (task_name, task_config) in &project.tasks { + let mut env = self.env.clone(); + env.extend(project.env.clone()); + env.extend(task_config.env.clone()); + let task = Task { + project_name: project.name.clone(), + project_root: project.root.clone(), + env, + task_name: task_name.clone(), + cmd: task_config.cmd.clone(), + depends_on: task_config.depends_on.clone(), + persistent: task_config.persistent, + inputs: task_config.inputs.clone(), + outputs: task_config.outputs.clone(), + ignore: task_config.ignore.clone(), + cache: task_config.cache, + restart: task_config.restart.clone(), + }; + let task_id = TaskId::new(&project.name, task_name); + task_map.insert(task_id, task); + } + } + task_map + } +} +fn default_vec_string() -> Vec { + Vec::new() +} + +fn default_env_map() -> EnvMap { + HashMap::new() +} + +fn default_tasks() -> HashMap { + HashMap::new() +} + +fn default_bool() -> bool { + false +} + +fn default_cmd() -> String { + "echo from fyrer".to_string() +} + +fn default_restart() -> RestartConfig { + RestartConfig { + strategy: RestartStrategy::Never, + delay: None, + } +} + +mod tests { + use super::*; + #[test] + fn test_valid_config() { + let yaml = r#" +version: 1 +env: + GLOBAL_VAR: global_value +projects: + - name: project1 + root: ./project1 + env: + PROJECT_VAR: project_value + env_path: .env + tasks: + build: + cmd: echo Building project1 + depends_on: [] + persistent: false + inputs: ["src/**/*"] + outputs: ["dist/**/*"] + ignore: [] + cache: true + restart: + strategy: FileChange + delay: 1000 + test: + cmd: "echo Testing project1" + depends_on: ["build"] + persistent: false + inputs: ["tests/**/*"] + outputs: [] + ignore: [] + cache: false + restart: + strategy: OnFailure + delay: 500 + - name: project2 + root: ./project2 + env: + PROJECT_VAR: project2_value + env_path: .env + tasks: + deploy: + cmd: "echo Deploying project2" + depends_on: [] + persistent: true + inputs: [] + outputs: [] + ignore: [] + cache: false + restart: + strategy: Never + delay: null +"#; + let config = FyrerConfig::new_from_str(yaml).expect("Failed to parse invalid config"); + assert_eq!(config.version, 1); + assert_eq!(config.env.get("GLOBAL_VAR").unwrap(), "global_value"); + assert_eq!(config.projects.len(), 2); + + let project1 = &config.projects[0]; + assert_eq!(project1.name, "project1"); + assert_eq!(project1.root, PathBuf::from("./project1")); + assert_eq!(project1.env.get("PROJECT_VAR").unwrap(), "project_value"); + assert_eq!(project1.env_path, ".env"); + assert_eq!(project1.tasks.len(), 2); + + let build_task = project1.tasks.get("build").unwrap(); + assert_eq!(build_task.cmd, "echo Building project1"); + assert_eq!(build_task.depends_on, Vec::::new()); + assert!(!build_task.persistent); + assert_eq!(build_task.inputs, vec!["src/**/*"]); + assert_eq!(build_task.outputs, vec!["dist/**/*"]); + assert_eq!(build_task.ignore, Vec::::new()); + assert!(build_task.cache); + assert_eq!(build_task.restart.strategy, RestartStrategy::FileChange); + assert_eq!(build_task.restart.delay, Some(1000)); + + let test_task = project1.tasks.get("test").unwrap(); + assert_eq!(test_task.cmd, "echo Testing project1"); + assert_eq!(test_task.depends_on, vec!["build"]); + assert!(!test_task.persistent); + assert_eq!(test_task.inputs, vec!["tests/**/*"]); + assert_eq!(test_task.outputs, Vec::::new()); + assert_eq!(test_task.ignore, Vec::::new()); + assert!(!test_task.cache); + assert_eq!(test_task.restart.strategy, RestartStrategy::OnFailure); + assert_eq!(test_task.restart.delay, Some(500)); + + let project2 = &config.projects[1]; + assert_eq!(project2.name, "project2"); + } + + #[test] + fn test_duplicate_project_names() { + let yaml = r#" +version: 1 +projects: + - name: project1 + root: ./project1 + env_path: .env + tasks: {} + - name: project1 + root: ./project2 + env_path: .env + tasks: {} +"#; + let err = FyrerConfig::new_from_str(yaml).err().unwrap(); + match err { + FyrerError::Config(ConfigError::InvalidConfig(msg)) => { + assert!(msg.contains("duplicate project name: project1")); + } + _ => panic!("Expected InvalidConfig error"), + } + } + + #[test] + #[ignore = "This test currently fails because serde_yaml allows duplicate keys and we need to add custom validation to detect them. This is a known issue that we will address in a future update."] + fn test_duplicate_task_names() { + let yaml = r#" +version: 1 +projects: + - name: project1 + root: ./project1 + env_path: .env + tasks: + build: + cmd: echo Building + depends_on: [] + persistent: false + inputs: [] + outputs: [] + ignore: [] + cache: false + restart: + strategy: Never + delay: null + build: + cmd: echo Building again + depends_on: [] + persistent: false + inputs: [] + outputs: [] + ignore: [] + cache: false + restart: + strategy: Never + delay: null +"#; + let err = FyrerConfig::new_from_str(yaml).err().unwrap(); + match err { + FyrerError::Config(ConfigError::InvalidConfig(msg)) => { + assert!(msg.contains("duplicate task name 'build' in project 'project1'")); + } + _ => panic!("Expected InvalidConfig error"), + } + } + + #[test] + fn test_invalid_version() { + let yaml = r#" +version: 2 +projects: [] +"#; + let err = FyrerConfig::new_from_str(yaml).err().unwrap(); + match err { + FyrerError::Config(ConfigError::InvalidConfig(msg)) => { + assert!(msg.contains("unsupported config version: 2")); + } + _ => panic!("Expected InvalidConfig error"), + } + } + + #[test] + fn test_empty_cmd() { + let yaml = r#" +version: 1 +env: {} +projects: + - name: project1 + root: ./project1 + env_path: .env + tasks: + build: + cmd: "" + depends_on: [] + persistent: false + inputs: [] + outputs: [] + ignore: [] + cache: false + restart: + strategy: Never + delay: null +"#; + let err = FyrerConfig::new_from_str(yaml).err().unwrap(); + match err { + FyrerError::Config(ConfigError::InvalidConfig(msg)) => { + assert!(msg.contains("task 'build' in project 'project1' has empty cmd")); + } + _ => panic!("Expected InvalidConfig error"), + } + } + + #[test] + fn test_cache_without_outputs() { + let yaml = r#" +version: 1 +env: {} +projects: + - name: project1 + root: ./project1 + env_path: .env + tasks: + build: + cmd: echo Building + depends_on: [] + persistent: false + inputs: [] + outputs: [] + ignore: [] + cache: true + restart: + strategy: Never + delay: null +"#; + let err = FyrerConfig::new_from_str(yaml).err().unwrap(); + match err { + FyrerError::Config(ConfigError::InvalidConfig(msg)) => { + dbg!(&msg); + assert!(msg.contains( + "task 'build' in project 'project1' has cache enabled but no outputs defined" + )); + } + _ => panic!("Expected InvalidConfig error"), + } + } + + #[test] + fn test_cache_and_persistent() { + let yaml = r#" +version: 1 +env: {} +projects: + - name: project1 + root: ./project1 + env_path: .env + tasks: + build: + cmd: echo Building + depends_on: [] + persistent: true + inputs: [] + outputs: ["dist/**/*"] + ignore: [] + cache: true + restart: + strategy: Never + delay: null +"#; + let err = FyrerConfig::new_from_str(yaml).err().unwrap(); + match err { + FyrerError::Config(ConfigError::InvalidConfig(msg)) => { + assert!(msg.contains( + "task 'build' in project 'project1' cannot be both cacheable and persistent" + )); + } + _ => panic!("Expected InvalidConfig error"), + } + } +} diff --git a/crates/core/src/fs.rs b/crates/core/src/fs.rs new file mode 100644 index 0000000..c849ba8 --- /dev/null +++ b/crates/core/src/fs.rs @@ -0,0 +1,21 @@ +use std::fs::read_to_string; + +use fyrer_error::{FyrerError, FyrerResult, config::ConfigError}; + +pub fn dir_exists(path: &str) -> bool { + std::path::Path::new(path).is_dir() +} + +pub fn file_exists(path: &str) -> bool { + std::path::Path::new(path).is_file() +} + +pub fn read_file(path: &str) -> FyrerResult { + let content = read_to_string(path).map_err(|e| { + FyrerError::Config(ConfigError::ReadFile { + path: path.to_string(), + source: e, + }) + })?; + Ok(content) +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs new file mode 100644 index 0000000..abff079 --- /dev/null +++ b/crates/core/src/lib.rs @@ -0,0 +1,3 @@ +pub mod config; +pub mod fs; +pub mod tasks; diff --git a/crates/core/src/tasks.rs b/crates/core/src/tasks.rs new file mode 100644 index 0000000..cf2bbae --- /dev/null +++ b/crates/core/src/tasks.rs @@ -0,0 +1,47 @@ +use std::{collections::HashMap, path::PathBuf}; + +use crate::config::{EnvMap, RestartConfig}; + +#[derive(Debug, Clone, PartialEq, Hash, Eq)] +pub struct TaskId { + project_name: String, + task_name: String, +} +#[derive(Debug, Clone)] +pub struct Task { + pub project_name: String, + pub project_root: PathBuf, + pub env: EnvMap, + pub task_name: String, + pub cmd: String, + pub depends_on: Vec, + pub persistent: bool, + pub inputs: Vec, + pub outputs: Vec, + pub ignore: Vec, + pub cache: bool, + pub restart: RestartConfig, +} + +pub type TaskMap = HashMap; + +impl TaskId { + pub fn new(project_name: &str, task_name: &str) -> TaskId { + TaskId { + project_name: project_name.to_string(), + task_name: task_name.to_string(), + } + } + + pub fn to_string(&self) -> String { + format!("{}:{}", self.project_name, self.task_name) + } + pub fn from_string(s: &str) -> Option { + let parts: Vec<&str> = s.split(':').collect(); + if parts.len() == 2 { + Some(TaskId::new(parts[0], parts[1])) + } else { + None + } + } +} diff --git a/crates/dev/Cargo.toml b/crates/dev/Cargo.toml new file mode 100644 index 0000000..83866e7 --- /dev/null +++ b/crates/dev/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "fyrer-dev" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +fyrer-types = { path = "../types" } diff --git a/crates/dev/src/lib.rs b/crates/dev/src/lib.rs new file mode 100644 index 0000000..43fb179 --- /dev/null +++ b/crates/dev/src/lib.rs @@ -0,0 +1,3 @@ +pub fn is_persistent_mode(mode: &str) -> bool { + mode == "persistent" +} diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml new file mode 100644 index 0000000..d142694 --- /dev/null +++ b/crates/env/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "fyrer-env" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +fyrer-types = { path = "../types" } diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs new file mode 100644 index 0000000..137de08 --- /dev/null +++ b/crates/env/src/lib.rs @@ -0,0 +1,10 @@ +use std::collections::HashMap; + +pub fn merge_env( + global: &HashMap, + project: &HashMap, +) -> HashMap { + let mut merged = global.clone(); + merged.extend(project.clone()); + merged +} diff --git a/crates/error/Cargo.toml b/crates/error/Cargo.toml new file mode 100644 index 0000000..de2530d --- /dev/null +++ b/crates/error/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "fyrer-error" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +thiserror.workspace = true +serde.workspace = true +serde_yaml.workspace = true + diff --git a/crates/error/src/config.rs b/crates/error/src/config.rs new file mode 100644 index 0000000..c9796a6 --- /dev/null +++ b/crates/error/src/config.rs @@ -0,0 +1,23 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ConfigError { + #[error("failed to read file at '{path}': {source}")] + ReadFile { + path: String, + #[source] + source: std::io::Error, + }, + #[error("failed to parse yaml config: {0}")] + ParseYaml(#[from] serde_yaml::Error), + #[error("invalid config: {0}")] + InvalidConfig(String), + #[error("{0}")] + Other(String), +} + +#[derive(Debug, Error)] +pub enum FyrerError { + #[error("config error: {0}")] + Config(#[from] ConfigError), +} diff --git a/crates/error/src/graph.rs b/crates/error/src/graph.rs new file mode 100644 index 0000000..c1f5e0a --- /dev/null +++ b/crates/error/src/graph.rs @@ -0,0 +1,17 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum GraphError { + #[error("cycle detected involving task '{0}'")] + CycleDetected(String), + #[error("task '{dependency}' referenced by '{dependent}' not found")] + MissingDependency { + dependent: String, + dependency: String, + }, + #[error("task '{0}' depends on itself")] + SelfDependency(String), + #[error("invalid dependency format '{dependency}' in task '{task}', expected 'project:task'")] + InvalidTaskId { dependency: String, task: String }, +} + diff --git a/crates/error/src/lib.rs b/crates/error/src/lib.rs new file mode 100644 index 0000000..2615b0c --- /dev/null +++ b/crates/error/src/lib.rs @@ -0,0 +1,17 @@ +pub mod config; +pub mod graph; + +use thiserror::Error; + +use crate::config::ConfigError; +use crate::graph::GraphError; + +#[derive(Debug, Error)] +pub enum FyrerError { + #[error("config error: {0}")] + Config(#[from] ConfigError), + #[error("graph error: {0}")] + Graph(#[from] GraphError), +} + +pub type FyrerResult = Result; diff --git a/crates/executor/Cargo.toml b/crates/executor/Cargo.toml new file mode 100644 index 0000000..b69ce8a --- /dev/null +++ b/crates/executor/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "fyrer-executor" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +anyhow.workspace = true +fyrer-types = { path = "../types" } diff --git a/crates/executor/src/lib.rs b/crates/executor/src/lib.rs new file mode 100644 index 0000000..811f711 --- /dev/null +++ b/crates/executor/src/lib.rs @@ -0,0 +1 @@ +pub struct ExecutorScaffold; diff --git a/crates/fyrer/Cargo.toml b/crates/fyrer/Cargo.toml new file mode 100644 index 0000000..d86878c --- /dev/null +++ b/crates/fyrer/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "fyrer" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +fyrer-core = {path = "../core/"} +fyrer-graph = {path = "../graph/"} +fyrer-error = { path = "../error/" } +anyhow.workspace = true + + diff --git a/crates/fyrer/src/main.rs b/crates/fyrer/src/main.rs new file mode 100644 index 0000000..2bf54e7 --- /dev/null +++ b/crates/fyrer/src/main.rs @@ -0,0 +1,13 @@ +use std::fs; + +use fyrer_core::config::FyrerConfig; +use fyrer_error::FyrerResult; +fn main() -> FyrerResult<()> { + let config_str = fs::read_to_string("fyrer.yml").expect("Failed to read config file"); + let config = FyrerConfig::new_from_str(&config_str)?; + let task_map = config.create_task_map(); + drop(config); + let task_graph = fyrer_graph::TaskGraph::new(&task_map)?; + task_graph.validate()?; + Ok(()) +} diff --git a/crates/graph/Cargo.toml b/crates/graph/Cargo.toml new file mode 100644 index 0000000..6f81406 --- /dev/null +++ b/crates/graph/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "fyrer-graph" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +anyhow.workspace = true +fyrer-types = { path = "../types" } +fyrer-core = { path = "../core" } +fyrer-error = { path = "../error" } diff --git a/crates/graph/src/lib.rs b/crates/graph/src/lib.rs new file mode 100644 index 0000000..9f679cf --- /dev/null +++ b/crates/graph/src/lib.rs @@ -0,0 +1,132 @@ +use std::collections::HashMap; + +use fyrer_core::tasks::{TaskId, TaskMap}; +use fyrer_error::{FyrerResult, graph::GraphError}; + +#[derive(Debug)] +pub struct TaskGraph { + pub nodes: HashMap, +} + +#[derive(Debug)] +pub struct TaskNode { + pub id: TaskId, + pub deps: Vec, + pub dependents: Vec, +} + +impl TaskGraph { + pub fn new(task_map: &TaskMap) -> FyrerResult { + let mut graph = TaskGraph { + nodes: HashMap::new(), + }; + + for (id, _) in task_map { + graph.nodes.insert( + id.clone(), + TaskNode { + id: id.clone(), + deps: vec![], + dependents: Vec::new(), + }, + ); + } + + for (id, task) in task_map { + for dep in &task.depends_on { + let dep_id = if let Some((proj, task_name)) = dep.split_once(':') { + TaskId::new(proj, task_name) + } else { + TaskId::new(&task.project_name, dep) + }; + + if dep_id == *id { + return Err(fyrer_error::FyrerError::Graph( + GraphError::SelfDependency(id.to_string()), + )); + } + + if !graph.nodes.contains_key(&dep_id) { + return Err(fyrer_error::FyrerError::Graph( + GraphError::MissingDependency { + dependent: id.to_string(), + dependency: dep_id.to_string(), + }, + )); + } + + graph.nodes.get_mut(id).unwrap().deps.push(dep_id.clone()); + graph + .nodes + .get_mut(&dep_id) + .unwrap() + .dependents + .push(id.clone()); + } + } + Ok(graph) + } + + pub fn validate(&self) -> FyrerResult<()> { + let mut visited = HashMap::new(); + for node in self.nodes.values() { + if !visited.contains_key(&node.id) { + if self.has_cycle(&node.id, &mut visited) { + return Err(fyrer_error::FyrerError::Graph(GraphError::CycleDetected( + node.id.to_string(), + ))); + } + } + } + Ok(()) + } + + fn has_cycle(&self, node_id: &TaskId, visited: &mut HashMap) -> bool { + visited.insert(node_id.clone(), true); + for dep in &self.nodes.get(node_id).unwrap().deps { + if let Some(&true) = visited.get(dep) { + return true; + } + if !visited.contains_key(dep) && self.has_cycle(dep, visited) { + return true; + } + } + visited.insert(node_id.clone(), false); + false + } + + pub fn get_exec_flow(&self, task: String) -> FyrerResult>> { + let mut flow = Vec::new(); + let task_id = TaskId::from_string(&task); + + match task_id { + Some(id) => { + self.build_flow(&id, &mut flow)?; + } + None => { + return Err(fyrer_error::FyrerError::Graph(GraphError::InvalidTaskId { + dependency: task.clone(), + task: task.clone(), + })); + } + } + + Ok(flow) + } + fn build_flow(&self, task_id: &TaskId, flow: &mut Vec>) -> FyrerResult<()> { + if let Some(node) = self.nodes.get(task_id) { + for dep in &node.deps { + self.build_flow(dep, flow)?; + } + flow.push(vec![task_id.clone()]); + } else { + return Err(fyrer_error::FyrerError::Graph( + GraphError::MissingDependency { + dependent: task_id.to_string(), + dependency: task_id.to_string(), + }, + )); + } + Ok(()) + } +} diff --git a/crates/scheduler/Cargo.toml b/crates/scheduler/Cargo.toml new file mode 100644 index 0000000..4efa992 --- /dev/null +++ b/crates/scheduler/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "fyrer-scheduler" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +fyrer-graph = { path = "../graph" } +fyrer-types = { path = "../types" } diff --git a/crates/scheduler/src/lib.rs b/crates/scheduler/src/lib.rs new file mode 100644 index 0000000..2daf17f --- /dev/null +++ b/crates/scheduler/src/lib.rs @@ -0,0 +1 @@ +pub struct SchedulerScaffold; diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml new file mode 100644 index 0000000..8807f07 --- /dev/null +++ b/crates/types/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "fyrer-types" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +serde.workspace = true diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs new file mode 100644 index 0000000..149040f --- /dev/null +++ b/crates/types/src/lib.rs @@ -0,0 +1 @@ +pub struct TypesScaffold; diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml new file mode 100644 index 0000000..a7c0f82 --- /dev/null +++ b/crates/utils/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "fyrer-utils" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs new file mode 100644 index 0000000..bf40084 --- /dev/null +++ b/crates/utils/src/lib.rs @@ -0,0 +1,3 @@ +pub fn normalize_path(input: &str) -> String { + input.trim().replace('\\', "/") +} diff --git a/fyrer.example.yml b/fyrer.example.yml new file mode 100644 index 0000000..57c921d --- /dev/null +++ b/fyrer.example.yml @@ -0,0 +1,102 @@ +version: 1 +env: + LOG_LEVEL: info + NODE_ENV: development +projects: + - name: web + root: ./apps/web + env: + PORT: "3000" + API_URL: "http://localhost:8080" + env_path: ./.env + tasks: + dev: + cmd: bun run dev + depends_on: + - ui:build + watch: true + ignore: + - node_modules/** + - .next/** + - "*.db" + cache: false + restart: + strategy: Always + delay: 300 + build: + cmd: bun run build + depends_on: + - ui:build + inputs: + - src/** + - public/** + - package.json + - next.config.ts + outputs: + - .next/** + watch: false + ignore: + - node_modules/** + cache: true + restart: + strategy: Never + - name: ui + root: ./packages/ui + env: + NODE_ENV: production + env_path: ./.env.production + tasks: + build: + cmd: bun run build + inputs: + - src/** + - package.json + - tsconfig.json + outputs: + - dist/** + watch: false + ignore: + - node_modules/** + - dist/** + cache: true + restart: + strategy: Never + dev: + cmd: bun run dev + watch: true + ignore: + - node_modules/** + - dist/** + cache: false + restart: + strategy: FileChange + delay: 200 + - name: api + root: ./apps/api + env: + RUST_LOG: debug + env_path: ./.env + tasks: + build: + cmd: cargo build --release + inputs: + - src/** + - Cargo.toml + - Cargo.lock + outputs: + - target/release/api + watch: false + ignore: + - target/debug/** + cache: true + restart: + strategy: Never + dev: + cmd: cargo watch -x run + watch: true + ignore: + - target/** + cache: false + restart: + strategy: Always + delay: 500 diff --git a/src/colors.rs b/src/colors.rs deleted file mode 100644 index f0eb0ee..0000000 --- a/src/colors.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub const COLORS: &[colored::Color] = &[ - colored::Color::Green, - colored::Color::Yellow, - colored::Color::Blue, - colored::Color::Magenta, - colored::Color::Cyan, - colored::Color::White, - colored::Color::BrightMagenta, -]; diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 3f8ce49..0000000 --- a/src/config.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::collections::HashMap; - -use serde_derive::Deserialize; - -#[derive(Debug, Deserialize)] -pub struct FyrerConfig { - pub installers: Option>, - pub services: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Service { - pub name: String, - pub dir: String, - pub cmd: String, - pub env: Option>, - pub watch: Option, - pub ignore: Option>, - pub env_path: Option, - pub quiet: Option, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Installer { - pub dir: String, - pub cmd: String, -} diff --git a/src/env_parser.rs b/src/env_parser.rs deleted file mode 100644 index ba65155..0000000 --- a/src/env_parser.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::collections::HashMap; - -pub fn parse_env(content: &str) -> HashMap { - let mut out = HashMap::new(); - if content.is_empty() { - return out; - } - for line in content.lines() { - if line.starts_with("#") || line.is_empty() { - continue; - } - if let Some((k, v)) = line.split_once("=") { - let key = k.trim(); - let val = v.trim(); - if !key.is_empty() { - out.insert(key.to_string(), val.to_string()); - } - } - } - - out -} diff --git a/src/installer.rs b/src/installer.rs deleted file mode 100644 index a186cc6..0000000 --- a/src/installer.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::config::FyrerConfig; -use colored::*; - -pub async fn run_installers(config: &FyrerConfig) { - if let Some(installers) = &config.installers { - println!("{}", " Running installer steps...".bright_cyan().bold()); - println!( - "{}", - "────────────────────────────────────────────".bright_black() - ); - - for installer in installers { - println!( - "{} {}", - "🔹 Running installer in:".bright_blue().bold(), - installer.dir.bright_yellow() - ); - println!( - "{} {}", - " Command:".bright_blue(), - installer.cmd.bright_green().italic() - ); - - #[cfg(unix)] - let mut cmd = tokio::process::Command::new("sh"); - #[cfg(unix)] - cmd.arg("-c").arg(&installer.cmd); - - #[cfg(windows)] - let mut cmd = tokio::process::Command::new("cmd"); - #[cfg(windows)] - cmd.arg("/C").arg(&installer.cmd); - - cmd.current_dir(&installer.dir); - cmd.stdout(std::process::Stdio::inherit()); - cmd.stderr(std::process::Stdio::inherit()); - - match cmd.status().await { - Ok(status) if status.success() => { - println!( - "{} {}\n", - " Installer completed successfully in:" - .bright_green() - .bold(), - installer.dir.bright_yellow() - ); - } - Ok(status) => { - eprintln!( - "{} {} {}", - " Installer failed in:".bright_red().bold(), - installer.dir.bright_yellow(), - format!("(Exit code: {})", status.code().unwrap_or(-1)).bright_red() - ); - println!(); - } - Err(e) => { - eprintln!( - "{} {}: {}", - " Failed to execute installer in:".bright_red().bold(), - installer.dir.bright_yellow(), - e.to_string().bright_red() - ); - println!(); - } - } - - println!( - "{}", - "────────────────────────────────────────────".bright_black() - ); - } - - println!("{}", " All installer steps completed!".bright_cyan().bold()); - println!(); - } else { - println!( - "{}", - "⚡ No installer steps defined.".bright_yellow().bold() - ); - } -} diff --git a/src/kill_process.rs b/src/kill_process.rs deleted file mode 100644 index 9308b6e..0000000 --- a/src/kill_process.rs +++ /dev/null @@ -1,64 +0,0 @@ -use tokio::process::Child; -use tokio::{ - process::Command, -}; -use std::process::Stdio; - -pub async fn kill_process(child: &mut Child) { - if let Some(pid) = child.id() { - #[cfg(unix)] - { - let output = Command::new("ps") - .arg("-o") - .arg("pid=") - .arg("--ppid") - .arg(pid.to_string()) - .output() - .await; - - match output { - Ok(output) => { - let pids = String::from_utf8_lossy(&output.stdout) - .lines() - .filter_map(|l| l.trim().parse::().ok()) - .collect::>(); - - for child_pid in pids { - let _ = Command::new("kill") - .arg("-9") - .arg(child_pid.to_string()) - .status() - .await; - } - - let _ = Command::new("kill") - .arg("-9") - .arg(pid.to_string()) - .status() - .await; - } - Err(_) => { - let _ = Command::new("kill") - .arg("-9") - .arg(pid.to_string()) - .status() - .await; - } - } - } - - #[cfg(windows)] - { - let _ = Command::new("taskkill") - .arg("/PID") - .arg(pid.to_string()) - .arg("/T") - .arg("/F") - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .status() - .await; - } - } -} - diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index f7aebf1..0000000 --- a/src/main.rs +++ /dev/null @@ -1,47 +0,0 @@ -use colored::Colorize; - -use crate::{colors::COLORS, parser::load_config}; - -mod colors; -mod config; -mod env_parser; -mod installer; -mod kill_process; -mod parser; -mod print_banner; -mod runner; -mod spawn_service; -mod watcher; - -#[tokio::main] -async fn main() { - clearscreen::clear().expect("Failed to clear the screen"); - let config = load_config("fyrer.yml"); - let mut handles = vec![]; - print_banner::print_banner(); - installer::run_installers(&config).await; - println!("{} {}", "┌─", "Starting services...".bright_cyan().bold()); - - println!("{}", "│".bright_black()); - - let max_name_len = config - .services - .iter() - .map(|s| s.name.len() + 2) // +2 for brackets [ ] - .max() - .unwrap_or(8); // default if no services - - for (i, service) in config.services.into_iter().enumerate() { - let color = COLORS[i % COLORS.len()]; - let handle = tokio::spawn(runner::runner(service, color, max_name_len)); - handles.push(handle); - } - tokio::signal::ctrl_c() - .await - .expect("Failed to listen for Ctrl+C"); - println!( - "\n{} {}", - "└─", - "Received Ctrl+C, shutting down...".bright_cyan().bold() - ); -} diff --git a/src/parser.rs b/src/parser.rs deleted file mode 100644 index 20dad72..0000000 --- a/src/parser.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::{fs, process}; - -use crate::config::FyrerConfig; - -pub fn load_config(path: &str) -> FyrerConfig { - let data = match fs::read_to_string(path) { - Ok(content) => content, - Err(_) => { - eprintln!("Failed to read configuration file: {}", path); - process::exit(1) - } - }; - match serde_yaml::from_str(&data) { - Ok(config) => config, - Err(e) => { - eprintln!( - "Failed to parse configuration file: {}\n incorrect YAML format", - e - ); - process::exit(1) - } - } -} diff --git a/src/print_banner.rs b/src/print_banner.rs deleted file mode 100644 index d7e02ca..0000000 --- a/src/print_banner.rs +++ /dev/null @@ -1,36 +0,0 @@ -use colored::*; - -pub fn print_banner() { - let banner_lines = vec![ - r#" ________ "#, - r#"/ | "#, - r#"$$$$$$$$/__ __ ______ ______ ______ "#, - r#"$$ |__ / | / | / \ / \ / \ "#, - r#"$$ | $$ | $$ |/$$$$$$ |/$$$$$$ |/$$$$$$ |"#, - r#"$$$$$/ $$ | $$ |$$ | $$/ $$ $$ |$$ | $$/ "#, - r#"$$ | $$ \__$$ |$$ | $$$$$$$$/ $$ | "#, - r#"$$ | $$ $$ |$$ | $$ |$$ | "#, - r#"$$/ $$$$$$$ |$$/ $$$$$$$/ $$/ "#, - r#" / \__$$ | "#, - r#" $$ $$/ version: 0.2.2 "#, - r#" $$$$$$/ made with <3 by CalC "#, - ]; - - let max_len = banner_lines.iter().map(|l| l.len()).max().unwrap_or(0); - let horizontal_border = "─".repeat(max_len + 1); - - println!("\n{}", format!("┌{}┐", horizontal_border).bright_yellow()); - - for line in banner_lines { - println!( - "{}{} {}{}", - "│".bright_yellow(), - line.bright_cyan().bold(), - "│".bright_yellow(), - "" - ); - } - - println!("{}", format!("└{}┘", horizontal_border).bright_yellow()); - println!(); -} diff --git a/src/runner.rs b/src/runner.rs deleted file mode 100644 index 707069f..0000000 --- a/src/runner.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::config::Service; -use crate::spawn_service::spawn_service; -use crate::watcher::run_with_watch; - -pub async fn runner(service: Service, color: colored::Color, max_name_len: usize) { - if service.watch.unwrap_or(false) { - run_with_watch(service, color, max_name_len).await; - } else { - spawn_service(&service, color, true, max_name_len).await; - } -} diff --git a/src/spawn_service.rs b/src/spawn_service.rs deleted file mode 100644 index 0d58bd3..0000000 --- a/src/spawn_service.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::config::Service; -use crate::env_parser::parse_env; -use colored::Colorize; -use std::fs::File; -use std::io::Read; -use std::path::Path; -use std::process::Stdio; -use tokio::io::AsyncBufReadExt; -use tokio::process::{Child, Command}; - -pub async fn spawn_service( - service: &Service, - color: colored::Color, - wait: bool, - max_name_len: usize, -) -> Option { - #[cfg(unix)] - let mut cmd = Command::new("sh"); - #[cfg(unix)] - cmd.arg("-c").arg(&service.cmd); - - #[cfg(windows)] - let mut cmd = Command::new("cmd"); - #[cfg(windows)] - cmd.arg("/C").arg(&service.cmd); - - cmd.current_dir(&service.dir); - cmd.stdout(Stdio::piped()); - cmd.stderr(Stdio::piped()); - let dotenv_path; - if let Some(env_path) = &service.env_path { - dotenv_path = Path::new(&service.dir).join(env_path); - } else { - dotenv_path = Path::new(&service.dir).join(".env"); - } - - if let Ok(mut dotenv) = File::open(&dotenv_path) { - let mut content = String::new(); - if let Err(_) = dotenv.read_to_string(&mut content) { - } else { - let envs = parse_env(&content); - for (k, v) in envs.into_iter() { - cmd.env(k, v); - } - } - } - - if let Some(envs) = &service.env { - for (key, value) in envs { - cmd.env(key, value); - } - } - - let out_prefix = format!("[{}]", service.name); - let padded_name = format!("{:| match res { - Ok(event) => { - if !event.paths.is_empty() { - let _ = tx.try_send(event.paths.clone()); - } - } - Err(e) => { - eprintln!("Watch error: {:?}", e); - } - }, - Config::default(), - ) - .unwrap_or_else(|e| { - eprintln!("Failed to create file watcher: {}", e); - std::process::exit(1); - }); - - watcher - .watch(watch_dir, RecursiveMode::Recursive) - .unwrap_or_else(|e| { - eprintln!("Failed to watch directory {}: {}", service.dir, e); - std::process::exit(1); - }); - watcher - }; - - let out_prefix = format!("[{}]", service.name); - let padded_name = format!("{: = spawn_service(&service, color, false, max_name_len).await; - - loop { - tokio::select! { - changed_files = rx.recv() => { - if let Some(paths) = changed_files { - let filtered_paths: Vec<_> = paths.into_iter() - .filter(|path| { - let rel_path = path.strip_prefix(&abs_service_dir).unwrap_or(path); - !glob_set.is_match(rel_path) - }) - .collect(); - - if filtered_paths.is_empty() { - continue; - } - - if let Some(mut c) = child.take() { - let padded_name = format!("{: { - let padded_name = format!("{: