docs: update HANDOFF.md — age-native redesign progress (tasks 1–7 complete)
This commit is contained in:
562
Cargo.lock
generated
562
Cargo.lock
generated
@@ -2,6 +2,16 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
@@ -97,12 +107,39 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "bech32"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.1"
|
||||
@@ -135,6 +172,30 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20poly1305"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"chacha20",
|
||||
"cipher",
|
||||
"poly1305",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
@@ -146,6 +207,17 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.0"
|
||||
@@ -192,12 +264,27 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
@@ -223,12 +310,73 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"curve25519-dalek-derive",
|
||||
"digest",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek-derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
@@ -256,6 +404,30 @@ version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9"
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"pkcs8",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"serde",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
@@ -290,6 +462,12 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
@@ -302,6 +480,16 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
@@ -371,6 +559,24 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
@@ -429,6 +635,15 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "integration"
|
||||
version = "0.1.0"
|
||||
@@ -465,6 +680,15 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
@@ -477,6 +701,12 @@ version = "0.2.183"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.15"
|
||||
@@ -548,12 +778,24 @@ name = "notsecrets"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"bech32",
|
||||
"chacha20poly1305",
|
||||
"curve25519-dalek",
|
||||
"dirs",
|
||||
"ed25519-dalek",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"notcore",
|
||||
"rand",
|
||||
"rpassword",
|
||||
"rsa",
|
||||
"scrypt",
|
||||
"sha2",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"which",
|
||||
"x25519-dalek",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -571,6 +813,42 @@ dependencies = [
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libm",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -578,6 +856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -592,12 +871,89 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs1"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
|
||||
dependencies = [
|
||||
"der",
|
||||
"pkcs8",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "3.1.4"
|
||||
@@ -659,6 +1015,36 @@ version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
@@ -698,6 +1084,27 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"digest",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"sha2",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rtoolbox"
|
||||
version = "0.0.3"
|
||||
@@ -708,6 +1115,15 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
@@ -727,6 +1143,15 @@ version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "salsa20"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@@ -736,6 +1161,18 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scrypt"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
|
||||
dependencies = [
|
||||
"password-hash",
|
||||
"pbkdf2",
|
||||
"salsa20",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
@@ -794,18 +1231,67 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
@@ -897,6 +1383,12 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
@@ -909,12 +1401,28 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
@@ -1302,6 +1810,58 @@ dependencies = [
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"rand_core",
|
||||
"serde",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
|
||||
68
HANDOFF.md
68
HANDOFF.md
@@ -1,9 +1,11 @@
|
||||
# HANDOFF.md
|
||||
|
||||
State of the `notfiles` repo as of 2026-03-31.
|
||||
State of the `notfiles` repo as of 2026-04-01.
|
||||
|
||||
## What Was Done
|
||||
|
||||
### Previous session (2026-03-31)
|
||||
|
||||
Restructured from a flat `src/` layout into a Cargo workspace of 5 crates:
|
||||
- `notcore` — shared library (types, config, paths, errors)
|
||||
- `notfiles` — dotfiles linker, migrated from `src/`
|
||||
@@ -11,27 +13,61 @@ Restructured from a flat `src/` layout into a Cargo workspace of 5 crates:
|
||||
- `nothooks` — Nushell hook runner with phase/state tracking
|
||||
- `notstrap` — new-machine bootstrap orchestrator
|
||||
|
||||
39 tests passing, 0 clippy warnings. Committed to `main`, not yet pushed to `gitea/main`.
|
||||
### This session (2026-04-01)
|
||||
|
||||
## Pending Issues
|
||||
Began age-native redesign of `notsecrets` — replacing sops shell-out and Bitwarden CLI dependency with a self-contained age encryption/decryption library implemented in pure Rust (modeled after rage, no dependency on it).
|
||||
|
||||
**Spec and plan written:**
|
||||
- `docs/superpowers/specs/2026-04-01-notsecrets-age-redesign.md`
|
||||
- `docs/superpowers/plans/2026-04-01-notsecrets-age-redesign.md`
|
||||
|
||||
**Implementation progress (Tasks 1–7 of 12 complete):**
|
||||
|
||||
| Task | Status | Commit |
|
||||
|------|--------|--------|
|
||||
| 1: Scaffold (Cargo.toml, error.rs, domain types, legacy shim) | ✅ | `34d9e3d` |
|
||||
| 2: format.rs (age wire format parser/serializer) | ✅ | `2476daa` |
|
||||
| 3: X25519 identity + recipient | ✅ | `6202c5d` |
|
||||
| 4: Scrypt identity + recipient | ✅ | `b899198` |
|
||||
| 5: SSH Ed25519 identity + recipient | ✅ | `566501c` |
|
||||
| 6: SSH RSA identity + recipient | ✅ | `a0fe6f7` |
|
||||
| 7: EncryptedIdentity (stub, real impl needs Decryptor) | ✅ | `545a45b` |
|
||||
| 8: Encryptor | ⏳ not started | |
|
||||
| 9: Decryptor + wire EncryptedIdentity | ⏳ not started | |
|
||||
| 10: sources/ migration (IdentitySource trait) | ⏳ not started | |
|
||||
| 11: resolve_identities() public API | ⏳ not started | |
|
||||
| 12: notstrap migration + cargo check --workspace | ⏳ not started | |
|
||||
|
||||
**Current state:**
|
||||
- `notsecrets` compiles and passes all existing tests
|
||||
- Legacy API (`AgeKeySource`, `resolve_age_key`, `install_age_key`, `decrypt_sops`) preserved in `_legacy.rs` — `notstrap` still works
|
||||
- `EncryptedIdentity::unwrap_file_key` is a stub returning an error until `Decryptor` is wired in Task 9
|
||||
|
||||
## Resuming
|
||||
|
||||
To continue the age-native redesign, pick up at Task 8 (Encryptor). The plan is at:
|
||||
`docs/superpowers/plans/2026-04-01-notsecrets-age-redesign.md`
|
||||
|
||||
Tasks 8–12 need to be executed in order (each depends on the previous). Use the subagent-driven-development skill to dispatch implementer subagents per task.
|
||||
|
||||
Key context for resuming:
|
||||
- `EncryptedIdentity` stub in `src/identities/encrypted.rs` must have its `unwrap_file_key` body replaced after `Decryptor` exists (end of Task 9)
|
||||
- The `EnvInjector` type in `notstrap` changes from `Box<dyn Fn>` to `Box<dyn FnOnce>` in Task 12
|
||||
- `_legacy.rs` is deleted in Task 10
|
||||
|
||||
## Pending Issues (carried from previous session)
|
||||
|
||||
### 1. Push to remote
|
||||
```bash
|
||||
git push
|
||||
```
|
||||
8 commits ahead of `gitea/main`. Nothing blocking this.
|
||||
Several commits ahead of `gitea/main`. Push when ready.
|
||||
|
||||
### 2. notstrap has an empty lib.rs
|
||||
`crates/notstrap/src/lib.rs` is empty — created as a stub. `notstrap` is binary-only so this is harmless, but it's dead weight. Either delete `[lib]` from `notstrap/Cargo.toml` (if there is one) or remove the file.
|
||||
`crates/notstrap/src/lib.rs` is empty — created as a stub. Either delete `[lib]` from `notstrap/Cargo.toml` or remove the file.
|
||||
|
||||
### 3. notstrap config not documented
|
||||
`notstrap.toml` format is defined inline in `notstrap/src/main.rs` via serde structs but never written down. A sample `notstrap.toml` should be created and documented.
|
||||
`notstrap.toml` format is defined inline via serde structs but never written down. A sample config would help.
|
||||
|
||||
### 4. notsecrets has no binary
|
||||
`notsecrets` is library-only. A small `notsecrets get-key` CLI binary could be useful for manual debugging/testing of the key retrieval chain, but not blocking.
|
||||
### 4. Integration test coverage for nothooks assumes nu in PATH
|
||||
`nothooks` integration tests call `nu` directly. CI needs `nu` installed or tests need `#[ignore]` + feature flag.
|
||||
|
||||
### 5. Integration test coverage for nothooks assumes nu in PATH
|
||||
`nothooks` integration tests call `nu` directly. If `nu` is not on PATH (e.g. on CI), the success/failure tests will fail for the wrong reason. CI should either install `nu` or the tests should be marked `#[ignore]` with a feature flag.
|
||||
|
||||
### 6. notstrap sops_file path is always joined to dotfiles_dir
|
||||
If `sops_file` in `notstrap.toml` is an absolute path, the join will silently override the base. This is a minor footgun in `repo.clone_if_missing` usage — low priority.
|
||||
### 5. notstrap sops_file path footgun
|
||||
If `sops_file` in `notstrap.toml` is an absolute path, the `join` will silently override the base. Low priority.
|
||||
|
||||
@@ -77,10 +77,12 @@ impl Config {
|
||||
if !config_path.exists() {
|
||||
return Ok(Config::default());
|
||||
}
|
||||
let content = std::fs::read_to_string(&config_path)
|
||||
.map_err(|e| NotfilesError::Config(format!("reading {}: {e}", config_path.display())))?;
|
||||
let config: Config = toml::from_str(&content)
|
||||
.map_err(|e| NotfilesError::Config(format!("parsing {}: {e}", config_path.display())))?;
|
||||
let content = std::fs::read_to_string(&config_path).map_err(|e| {
|
||||
NotfilesError::Config(format!("reading {}: {e}", config_path.display()))
|
||||
})?;
|
||||
let config: Config = toml::from_str(&content).map_err(|e| {
|
||||
NotfilesError::Config(format!("parsing {}: {e}", config_path.display()))
|
||||
})?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,10 @@ pub struct Report {
|
||||
|
||||
impl Report {
|
||||
pub fn add(&mut self, name: impl Into<String>, status: StepStatus) {
|
||||
self.steps.push(Step { name: name.into(), status });
|
||||
self.steps.push(Step {
|
||||
name: name.into(),
|
||||
status,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn print(&self) {
|
||||
@@ -67,7 +70,9 @@ impl Report {
|
||||
}
|
||||
|
||||
pub fn has_failures(&self) -> bool {
|
||||
self.steps.iter().any(|s| matches!(s.status, StepStatus::Failed(_)))
|
||||
self.steps
|
||||
.iter()
|
||||
.any(|s| matches!(s.status, StepStatus::Failed(_)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,9 @@ impl IgnoreMatcher {
|
||||
// Match the pattern as a filename component and also as a path suffix.
|
||||
let glob = Glob::new(pattern)
|
||||
.or_else(|_| Glob::new(&format!("**/{pattern}")))
|
||||
.map_err(|e| NotfilesError::Other(format!("invalid ignore pattern '{pattern}': {e}")))?;
|
||||
.map_err(|e| {
|
||||
NotfilesError::Other(format!("invalid ignore pattern '{pattern}': {e}"))
|
||||
})?;
|
||||
builder.add(glob);
|
||||
// Also add a recursive variant so "foo" matches "a/foo" etc.
|
||||
#[allow(clippy::collapsible_if)]
|
||||
|
||||
@@ -10,11 +10,7 @@ use std::path::Path;
|
||||
pub use linker::{LinkOptions, State};
|
||||
pub use package::resolve_packages;
|
||||
|
||||
pub fn link(
|
||||
dotfiles_dir: &Path,
|
||||
packages: &[String],
|
||||
opts: &LinkOptions,
|
||||
) -> Result<State> {
|
||||
pub fn link(dotfiles_dir: &Path, packages: &[String], opts: &LinkOptions) -> Result<State> {
|
||||
let config = notcore::Config::load(dotfiles_dir)?;
|
||||
let mut state = State::load(dotfiles_dir)?;
|
||||
let pkgs = resolve_packages(dotfiles_dir, packages)?;
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::path::{Path, PathBuf};
|
||||
use chrono::Utc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use notcore::{Config, Method, NotfilesError, expand_tilde};
|
||||
use crate::package::collect_files;
|
||||
use notcore::{Config, Method, NotfilesError, expand_tilde};
|
||||
|
||||
const STATE_FILE: &str = ".notfiles-state.toml";
|
||||
|
||||
@@ -46,7 +46,10 @@ impl State {
|
||||
}
|
||||
|
||||
pub fn entries_for_package(&self, package: &str) -> Vec<&StateEntry> {
|
||||
self.entries.iter().filter(|e| e.package == package).collect()
|
||||
self.entries
|
||||
.iter()
|
||||
.filter(|e| e.package == package)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn remove_package(&mut self, package: &str) {
|
||||
@@ -55,7 +58,8 @@ impl State {
|
||||
|
||||
pub fn add_entry(&mut self, entry: StateEntry) {
|
||||
// Remove existing entry for same source+target, then add new
|
||||
self.entries.retain(|e| !(e.source == entry.source && e.target == entry.target));
|
||||
self.entries
|
||||
.retain(|e| !(e.source == entry.source && e.target == entry.target));
|
||||
self.entries.push(entry);
|
||||
}
|
||||
}
|
||||
@@ -113,10 +117,18 @@ pub fn link_package(
|
||||
if !opts.no_backup {
|
||||
let backup = backup_path(&target);
|
||||
if opts.dry_run {
|
||||
println!(" \x1b[33mwould backup\x1b[0m {} -> {}", target.display(), backup.display());
|
||||
println!(
|
||||
" \x1b[33mwould backup\x1b[0m {} -> {}",
|
||||
target.display(),
|
||||
backup.display()
|
||||
);
|
||||
} else {
|
||||
if opts.verbose {
|
||||
println!(" \x1b[33mbackup\x1b[0m {} -> {}", target.display(), backup.display());
|
||||
println!(
|
||||
" \x1b[33mbackup\x1b[0m {} -> {}",
|
||||
target.display(),
|
||||
backup.display()
|
||||
);
|
||||
}
|
||||
fs::rename(&target, &backup)?;
|
||||
}
|
||||
@@ -147,7 +159,10 @@ pub fn link_package(
|
||||
};
|
||||
|
||||
if opts.dry_run {
|
||||
println!(" \x1b[36mwould {action_word}\x1b[0m {source_display} -> {}", target.display());
|
||||
println!(
|
||||
" \x1b[36mwould {action_word}\x1b[0m {source_display} -> {}",
|
||||
target.display()
|
||||
);
|
||||
} else {
|
||||
match method {
|
||||
Method::Symlink => {
|
||||
@@ -161,7 +176,10 @@ pub fn link_package(
|
||||
}
|
||||
}
|
||||
if opts.verbose {
|
||||
println!(" \x1b[32m{action_word}\x1b[0m {source_display} -> {}", target.display());
|
||||
println!(
|
||||
" \x1b[32m{action_word}\x1b[0m {source_display} -> {}",
|
||||
target.display()
|
||||
);
|
||||
}
|
||||
|
||||
state.add_entry(StateEntry {
|
||||
@@ -183,7 +201,11 @@ pub fn unlink_package(
|
||||
package: &str,
|
||||
opts: &LinkOptions,
|
||||
) -> Result<(), NotfilesError> {
|
||||
let entries: Vec<StateEntry> = state.entries_for_package(package).into_iter().cloned().collect();
|
||||
let entries: Vec<StateEntry> = state
|
||||
.entries_for_package(package)
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
if entries.is_empty() {
|
||||
if opts.verbose {
|
||||
@@ -278,7 +300,10 @@ fn cleanup_empty_parents(path: &Path) {
|
||||
if Some(parent.to_path_buf()) == dirs::home_dir() || parent == Path::new("/") {
|
||||
break;
|
||||
}
|
||||
if fs::read_dir(parent).map(|mut d| d.next().is_none()).unwrap_or(false) {
|
||||
if fs::read_dir(parent)
|
||||
.map(|mut d| d.next().is_none())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let _ = fs::remove_dir(parent);
|
||||
dir = parent.parent();
|
||||
} else {
|
||||
|
||||
@@ -2,11 +2,11 @@ use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use std::fs;
|
||||
|
||||
use notcore::Config;
|
||||
use notfiles::cli::{Cli, Command};
|
||||
use notfiles::linker::{LinkOptions, State};
|
||||
use notfiles::package::resolve_packages;
|
||||
use notfiles::{linker, status};
|
||||
use notcore::Config;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
@@ -46,7 +46,10 @@ fn main() -> Result<()> {
|
||||
|
||||
if !cli.dry_run {
|
||||
state.save(&dotfiles_dir)?;
|
||||
let count: usize = pkgs.iter().map(|p| state.entries_for_package(p).len()).sum();
|
||||
let count: usize = pkgs
|
||||
.iter()
|
||||
.map(|p| state.entries_for_package(p).len())
|
||||
.sum();
|
||||
println!(
|
||||
"\x1b[32mLinked {count} file{} across {} package{}.\x1b[0m",
|
||||
if count == 1 { "" } else { "s" },
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use notcore::{Config, NotfilesError};
|
||||
use crate::ignore::IgnoreMatcher;
|
||||
use notcore::{Config, NotfilesError};
|
||||
|
||||
/// Discover available packages (subdirectories of the dotfiles dir).
|
||||
pub fn discover_packages(dotfiles_dir: &Path) -> Result<Vec<String>, NotfilesError> {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use notcore::{Config, Method, expand_tilde};
|
||||
use crate::linker::State;
|
||||
use crate::package::collect_files;
|
||||
use notcore::{Config, Method, expand_tilde};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum FileStatus {
|
||||
@@ -69,8 +69,7 @@ pub fn package_status(
|
||||
}
|
||||
Method::Copy => {
|
||||
let has_state = state.entries.iter().any(|e| {
|
||||
e.package == package
|
||||
&& e.target == target.to_string_lossy().as_ref()
|
||||
e.package == package && e.target == target.to_string_lossy().as_ref()
|
||||
});
|
||||
if has_state && target.exists() {
|
||||
FileStatus::Copied
|
||||
@@ -122,6 +121,11 @@ pub fn print_status(package: &str, entries: &[StatusEntry]) {
|
||||
}
|
||||
println!(" \x1b[1m{package}\x1b[0m:");
|
||||
for entry in entries {
|
||||
println!(" {} {} -> {}", entry.status, entry.source_display, entry.target.display());
|
||||
println!(
|
||||
" {} {} -> {}",
|
||||
entry.status,
|
||||
entry.source_display,
|
||||
entry.target.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,13 @@ fn test_link_and_status() {
|
||||
|
||||
let gitconfig = target.join(".gitconfig");
|
||||
assert!(gitconfig.exists());
|
||||
assert!(gitconfig.symlink_metadata().unwrap().file_type().is_symlink());
|
||||
assert!(
|
||||
gitconfig
|
||||
.symlink_metadata()
|
||||
.unwrap()
|
||||
.file_type()
|
||||
.is_symlink()
|
||||
);
|
||||
|
||||
// State file should exist
|
||||
assert!(dotfiles.join(".notfiles-state.toml").exists());
|
||||
@@ -199,7 +205,14 @@ fn test_force_with_backup() {
|
||||
assert!(ok);
|
||||
|
||||
// The link should now exist
|
||||
assert!(target.join(".gitconfig").symlink_metadata().unwrap().file_type().is_symlink());
|
||||
assert!(
|
||||
target
|
||||
.join(".gitconfig")
|
||||
.symlink_metadata()
|
||||
.unwrap()
|
||||
.file_type()
|
||||
.is_symlink()
|
||||
);
|
||||
|
||||
// A backup should exist
|
||||
let backups: Vec<_> = fs::read_dir(&target)
|
||||
@@ -232,11 +245,7 @@ fn test_force_no_backup() {
|
||||
let backups: Vec<_> = fs::read_dir(&target)
|
||||
.unwrap()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| {
|
||||
e.file_name()
|
||||
.to_string_lossy()
|
||||
.contains("notfiles-backup")
|
||||
})
|
||||
.filter(|e| e.file_name().to_string_lossy().contains("notfiles-backup"))
|
||||
.collect();
|
||||
assert_eq!(backups.len(), 0);
|
||||
}
|
||||
@@ -272,8 +281,17 @@ method = "copy"
|
||||
let ssh_config = target.join(".ssh/config");
|
||||
assert!(ssh_config.exists());
|
||||
// Should NOT be a symlink
|
||||
assert!(!ssh_config.symlink_metadata().unwrap().file_type().is_symlink());
|
||||
assert_eq!(fs::read_to_string(&ssh_config).unwrap(), "Host *\n AddKeysToAgent yes");
|
||||
assert!(
|
||||
!ssh_config
|
||||
.symlink_metadata()
|
||||
.unwrap()
|
||||
.file_type()
|
||||
.is_symlink()
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(&ssh_config).unwrap(),
|
||||
"Host *\n AddKeysToAgent yes"
|
||||
);
|
||||
|
||||
// Status should show "copied"
|
||||
let (stdout, _, _) = run(&dotfiles, &["status", "ssh"]);
|
||||
|
||||
121
crates/nothooks/README.md
Normal file
121
crates/nothooks/README.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# nothooks
|
||||
|
||||
Nushell hook runner for the notfiles workspace. Executes `.nu` scripts in two
|
||||
distinct lifecycle phases — **dot** (always) and **setup** (once) — with
|
||||
state persistence so setup hooks are not re-run across invocations.
|
||||
|
||||
## Concepts
|
||||
|
||||
### HookPhase
|
||||
|
||||
Each hook belongs to exactly one phase:
|
||||
|
||||
| Phase | When it runs | Typical use |
|
||||
|-------|-------------|-------------|
|
||||
| `dot` | Every time nothooks is invoked | Apply shell config, set env vars, refresh symlinks |
|
||||
| `setup` | Once per machine (tracked in state) | Install packages, create dirs, first-time configuration |
|
||||
|
||||
### HookSpec
|
||||
|
||||
A hook is described by three fields (defined in `notcore`):
|
||||
|
||||
```toml
|
||||
[[hooks]]
|
||||
name = "install-starship"
|
||||
script = "hooks/setup/install-starship.nu"
|
||||
phase = "setup"
|
||||
|
||||
[[hooks]]
|
||||
name = "set-default-shell"
|
||||
script = "hooks/dot/shell.nu"
|
||||
phase = "dot"
|
||||
```
|
||||
|
||||
### State tracking
|
||||
|
||||
Setup hooks are tracked in `.nothooks-state.toml` in the state directory
|
||||
(typically the dotfiles root). Once a setup hook completes successfully its
|
||||
name is recorded and it will be **skipped** on all future runs.
|
||||
|
||||
```toml
|
||||
# .nothooks-state.toml (auto-generated, do not edit manually)
|
||||
completed_setup_hooks = ["install-starship", "configure-git"]
|
||||
```
|
||||
|
||||
Dot hooks are **never** recorded in state — they run unconditionally on every
|
||||
invocation.
|
||||
|
||||
### --force re-run
|
||||
|
||||
To re-run setup hooks (e.g. after a machine rebuild or to apply changes),
|
||||
construct the runner with `HookRunner::with_force`. This bypasses the state
|
||||
check so all setup hooks execute regardless of prior completion.
|
||||
|
||||
The notstrap CLI exposes this as `--force`.
|
||||
|
||||
## Hook script lifecycle
|
||||
|
||||
```
|
||||
notstrap / nothooks invoked
|
||||
│
|
||||
├── dot phase
|
||||
│ └── for each hook where phase == "dot"
|
||||
│ └── nu <script> # always runs
|
||||
│
|
||||
└── setup phase
|
||||
└── for each hook where phase == "setup"
|
||||
├── state.is_done(name)?
|
||||
│ ├── yes (and !force) → skip
|
||||
│ └── no (or force) → nu <script>
|
||||
│ └── success → state.mark_done(name)
|
||||
└── report collected
|
||||
```
|
||||
|
||||
## Writing a hook script
|
||||
|
||||
Hook scripts are plain Nushell (`.nu`) files. They receive no arguments and
|
||||
communicate success/failure via exit code (0 = success, non-zero = failure).
|
||||
|
||||
```nushell
|
||||
#!/usr/bin/env nu
|
||||
# hooks/setup/install-starship.nu
|
||||
# Installs starship prompt if not already present.
|
||||
|
||||
if (which starship | is-empty) {
|
||||
print "Installing starship..."
|
||||
sh -c "curl -sS https://starship.rs/install.sh | sh -s -- -y"
|
||||
} else {
|
||||
print "starship already installed, skipping"
|
||||
}
|
||||
```
|
||||
|
||||
```nushell
|
||||
#!/usr/bin/env nu
|
||||
# hooks/dot/xdg-dirs.nu
|
||||
# Ensure XDG base directories exist on every login.
|
||||
|
||||
mkdir ~/.config ~/.cache ~/.local/share ~/.local/state
|
||||
```
|
||||
|
||||
## Usage from Rust
|
||||
|
||||
```rust
|
||||
use nothooks::{HookRunner, run_phase};
|
||||
use notcore::{HookPhase, HookSpec};
|
||||
use std::path::PathBuf;
|
||||
|
||||
let hooks: Vec<HookSpec> = /* load from notfiles.toml */;
|
||||
let state_dir = PathBuf::from("/path/to/dotfiles");
|
||||
|
||||
// Normal run
|
||||
let runner = HookRunner::new(state_dir.clone());
|
||||
|
||||
// Force re-run of setup hooks
|
||||
let runner = HookRunner::with_force(state_dir.clone());
|
||||
|
||||
let dot_report = run_phase(&hooks, &HookPhase::Dot, &runner);
|
||||
let setup_report = run_phase(&hooks, &HookPhase::Setup, &runner);
|
||||
```
|
||||
|
||||
`run_phase` returns a `notcore::Report` containing a `StepStatus` (`Ok`,
|
||||
`Skipped`, or `Failed(msg)`) for each hook in that phase.
|
||||
@@ -1,8 +1,8 @@
|
||||
pub mod runner;
|
||||
pub mod state;
|
||||
|
||||
pub use runner::HookRunner;
|
||||
use notcore::{HookPhase, HookSpec, Report, StepStatus};
|
||||
pub use runner::HookRunner;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum HookResult {
|
||||
@@ -12,11 +12,7 @@ pub enum HookResult {
|
||||
}
|
||||
|
||||
/// Run all hooks matching `phase` and collect into a `Report`.
|
||||
pub fn run_phase(
|
||||
hooks: &[HookSpec],
|
||||
phase: &HookPhase,
|
||||
runner: &HookRunner,
|
||||
) -> Report {
|
||||
pub fn run_phase(hooks: &[HookSpec], phase: &HookPhase, runner: &HookRunner) -> Report {
|
||||
let mut report = Report::default();
|
||||
for hook in hooks.iter().filter(|h| &h.phase == phase) {
|
||||
let result = runner.run_hook(hook);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use nothooks::{run_phase, HookRunner};
|
||||
use notcore::{HookPhase, HookSpec};
|
||||
use nothooks::{HookRunner, run_phase};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Parser)]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use notcore::{HookPhase, HookSpec};
|
||||
use crate::HookResult;
|
||||
use crate::state::HookState;
|
||||
use notcore::{HookPhase, HookSpec};
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
pub struct HookRunner {
|
||||
state_dir: PathBuf,
|
||||
@@ -11,11 +11,17 @@ pub struct HookRunner {
|
||||
|
||||
impl HookRunner {
|
||||
pub fn new(state_dir: PathBuf) -> Self {
|
||||
Self { state_dir, force: false }
|
||||
Self {
|
||||
state_dir,
|
||||
force: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_force(state_dir: PathBuf) -> Self {
|
||||
Self { state_dir, force: true }
|
||||
Self {
|
||||
state_dir,
|
||||
force: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_hook(&self, spec: &HookSpec) -> HookResult {
|
||||
@@ -25,9 +31,8 @@ impl HookRunner {
|
||||
return HookResult::Skipped;
|
||||
}
|
||||
|
||||
let result = Command::new("nu")
|
||||
.arg(&spec.script)
|
||||
.status();
|
||||
// TODO: support other shells
|
||||
let result = Command::new("nu").arg(&spec.script).status();
|
||||
|
||||
match result {
|
||||
Ok(status) if status.success() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
|
||||
const STATE_FILE: &str = ".nothooks-state.toml";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use notcore::{HookPhase, HookSpec};
|
||||
use nothooks::{HookResult, HookRunner};
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
use nothooks::{HookResult, HookRunner};
|
||||
use notcore::{HookPhase, HookSpec};
|
||||
|
||||
fn make_hook_script(dir: &TempDir, name: &str, content: &str) -> HookSpec {
|
||||
let path = dir.path().join(format!("{name}.nu"));
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use anyhow::{Context, Result};
|
||||
use notcore::{HookPhase, Report, StepStatus};
|
||||
use notfiles::{link, LinkOptions};
|
||||
use nothooks::{run_phase, HookRunner};
|
||||
use notsecrets::{install_age_key, resolve_age_key, BitwardenSource, FileSource, PromptSource};
|
||||
use notfiles::{LinkOptions, link};
|
||||
use nothooks::{HookRunner, run_phase};
|
||||
use notsecrets::{BitwardenSource, FileSource, PromptSource, install_age_key, resolve_age_key};
|
||||
use serde::Deserialize;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -28,8 +28,12 @@ pub struct BootstrapSection {
|
||||
pub sops_file: String,
|
||||
}
|
||||
|
||||
pub(crate) fn default_bw_item() -> String { "age-key-dotfiles".to_string() }
|
||||
pub(crate) fn default_sops_file() -> String { "secrets/bootstrap.sops.env".to_string() }
|
||||
pub(crate) fn default_bw_item() -> String {
|
||||
"age-key-dotfiles".to_string()
|
||||
}
|
||||
pub(crate) fn default_sops_file() -> String {
|
||||
"secrets/bootstrap.sops.env".to_string()
|
||||
}
|
||||
|
||||
pub struct BootstrapOptions {
|
||||
pub config: PathBuf,
|
||||
@@ -48,7 +52,9 @@ pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
||||
// 1. Prerequisites
|
||||
if let Some(check) = opts.check_prereqs {
|
||||
match check() {
|
||||
Ok(_) => { report.add("prerequisites", StepStatus::Ok); }
|
||||
Ok(_) => {
|
||||
report.add("prerequisites", StepStatus::Ok);
|
||||
}
|
||||
Err(e) => {
|
||||
report.add("prerequisites", StepStatus::Failed(e.to_string()));
|
||||
return Ok(report);
|
||||
@@ -63,15 +69,20 @@ pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
||||
|
||||
let dotfiles_dir = match opts.dotfiles {
|
||||
Some(d) => d,
|
||||
None => notcore::expand_tilde(&cfg.bootstrap.dotfiles_dir)
|
||||
.with_context(|| format!("cannot expand dotfiles_dir: {}", cfg.bootstrap.dotfiles_dir))?,
|
||||
None => notcore::expand_tilde(&cfg.bootstrap.dotfiles_dir).with_context(|| {
|
||||
format!("cannot expand dotfiles_dir: {}", cfg.bootstrap.dotfiles_dir)
|
||||
})?,
|
||||
};
|
||||
|
||||
// 3. Clone dotfiles if missing
|
||||
match repo::clone_if_missing(&cfg.bootstrap.dotfiles_repo, &dotfiles_dir) {
|
||||
Ok(true) => { report.add("clone dotfiles", StepStatus::Ok); }
|
||||
Ok(false) => { report.add("clone dotfiles", StepStatus::Skipped); }
|
||||
Err(e) => {
|
||||
Ok(true) => {
|
||||
report.add("clone dotfiles", StepStatus::Ok);
|
||||
}
|
||||
Ok(false) => {
|
||||
report.add("clone dotfiles", StepStatus::Skipped);
|
||||
}
|
||||
Err(e) => {
|
||||
report.add("clone dotfiles", StepStatus::Failed(e.to_string()));
|
||||
return Ok(report);
|
||||
}
|
||||
@@ -110,7 +121,9 @@ pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
||||
// threads exist. Subsequent Command::spawn calls (git, hooks) will
|
||||
// inherit these vars — keys are validated by parse_env_line before
|
||||
// reaching this point (null bytes and dangerous keys are rejected).
|
||||
unsafe { std::env::set_var(&k, &v); }
|
||||
unsafe {
|
||||
std::env::set_var(&k, &v);
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
@@ -129,7 +142,12 @@ pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
||||
}
|
||||
|
||||
// 6. Link dotfiles
|
||||
let link_opts = LinkOptions { force: opts.force, no_backup: false, dry_run: false, verbose: false };
|
||||
let link_opts = LinkOptions {
|
||||
force: opts.force,
|
||||
no_backup: false,
|
||||
dry_run: false,
|
||||
verbose: false,
|
||||
};
|
||||
match link(&dotfiles_dir, &[], &link_opts) {
|
||||
Ok(state) => {
|
||||
let count = state.entries.len();
|
||||
@@ -147,9 +165,14 @@ pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
||||
HookRunner::new(dotfiles_dir.clone())
|
||||
};
|
||||
|
||||
for (phase, label) in [(HookPhase::Dot, "dot hooks"), (HookPhase::Setup, "setup hooks")] {
|
||||
for (phase, label) in [
|
||||
(HookPhase::Dot, "dot hooks"),
|
||||
(HookPhase::Setup, "setup hooks"),
|
||||
] {
|
||||
let phase_report = run_phase(&cfg.hooks, &phase, &runner);
|
||||
let failed = phase_report.steps.iter()
|
||||
let failed = phase_report
|
||||
.steps
|
||||
.iter()
|
||||
.filter(|s| matches!(s.status, notcore::StepStatus::Failed(_)))
|
||||
.count();
|
||||
let summary = if failed > 0 {
|
||||
@@ -223,7 +246,12 @@ mod tests {
|
||||
fn parse_env_line_blocks_ld_preload() {
|
||||
let result = parse_env_line("LD_PRELOAD=/evil/lib.so");
|
||||
assert!(result.is_err(), "expected error for LD_PRELOAD");
|
||||
assert!(result.unwrap_err().to_string().contains("sensitive env key"));
|
||||
assert!(
|
||||
result
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("sensitive env key")
|
||||
);
|
||||
}
|
||||
|
||||
/// Issue #5: PATH must be blocked.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use notstrap::{prereqs, run, BootstrapOptions};
|
||||
use notstrap::{BootstrapOptions, prereqs, run};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "notstrap", about = "Bootstrap a new machine from dotfiles")]
|
||||
@@ -33,7 +33,12 @@ enum Cmd {
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
let Cmd::Run { config, force, key_file, dotfiles } = cli.command;
|
||||
let Cmd::Run {
|
||||
config,
|
||||
force,
|
||||
key_file,
|
||||
dotfiles,
|
||||
} = cli.command;
|
||||
let opts = BootstrapOptions {
|
||||
config,
|
||||
force,
|
||||
|
||||
@@ -7,9 +7,18 @@ pub struct Prereq {
|
||||
}
|
||||
|
||||
const PREREQS: &[Prereq] = &[
|
||||
Prereq { cmd: "nu", install_hint: "brew install nushell OR nix-env -iA nixpkgs.nushell" },
|
||||
Prereq { cmd: "sops", install_hint: "brew install sops OR nix-env -iA nixpkgs.sops" },
|
||||
Prereq { cmd: "age", install_hint: "brew install age OR nix-env -iA nixpkgs.age" },
|
||||
Prereq {
|
||||
cmd: "nu",
|
||||
install_hint: "brew install nushell OR nix-env -iA nixpkgs.nushell",
|
||||
},
|
||||
Prereq {
|
||||
cmd: "sops",
|
||||
install_hint: "brew install sops OR nix-env -iA nixpkgs.sops",
|
||||
},
|
||||
Prereq {
|
||||
cmd: "age",
|
||||
install_hint: "brew install age OR nix-env -iA nixpkgs.age",
|
||||
},
|
||||
];
|
||||
|
||||
pub fn check_prerequisites() -> Result<()> {
|
||||
|
||||
94
mise.toml
Normal file
94
mise.toml
Normal file
@@ -0,0 +1,94 @@
|
||||
[tools]
|
||||
rust = "1.91.1"
|
||||
|
||||
[tasks]
|
||||
|
||||
# ── Quality Gates ──────────────────────────────────────────────────────────────
|
||||
|
||||
[tasks."all:check"]
|
||||
description = "cargo check across workspace"
|
||||
run = "cargo check --workspace"
|
||||
|
||||
[tasks."all:fmt"]
|
||||
description = "Format all code"
|
||||
run = "cargo fmt"
|
||||
|
||||
[tasks."all:fmt-check"]
|
||||
description = "Check formatting (no changes)"
|
||||
run = "cargo fmt --check"
|
||||
|
||||
[tasks."all:clippy"]
|
||||
description = "Clippy with warnings as errors"
|
||||
run = "cargo clippy --workspace -- -D warnings"
|
||||
|
||||
[tasks."all:test"]
|
||||
description = "Run all tests via nextest"
|
||||
run = "cargo nextest run --workspace --cargo-profile release"
|
||||
|
||||
[tasks."all:test-unit"]
|
||||
description = "Run unit tests only (exclude integration)"
|
||||
run = "cargo nextest run --workspace --lib --cargo-profile release"
|
||||
|
||||
[tasks."all:test-integration"]
|
||||
description = "Run integration tests"
|
||||
run = "cargo nextest run -p integration --cargo-profile release"
|
||||
|
||||
[tasks."all:ci"]
|
||||
description = "Full CI gate: fmt-check + clippy + nextest"
|
||||
run = """
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
echo "── fmt ──────────────────────────────"
|
||||
cargo fmt --check
|
||||
echo "── clippy ───────────────────────────"
|
||||
cargo clippy --workspace -- -D warnings
|
||||
echo "── dep-boundaries ───────────────────"
|
||||
python3 scripts/check-dep-boundaries.py
|
||||
echo "── test ─────────────────────────────"
|
||||
cargo nextest run --workspace --cargo-profile release
|
||||
echo "✓ all gates passed"
|
||||
"""
|
||||
|
||||
[tasks."all:ci-setup"]
|
||||
description = "One-time setup: install cargo-nextest, cargo-deny"
|
||||
run = """
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
echo "Installing cargo-nextest..."
|
||||
command -v cargo-nextest >/dev/null 2>&1 || ~/.local/bin/mise exec -- cargo install cargo-nextest --locked
|
||||
echo "Installing cargo-deny..."
|
||||
command -v cargo-deny >/dev/null 2>&1 || ~/.local/bin/mise exec -- cargo install cargo-deny --locked
|
||||
echo "✓ CI setup complete"
|
||||
"""
|
||||
|
||||
[tasks."all:dep-boundaries"]
|
||||
description = "Enforce crate dependency boundary rules"
|
||||
run = "python3 scripts/check-dep-boundaries.py"
|
||||
|
||||
[tasks."all:deny"]
|
||||
description = "Run cargo-deny license/security checks"
|
||||
run = "cargo deny check"
|
||||
|
||||
# ── Dev Workflow ───────────────────────────────────────────────────────────────
|
||||
|
||||
[tasks."all:build"]
|
||||
description = "Build all workspace crates"
|
||||
run = "cargo build"
|
||||
|
||||
[tasks."all:build-release"]
|
||||
description = "Build release binaries"
|
||||
run = "cargo build --release"
|
||||
|
||||
[tasks."all:commit"]
|
||||
description = "Stage all + commit. Usage: mise run all:commit -- 'message'"
|
||||
run = """
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
MSG="${1:?usage: mise run all:commit -- 'message'}"
|
||||
git add -A
|
||||
git commit -m "$MSG"
|
||||
"""
|
||||
|
||||
[tasks."all:watch"]
|
||||
description = "Watch for changes and run check + nextest"
|
||||
run = "cargo watch -x 'check --workspace' -x 'nextest run --workspace --cargo-profile release'"
|
||||
56
scripts/check-dep-boundaries.py
Normal file
56
scripts/check-dep-boundaries.py
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Enforce crate dependency boundaries for the notfiles workspace:
|
||||
- notcore must remain a leaf crate (no deps on other notfiles crates)
|
||||
- notfiles, notsecrets, nothooks may depend on notcore but NOT on each other
|
||||
- notstrap may depend on notcore + feature crates (it's the orchestrator)
|
||||
|
||||
Exits 0 if boundaries are clean, 1 if violations found.
|
||||
"""
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
LEAF = "notcore"
|
||||
FEATURE_CRATES = {"notfiles", "notsecrets", "nothooks"}
|
||||
ORCHESTRATOR = "notstrap"
|
||||
WORKSPACE_CRATES = FEATURE_CRATES | {LEAF, ORCHESTRATOR}
|
||||
|
||||
result = subprocess.run(
|
||||
["cargo", "metadata", "--no-deps", "--format-version", "1"],
|
||||
capture_output=True, text=True, check=True,
|
||||
)
|
||||
meta = json.loads(result.stdout)
|
||||
|
||||
# Build name -> deps map for workspace members only
|
||||
crate_deps: dict[str, set[str]] = {}
|
||||
for pkg in meta["packages"]:
|
||||
name = pkg["name"]
|
||||
if name not in WORKSPACE_CRATES:
|
||||
continue
|
||||
deps = {d["name"] for d in pkg["dependencies"] if d["name"] in WORKSPACE_CRATES}
|
||||
crate_deps[name] = deps
|
||||
|
||||
violations = []
|
||||
|
||||
# notcore must have no workspace deps
|
||||
if crate_deps.get(LEAF):
|
||||
violations.append(f"{LEAF} depends on workspace crates: {crate_deps[LEAF]}")
|
||||
|
||||
# feature crates must not depend on each other
|
||||
for crate in FEATURE_CRATES:
|
||||
forbidden = crate_deps.get(crate, set()) & FEATURE_CRATES
|
||||
if forbidden:
|
||||
violations.append(f"{crate} depends on sibling feature crates: {forbidden}")
|
||||
|
||||
if violations:
|
||||
print("FAIL: crate dependency boundary violations detected:")
|
||||
for v in violations:
|
||||
print(f" - {v}")
|
||||
sys.exit(1)
|
||||
|
||||
print("OK: crate dependency boundaries are clean")
|
||||
print(f" {LEAF}: no workspace deps (leaf)")
|
||||
for c in sorted(FEATURE_CRATES):
|
||||
print(f" {c}: depends only on {LEAF}")
|
||||
print(f" {ORCHESTRATOR}: orchestrator (unrestricted)")
|
||||
@@ -1,6 +1,6 @@
|
||||
use notcore::{HookPhase, HookSpec, StepStatus};
|
||||
use nothooks::{run_phase, HookRunner};
|
||||
use notstrap::{run, BootstrapOptions};
|
||||
use nothooks::{HookRunner, run_phase};
|
||||
use notstrap::{BootstrapOptions, run};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::TempDir;
|
||||
|
||||
@@ -3,9 +3,9 @@ use std::path::Path;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use notcore::{HookPhase, HookSpec};
|
||||
use notfiles::{LinkOptions, link};
|
||||
use nothooks::{HookResult, HookRunner};
|
||||
use notsecrets::{resolve_age_key, AgeKeySource, FileSource};
|
||||
use notfiles::{link, LinkOptions};
|
||||
use notsecrets::{AgeKeySource, FileSource, resolve_age_key};
|
||||
|
||||
/// Test that notsecrets and nothooks can each be used independently
|
||||
/// in the same integration boundary — resolving an age key from a file
|
||||
@@ -69,7 +69,12 @@ fn test_notfiles_respects_default_ignore() {
|
||||
fs::write(pkg.join(".notfiles-state.toml"), "# state\n").unwrap();
|
||||
fs::write(pkg.join(".nothooks-state.toml"), "# state\n").unwrap();
|
||||
|
||||
let opts = LinkOptions { force: false, no_backup: false, dry_run: false, verbose: false };
|
||||
let opts = LinkOptions {
|
||||
force: false,
|
||||
no_backup: false,
|
||||
dry_run: false,
|
||||
verbose: false,
|
||||
};
|
||||
let state = link(d, &[], &opts).expect("link() failed");
|
||||
|
||||
// foo.txt linked
|
||||
|
||||
Reference in New Issue
Block a user