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.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
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]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -97,12 +107,39 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
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]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.11.0"
|
version = "2.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
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]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "1.12.1"
|
version = "1.12.1"
|
||||||
@@ -135,6 +172,30 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
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]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.44"
|
version = "0.4.44"
|
||||||
@@ -146,6 +207,17 @@ dependencies = [
|
|||||||
"windows-link",
|
"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]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.6.0"
|
version = "4.6.0"
|
||||||
@@ -192,12 +264,27 @@ version = "1.0.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-oid"
|
||||||
|
version = "0.9.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
@@ -223,12 +310,73 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
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]]
|
[[package]]
|
||||||
name = "difflib"
|
name = "difflib"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
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]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
@@ -256,6 +404,30 @@ version = "0.3.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9"
|
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]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
@@ -290,6 +462,12 @@ version = "2.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fiat-crypto"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -302,6 +480,16 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
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]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -371,6 +559,24 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
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]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.65"
|
version = "0.1.65"
|
||||||
@@ -429,6 +635,15 @@ dependencies = [
|
|||||||
"serde_core",
|
"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]]
|
[[package]]
|
||||||
name = "integration"
|
name = "integration"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -465,6 +680,15 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"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]]
|
[[package]]
|
||||||
name = "leb128fmt"
|
name = "leb128fmt"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -477,6 +701,12 @@ version = "0.2.183"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.15"
|
version = "0.1.15"
|
||||||
@@ -548,12 +778,24 @@ name = "notsecrets"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"base64",
|
||||||
|
"bech32",
|
||||||
|
"chacha20poly1305",
|
||||||
|
"curve25519-dalek",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
"ed25519-dalek",
|
||||||
|
"hkdf",
|
||||||
|
"hmac",
|
||||||
"notcore",
|
"notcore",
|
||||||
|
"rand",
|
||||||
"rpassword",
|
"rpassword",
|
||||||
|
"rsa",
|
||||||
|
"scrypt",
|
||||||
|
"sha2",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"which",
|
"x25519-dalek",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -571,6 +813,42 @@ dependencies = [
|
|||||||
"which",
|
"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]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -578,6 +856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
|
"libm",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -592,12 +871,89 @@ version = "1.70.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "option-ext"
|
name = "option-ext"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
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]]
|
[[package]]
|
||||||
name = "predicates"
|
name = "predicates"
|
||||||
version = "3.1.4"
|
version = "3.1.4"
|
||||||
@@ -659,6 +1015,36 @@ version = "6.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
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]]
|
[[package]]
|
||||||
name = "redox_users"
|
name = "redox_users"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -698,6 +1084,27 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"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]]
|
[[package]]
|
||||||
name = "rtoolbox"
|
name = "rtoolbox"
|
||||||
version = "0.0.3"
|
version = "0.0.3"
|
||||||
@@ -708,6 +1115,15 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"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]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -727,6 +1143,15 @@ version = "1.0.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "salsa20"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
|
||||||
|
dependencies = [
|
||||||
|
"cipher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@@ -736,6 +1161,18 @@ dependencies = [
|
|||||||
"winapi-util",
|
"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]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.27"
|
version = "1.0.27"
|
||||||
@@ -794,18 +1231,67 @@ dependencies = [
|
|||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
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]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.117"
|
version = "2.0.117"
|
||||||
@@ -897,6 +1383,12 @@ version = "0.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
@@ -909,12 +1401,28 @@ version = "0.2.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
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]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -1302,6 +1810,58 @@ dependencies = [
|
|||||||
"wasmparser",
|
"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]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
|
|||||||
68
HANDOFF.md
68
HANDOFF.md
@@ -1,9 +1,11 @@
|
|||||||
# HANDOFF.md
|
# 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
|
## What Was Done
|
||||||
|
|
||||||
|
### Previous session (2026-03-31)
|
||||||
|
|
||||||
Restructured from a flat `src/` layout into a Cargo workspace of 5 crates:
|
Restructured from a flat `src/` layout into a Cargo workspace of 5 crates:
|
||||||
- `notcore` — shared library (types, config, paths, errors)
|
- `notcore` — shared library (types, config, paths, errors)
|
||||||
- `notfiles` — dotfiles linker, migrated from `src/`
|
- `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
|
- `nothooks` — Nushell hook runner with phase/state tracking
|
||||||
- `notstrap` — new-machine bootstrap orchestrator
|
- `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
|
### 1. Push to remote
|
||||||
```bash
|
Several commits ahead of `gitea/main`. Push when ready.
|
||||||
git push
|
|
||||||
```
|
|
||||||
8 commits ahead of `gitea/main`. Nothing blocking this.
|
|
||||||
|
|
||||||
### 2. notstrap has an empty lib.rs
|
### 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
|
### 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
|
### 4. Integration test coverage for nothooks assumes nu in PATH
|
||||||
`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.
|
`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
|
### 5. notstrap sops_file path footgun
|
||||||
`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.
|
If `sops_file` in `notstrap.toml` is an absolute path, the `join` will silently override the base. Low priority.
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|||||||
@@ -77,10 +77,12 @@ impl Config {
|
|||||||
if !config_path.exists() {
|
if !config_path.exists() {
|
||||||
return Ok(Config::default());
|
return Ok(Config::default());
|
||||||
}
|
}
|
||||||
let content = std::fs::read_to_string(&config_path)
|
let content = std::fs::read_to_string(&config_path).map_err(|e| {
|
||||||
.map_err(|e| NotfilesError::Config(format!("reading {}: {e}", config_path.display())))?;
|
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 config: Config = toml::from_str(&content).map_err(|e| {
|
||||||
|
NotfilesError::Config(format!("parsing {}: {e}", config_path.display()))
|
||||||
|
})?;
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,10 @@ pub struct Report {
|
|||||||
|
|
||||||
impl Report {
|
impl Report {
|
||||||
pub fn add(&mut self, name: impl Into<String>, status: StepStatus) {
|
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) {
|
pub fn print(&self) {
|
||||||
@@ -67,7 +70,9 @@ impl Report {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_failures(&self) -> bool {
|
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.
|
// Match the pattern as a filename component and also as a path suffix.
|
||||||
let glob = Glob::new(pattern)
|
let glob = Glob::new(pattern)
|
||||||
.or_else(|_| Glob::new(&format!("**/{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);
|
builder.add(glob);
|
||||||
// Also add a recursive variant so "foo" matches "a/foo" etc.
|
// Also add a recursive variant so "foo" matches "a/foo" etc.
|
||||||
#[allow(clippy::collapsible_if)]
|
#[allow(clippy::collapsible_if)]
|
||||||
|
|||||||
@@ -10,11 +10,7 @@ use std::path::Path;
|
|||||||
pub use linker::{LinkOptions, State};
|
pub use linker::{LinkOptions, State};
|
||||||
pub use package::resolve_packages;
|
pub use package::resolve_packages;
|
||||||
|
|
||||||
pub fn link(
|
pub fn link(dotfiles_dir: &Path, packages: &[String], opts: &LinkOptions) -> Result<State> {
|
||||||
dotfiles_dir: &Path,
|
|
||||||
packages: &[String],
|
|
||||||
opts: &LinkOptions,
|
|
||||||
) -> Result<State> {
|
|
||||||
let config = notcore::Config::load(dotfiles_dir)?;
|
let config = notcore::Config::load(dotfiles_dir)?;
|
||||||
let mut state = State::load(dotfiles_dir)?;
|
let mut state = State::load(dotfiles_dir)?;
|
||||||
let pkgs = resolve_packages(dotfiles_dir, packages)?;
|
let pkgs = resolve_packages(dotfiles_dir, packages)?;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use std::path::{Path, PathBuf};
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use notcore::{Config, Method, NotfilesError, expand_tilde};
|
|
||||||
use crate::package::collect_files;
|
use crate::package::collect_files;
|
||||||
|
use notcore::{Config, Method, NotfilesError, expand_tilde};
|
||||||
|
|
||||||
const STATE_FILE: &str = ".notfiles-state.toml";
|
const STATE_FILE: &str = ".notfiles-state.toml";
|
||||||
|
|
||||||
@@ -46,7 +46,10 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn entries_for_package(&self, package: &str) -> Vec<&StateEntry> {
|
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) {
|
pub fn remove_package(&mut self, package: &str) {
|
||||||
@@ -55,7 +58,8 @@ impl State {
|
|||||||
|
|
||||||
pub fn add_entry(&mut self, entry: StateEntry) {
|
pub fn add_entry(&mut self, entry: StateEntry) {
|
||||||
// Remove existing entry for same source+target, then add new
|
// 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);
|
self.entries.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,10 +117,18 @@ pub fn link_package(
|
|||||||
if !opts.no_backup {
|
if !opts.no_backup {
|
||||||
let backup = backup_path(&target);
|
let backup = backup_path(&target);
|
||||||
if opts.dry_run {
|
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 {
|
} else {
|
||||||
if opts.verbose {
|
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)?;
|
fs::rename(&target, &backup)?;
|
||||||
}
|
}
|
||||||
@@ -147,7 +159,10 @@ pub fn link_package(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if opts.dry_run {
|
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 {
|
} else {
|
||||||
match method {
|
match method {
|
||||||
Method::Symlink => {
|
Method::Symlink => {
|
||||||
@@ -161,7 +176,10 @@ pub fn link_package(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if opts.verbose {
|
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 {
|
state.add_entry(StateEntry {
|
||||||
@@ -183,7 +201,11 @@ pub fn unlink_package(
|
|||||||
package: &str,
|
package: &str,
|
||||||
opts: &LinkOptions,
|
opts: &LinkOptions,
|
||||||
) -> Result<(), NotfilesError> {
|
) -> 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 entries.is_empty() {
|
||||||
if opts.verbose {
|
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("/") {
|
if Some(parent.to_path_buf()) == dirs::home_dir() || parent == Path::new("/") {
|
||||||
break;
|
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);
|
let _ = fs::remove_dir(parent);
|
||||||
dir = parent.parent();
|
dir = parent.parent();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ use anyhow::{Context, Result};
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
|
use notcore::Config;
|
||||||
use notfiles::cli::{Cli, Command};
|
use notfiles::cli::{Cli, Command};
|
||||||
use notfiles::linker::{LinkOptions, State};
|
use notfiles::linker::{LinkOptions, State};
|
||||||
use notfiles::package::resolve_packages;
|
use notfiles::package::resolve_packages;
|
||||||
use notfiles::{linker, status};
|
use notfiles::{linker, status};
|
||||||
use notcore::Config;
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
@@ -46,7 +46,10 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
if !cli.dry_run {
|
if !cli.dry_run {
|
||||||
state.save(&dotfiles_dir)?;
|
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!(
|
println!(
|
||||||
"\x1b[32mLinked {count} file{} across {} package{}.\x1b[0m",
|
"\x1b[32mLinked {count} file{} across {} package{}.\x1b[0m",
|
||||||
if count == 1 { "" } else { "s" },
|
if count == 1 { "" } else { "s" },
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use notcore::{Config, NotfilesError};
|
|
||||||
use crate::ignore::IgnoreMatcher;
|
use crate::ignore::IgnoreMatcher;
|
||||||
|
use notcore::{Config, NotfilesError};
|
||||||
|
|
||||||
/// Discover available packages (subdirectories of the dotfiles dir).
|
/// Discover available packages (subdirectories of the dotfiles dir).
|
||||||
pub fn discover_packages(dotfiles_dir: &Path) -> Result<Vec<String>, NotfilesError> {
|
pub fn discover_packages(dotfiles_dir: &Path) -> Result<Vec<String>, NotfilesError> {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use notcore::{Config, Method, expand_tilde};
|
|
||||||
use crate::linker::State;
|
use crate::linker::State;
|
||||||
use crate::package::collect_files;
|
use crate::package::collect_files;
|
||||||
|
use notcore::{Config, Method, expand_tilde};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum FileStatus {
|
pub enum FileStatus {
|
||||||
@@ -69,8 +69,7 @@ pub fn package_status(
|
|||||||
}
|
}
|
||||||
Method::Copy => {
|
Method::Copy => {
|
||||||
let has_state = state.entries.iter().any(|e| {
|
let has_state = state.entries.iter().any(|e| {
|
||||||
e.package == package
|
e.package == package && e.target == target.to_string_lossy().as_ref()
|
||||||
&& e.target == target.to_string_lossy().as_ref()
|
|
||||||
});
|
});
|
||||||
if has_state && target.exists() {
|
if has_state && target.exists() {
|
||||||
FileStatus::Copied
|
FileStatus::Copied
|
||||||
@@ -122,6 +121,11 @@ pub fn print_status(package: &str, entries: &[StatusEntry]) {
|
|||||||
}
|
}
|
||||||
println!(" \x1b[1m{package}\x1b[0m:");
|
println!(" \x1b[1m{package}\x1b[0m:");
|
||||||
for entry in entries {
|
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");
|
let gitconfig = target.join(".gitconfig");
|
||||||
assert!(gitconfig.exists());
|
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
|
// State file should exist
|
||||||
assert!(dotfiles.join(".notfiles-state.toml").exists());
|
assert!(dotfiles.join(".notfiles-state.toml").exists());
|
||||||
@@ -199,7 +205,14 @@ fn test_force_with_backup() {
|
|||||||
assert!(ok);
|
assert!(ok);
|
||||||
|
|
||||||
// The link should now exist
|
// 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
|
// A backup should exist
|
||||||
let backups: Vec<_> = fs::read_dir(&target)
|
let backups: Vec<_> = fs::read_dir(&target)
|
||||||
@@ -232,11 +245,7 @@ fn test_force_no_backup() {
|
|||||||
let backups: Vec<_> = fs::read_dir(&target)
|
let backups: Vec<_> = fs::read_dir(&target)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
.filter(|e| {
|
.filter(|e| e.file_name().to_string_lossy().contains("notfiles-backup"))
|
||||||
e.file_name()
|
|
||||||
.to_string_lossy()
|
|
||||||
.contains("notfiles-backup")
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(backups.len(), 0);
|
assert_eq!(backups.len(), 0);
|
||||||
}
|
}
|
||||||
@@ -272,8 +281,17 @@ method = "copy"
|
|||||||
let ssh_config = target.join(".ssh/config");
|
let ssh_config = target.join(".ssh/config");
|
||||||
assert!(ssh_config.exists());
|
assert!(ssh_config.exists());
|
||||||
// Should NOT be a symlink
|
// Should NOT be a symlink
|
||||||
assert!(!ssh_config.symlink_metadata().unwrap().file_type().is_symlink());
|
assert!(
|
||||||
assert_eq!(fs::read_to_string(&ssh_config).unwrap(), "Host *\n AddKeysToAgent yes");
|
!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"
|
// Status should show "copied"
|
||||||
let (stdout, _, _) = run(&dotfiles, &["status", "ssh"]);
|
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 runner;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
|
||||||
pub use runner::HookRunner;
|
|
||||||
use notcore::{HookPhase, HookSpec, Report, StepStatus};
|
use notcore::{HookPhase, HookSpec, Report, StepStatus};
|
||||||
|
pub use runner::HookRunner;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum HookResult {
|
pub enum HookResult {
|
||||||
@@ -12,11 +12,7 @@ pub enum HookResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Run all hooks matching `phase` and collect into a `Report`.
|
/// Run all hooks matching `phase` and collect into a `Report`.
|
||||||
pub fn run_phase(
|
pub fn run_phase(hooks: &[HookSpec], phase: &HookPhase, runner: &HookRunner) -> Report {
|
||||||
hooks: &[HookSpec],
|
|
||||||
phase: &HookPhase,
|
|
||||||
runner: &HookRunner,
|
|
||||||
) -> Report {
|
|
||||||
let mut report = Report::default();
|
let mut report = Report::default();
|
||||||
for hook in hooks.iter().filter(|h| &h.phase == phase) {
|
for hook in hooks.iter().filter(|h| &h.phase == phase) {
|
||||||
let result = runner.run_hook(hook);
|
let result = runner.run_hook(hook);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use nothooks::{run_phase, HookRunner};
|
|
||||||
use notcore::{HookPhase, HookSpec};
|
use notcore::{HookPhase, HookSpec};
|
||||||
|
use nothooks::{HookRunner, run_phase};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
|
||||||
use notcore::{HookPhase, HookSpec};
|
|
||||||
use crate::HookResult;
|
use crate::HookResult;
|
||||||
use crate::state::HookState;
|
use crate::state::HookState;
|
||||||
|
use notcore::{HookPhase, HookSpec};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
pub struct HookRunner {
|
pub struct HookRunner {
|
||||||
state_dir: PathBuf,
|
state_dir: PathBuf,
|
||||||
@@ -11,11 +11,17 @@ pub struct HookRunner {
|
|||||||
|
|
||||||
impl HookRunner {
|
impl HookRunner {
|
||||||
pub fn new(state_dir: PathBuf) -> Self {
|
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 {
|
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 {
|
pub fn run_hook(&self, spec: &HookSpec) -> HookResult {
|
||||||
@@ -25,9 +31,8 @@ impl HookRunner {
|
|||||||
return HookResult::Skipped;
|
return HookResult::Skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = Command::new("nu")
|
// TODO: support other shells
|
||||||
.arg(&spec.script)
|
let result = Command::new("nu").arg(&spec.script).status();
|
||||||
.status();
|
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(status) if status.success() => {
|
Ok(status) if status.success() => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
use std::path::Path;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
const STATE_FILE: &str = ".nothooks-state.toml";
|
const STATE_FILE: &str = ".nothooks-state.toml";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
use notcore::{HookPhase, HookSpec};
|
||||||
|
use nothooks::{HookResult, HookRunner};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use nothooks::{HookResult, HookRunner};
|
|
||||||
use notcore::{HookPhase, HookSpec};
|
|
||||||
|
|
||||||
fn make_hook_script(dir: &TempDir, name: &str, content: &str) -> HookSpec {
|
fn make_hook_script(dir: &TempDir, name: &str, content: &str) -> HookSpec {
|
||||||
let path = dir.path().join(format!("{name}.nu"));
|
let path = dir.path().join(format!("{name}.nu"));
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use notcore::{HookPhase, Report, StepStatus};
|
use notcore::{HookPhase, Report, StepStatus};
|
||||||
use notfiles::{link, LinkOptions};
|
use notfiles::{LinkOptions, link};
|
||||||
use nothooks::{run_phase, HookRunner};
|
use nothooks::{HookRunner, run_phase};
|
||||||
use notsecrets::{install_age_key, resolve_age_key, BitwardenSource, FileSource, PromptSource};
|
use notsecrets::{BitwardenSource, FileSource, PromptSource, install_age_key, resolve_age_key};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
@@ -28,8 +28,12 @@ pub struct BootstrapSection {
|
|||||||
pub sops_file: String,
|
pub sops_file: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn default_bw_item() -> String { "age-key-dotfiles".to_string() }
|
pub(crate) fn default_bw_item() -> String {
|
||||||
pub(crate) fn default_sops_file() -> String { "secrets/bootstrap.sops.env".to_string() }
|
"age-key-dotfiles".to_string()
|
||||||
|
}
|
||||||
|
pub(crate) fn default_sops_file() -> String {
|
||||||
|
"secrets/bootstrap.sops.env".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
pub struct BootstrapOptions {
|
pub struct BootstrapOptions {
|
||||||
pub config: PathBuf,
|
pub config: PathBuf,
|
||||||
@@ -48,7 +52,9 @@ pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
|||||||
// 1. Prerequisites
|
// 1. Prerequisites
|
||||||
if let Some(check) = opts.check_prereqs {
|
if let Some(check) = opts.check_prereqs {
|
||||||
match check() {
|
match check() {
|
||||||
Ok(_) => { report.add("prerequisites", StepStatus::Ok); }
|
Ok(_) => {
|
||||||
|
report.add("prerequisites", StepStatus::Ok);
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
report.add("prerequisites", StepStatus::Failed(e.to_string()));
|
report.add("prerequisites", StepStatus::Failed(e.to_string()));
|
||||||
return Ok(report);
|
return Ok(report);
|
||||||
@@ -63,14 +69,19 @@ pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
|||||||
|
|
||||||
let dotfiles_dir = match opts.dotfiles {
|
let dotfiles_dir = match opts.dotfiles {
|
||||||
Some(d) => d,
|
Some(d) => d,
|
||||||
None => notcore::expand_tilde(&cfg.bootstrap.dotfiles_dir)
|
None => notcore::expand_tilde(&cfg.bootstrap.dotfiles_dir).with_context(|| {
|
||||||
.with_context(|| format!("cannot expand dotfiles_dir: {}", cfg.bootstrap.dotfiles_dir))?,
|
format!("cannot expand dotfiles_dir: {}", cfg.bootstrap.dotfiles_dir)
|
||||||
|
})?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. Clone dotfiles if missing
|
// 3. Clone dotfiles if missing
|
||||||
match repo::clone_if_missing(&cfg.bootstrap.dotfiles_repo, &dotfiles_dir) {
|
match repo::clone_if_missing(&cfg.bootstrap.dotfiles_repo, &dotfiles_dir) {
|
||||||
Ok(true) => { report.add("clone dotfiles", StepStatus::Ok); }
|
Ok(true) => {
|
||||||
Ok(false) => { report.add("clone dotfiles", StepStatus::Skipped); }
|
report.add("clone dotfiles", StepStatus::Ok);
|
||||||
|
}
|
||||||
|
Ok(false) => {
|
||||||
|
report.add("clone dotfiles", StepStatus::Skipped);
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
report.add("clone dotfiles", StepStatus::Failed(e.to_string()));
|
report.add("clone dotfiles", StepStatus::Failed(e.to_string()));
|
||||||
return Ok(report);
|
return Ok(report);
|
||||||
@@ -110,7 +121,9 @@ pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
|||||||
// threads exist. Subsequent Command::spawn calls (git, hooks) will
|
// threads exist. Subsequent Command::spawn calls (git, hooks) will
|
||||||
// inherit these vars — keys are validated by parse_env_line before
|
// inherit these vars — keys are validated by parse_env_line before
|
||||||
// reaching this point (null bytes and dangerous keys are rejected).
|
// 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) => {}
|
Ok(None) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -129,7 +142,12 @@ pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 6. Link dotfiles
|
// 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) {
|
match link(&dotfiles_dir, &[], &link_opts) {
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
let count = state.entries.len();
|
let count = state.entries.len();
|
||||||
@@ -147,9 +165,14 @@ pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
|||||||
HookRunner::new(dotfiles_dir.clone())
|
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 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(_)))
|
.filter(|s| matches!(s.status, notcore::StepStatus::Failed(_)))
|
||||||
.count();
|
.count();
|
||||||
let summary = if failed > 0 {
|
let summary = if failed > 0 {
|
||||||
@@ -223,7 +246,12 @@ mod tests {
|
|||||||
fn parse_env_line_blocks_ld_preload() {
|
fn parse_env_line_blocks_ld_preload() {
|
||||||
let result = parse_env_line("LD_PRELOAD=/evil/lib.so");
|
let result = parse_env_line("LD_PRELOAD=/evil/lib.so");
|
||||||
assert!(result.is_err(), "expected error for LD_PRELOAD");
|
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.
|
/// Issue #5: PATH must be blocked.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use notstrap::{prereqs, run, BootstrapOptions};
|
use notstrap::{BootstrapOptions, prereqs, run};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "notstrap", about = "Bootstrap a new machine from dotfiles")]
|
#[command(name = "notstrap", about = "Bootstrap a new machine from dotfiles")]
|
||||||
@@ -33,7 +33,12 @@ enum Cmd {
|
|||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let cli = Cli::parse();
|
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 {
|
let opts = BootstrapOptions {
|
||||||
config,
|
config,
|
||||||
force,
|
force,
|
||||||
|
|||||||
@@ -7,9 +7,18 @@ pub struct Prereq {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PREREQS: &[Prereq] = &[
|
const PREREQS: &[Prereq] = &[
|
||||||
Prereq { cmd: "nu", install_hint: "brew install nushell OR nix-env -iA nixpkgs.nushell" },
|
Prereq {
|
||||||
Prereq { cmd: "sops", install_hint: "brew install sops OR nix-env -iA nixpkgs.sops" },
|
cmd: "nu",
|
||||||
Prereq { cmd: "age", install_hint: "brew install age OR nix-env -iA nixpkgs.age" },
|
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<()> {
|
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 notcore::{HookPhase, HookSpec, StepStatus};
|
||||||
use nothooks::{run_phase, HookRunner};
|
use nothooks::{HookRunner, run_phase};
|
||||||
use notstrap::{run, BootstrapOptions};
|
use notstrap::{BootstrapOptions, run};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ use std::path::Path;
|
|||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
use notcore::{HookPhase, HookSpec};
|
use notcore::{HookPhase, HookSpec};
|
||||||
|
use notfiles::{LinkOptions, link};
|
||||||
use nothooks::{HookResult, HookRunner};
|
use nothooks::{HookResult, HookRunner};
|
||||||
use notsecrets::{resolve_age_key, AgeKeySource, FileSource};
|
use notsecrets::{AgeKeySource, FileSource, resolve_age_key};
|
||||||
use notfiles::{link, LinkOptions};
|
|
||||||
|
|
||||||
/// Test that notsecrets and nothooks can each be used independently
|
/// Test that notsecrets and nothooks can each be used independently
|
||||||
/// in the same integration boundary — resolving an age key from a file
|
/// 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(".notfiles-state.toml"), "# state\n").unwrap();
|
||||||
fs::write(pkg.join(".nothooks-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");
|
let state = link(d, &[], &opts).expect("link() failed");
|
||||||
|
|
||||||
// foo.txt linked
|
// foo.txt linked
|
||||||
|
|||||||
Reference in New Issue
Block a user