Architecture
craft is a Cargo workspace. The binary in apps/craft is thin: it parses the command line, builds a Context, and hands an action to the manager.
text
apps/craft main: CLI → Context → CraftManager, error printing
crates/
├── cli clap definitions; turns arguments into an Action
├── manager dispatches each Action to its actor
├── actor one actor per command: install, add, remove, update, outdated,
│ list/why, run/exec, dlx, trust, init, ci, cache
├── pipeline the install pipeline: resolve → peers → link → prune → scripts → lockfile
├── resolver registry and git resolution, the dependency graph, lockfile preferences
├── network HTTP client: retries, concurrency limits, metadata cache, integrity
├── tarball safe, atomic tarball extraction
├── package package.json types, dependency specifiers, platform constraints
├── lockfile craft.lock types
├── process spawning scripts and bins, their environment and argument quoting
├── fsutil links, junctions, bin shims, store imports, atomic writes
├── progress the status line and summaries
├── contract shared types: Context, errors, .npmrc configuration
├── debug --debug logging
└── testkit the mock registry and package fixtures used by the testsAn install, step by step
InstallActorreadspackage.jsonandcraft.lock, and stops early withAlready up to datewhen the installed state matches.InstallPiperesolves every dependency into aResolutionGraphof nodes (name@version) and edges. Registry requests share one packument fetch per name; locked versions are preferred; local and git sources are materialized while resolving, since their metadata comes from their ownpackage.json.- The peer pass rewrites the graph so each package instance knows its resolved peers (
ui@1.0.0_react@19.2.0). fetchdownloads and extracts missing registry packages into the global store.LinkerPipeimports store entries intonode_modules/.craft, links dependencies, hoists, links bins, and prunes.LifecyclePiperuns install scripts of trusted packages.- The lockfile is serialized from the graph and written if it changed.
Conventions
- Errors are
thiserrortypes incontract::errors, with context added at IO boundaries (fsutilnames the path for every failure), and printed bymainaserror:,caused by:andhint:lines. - Filesystem changes go through
fsutil, which handles dangling links, concurrent writers and the differences between Unix and Windows. - Platform-specific code lives behind
cfg(unix)/cfg(windows)in small functions, with the shared logic compiled and tested everywhere.
