From 8d68b6a056244160da38681a0785e30d238f62b3 Mon Sep 17 00:00:00 2001 From: 07calc Date: Sat, 25 Apr 2026 22:51:11 +0530 Subject: [PATCH 1/6] init --- .gitignore | 1 + Cargo.lock | 837 ++++++------------------------------ Cargo.toml | 30 +- crates/cache/Cargo.toml | 10 + crates/cache/src/lib.rs | 3 + crates/cli/Cargo.toml | 18 + crates/cli/src/main.rs | 3 + crates/config/Cargo.toml | 12 + crates/config/src/lib.rs | 1 + crates/dev/Cargo.toml | 10 + crates/dev/src/lib.rs | 3 + crates/env/Cargo.toml | 10 + crates/env/src/lib.rs | 10 + crates/executor/Cargo.toml | 11 + crates/executor/src/lib.rs | 1 + crates/graph/Cargo.toml | 11 + crates/graph/src/lib.rs | 1 + crates/scheduler/Cargo.toml | 11 + crates/scheduler/src/lib.rs | 1 + crates/types/Cargo.toml | 10 + crates/types/src/lib.rs | 1 + crates/utils/Cargo.toml | 9 + crates/utils/src/lib.rs | 3 + src/colors.rs | 9 - src/config.rs | 27 -- src/env_parser.rs | 22 - src/installer.rs | 82 ---- src/kill_process.rs | 64 --- src/main.rs | 47 -- src/parser.rs | 23 - src/print_banner.rs | 36 -- src/runner.rs | 11 - src/spawn_service.rs | 107 ----- src/watcher.rs | 122 ------ 34 files changed, 277 insertions(+), 1280 deletions(-) create mode 100644 crates/cache/Cargo.toml create mode 100644 crates/cache/src/lib.rs create mode 100644 crates/cli/Cargo.toml create mode 100644 crates/cli/src/main.rs create mode 100644 crates/config/Cargo.toml create mode 100644 crates/config/src/lib.rs create mode 100644 crates/dev/Cargo.toml create mode 100644 crates/dev/src/lib.rs create mode 100644 crates/env/Cargo.toml create mode 100644 crates/env/src/lib.rs create mode 100644 crates/executor/Cargo.toml create mode 100644 crates/executor/src/lib.rs create mode 100644 crates/graph/Cargo.toml create mode 100644 crates/graph/src/lib.rs create mode 100644 crates/scheduler/Cargo.toml create mode 100644 crates/scheduler/src/lib.rs create mode 100644 crates/types/Cargo.toml create mode 100644 crates/types/src/lib.rs create mode 100644 crates/utils/Cargo.toml create mode 100644 crates/utils/src/lib.rs delete mode 100644 src/colors.rs delete mode 100644 src/config.rs delete mode 100644 src/env_parser.rs delete mode 100644 src/installer.rs delete mode 100644 src/kill_process.rs delete mode 100644 src/main.rs delete mode 100644 src/parser.rs delete mode 100644 src/print_banner.rs delete mode 100644 src/runner.rs delete mode 100644 src/spawn_service.rs delete mode 100644 src/watcher.rs 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..7e323d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,416 +3,230 @@ version = 4 [[package]] -name = "aho-corasick" -version = "1.1.3" +name = "anstream" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ - "memchr", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] [[package]] -name = "bitflags" -version = "1.3.2" +name = "anstyle" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] -name = "bitflags" -version = "2.10.0" +name = "anstyle-parse" +version = "0.2.7" 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" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ - "memchr", - "serde", + "utf8parse", ] [[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" +name = "anstyle-query" +version = "1.1.5" 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" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "lazy_static", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] -name = "crossbeam-channel" -version = "0.5.15" +name = "anstyle-wincon" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ - "crossbeam-utils", + "anstyle", + "once_cell_polyfill", + "windows-sys", ] [[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" +name = "anyhow" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] -name = "equivalent" -version = "1.0.2" +name = "clap" +version = "4.5.60" 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" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ - "libc", - "windows-sys 0.61.2", + "clap_builder", + "clap_derive", ] [[package]] -name = "filetime" -version = "0.2.26" +name = "clap_builder" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.60.2", + "anstream", + "anstyle", + "clap_lex", + "strsim", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "clap_derive" +version = "4.5.55" 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" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ - "libc", -] - -[[package]] -name = "fyrer" -version = "0.2.2" -dependencies = [ - "clearscreen", - "colored", - "globset", - "notify", - "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", + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "hashbrown" -version = "0.16.0" +name = "clap_lex" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] -name = "indexmap" -version = "2.12.0" +name = "colorchoice" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" -dependencies = [ - "equivalent", - "hashbrown", -] +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] -name = "inotify" -version = "0.9.6" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" -dependencies = [ - "bitflags 1.3.2", - "inotify-sys", - "libc", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +name = "fyrer" +version = "0.3.0" dependencies = [ - "libc", + "anyhow", + "clap", + "fyrer-config", + "fyrer-executor", + "fyrer-graph", + "fyrer-scheduler", + "fyrer-types", ] [[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" +name = "fyrer-cache" +version = "0.3.0" dependencies = [ - "kqueue-sys", - "libc", + "fyrer-types", ] [[package]] -name = "kqueue-sys" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +name = "fyrer-config" +version = "0.3.0" 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", + "anyhow", + "fyrer-types", + "serde_yaml", ] [[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" +name = "fyrer-dev" +version = "0.3.0" dependencies = [ - "scopeguard", + "fyrer-types", ] [[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" +name = "fyrer-env" +version = "0.3.0" dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.48.0", + "fyrer-types", ] [[package]] -name = "mio" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +name = "fyrer-executor" +version = "0.3.0" dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", + "anyhow", + "fyrer-types", ] [[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +name = "fyrer-graph" +version = "0.3.0" dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases", - "libc", + "anyhow", + "fyrer-types", ] [[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +name = "fyrer-scheduler" +version = "0.3.0" dependencies = [ - "memchr", - "minimal-lexical", + "fyrer-graph", + "fyrer-types", ] [[package]] -name = "notify" -version = "6.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +name = "fyrer-types" +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", + "serde", ] [[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] +name = "fyrer-utils" +version = "0.3.0" [[package]] -name = "parking_lot_core" -version = "0.9.12" +name = "hashbrown" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] -name = "phf" -version = "0.11.3" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "phf_codegen" -version = "0.11.3" +name = "indexmap" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ - "phf_generator", - "phf_shared", + "equivalent", + "hashbrown", ] [[package]] -name = "phf_generator" -version = "0.11.3" +name = "is_terminal_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand", -] +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] -name = "phf_shared" -version = "0.11.3" +name = "itoa" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "proc-macro2" @@ -432,81 +246,12 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "serde" version = "1.0.228" @@ -551,35 +296,10 @@ dependencies = [ ] [[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" +name = "strsim" +version = "0.11.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", -] +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" @@ -592,66 +312,6 @@ dependencies = [ "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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "unicode-ident" version = "1.0.20" @@ -665,40 +325,10 @@ 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" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "windows-link" @@ -706,33 +336,6 @@ 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" @@ -741,195 +344,3 @@ 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..4f8014c 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/cli"] + +[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/cli/Cargo.toml b/crates/cli/Cargo.toml new file mode 100644 index 0000000..b00b888 --- /dev/null +++ b/crates/cli/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "fyrer" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +description = "Graph-based monorepo execution engine" +readme = "../../README.md" + +[dependencies] +anyhow.workspace = true +clap.workspace = true +fyrer-config = { path = "../config" } +fyrer-executor = { path = "../executor" } +fyrer-graph = { path = "../graph" } +fyrer-scheduler = { path = "../scheduler" } +fyrer-types = { path = "../types" } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs new file mode 100644 index 0000000..5d7c953 --- /dev/null +++ b/crates/cli/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("fyrer scaffold initialized"); +} diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml new file mode 100644 index 0000000..b6d3da4 --- /dev/null +++ b/crates/config/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "fyrer-config" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +anyhow.workspace = true +serde_yaml.workspace = true +fyrer-types = { path = "../types" } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs new file mode 100644 index 0000000..f42d5a8 --- /dev/null +++ b/crates/config/src/lib.rs @@ -0,0 +1 @@ +pub struct ConfigScaffold; 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/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/graph/Cargo.toml b/crates/graph/Cargo.toml new file mode 100644 index 0000000..9d538dd --- /dev/null +++ b/crates/graph/Cargo.toml @@ -0,0 +1,11 @@ +[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" } diff --git a/crates/graph/src/lib.rs b/crates/graph/src/lib.rs new file mode 100644 index 0000000..0602b99 --- /dev/null +++ b/crates/graph/src/lib.rs @@ -0,0 +1 @@ +pub struct GraphScaffold; 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/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!("{: Date: Sun, 17 May 2026 13:31:43 +0530 Subject: [PATCH 2/6] feat: config --- Cargo.lock | 204 ++++++----------------------- crates/cli/Cargo.toml | 7 - crates/config/src/lib.rs | 1 - crates/{config => core}/Cargo.toml | 6 +- crates/core/src/config.rs | 127 ++++++++++++++++++ crates/core/src/error.rs | 26 ++++ crates/core/src/fs.rs | 21 +++ crates/core/src/lib.rs | 3 + fyrer.example.yml | 104 +++++++++++++++ 9 files changed, 321 insertions(+), 178 deletions(-) delete mode 100644 crates/config/src/lib.rs rename crates/{config => core}/Cargo.toml (68%) create mode 100644 crates/core/src/config.rs create mode 100644 crates/core/src/error.rs create mode 100644 crates/core/src/fs.rs create mode 100644 crates/core/src/lib.rs create mode 100644 fyrer.example.yml diff --git a/Cargo.lock b/Cargo.lock index 7e323d9..6943fea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,108 +2,12 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "clap" -version = "4.5.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - [[package]] name = "equivalent" version = "1.0.2" @@ -113,15 +17,6 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "fyrer" version = "0.3.0" -dependencies = [ - "anyhow", - "clap", - "fyrer-config", - "fyrer-executor", - "fyrer-graph", - "fyrer-scheduler", - "fyrer-types", -] [[package]] name = "fyrer-cache" @@ -131,12 +26,12 @@ dependencies = [ ] [[package]] -name = "fyrer-config" +name = "fyrer-core" version = "0.3.0" dependencies = [ - "anyhow", - "fyrer-types", + "serde", "serde_yaml", + "thiserror", ] [[package]] @@ -190,67 +85,49 @@ version = "0.3.0" [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "indexmap" -version = "2.12.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +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 = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "serde" @@ -295,17 +172,11 @@ dependencies = [ "unsafe-libyaml", ] -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[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", @@ -313,34 +184,33 @@ dependencies = [ ] [[package]] -name = "unicode-ident" -version = "1.0.20" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] [[package]] -name = "utf8parse" -version = "0.2.2" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "windows-link" -version = "0.2.1" +name = "unicode-ident" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] -name = "windows-sys" -version = "0.61.2" +name = "unsafe-libyaml" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index b00b888..c6c8bed 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -9,10 +9,3 @@ description = "Graph-based monorepo execution engine" readme = "../../README.md" [dependencies] -anyhow.workspace = true -clap.workspace = true -fyrer-config = { path = "../config" } -fyrer-executor = { path = "../executor" } -fyrer-graph = { path = "../graph" } -fyrer-scheduler = { path = "../scheduler" } -fyrer-types = { path = "../types" } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs deleted file mode 100644 index f42d5a8..0000000 --- a/crates/config/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub struct ConfigScaffold; diff --git a/crates/config/Cargo.toml b/crates/core/Cargo.toml similarity index 68% rename from crates/config/Cargo.toml rename to crates/core/Cargo.toml index b6d3da4..47a4c19 100644 --- a/crates/config/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "fyrer-config" +name = "fyrer-core" version.workspace = true edition.workspace = true authors.workspace = true @@ -7,6 +7,6 @@ license.workspace = true repository.workspace = true [dependencies] -anyhow.workspace = true +thiserror.workspace = true +serde.workspace = true serde_yaml.workspace = true -fyrer-types = { path = "../types" } diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs new file mode 100644 index 0000000..df66eea --- /dev/null +++ b/crates/core/src/config.rs @@ -0,0 +1,127 @@ +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, path::PathBuf}; + +use crate::error::FyrerResult; + +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, + 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_vec_string")] + pub inputs: Vec, + #[serde(default = "default_vec_string")] + pub outputs: Vec, + #[serde(default = "default_bool")] + pub watch: bool, + #[serde(default = "default_vec_string")] + pub ignore: Vec, + #[serde(default = "default_bool")] + pub cache: bool, + #[serde(default = "default_restart")] + pub restart: RestartConfig, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RestartConfig { + pub strategy: RestartStrategy, + pub delay: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +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| { + crate::error::FyrerError::Config(crate::error::ConfigError::ReadFile { + path: path.to_string(), + source: e, + }) + })?; + + let config: FyrerConfig = serde_yaml::from_str(content.as_str()).map_err(|e| { + crate::error::FyrerError::Config(crate::error::ConfigError::ParseYaml(e)) + })?; + config.validate().map_err(|e| { + crate::error::FyrerError::Config(crate::error::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| { + crate::error::FyrerError::Config(crate::error::ConfigError::ParseYaml(e)) + })?; + config.validate().map_err(|e| { + crate::error::FyrerError::Config(crate::error::ConfigError::InvalidConfig( + e.to_string(), + )) + })?; + Ok(config) + } + + fn validate(&self) -> FyrerResult<()> { + Ok(()) + } + + fn validate_projects(&self) -> FyrerResult<()> { + Ok(()) + } +} +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, + } +} diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs new file mode 100644 index 0000000..fc3f3bc --- /dev/null +++ b/crates/core/src/error.rs @@ -0,0 +1,26 @@ +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), +} + +pub type FyrerResult = Result; diff --git a/crates/core/src/fs.rs b/crates/core/src/fs.rs new file mode 100644 index 0000000..29206bd --- /dev/null +++ b/crates/core/src/fs.rs @@ -0,0 +1,21 @@ +use std::fs::read_to_string; + +use crate::error::FyrerResult; + +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| { + crate::error::FyrerError::Config(crate::error::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..8306048 --- /dev/null +++ b/crates/core/src/lib.rs @@ -0,0 +1,3 @@ +pub mod config; +pub mod error; +pub mod fs; diff --git a/fyrer.example.yml b/fyrer.example.yml new file mode 100644 index 0000000..bc723a5 --- /dev/null +++ b/fyrer.example.yml @@ -0,0 +1,104 @@ +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 From ab269f1a9a16c7a01f0d5fe1369e25493d2705f8 Mon Sep 17 00:00:00 2001 From: 07calc Date: Tue, 19 May 2026 22:55:17 +0530 Subject: [PATCH 3/6] feat: config semantic validation --- crates/core/src/config.rs | 386 +++++++++++++++++++++++++++++++++++++- fyrer.example.yml | 2 - 2 files changed, 381 insertions(+), 7 deletions(-) diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index df66eea..4bd4eb3 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -1,7 +1,10 @@ use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, path::PathBuf}; +use std::{ + collections::{HashMap, HashSet}, + path::PathBuf, +}; -use crate::error::FyrerResult; +use crate::error::{ConfigError, FyrerError, FyrerResult}; pub type EnvMap = HashMap; @@ -19,6 +22,7 @@ pub struct FyrerConfig { 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")] @@ -32,18 +36,20 @@ pub struct TaskConfig { 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_bool")] - pub watch: bool, #[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)] @@ -52,7 +58,7 @@ pub struct RestartConfig { pub delay: Option, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum RestartStrategy { FileChange, OnFailure, @@ -92,10 +98,107 @@ impl FyrerConfig { } 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( + crate::error::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::new(); + if !project_names.insert(&project.name) { + return Err(FyrerError::Config( + crate::error::ConfigError::InvalidConfig(format!( + "duplicate project name: {}", + project.name + )), + )); + } + if project.root.as_os_str().is_empty() { + return Err(FyrerError::Config( + crate::error::ConfigError::InvalidConfig(format!( + "project '{}' has empty root path", + project.name + )), + )); + } + if project.root.is_absolute() { + return Err(FyrerError::Config( + crate::error::ConfigError::InvalidConfig(format!( + "project '{}' has absolute root path '{}'", + project.name, + project.root.display() + )), + )); + } + if project.env_path.is_empty() { + return Err(FyrerError::Config( + crate::error::ConfigError::InvalidConfig(format!( + "project '{}' has empty env_path", + project.name + )), + )); + } + for task_name in project.tasks.keys() { + if !task_names.insert(task_name) { + return Err(FyrerError::Config( + crate::error::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(()) } } @@ -125,3 +228,276 @@ fn default_restart() -> RestartConfig { 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/fyrer.example.yml b/fyrer.example.yml index bc723a5..57c921d 100644 --- a/fyrer.example.yml +++ b/fyrer.example.yml @@ -1,9 +1,7 @@ version: 1 - env: LOG_LEVEL: info NODE_ENV: development - projects: - name: web root: ./apps/web From c55f0735472589672882edd5f9b5ae548226010c Mon Sep 17 00:00:00 2001 From: 07calc Date: Wed, 20 May 2026 00:10:07 +0530 Subject: [PATCH 4/6] feat: task graph --- Cargo.lock | 6 +++ Cargo.toml | 2 +- crates/cli/src/main.rs | 3 -- crates/core/src/config.rs | 30 +++++++++++- crates/core/src/lib.rs | 1 + crates/core/src/tasks.rs | 39 ++++++++++++++++ crates/{cli => fyrer}/Cargo.toml | 6 ++- crates/fyrer/src/main.rs | 12 +++++ crates/graph/Cargo.toml | 1 + crates/graph/src/lib.rs | 79 +++++++++++++++++++++++++++++++- 10 files changed, 171 insertions(+), 8 deletions(-) delete mode 100644 crates/cli/src/main.rs create mode 100644 crates/core/src/tasks.rs rename crates/{cli => fyrer}/Cargo.toml (64%) create mode 100644 crates/fyrer/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 6943fea..f606aa9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,11 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "fyrer" version = "0.3.0" +dependencies = [ + "anyhow", + "fyrer-core", + "fyrer-graph", +] [[package]] name = "fyrer-cache" @@ -61,6 +66,7 @@ name = "fyrer-graph" version = "0.3.0" dependencies = [ "anyhow", + "fyrer-core", "fyrer-types", ] diff --git a/Cargo.toml b/Cargo.toml index 4f8014c..4d91950 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "3" members = ["crates/*"] -default-members = ["crates/cli"] +default-members = ["crates/fyrer"] [workspace.package] version = "0.3.0" diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs deleted file mode 100644 index 5d7c953..0000000 --- a/crates/cli/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("fyrer scaffold initialized"); -} diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index 4bd4eb3..0bf3340 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -4,7 +4,10 @@ use std::{ path::PathBuf, }; -use crate::error::{ConfigError, FyrerError, FyrerResult}; +use crate::{ + error::{ConfigError, FyrerError, FyrerResult}, + tasks::{Task, TaskId, TaskMap}, +}; pub type EnvMap = HashMap; @@ -201,6 +204,31 @@ impl FyrerConfig { } 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 task = Task { + project_name: project.name.clone(), + project_root: project.root.clone(), + env: project.env.clone(), + 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() diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 8306048..25afe21 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,3 +1,4 @@ pub mod config; pub mod error; 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..189294d --- /dev/null +++ b/crates/core/src/tasks.rs @@ -0,0 +1,39 @@ +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) + } +} diff --git a/crates/cli/Cargo.toml b/crates/fyrer/Cargo.toml similarity index 64% rename from crates/cli/Cargo.toml rename to crates/fyrer/Cargo.toml index c6c8bed..b15c151 100644 --- a/crates/cli/Cargo.toml +++ b/crates/fyrer/Cargo.toml @@ -5,7 +5,9 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true -description = "Graph-based monorepo execution engine" -readme = "../../README.md" [dependencies] +fyrer-core = {path = "../core/"} +fyrer-graph = {path = "../graph/"} +anyhow.workspace = true + diff --git a/crates/fyrer/src/main.rs b/crates/fyrer/src/main.rs new file mode 100644 index 0000000..b766e97 --- /dev/null +++ b/crates/fyrer/src/main.rs @@ -0,0 +1,12 @@ +use std::fs; + +use fyrer_core::{config::FyrerConfig, 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); + dbg!(task_graph.validate().unwrap()); + Ok(()) +} diff --git a/crates/graph/Cargo.toml b/crates/graph/Cargo.toml index 9d538dd..1ec63f4 100644 --- a/crates/graph/Cargo.toml +++ b/crates/graph/Cargo.toml @@ -9,3 +9,4 @@ repository.workspace = true [dependencies] anyhow.workspace = true fyrer-types = { path = "../types" } +fyrer-core = { path = "../core" } diff --git a/crates/graph/src/lib.rs b/crates/graph/src/lib.rs index 0602b99..786b270 100644 --- a/crates/graph/src/lib.rs +++ b/crates/graph/src/lib.rs @@ -1 +1,78 @@ -pub struct GraphScaffold; +use std::collections::HashMap; + +use fyrer_core::tasks::{TaskId, TaskMap}; + +#[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) -> TaskGraph { + 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 parts: Vec<&str> = dep.split(':').collect(); + let dep_id = TaskId::new(parts[0], parts[1]); + graph.nodes.get_mut(id).unwrap().deps.push(dep_id.clone()); + graph + .nodes + .get_mut(&dep_id) + .unwrap() + .dependents + .push(id.clone()); + } + } + graph + } + + pub fn validate(&self) -> Result<(), String> { + 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(format!( + "Cycle detected involving task '{}'", + 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 + } +} From 0ec06179fc7cc45638fb4bd534aebf321b22ddab Mon Sep 17 00:00:00 2001 From: 07calc Date: Wed, 20 May 2026 14:07:53 +0530 Subject: [PATCH 5/6] feat: graph errors --- Cargo.lock | 12 ++ crates/core/Cargo.toml | 1 + crates/core/src/config.rs | 118 ++++++++---------- crates/core/src/fs.rs | 4 +- crates/core/src/lib.rs | 1 - crates/error/Cargo.toml | 13 ++ .../src/error.rs => error/src/config.rs} | 3 - crates/error/src/graph.rs | 17 +++ crates/error/src/lib.rs | 17 +++ crates/fyrer/Cargo.toml | 2 + crates/fyrer/src/main.rs | 5 +- crates/graph/Cargo.toml | 1 + crates/graph/src/lib.rs | 10 +- 13 files changed, 126 insertions(+), 78 deletions(-) create mode 100644 crates/error/Cargo.toml rename crates/{core/src/error.rs => error/src/config.rs} (91%) create mode 100644 crates/error/src/graph.rs create mode 100644 crates/error/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f606aa9..b81281b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,7 @@ version = "0.3.0" dependencies = [ "anyhow", "fyrer-core", + "fyrer-error", "fyrer-graph", ] @@ -34,6 +35,7 @@ dependencies = [ name = "fyrer-core" version = "0.3.0" dependencies = [ + "fyrer-error", "serde", "serde_yaml", "thiserror", @@ -53,6 +55,15 @@ dependencies = [ "fyrer-types", ] +[[package]] +name = "fyrer-error" +version = "0.3.0" +dependencies = [ + "serde", + "serde_yaml", + "thiserror", +] + [[package]] name = "fyrer-executor" version = "0.3.0" @@ -67,6 +78,7 @@ version = "0.3.0" dependencies = [ "anyhow", "fyrer-core", + "fyrer-error", "fyrer-types", ] diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 47a4c19..d5fc5d9 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -10,3 +10,4 @@ repository.workspace = true 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 index 0bf3340..4c316b3 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -1,13 +1,11 @@ +use fyrer_error::{FyrerError, FyrerResult, config::ConfigError}; use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, path::PathBuf, }; -use crate::{ - error::{ConfigError, FyrerError, FyrerResult}, - tasks::{Task, TaskId, TaskMap}, -}; +use crate::tasks::{Task, TaskId, TaskMap}; pub type EnvMap = HashMap; @@ -71,32 +69,26 @@ pub enum RestartStrategy { impl FyrerConfig { pub fn new_from_path(path: &str) -> FyrerResult { let content = std::fs::read_to_string(path).map_err(|e| { - crate::error::FyrerError::Config(crate::error::ConfigError::ReadFile { + FyrerError::Config(ConfigError::ReadFile { path: path.to_string(), source: e, }) })?; - let config: FyrerConfig = serde_yaml::from_str(content.as_str()).map_err(|e| { - crate::error::FyrerError::Config(crate::error::ConfigError::ParseYaml(e)) - })?; - config.validate().map_err(|e| { - crate::error::FyrerError::Config(crate::error::ConfigError::InvalidConfig( - e.to_string(), - )) - })?; + 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| { - crate::error::FyrerError::Config(crate::error::ConfigError::ParseYaml(e)) - })?; - config.validate().map_err(|e| { - crate::error::FyrerError::Config(crate::error::ConfigError::InvalidConfig( - e.to_string(), - )) - })?; + 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) } @@ -109,12 +101,10 @@ impl FyrerConfig { fn validate_version(&self) -> FyrerResult<()> { if self.version != 1 { - return Err(FyrerError::Config( - crate::error::ConfigError::InvalidConfig(format!( - "unsupported config version: {}", - self.version - )), - )); + return Err(FyrerError::Config(ConfigError::InvalidConfig(format!( + "unsupported config version: {}", + self.version + )))); } Ok(()) } @@ -122,50 +112,46 @@ impl FyrerConfig { fn validate_projects(&self) -> FyrerResult<()> { let mut project_names = HashSet::new(); for project in &self.projects { - let mut task_names = HashSet::new(); + // let mut task_names: HashSet = HashSet::new(); if !project_names.insert(&project.name) { - return Err(FyrerError::Config( - crate::error::ConfigError::InvalidConfig(format!( - "duplicate project name: {}", - 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( - crate::error::ConfigError::InvalidConfig(format!( - "project '{}' has empty root path", - project.name - )), - )); + return Err(FyrerError::Config(ConfigError::InvalidConfig(format!( + "project '{}' has empty root path", + project.name + )))); } if project.root.is_absolute() { - return Err(FyrerError::Config( - crate::error::ConfigError::InvalidConfig(format!( - "project '{}' has absolute root path '{}'", - project.name, - project.root.display() - )), - )); + 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( - crate::error::ConfigError::InvalidConfig(format!( - "project '{}' has empty env_path", - project.name - )), - )); - } - for task_name in project.tasks.keys() { - if !task_names.insert(task_name) { - return Err(FyrerError::Config( - crate::error::ConfigError::InvalidConfig(format!( - "duplicate task name '{}' in project '{}'", - task_name, project.name - )), - )); - } + 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(()) } @@ -209,10 +195,13 @@ impl FyrerConfig { 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: project.env.clone(), + env, task_name: task_name.clone(), cmd: task_config.cmd.clone(), depends_on: task_config.depends_on.clone(), @@ -259,7 +248,6 @@ fn default_restart() -> RestartConfig { mod tests { use super::*; - #[test] fn test_valid_config() { let yaml = r#" diff --git a/crates/core/src/fs.rs b/crates/core/src/fs.rs index 29206bd..c849ba8 100644 --- a/crates/core/src/fs.rs +++ b/crates/core/src/fs.rs @@ -1,6 +1,6 @@ use std::fs::read_to_string; -use crate::error::FyrerResult; +use fyrer_error::{FyrerError, FyrerResult, config::ConfigError}; pub fn dir_exists(path: &str) -> bool { std::path::Path::new(path).is_dir() @@ -12,7 +12,7 @@ pub fn file_exists(path: &str) -> bool { pub fn read_file(path: &str) -> FyrerResult { let content = read_to_string(path).map_err(|e| { - crate::error::FyrerError::Config(crate::error::ConfigError::ReadFile { + FyrerError::Config(ConfigError::ReadFile { path: path.to_string(), source: e, }) diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 25afe21..abff079 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,4 +1,3 @@ pub mod config; -pub mod error; pub mod fs; pub mod tasks; 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/core/src/error.rs b/crates/error/src/config.rs similarity index 91% rename from crates/core/src/error.rs rename to crates/error/src/config.rs index fc3f3bc..c9796a6 100644 --- a/crates/core/src/error.rs +++ b/crates/error/src/config.rs @@ -1,7 +1,6 @@ use thiserror::Error; #[derive(Debug, Error)] - pub enum ConfigError { #[error("failed to read file at '{path}': {source}")] ReadFile { @@ -22,5 +21,3 @@ pub enum FyrerError { #[error("config error: {0}")] Config(#[from] ConfigError), } - -pub type FyrerResult = Result; 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/fyrer/Cargo.toml b/crates/fyrer/Cargo.toml index b15c151..d86878c 100644 --- a/crates/fyrer/Cargo.toml +++ b/crates/fyrer/Cargo.toml @@ -9,5 +9,7 @@ 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 index b766e97..4f5a58f 100644 --- a/crates/fyrer/src/main.rs +++ b/crates/fyrer/src/main.rs @@ -1,12 +1,13 @@ use std::fs; -use fyrer_core::{config::FyrerConfig, error::FyrerResult}; +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); - dbg!(task_graph.validate().unwrap()); + let validation = task_graph.validate()?; Ok(()) } diff --git a/crates/graph/Cargo.toml b/crates/graph/Cargo.toml index 1ec63f4..6f81406 100644 --- a/crates/graph/Cargo.toml +++ b/crates/graph/Cargo.toml @@ -10,3 +10,4 @@ repository.workspace = true 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 index 786b270..2127d61 100644 --- a/crates/graph/src/lib.rs +++ b/crates/graph/src/lib.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use fyrer_core::tasks::{TaskId, TaskMap}; +use fyrer_error::{FyrerResult, graph::GraphError}; #[derive(Debug)] pub struct TaskGraph { @@ -47,15 +48,14 @@ impl TaskGraph { graph } - pub fn validate(&self) -> Result<(), String> { + 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(format!( - "Cycle detected involving task '{}'", - node.id.to_string() - )); + return Err(fyrer_error::FyrerError::Graph(GraphError::CycleDetected( + node.id.to_string(), + ))); } } } From b04235ea17b4624cdd1d4b481813e84d99d71b26 Mon Sep 17 00:00:00 2001 From: 07calc Date: Wed, 27 May 2026 21:33:37 +0530 Subject: [PATCH 6/6] feat: graph validation --- crates/core/src/tasks.rs | 8 ++++++ crates/fyrer/src/main.rs | 4 +-- crates/graph/src/lib.rs | 62 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/crates/core/src/tasks.rs b/crates/core/src/tasks.rs index 189294d..cf2bbae 100644 --- a/crates/core/src/tasks.rs +++ b/crates/core/src/tasks.rs @@ -36,4 +36,12 @@ impl TaskId { 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/fyrer/src/main.rs b/crates/fyrer/src/main.rs index 4f5a58f..2bf54e7 100644 --- a/crates/fyrer/src/main.rs +++ b/crates/fyrer/src/main.rs @@ -7,7 +7,7 @@ fn main() -> FyrerResult<()> { 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); - let validation = task_graph.validate()?; + let task_graph = fyrer_graph::TaskGraph::new(&task_map)?; + task_graph.validate()?; Ok(()) } diff --git a/crates/graph/src/lib.rs b/crates/graph/src/lib.rs index 2127d61..9f679cf 100644 --- a/crates/graph/src/lib.rs +++ b/crates/graph/src/lib.rs @@ -16,7 +16,7 @@ pub struct TaskNode { } impl TaskGraph { - pub fn new(task_map: &TaskMap) -> TaskGraph { + pub fn new(task_map: &TaskMap) -> FyrerResult { let mut graph = TaskGraph { nodes: HashMap::new(), }; @@ -34,8 +34,27 @@ impl TaskGraph { for (id, task) in task_map { for dep in &task.depends_on { - let parts: Vec<&str> = dep.split(':').collect(); - let dep_id = TaskId::new(parts[0], parts[1]); + 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 @@ -45,7 +64,7 @@ impl TaskGraph { .push(id.clone()); } } - graph + Ok(graph) } pub fn validate(&self) -> FyrerResult<()> { @@ -75,4 +94,39 @@ impl TaskGraph { 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(()) + } }