Dependency installation
Commit flog.lock, then use plain flog install for everyday development and
CI. When the lock matches every workspace manifest and represents the current
platform, flog installs its exact graph without registry metadata requests,
lockfile writes, or implicit version changes.
A new project first runs flog lock, reviews and commits the result, and then
runs flog install. A missing, stale, invalid, or unsupported lock fails with a
specific diagnostic instead of resolving or changing the graph. Reconcile a
deliberate manual manifest edit with flog lock; change dependencies through
flog add, flog remove, or flog upgrade.
The command reference summarizes each invocation and its common
flags.
Use --cwd <directory> to select a project explicitly:
flog install --cwd apps/website
Install accepts no positional arguments. Use --cwd for a project directory and
flog add for package specifiers. Removed positional forms fail rather than
being reinterpreted.
Adding dependencies#
add is the only operation that creates dependency declarations:
flog add rxjs @types/node@^24
flog add compat@npm:actual@next
An omitted version for a new declaration selects the registry's latest
version and records a caret range for the selected version. A bare package name
that is already declared and represented by the lock preserves both its range
and locked version without registry metadata, then materializes the locked
graph. If the existing lock does not match any project manifest, addition fails
with flog lock guidance rather than implicitly reconciling
unrelated edits. Explicit versions,
ranges, dist-tags, and npm: aliases are preserved. Multiple arguments are
resolved together and
written deterministically. Additions default to dependencies; use --dev
(-D) or --optional (-O) to move them to devDependencies or
optionalDependencies. Existing occurrences in other dependency fields are
removed. From inside a workspace—or when using --cwd—only that importer's
manifest is changed; package.json workspace discovery follows the same rules as
ordinary installation.
Flog resolves the complete proposed project before writing. It then publishes
the selected package.json and flog.lock as one rollback-capable update and
installs that exact lock through the normal locked installer. Resolution errors
leave all files untouched. Download, extraction, or lifecycle failures restore
the manifest and lock while the install transaction preserves the prior
node_modules; verified cache entries and external side effects from lifecycle
scripts are not rolled back. Concurrent edits after publication are never
overwritten during rollback. --no-install publishes the manifest and lock
without materializing packages or running dependency lifecycle scripts.
Package additions do not support --workspace; select their importer with
--cwd instead.
Manifest and lockfile transaction guarantees#
Dependency addition/removal and dependency upgrade use the same low-level file
operations. Proposed bytes are written and synced to temporary files beside
their targets with the target permissions. Replacement uses rename on Unix,
write-through MoveFileExW on Windows, and remove-then-rename only on other
platforms where replacement rename is unavailable. The last fallback can leave
a target temporarily absent, so rollback always includes the replacement that
reported failure as well as earlier successful replacements.
Both workflows revalidate every discovered workspace manifest before writing, reject symlink and other non-regular inputs or targets, stage all outputs before the first replacement, write the lockfile last, and restore in reverse order. If restoration of an existing file fails, its independently staged original is retained and the error reports that path with recovery guidance. Successful transactions and successful restorations remove all staging and backup files. These are rollback-capable multi-file updates, not crash-atomic transactions; an interruption can still leave individually valid files from different sides of the transaction.
The intentional differences are at the workflow boundary. Addition can create
flog.lock when none existed, so rollback removes that new file rather than
inventing original bytes; upgrade only replaces files captured by its validated
plan. Apply-time replacement failure is handled before installation begins.
After publication succeeds, a later installer failure invokes the prepared
operation's rollback, which first verifies that no user or process changed a
published target. Concurrent edits are refused rather than overwritten.
Removing dependencies#
flog remove <name...> removes every requested declared name from the selected
importer, updates the complete lock, and installs the result. If a name appears
in more than one dependency field, all of its declarations are removed. Every
requested name must exist; a missing or duplicate name fails the complete
operation before manifest, lock, or node_modules changes. Workspace
dependencies follow the same rule. Use --cwd to select an importer and
--no-install to commit only the atomic manifest and lock update.
Flog discovers the containing workspace root before installation. Running bare
flog install from the root, a workspace package, or a nested directory always
installs the same complete project from the same root lockfile. The current
directory never implicitly narrows installation scope.
Workspace membership comes only from the root package.json workspaces
field. Install, add, run, build, x, peer checks, pack, and publish use this same
canonical declaration. Projects that previously relied only on
pnpm-workspace.yaml must copy their workspace patterns into package.json;
Flog ignores package-manager-specific workspace files.
Workspace selection#
Use --workspace to materialize only one workspace and the workspace packages
it depends on:
flog install --workspace apps/website
flog install --workspace @app/website
flog install --workspace apps/website/package.json
flog install --workspace './apps/web*'
Exact selectors accept a workspace package name, directory, or file owned by a
workspace. Glob selectors match workspace paths. Relative paths resolve from
--cwd (or the process directory). Repeated selectors must all be exact or all
be globs, and selecting the same workspace through aliases or overlapping globs
is an error. Flog installs the selected dependency closures. The root importer
is also materialized so shared development tools remain available. Unselected
workspace node_modules directories are not modified, and the root links only
the selected closure.
Workspace selection affects materialization only. Flog validates the complete
project and never writes flog.lock. Plain install from inside a workspace
remains an unscoped project install; only --workspace narrows materialization.
Production runtime closures#
Use --prod with a deployment workspace to materialize only runtime
dependencies:
flog install --workspace apps/website --prod
Development dependencies and development-only workspace branches such as editor
and test adapters are reported and omitted from links and materialization.
Multiple --workspace selectors form one validated seed set. Selection uses
the active target in the complete lock, so a platform-skipped optional workspace
is not selected and a schema-1 target matrix is never rewritten or
collapsed. Like npm ci --omit=dev, this tree is not expected to contain the
compilers and generators needed for a source build. Run flog build
only after a normal development-capable install.
Layout#
Each importer receives its direct production, development, and optional
dependencies. Flog does not hoist dependencies. It materializes every exact
registry snapshot environment once in the root node_modules/.flog virtual
store and links each logical dependency occurrence to that canonical directory.
Packages with the same name and version but different peer contexts remain
physically isolated. Canonical identity includes both the package's lockfile ID
and the exact locked provider IDs selected by its surrounding environment. Each
canonical package receives locked links for its regular, optional, and
peer-provider environment, so Node resolution remains deterministic after
resolving the package symlink.
Logical package directories use relative symbolic links on Unix and directory junctions on Windows. Windows links are finalized only after the root virtual store is activated, so their targets exist and retain directory semantics; the junction implementation does not require Developer Mode or symbolic-link privileges.
The virtual-store directory names are fixed-length hashes of complete lockfile
snapshot IDs. They do not contain checkout paths or package names and remain
portable across platforms. Flog owns node_modules/.flog once it creates it and
records that ownership in .flog-managed.json; an existing unmanaged path at
that location is rejected rather than overwritten. Lifecycle scripts run once
per canonical snapshot, and their output is visible through every logical link.
When a required peer has no explicit provider in the applicable importer or
ancestor environment, flog selects a compatible provider automatically and
installs it in the nearest environment shared by the consumers. An explicit
provider always wins and must satisfy every applicable range. Optional peers are
never selected automatically.
Workspace packages are linked into importers where selected by the resolver.
The root also links non-colliding workspace package names. Package binaries are
made visible in the appropriate parent node_modules/.bin directory, including
Unix launchers and Windows .cmd shims. A workspace binary may be generated by
the package's later build: installation creates its launcher before the target
exists, and that launcher becomes usable without reinstalling once the build
writes the declared file. Published registry packages must contain every bin
target declared by their archive manifest.
Cache and activation#
Downloaded tarballs are verified against the lockfile integrity value before
use and before being written to the artifact cache. Cache contents never define
the installation layout. --offline rejects an install if any required verified
artifact is absent. Use --cache <directory> to select a cache explicitly.
For script-disabled installs on Linux, the default fast materializer hardlinks read-only package payloads from the verified cache when both locations share a filesystem. This matches pnpm's default performance policy: changing permissions and editing a linked file can also change the cache and any other installation that shares that inode. Ordinary installs fall back to clones or copies when hardlinks are unavailable, and lifecycle-capable packages always use independent files.
Use --strict when live package files must remain isolated from the cache and
every no-op install must detect in-place payload changes:
flog install --strict
flog install --ignore-scripts --strict
Strict mode uses reflinks or independent copies, validates every managed package entry, and repairs cache corruption from the integrity-checked archive. It is deliberately slower on filesystems without copy-on-write support. Switching between default and strict mode rematerializes the selected installation.
Flog discovers peer-free selected registry subgraphs breadth-first and overlaps each round of independent metadata requests with a fixed, bounded worker count. Packages with required peers remain behind the authoritative peer check so a skipped optional package cannot trigger requests or authentication for its descendants. Artifact requests use the same bounded strategy. Resolution decisions, lockfile output, optional-package failure handling, integrity verification, lifecycle execution, and filesystem activation remain deterministic. Metadata discovery is advisory: the ordinary resolver remains authoritative and retries anything the prefetch pass could not select. Artifact prefetch failures are likewise interpreted by the ordinary dependency traversal so an unavailable optional package can still be skipped while the same failure for a required package retains its exact importer or dependency context.
After artifacts are verified, Flog prepares independent context-free virtual store snapshots with bounded concurrency. Peer-context-sensitive snapshots, dependency links, package binaries, lifecycle jobs, and importer activation remain ordered by the deterministic graph traversal. Preparation failures are recorded in resolution order, so optional-package skipping and required-package diagnostics do not depend on worker completion order. Snapshots prepared for a package the graph traversal never reaches are removed before activation.
After extraction, flog verifies that the archive's package name and version
match the locked registry snapshot and validates executable and lifecycle
metadata it consumes. Dependency fields inside the archive do not redefine the
graph: registry metadata was already resolved and pinned in flog.lock. This
also means development-only archive specifiers such as file:. are retained as
package metadata but are not interpreted or installed.
Every importer is built in a sibling staging directory. Flog preserves unrelated
existing files, removes paths recorded as managed by the previous install, runs
authorized dependency lifecycle scripts against the staged layout, and only
then replaces node_modules. If activation of a multi-importer project fails,
already activated importers are rolled back.
Automatically selected peer providers use the same verified cache, integrity,
platform, and lifecycle-script rules as declared dependencies. In particular,
peer metadata cannot grant script permission; the root allowScripts policy or
--ignore-scripts still applies.
Lifecycle scripts#
Dependency preinstall, install, and postinstall scripts are denied by
default. A package runs them only when the root manifest contains an explicit
true entry:
{
"allowScripts": {
"esbuild": true,
"oracledb": true
}
}
--ignore-scripts disables all dependency lifecycle scripts. A failure in an
optional package, including an allowed lifecycle failure, skips that package
unless it is required as a peer provider. Required package failures abort the
install and leave the previous activated tree intact.
Portable target locks#
Most projects create their first lock with flog lock. When a
repository must commit one lock for multiple platforms, declare the complete
matrix explicitly. For example, a glibc development host and Alpine deployment
image can share one lock generated with:
flog lock \
--target linux/x64/glibc \
--target linux/x64/musl
flog lock resolves and writes the reconciled lock without downloading,
materializing packages, or running lifecycle scripts. See the lockfile target model
for matrix replacement, migration, and platform-alias details.
Explicit lock control#
flog install is always strict and read-only: there is no frozen, update,
lockfile-only, or no-lockfile install mode. flog lock is the sole command for
creating or reconciling the graph, and a matching lock is a byte-for-byte no-op.
Unsupported lock schemas are rejected without registry access or file changes.
After reviewing a changed lock, run flog install to materialize it.
Pass --json for a machine-readable install summary. Dependency mutation
results also report manifestChanged, lockfileChanged, and noInstall.