A utility for building dependency graphs between applications and calculating their hashes.
- Scans directories for
yeth.tomlfiles - Builds a dependency graph between applications
- Checks for circular dependencies
- Calculates the hash of each application including its dependencies' hashes
- Outputs the results
cargo build --releaseBinary will be in target/release/yeth
Try out yeth with the included demo project:
# Build yeth
cargo build --release
# Run demo
cd demo
./quick-test.shThe demo includes 5 applications with various dependency types. See demo/README.md for details.
Output hashes of all applications:
yethyeth --app my-appUseful for scripts:
yeth --app my-app --hash-onlyyeth --root /path/to/monorepoyeth --show-graphyeth --verboseSave each application's hash to yeth.version file next to yeth.toml:
yeth --write-versionsCreate a yeth.toml file in the root of each application:
[app]
dependencies = ["app1", "app2"]If the application has no dependencies:
[app]
dependencies = []You can specify two types of dependencies:
- Dependencies on other applications (names without slashes):
[app]
dependencies = ["app1", "backend", "shared"]- Dependencies on files and directories (relative paths):
[app]
dependencies = [
"../shared/config.json", # file
"./vendor", # directory
"../../../root-config.yaml" # path up the tree
]- Mixed dependencies:
[app]
dependencies = [
"app1", # application dependency
"../shared/utils", # directory dependency
"./config.json" # file dependency
]Type determination rule:
- If string contains
/or starts with.→ it's a path to file/directory - Otherwise → it's an application name
Important: Paths are resolved relative to the application directory (where yeth.toml is located).
You can specify files and directories to exclude from application hash calculation:
[app]
dependencies = ["app1"]
exclude = [
"node_modules", # ignore node_modules directory
"dist", # ignore dist directory
"target", # ignore target directory
".env", # ignore .env file
"tests", # ignore everything in tests/
"src/generated" # ignore src/generated/
]How exclusion works:
- Patterns are checked relative to application root
- You can specify directory name (
node_modules) — will be excluded wherever it appears - You can specify path (
src/generated) — will exclude specific path - Prefix matching: if path starts with pattern, it's excluded
- Important: Patterns with paths (
../shared/README.md) apply globally — will exclude files even inside dependencies
Examples:
# Basic configuration without exclusions
[app]
dependencies = []
# With local file exclusions
[app]
dependencies = ["backend"]
exclude = ["node_modules", "dist", "tmp"]
# Excluding files from dependencies
[app]
dependencies = ["../shared"]
exclude = ["../shared/README.md", "../shared/docs"]
# Combined exclusion
[app]
dependencies = ["../shared", "../common"]
exclude = [
"node_modules", # local exclusion
"../shared/README.md", # file exclusion from dependency
"../common/tests" # directory exclusion from dependency
]monorepo/
├── app1/
│ ├── yeth.toml # dependencies = []
│ └── src/
├── app2/
│ ├── yeth.toml # dependencies = ["app1"]
│ └── src/
└── app3/
├── yeth.toml # dependencies = ["app1", "app2"]
└── src/
monorepo/
├── shared/
│ ├── config.json
│ └── utils/
├── apps/
│ ├── frontend/
│ │ ├── yeth.toml # dependencies = ["backend", "../../shared/config.json"]
│ │ │ # exclude = ["node_modules", "dist"]
│ │ ├── node_modules/
│ │ ├── dist/
│ │ └── src/
│ └── backend/
│ ├── yeth.toml # dependencies = ["../../shared/utils"]
│ │ # exclude = ["target"]
│ ├── target/
│ └── src/
└── config.yaml
frontend/yeth.toml config:
[app]
dependencies = ["backend", "../../shared/config.json"]
exclude = ["node_modules", "dist", ".next"]backend/yeth.toml config:
[app]
dependencies = ["../../shared/utils"]
exclude = ["target", "*.log"]$ yeth
a1b2c3d4... app1
e5f6g7h8... app2
i9j0k1l2... app3
Execution time: 123.45ms
Applications processed: 3$ yeth --show-graph
Dependency graph:
app1
└─ (no dependencies)
app2
├─ app1 (app)
└─ ../shared/config.json (file)
app3
├─ app1 (app)
├─ app2 (app)
└─ ../shared/utils (dir)Get application hash to determine if rebuild is needed:
#!/bin/bash
APP_HASH=$(yeth --app my-app --hash-only --verbose)
echo "Current hash: $APP_HASH"
# Compare with saved hash and decide if rebuild is needed
if [ "$APP_HASH" != "$LAST_BUILD_HASH" ]; then
echo "Changes detected, starting build..."
# build commands here
fiProject structure:
example/
├── app1/
│ ├── yeth.toml # dependencies = [], exclude = ["node_modules", "dist"]
│ ├── main.js
│ ├── node_modules/ # excluded from hash
│ └── dist/ # excluded from hash
└── app2/
├── yeth.toml # dependencies = ["app1"], exclude = ["target"]
├── main.rs
└── target/ # excluded from hash
Important: Files in node_modules, dist and target don't affect hash, as they're specified in exclude.
Structure:
example/
├── catalog/
│ ├── yeth.toml # dependencies = ["../shared"]
│ │ # exclude = ["../shared/README.md"]
│ └── index.js
└── shared/
├── yeth.toml
├── utils.js
└── README.md # excluded from catalog hash, but NOT excluded from shared hash
catalog/yeth.toml config:
[app]
dependencies = ["../shared"]
exclude = ["../shared/README.md", "node_modules"]Result:
- Changing
shared/README.md→cataloghash does NOT change ✅ - Changing
shared/README.md→sharedhash changes (it doesn't have this exclusion) - Changing
shared/utils.js→cataloghash changes ✅
Execution:
$ yeth --root example
47aa9e986c6e4c0b7bd839d97eda81700fccc8575e1cfa8cf7ce70809c4bfb1e catalog
d98a899314cd6581de6446f1a427a9822013b3065a92a38f61d381571c86da7d shared
# Modify shared/README.md
$ echo "update" >> shared/README.md
$ yeth --root example
47aa9e986c6e4c0b7bd839d97eda81700fccc8575e1cfa8cf7ce70809c4bfb1e catalog ← unchanged
00214c62f5d76e98dac137675059581576eeabfc8d084dc8b6206f84dd84f692 shared ← changed
# Modify shared/utils.js
$ echo "update" >> shared/utils.js
$ yeth --root example
54010998be564b7a736a48e418084ec3247c23e8d2d5d1ba8c4065d75ea988fa catalog ← changed
25116e4ece02de6be08a5093f3b867092e0b1df4713f087470bb932afe5785bb shared ← changedOptions:
-r, --root <ROOT> Root directory to search for applications [default: .]
-a, --app <APP> Name of specific application to output hash for
-H, --hash-only Show only hash without application name
-v, --verbose Show execution time statistics
-g, --show-graph Show dependency graph
-w, --write-versions Save each application's hash to yeth.version next to yeth.toml
-h, --help Print help
The project is split into modules:
cli.rs- Command line argument parsing (clap)config.rs- Reading and parsing configuration filesgraph.rs- Building dependency graph and topological sortinghash.rs- Calculating directory and application hashesmain.rs- Entry point and work coordination
- For each application, calculate its own hash (SHA256 of all files in directory)
- For path dependencies, calculate file or directory hash
- Applications are processed in topological order (by application dependencies)
- Final hash = SHA256(own_hash + dependency_hash_1 + ... + dependency_hash_N)
Important points:
- Changes in any dependency (application, file, directory) will affect the hash of all applications depending on it
- File/directory dependencies don't participate in topological sorting (they can't be circular)
- Path dependencies are checked for existence at program start
- System files (
.git,.DS_Store,yeth.version) are automatically ignored - Additional files can be excluded via the
excludefield in config