diff --git a/Cargo.lock b/Cargo.lock index dd5c19e..56d355d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,21 +13,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.14" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "cfg-if" @@ -37,9 +31,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.6.1" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -57,18 +51,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.2" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff7a1dccbdd8b078c2bdebff47e404615151534d5043da397ec50286816f9cb" +checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -84,12 +78,13 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "console" -version = "0.16.3" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" dependencies = [ "encode_unicode", "libc", + "once_cell", "unicode-width", "windows-sys", ] @@ -106,25 +101,22 @@ dependencies = [ [[package]] name = "eh" -version = "0.2.0" +version = "0.1.7" dependencies = [ "clap", "dialoguer", "eh-log", "regex", - "serde", "serde_json", "tempfile", - "textwrap", "thiserror", - "toml", "walkdir", "yansi", ] [[package]] name = "eh-log" -version = "0.2.0" +version = "0.1.7" dependencies = [ "yansi", ] @@ -135,12 +127,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - [[package]] name = "errno" version = "0.3.14" @@ -153,85 +139,39 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "getrandom" -version = "0.4.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", "wasip2", - "wasip3", ] -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" - [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown 0.17.0", - "serde", - "serde_core", -] - [[package]] name = "itoa" -version = "1.0.18" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "linux-raw-sys" @@ -239,33 +179,17 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - [[package]] name = "memchr" -version = "2.8.0" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "once_cell" -version = "1.21.4" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "proc-macro2" @@ -287,9 +211,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "6.0.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "regex" @@ -305,9 +229,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -316,9 +240,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustix" @@ -342,12 +266,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - [[package]] name = "serde" version = "1.0.228" @@ -355,7 +273,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", - "serde_derive", ] [[package]] @@ -391,27 +308,12 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - [[package]] name = "shell-words" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" -[[package]] -name = "smawk" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" - [[package]] name = "syn" version = "2.0.117" @@ -436,17 +338,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "textwrap" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" -dependencies = [ - "smawk", - "unicode-linebreak", - "unicode-width", -] - [[package]] name = "thiserror" version = "2.0.18" @@ -467,48 +358,11 @@ dependencies = [ "syn", ] -[[package]] -name = "toml" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" -dependencies = [ - "serde_core", - "serde_spanned", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow", -] - [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-width" @@ -516,12 +370,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "walkdir" version = "2.5.0" @@ -534,54 +382,11 @@ dependencies = [ [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", + "wit-bindgen", ] [[package]] @@ -608,109 +413,15 @@ dependencies = [ "windows-link", ] -[[package]] -name = "winnow" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" - [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] [[package]] name = "xtask" -version = "0.2.0" +version = "0.1.7" dependencies = [ "clap", "clap_complete", @@ -725,6 +436,6 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" diff --git a/Cargo.toml b/Cargo.toml index 931f336..f4f3cf7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,20 +9,17 @@ description = "Ergonomic Nix CLI helper" edition = "2024" license = "MPL-2.0" readme = true -rust-version = "1.94.0" -version = "0.2.0" +rust-version = "1.91.0" +version = "0.1.7" [workspace.dependencies] clap = { default-features = false, features = [ "std", "help", "derive" ], version = "4.6.0" } clap_complete = "4.6.0" dialoguer = { default-features = false, version = "0.12.0" } regex = "1.12.3" -serde = { features = [ "derive" ], version = "1.0.149" } serde_json = "1.0.149" tempfile = "3.27.0" -textwrap = "0.16.2" thiserror = "2.0.18" -toml = { default-features = false, features = [ "parse", "serde" ], version = "1.1.2" } walkdir = "2.5.0" yansi = "1.0.1" diff --git a/README.md b/README.md index 1c4a320..4689ebe 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,6 @@ of building the package. The following variables are supported: - **Insecure packages**: Sets `NIXPKGS_ALLOW_INSECURE=1` - **Broken packages**: Sets `NIXPKGS_ALLOW_BROKEN=1` -Auto-retry requires that `--impure` is not explicitly disabled for the relevant -command in the config file. By default retries are automatic. See -[Configuration](#configuration). - ### Hash Auto-Fix When a hash mismatch is detected in the underlying `nix build`, `eh` can @@ -59,92 +55,6 @@ nb .#myPackage # nix build .#myPackage nu # nix flake update ``` -## Configuration - -`eh` reads configuration from the first `.eh.toml` found by walking up from the -current directory, falling back to `~/.config/eh/config.toml`. If no file -exists, all defaults apply and no extra flags are passed to Nix. - -### Global settings - -Top-level keys apply to every command unless overridden per-command: - -```toml -# Explicitly enable --impure for all commands (also passes it on initial run). -impure = true - -# Explicitly disable impure retries for all commands. -impure = false -``` - -When `impure` is absent (the default), auto-retry with `--impure` is -**automatic** — `eh` will add `--impure` and the appropriate `NIXPKGS_ALLOW_*` -variable whenever it detects an unfree, insecure, or broken package. - - - -| Key | Type | Default | Description | -| -------- | ---- | ------- | -------------------------------------------------------------- | -| `impure` | bool | - | `true` passes `--impure` always; `false` blocks impure retries | - - - -### Per-command settings - -Each command can be configured independently under `[commands.]`. A -per-command setting takes precedence over the global one; the global setting -applies to commands that do not have their own entry. - -```toml -[commands.build] -impure = true -env = { NIXPKGS_ALLOW_UNFREE = "1" } - -[commands.develop] -impure = false - -[commands.develop.env] -MY_DEV_VAR = "1" -``` - - - -| Key | Type | Default | Description | -| -------- | ----- | ------- | ------------------------------------------------------------------------------- | -| `impure` | bool | - | `true` passes `--impure` always; `false` blocks impure retries for this command | -| `env` | table | `{}` | Extra environment variables to set for the command | - - - -### Impure mode and unfree/insecure/broken packages - -When `eh` detects that a package requires `--impure` (unfree, insecure, or -broken), it retries automatically with the appropriate `NIXPKGS_ALLOW_*` -variable and `--impure` by default. - -If `impure = false` is set for the active command (or globally), the retry is -blocked and an error is shown instead: - -```plaintext -! package has an unfree license but `--impure` is disabled for `build` in config -~ set `impure = true` for this command (or globally) in .eh.toml or - ~/.config/eh/config.toml, or pass `--impure` manually -``` - -To explicitly enable `--impure` for a specific command (also adds it to the -initial run, not just retries): - -```toml -[commands.build] -impure = true -``` - -To disable impure retries globally: - -```toml -impure = false -``` - ## License diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 0254d57..a580cda 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -42,8 +42,6 @@ enum Binary { Nr, Ns, Nb, - Nd, - Ni, Nu, } @@ -53,8 +51,6 @@ impl Binary { Self::Nr => "nr", Self::Ns => "ns", Self::Nb => "nb", - Self::Nd => "nd", - Self::Ni => "ni", Self::Nu => "nu", } } @@ -96,14 +92,7 @@ fn create_multicall_binaries( ); } - let multicall_binaries = [ - Binary::Nr, - Binary::Ns, - Binary::Nb, - Binary::Nd, - Binary::Ni, - Binary::Nu, - ]; + let multicall_binaries = [Binary::Nr, Binary::Ns, Binary::Nb, Binary::Nu]; let bin_path = Path::new(bin_dir); for binary in multicall_binaries { @@ -166,7 +155,7 @@ fn generate_completions( println!("completion file generated: {}", completion_file.display()); // Create symlinks for multicall binaries - let multicall_names = ["nb", "nd", "ni", "nr", "ns", "nu"]; + let multicall_names = ["nb", "nr", "ns", "nu"]; for name in &multicall_names { let symlink_path = output_dir.join(format!("{name}.{shell}")); if symlink_path.exists() { diff --git a/eh/Cargo.toml b/eh/Cargo.toml index f3f4e51..92dd751 100644 --- a/eh/Cargo.toml +++ b/eh/Cargo.toml @@ -15,11 +15,8 @@ clap.workspace = true dialoguer.workspace = true eh-log.workspace = true regex.workspace = true -serde.workspace = true serde_json.workspace = true tempfile.workspace = true -textwrap.workspace = true thiserror.workspace = true -toml.workspace = true walkdir.workspace = true yansi.workspace = true diff --git a/eh/src/commands/info.rs b/eh/src/commands/info.rs deleted file mode 100644 index e0a34c3..0000000 --- a/eh/src/commands/info.rs +++ /dev/null @@ -1,295 +0,0 @@ -use std::collections::HashMap; - -use eh_log::{log_error, log_info}; -use serde::Deserialize; -use yansi::Paint; - -use crate::{ - commands::NixCommand, - error::{EhError, Result}, - util::{make_eval_expr, print_error_suggestions}, -}; - -#[derive(Debug, Deserialize)] -struct PackageMeta { - name: String, - version: Option, - description: Option, - long_description: Option, - license: Option, - homepage: Option, - platforms: Option>, - broken: Option, - insecure: Option, - #[serde(rename = "unfree")] - unfree: Option, -} - -#[derive(Debug, Deserialize)] -struct PackageOutputs { - #[serde(flatten)] - outputs: HashMap, -} - -pub fn handle_info( - args: &[String], - cfg: &crate::config::CommandConfig, -) -> Result { - // Get the package argument (skip flags) - let pkg = args - .iter() - .find(|arg| !arg.starts_with('-')) - .cloned() - .unwrap_or_else(|| ".".to_string()); - - let eval_arg = make_eval_expr(&pkg); - let pkg_name: String = if eval_arg.contains("#") { - eval_arg - .split("#") - .last() - .unwrap_or(&eval_arg) - .trim_end_matches(".meta") - .to_string() - } else { - eval_arg.trim_end_matches(".meta").to_string() - }; - // Handle .# case - show "default" as the package name - let pkg_name = if pkg_name.is_empty() { - "default".to_string() - } else { - pkg_name - }; - - log_info!("Fetching info for {}", pkg_name.bold()); - - // Fetch metadata - let meta_cmd = NixCommand::new("eval") - .arg("--json") - .arg(&eval_arg) - .print_build_logs(false) - .with_config(cfg); - - let meta_output = meta_cmd.output()?; - - if !meta_output.status.success() { - log_error!("Failed to fetch package info"); - print_error_suggestions(&meta_output.stderr); - return Err(EhError::NixCommandFailed { - command: "eval".to_string(), - }); - } - - let meta: PackageMeta = - serde_json::from_slice(&meta_output.stdout).map_err(|e| { - EhError::Io(std::io::Error::other(format!( - "Failed to parse package metadata: {}", - e - ))) - })?; - - // Fetch outputs - let outputs_expr = eval_arg - .strip_suffix(".meta") - .unwrap_or(&eval_arg) - .to_string(); - let outputs_cmd = NixCommand::new("eval") - .arg("--json") - .arg(format!("{}.outputs", outputs_expr)) - .print_build_logs(false) - .with_config(cfg); - - let outputs_output = outputs_cmd.output()?; - let outputs: Option = if outputs_output.status.success() { - serde_json::from_slice(&outputs_output.stdout).ok() - } else { - None - }; - - // Print formatted info - print_package_info(&meta, outputs.as_ref(), &pkg); - - Ok(0) -} - -fn print_package_info( - meta: &PackageMeta, - outputs: Option<&PackageOutputs>, - pkg_ref: &str, -) { - println!(); - - // Header - println!(" {} {}", "Package:".bold(), meta.name); - - if let Some(ref version) = meta.version { - println!(" {} {}", "Version:".bold(), version); - } - - if let Some(ref desc) = meta.description { - println!(" {} {}", "Description:".bold(), desc); - } - - // Show long description if available and different from short description - if let Some(ref long_desc) = meta.long_description { - let should_show = meta - .description - .as_ref() - .map(|d| d != long_desc) - .unwrap_or(true); - if should_show { - println!(); - // Wrap long description to 70 chars for readability - let wrapped = textwrap::fill(long_desc, 70); - for line in wrapped.lines() { - println!(" {}", line); - } - } - } - - // License - if let Some(ref license) = meta.license { - let license_str = match license { - serde_json::Value::String(s) => s.clone(), - serde_json::Value::Object(obj) => { - obj - .get("spdxId") - .and_then(|v| v.as_str()) - .or_else(|| obj.get("shortName").and_then(|v| v.as_str())) - .unwrap_or("Unknown") - .to_string() - }, - serde_json::Value::Array(licenses) => { - // Handle multiple licenses (e.g., neovim has Apache-2.0 AND Vim) - let license_names: Vec = licenses - .iter() - .filter_map(|lic| { - match lic { - serde_json::Value::Object(obj) => { - obj - .get("spdxId") - .and_then(|v| v.as_str()) - .or_else(|| obj.get("shortName").and_then(|v| v.as_str())) - .map(|s| s.to_string()) - }, - serde_json::Value::String(s) => Some(s.clone()), - _ => None, - } - }) - .collect(); - - if license_names.is_empty() { - "Unknown".to_string() - } else { - license_names.join(", ") - } - }, - _ => "Unknown".to_string(), - }; - println!(" {} {}", "License:".bold(), license_str); - } - - // Homepage - if let Some(ref homepage) = meta.homepage { - println!(" {} {}", "Homepage:".bold(), homepage); - } - - // Meta section - println!(); - println!(" {}", "Meta:".bold()); - - // Status indicators - let mut status_parts = Vec::new(); - if meta.broken == Some(true) { - status_parts.push("Broken".red().to_string()); - } - if meta.insecure == Some(true) { - status_parts.push("Insecure".red().to_string()); - } - if meta.unfree == Some(true) { - status_parts.push("Unfree".yellow().to_string()); - } - - if status_parts.is_empty() { - println!(" {} {}", "Status:".bold(), "✓ Available".green()); - } else { - println!(" {} {}", "Status:".bold(), status_parts.join(", ")); - } - - // Platforms - if let Some(ref platforms) = meta.platforms { - let platform_list: Vec<_> = platforms.iter().take(4).cloned().collect(); - let platform_str = if platforms.len() > 4 { - format!( - "{} + {} more", - platform_list.join(", "), - platforms.len() - 4 - ) - } else { - platform_list.join(", ") - }; - println!(" {} {}", "Platforms:".bold(), platform_str); - } - - // Outputs section - if let Some(outputs) = outputs { - println!(); - println!(" {}", "Outputs:".bold()); - let output_names: Vec<_> = outputs.outputs.keys().cloned().collect(); - for name in output_names { - let marker = if name == "out" { " (default)" } else { "" }; - println!(" • {}{}", name, marker.dim()); - } - } - - // Usage section - println!(); - println!(" {}", "Usage:".bold()); - println!( - " {} {} {}", - "eh run".dim(), - pkg_ref, - "# Run the package".dim() - ); - println!( - " {} {} {}", - "eh shell".dim(), - pkg_ref, - "# Enter shell with package".dim() - ); - println!(); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_package_meta_deserialization() { - let json = r#"{ - "name": "hello", - "version": "2.12.1", - "description": "A greeting program", - "license": "GPL-3.0", - "homepage": "https://example.com", - "platforms": ["x86_64-linux"], - "broken": false, - "insecure": false, - "unfree": false - }"#; - - let meta: PackageMeta = serde_json::from_str(json).unwrap(); - assert_eq!(meta.name, "hello"); - assert_eq!(meta.version, Some("2.12.1".to_string())); - } - - #[test] - fn test_license_object_parsing() { - let json = r#"{ - "name": "test", - "license": {"spdxId": "MIT", "fullName": "MIT License"} - }"#; - - let meta: PackageMeta = serde_json::from_str(json).unwrap(); - assert!(meta.license.is_some()); - } -} diff --git a/eh/src/commands/mod.rs b/eh/src/commands/mod.rs index eeacf35..6cc417d 100644 --- a/eh/src/commands/mod.rs +++ b/eh/src/commands/mod.rs @@ -16,7 +16,6 @@ use crate::{ }, }; -pub mod info; pub mod update; const DEFAULT_BUFFER_SIZE: usize = 4096; @@ -131,21 +130,6 @@ impl NixCommand { self } - /// Apply per-command configuration: sets `--impure` (when explicitly enabled) - /// and any extra environment variables declared in the config file. Call - /// this before any retry-specific overrides so that retry logic can still - /// force `impure(true)` afterwards. - #[must_use] - pub fn with_config(mut self, cfg: &crate::config::CommandConfig) -> Self { - if cfg.impure == Some(true) { - self = self.impure(true); - } - for (k, v) in &cfg.env { - self = self.env(k, v); - } - self - } - fn build_command(&self) -> Command { let mut cmd = Command::new("nix"); cmd.arg(&self.subcommand); @@ -336,7 +320,6 @@ pub fn handle_nix_command( hash_extractor: &dyn HashExtractor, fixer: &dyn NixFileFixer, classifier: &dyn NixErrorClassifier, - cfg: &crate::config::CommandConfig, ) -> Result { let intercept_env = matches!(command, "run" | "shell"); handle_nix_with_retry( @@ -346,7 +329,6 @@ pub fn handle_nix_command( fixer, classifier, intercept_env, - cfg, ) } diff --git a/eh/src/commands/update.rs b/eh/src/commands/update.rs index bd2634c..4640b13 100644 --- a/eh/src/commands/update.rs +++ b/eh/src/commands/update.rs @@ -55,10 +55,7 @@ fn prompt_input_selection(inputs: &[String]) -> Result> { /// /// If `args` is non-empty, use them as explicit input names. /// Otherwise, fetch inputs interactively and prompt for selection. -pub fn handle_update( - args: &[String], - cfg: &crate::config::CommandConfig, -) -> Result { +pub fn handle_update(args: &[String]) -> Result { let selected = if args.is_empty() { let inputs = fetch_flake_inputs()?; if inputs.is_empty() { @@ -69,7 +66,7 @@ pub fn handle_update( args.to_vec() }; - let mut cmd = NixCommand::new("flake").arg("lock").with_config(cfg); + let mut cmd = NixCommand::new("flake").arg("lock"); for name in &selected { cmd = cmd.arg("--update-input").arg(name); } diff --git a/eh/src/config.rs b/eh/src/config.rs deleted file mode 100644 index d30fb2e..0000000 --- a/eh/src/config.rs +++ /dev/null @@ -1,236 +0,0 @@ -use std::{ - collections::HashMap, - env, - fs, - path::{Path, PathBuf}, -}; - -use serde::Deserialize; - -#[derive(Debug, Deserialize, Default)] -#[serde(deny_unknown_fields)] -pub struct Config { - /// When `Some(true)`, pass `--impure` to every Nix command. - /// When `Some(false)`, block automatic impure retries for every command. - /// When absent (`None`), retry behaviour is automatic (default). - #[serde(default)] - pub impure: Option, - #[serde(default)] - pub commands: HashMap, -} - -/// Per-command configuration. -#[derive(Debug, Deserialize, Default, Clone)] -#[serde(deny_unknown_fields)] -pub struct CommandConfig { - /// When `Some(true)`, pass `--impure` to the underlying Nix command. - /// When `Some(false)`, block automatic impure retries for this command. - /// When absent (`None`), the global setting is used; if that is also absent, - /// retry behaviour is automatic (default). - #[serde(default)] - pub impure: Option, - /// Additional environment variables to set for the Nix command. - #[serde(default)] - pub env: HashMap, -} - -impl Config { - /// Return the [`CommandConfig`] for `command`. - /// - /// Resolution order: per-command `impure` takes precedence over the global - /// `impure`. Neither being set means automatic retry behaviour. - pub fn for_command(&self, command: &str) -> CommandConfig { - let mut cmd = self.commands.get(command).cloned().unwrap_or_default(); - // Per-command setting wins; fall back to global. - if cmd.impure.is_none() { - cmd.impure = self.impure; - } - cmd - } -} - -/// Load configuration from the first `.eh.toml` found by walking up from the -/// current directory, or from `~/.config/eh/config.toml` as a global -/// fallback. Returns a default (empty) config if no file is found or if -/// parsing fails. -pub fn load() -> Config { - if let Some(path) = find_project_config() - && let Some(cfg) = load_from_file(&path) - { - return cfg; - } - - if let Some(path) = global_config_path() - && let Some(cfg) = load_from_file(&path) - { - return cfg; - } - - Config::default() -} - -fn find_project_config() -> Option { - let mut dir = env::current_dir().ok()?; - loop { - let candidate = dir.join(".eh.toml"); - if candidate.exists() { - return Some(candidate); - } - if !dir.pop() { - return None; - } - } -} - -fn global_config_path() -> Option { - let home = env::var("HOME").ok()?; - Some( - PathBuf::from(home) - .join(".config") - .join("eh") - .join("config.toml"), - ) -} - -fn load_from_file(path: &Path) -> Option { - let content = fs::read_to_string(path).ok()?; - match toml::de::from_str::(&content) { - Ok(cfg) => Some(cfg), - Err(e) => { - eprintln!( - "eh: warning: failed to parse config file {}: {}", - path.display(), - e - ); - None - }, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_empty_config_defaults() { - let cfg: Config = toml::from_str("").unwrap(); - assert!(cfg.impure.is_none()); - assert!(cfg.commands.is_empty()); - } - - #[test] - fn test_command_config_impure_true() { - let cfg: Config = toml::from_str( - r#" - [commands.build] - impure = true - "#, - ) - .unwrap(); - assert_eq!(cfg.for_command("build").impure, Some(true)); - assert_eq!(cfg.for_command("run").impure, None); - } - - #[test] - fn test_command_config_impure_false() { - let cfg: Config = toml::from_str( - r#" - [commands.build] - impure = false - "#, - ) - .unwrap(); - assert_eq!(cfg.for_command("build").impure, Some(false)); - assert_eq!(cfg.for_command("run").impure, None); - } - - #[test] - fn test_global_impure_propagates_to_unconfigured_commands() { - let cfg: Config = toml::from_str("impure = true").unwrap(); - // Commands with no per-command entry inherit global. - assert_eq!(cfg.for_command("build").impure, Some(true)); - assert_eq!(cfg.for_command("nonexistent").impure, Some(true)); - } - - #[test] - fn test_global_impure_false_propagates_to_unconfigured_commands() { - let cfg: Config = toml::from_str("impure = false").unwrap(); - assert_eq!(cfg.for_command("build").impure, Some(false)); - } - - #[test] - fn test_per_command_impure_overrides_global() { - // Per-command setting wins over global. - let cfg: Config = toml::from_str( - r#" - impure = false - - [commands.build] - impure = true - "#, - ) - .unwrap(); - assert_eq!(cfg.for_command("build").impure, Some(true)); - // Command without per-command entry falls back to global false. - assert_eq!(cfg.for_command("run").impure, Some(false)); - } - - #[test] - fn test_command_config_env() { - let cfg: Config = toml::from_str( - r#" - [commands.develop] - env = { FOO = "bar", BAZ = "1" } - "#, - ) - .unwrap(); - let dev = cfg.for_command("develop"); - assert_eq!(dev.env.get("FOO").map(String::as_str), Some("bar")); - assert_eq!(dev.env.get("BAZ").map(String::as_str), Some("1")); - } - - #[test] - fn test_command_config_env_table_syntax() { - let cfg: Config = toml::from_str( - r#" - [commands.shell] - impure = true - - [commands.shell.env] - MY_VAR = "hello" - "#, - ) - .unwrap(); - let shell = cfg.for_command("shell"); - assert_eq!(shell.impure, Some(true)); - assert_eq!(shell.env.get("MY_VAR").map(String::as_str), Some("hello")); - } - - #[test] - fn test_for_command_missing_returns_default() { - let cfg = Config::default(); - let cc = cfg.for_command("nonexistent"); - assert_eq!(cc.impure, None); - assert!(cc.env.is_empty()); - } - - #[test] - fn test_unknown_top_level_key_is_rejected() { - let result = toml::de::from_str::("unknown_key = true"); - assert!(result.is_err(), "unknown top-level keys should be rejected"); - } - - #[test] - fn test_unknown_command_key_is_rejected() { - let result = toml::de::from_str::( - r#" - [commands.build] - typo_key = true - "#, - ); - assert!( - result.is_err(), - "unknown per-command keys should be rejected" - ); - } -} diff --git a/eh/src/error.rs b/eh/src/error.rs index 478c49f..2846bb9 100644 --- a/eh/src/error.rs +++ b/eh/src/error.rs @@ -54,11 +54,6 @@ pub enum EhError { #[error("no inputs selected")] UpdateCancelled, - - #[error( - "package {reason} but `--impure` is disabled for `{command}` in config" - )] - ImpureRequired { command: String, reason: String }, } pub type Result = std::result::Result; @@ -82,7 +77,6 @@ impl EhError { Self::JsonParse { .. } => 13, Self::NoFlakeInputs => 14, Self::UpdateCancelled => 0, - Self::ImpureRequired { .. } => 15, } } @@ -116,12 +110,6 @@ impl EhError { Self::NoFlakeInputs => { Some("run this from a directory with a flake.lock that has inputs") }, - Self::ImpureRequired { .. } => { - Some( - "set `impure = true` for this command (or globally) in .eh.toml or \ - ~/.config/eh/config.toml, or pass `--impure` manually", - ) - }, Self::Io(_) | Self::Regex(_) | Self::Utf8(_) diff --git a/eh/src/lib.rs b/eh/src/lib.rs index 532625a..f76e705 100644 --- a/eh/src/lib.rs +++ b/eh/src/lib.rs @@ -1,5 +1,4 @@ pub mod commands; -pub mod config; pub mod error; pub mod util; @@ -32,16 +31,6 @@ pub enum Command { #[arg(trailing_var_arg = true)] args: Vec, }, - /// Enter a Nix development shell - Develop { - #[arg(trailing_var_arg = true)] - args: Vec, - }, - /// Show package information - Info { - #[arg(trailing_var_arg = true)] - args: Vec, - }, /// Update flake inputs interactively Update { #[arg(trailing_var_arg = true)] diff --git a/eh/src/main.rs b/eh/src/main.rs index 38cf7c7..074952f 100644 --- a/eh/src/main.rs +++ b/eh/src/main.rs @@ -4,7 +4,6 @@ use eh::{Cli, Command, CommandFactory, Parser}; use yansi::Paint; mod commands; -mod config; mod error; mod util; @@ -30,21 +29,16 @@ fn handle_command(command: &str, args: &[String]) -> error::Result { let hash_extractor = util::RegexHashExtractor; let fixer = util::DefaultNixFileFixer; let classifier = util::DefaultNixErrorClassifier; - let cfg = config::load(); - let cmd_cfg = cfg.for_command(command); match command { - "info" => commands::info::handle_info(args, &cmd_cfg), - - "update" => commands::update::handle_update(args, &cmd_cfg), - "run" | "shell" | "build" | "develop" => { + "update" => commands::update::handle_update(args), + "run" | "shell" | "build" => { commands::handle_nix_command( command, args, &hash_extractor, &fixer, &classifier, - &cmd_cfg, ) }, _ => unreachable!(), @@ -61,8 +55,6 @@ fn dispatch_multicall( "nr" => "run", "ns" => "shell", "nb" => "build", - "nd" => "develop", - "ni" => "info", "nu" => "update", _ => return None, }; @@ -112,10 +104,6 @@ fn run_app() -> error::Result { Some(Command::Build { args }) => handle_command("build", &args), - Some(Command::Develop { args }) => handle_command("develop", &args), - - Some(Command::Info { args }) => handle_command("info", &args), - Some(Command::Update { args }) => handle_command("update", &args), None => { diff --git a/eh/src/util.rs b/eh/src/util.rs index 9076834..d29dd67 100644 --- a/eh/src/util.rs +++ b/eh/src/util.rs @@ -1,5 +1,5 @@ use std::{ - io::{BufWriter, IsTerminal, Write}, + io::{BufWriter, Write}, path::{Path, PathBuf}, sync::LazyLock, }; @@ -42,13 +42,6 @@ static HASH_FIX_PATTERNS: LazyLock<[Regex; 3]> = LazyLock::new(|| { ] }); -/// Regex to extract suggestions from Nix's "Did you mean" error line. -/// Matches patterns like: -/// - "Did you mean one of hello, world, or foo?" -/// - "Did you mean lib.hello?" -static DID_YOU_MEAN_PATTERN: LazyLock = - LazyLock::new(|| Regex::new(r#"Did you mean (?:one of )?(.+?)\?"#).unwrap()); - /// Trait for extracting store paths and hashes from nix output. pub trait HashExtractor { /// Extract the new store path/hash from nix output. @@ -318,20 +311,9 @@ fn is_hash_mismatch_error(stderr: &str) -> bool { /// Construct the eval expression for a given argument. /// Handles both plain package names and flake references. -pub fn make_eval_expr(eval_arg: &str) -> String { - // Handle . (current directory) as .# (default package of current flake) - // Nix treats `nix build .` and `nix build .#` as equivalent - let eval_arg = if eval_arg == "." { ".#" } else { eval_arg }; - +fn make_eval_expr(eval_arg: &str) -> String { if eval_arg.contains('#') { - // Handle .# (current flake default package) case - // .# needs to become .#default for meta evaluation to work - // because .#.meta evaluates 'meta' on the flake itself, not the package - if eval_arg.ends_with('#') { - format!("{eval_arg}default.meta") - } else { - format!("{eval_arg}.meta") - } + format!("{eval_arg}.meta") } else { format!("nixpkgs#{eval_arg}.meta") } @@ -485,7 +467,6 @@ pub fn handle_nix_with_retry( fixer: &dyn NixFileFixer, classifier: &dyn NixErrorClassifier, interactive: bool, - cfg: &crate::config::CommandConfig, ) -> Result { validate_nix_args(args)?; @@ -495,17 +476,10 @@ pub fn handle_nix_with_retry( let pkg = package_name(args); let pre_eval_action = pre_evaluate(args)?; if let Some((env_var, reason)) = pre_eval_action.env_override() { - if cfg.impure == Some(false) { - return Err(EhError::ImpureRequired { - command: subcommand.to_string(), - reason: reason.to_string(), - }); - } print_retry_msg(pkg, reason, env_var); let mut retry_cmd = NixCommand::new(subcommand) .print_build_logs(true) .args_ref(args) - .with_config(cfg) .env(env_var, "1") .impure(true); if interactive { @@ -521,7 +495,6 @@ pub fn handle_nix_with_retry( .print_build_logs(true) .interactive(true) .args_ref(args) - .with_config(cfg) .run_with_logs(StdIoInterceptor)?; if status.success() { return Ok(0); @@ -531,38 +504,13 @@ pub fn handle_nix_with_retry( // Capture output to check for errors that need retry (hash mismatches etc.) let output_cmd = NixCommand::new(subcommand) .print_build_logs(true) - .args_ref(args) - .with_config(cfg); + .args_ref(args); let output = output_cmd.output()?; let stderr = String::from_utf8_lossy(&output.stderr); // Check for hash mismatch errors if let Some(new_hash) = hash_extractor.extract_hash(&stderr) { let old_hash = hash_extractor.extract_old_hash(&stderr); - - // Ask for confirmation before fixing hash (skip in non-interactive mode) - let should_fix = if std::io::stdin().is_terminal() { - dialoguer::Confirm::new() - .with_prompt(format!( - "Hash mismatch detected for {}. Update hash in local .nix files?", - pkg.bold() - )) - .default(true) - .interact() - .map_err(|e| EhError::Io(std::io::Error::other(e)))? - } else { - log_warn!( - "{}: hash mismatch detected in non-interactive mode, skipping auto-fix", - pkg.bold() - ); - false - }; - - if !should_fix { - log_warn!("{}: hash fix cancelled", pkg.bold()); - return Err(EhError::ProcessExit { code: 1 }); - } - match fixer.fix_hash_in_files(old_hash.as_deref(), &new_hash) { Ok(true) => { log_info!( @@ -571,8 +519,7 @@ pub fn handle_nix_with_retry( ); let mut retry_cmd = NixCommand::new(subcommand) .print_build_logs(true) - .args_ref(args) - .with_config(cfg); + .args_ref(args); if interactive { retry_cmd = retry_cmd.interactive(true); } @@ -605,17 +552,10 @@ pub fn handle_nix_with_retry( if classifier.should_retry(&stderr) { let action = classify_retry_action(&stderr); if let Some((env_var, reason)) = action.env_override() { - if cfg.impure == Some(false) { - return Err(EhError::ImpureRequired { - command: subcommand.to_string(), - reason: reason.to_string(), - }); - } print_retry_msg(pkg, reason, env_var); let mut retry_cmd = NixCommand::new(subcommand) .print_build_logs(true) .args_ref(args) - .with_config(cfg) .env(env_var, "1") .impure(true); if interactive { @@ -636,9 +576,6 @@ pub fn handle_nix_with_retry( .write_all(&output.stderr) .map_err(EhError::Io)?; - // Print contextual suggestions for common errors - print_error_suggestions(&output.stderr); - match output.status.code() { Some(code) => Err(EhError::ProcessExit { code }), // No exit code means the process was killed by a signal @@ -658,46 +595,6 @@ impl NixErrorClassifier for DefaultNixErrorClassifier { } } -/// Parse suggestions from Nix's "Did you mean" error line. -/// Input: "Did you mean one of neovim, hevi, navi, neo or neo4j?" -/// Output: vec!["neovim", "hevi", "navi", "neo", "neo4j"] -fn parse_nix_suggestions(did_you_mean_line: &str) -> Vec { - DID_YOU_MEAN_PATTERN - .captures(did_you_mean_line) - .and_then(|caps| caps.get(1)) - .map(|m| m.as_str()) - .map(|suggestions| { - suggestions - .split(", ") - .flat_map(|part| part.split(" or ")) - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) - .collect() - }) - .unwrap_or_default() -} - -/// Print contextual error suggestions when a command fails. -/// Parses Nix's own "Did you mean" suggestions from stderr and presents them -/// nicely to the user. -pub fn print_error_suggestions(stderr: &[u8]) { - let stderr_str = String::from_utf8_lossy(stderr); - - // Look for Nix's "Did you mean" line in the error output - if let Some(line) = stderr_str.lines().find(|l| l.contains("Did you mean")) { - let suggestions = parse_nix_suggestions(line); - - if !suggestions.is_empty() { - let formatted = suggestions - .iter() - .map(|s| s.bold().to_string()) - .collect::>() - .join(", "); - log_info!("Did you mean: {}?", formatted); - } - } -} - #[cfg(test)] mod tests { use std::io::Write; diff --git a/flake.lock b/flake.lock index dfdfdf9..6f6bfa6 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1776548001, - "narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=", + "lastModified": 1769461804, + "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc", + "rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d", "type": "github" }, "original": {