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.
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; pnpm-workspace fallback 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.
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 from the root package.json workspaces field. To
support incremental migration from pnpm, flog also reads the top-level
packages sequence in pnpm-workspace.yaml when that field is absent. If both
are present, package.json is authoritative. Install, run, and x use this same
discovery rule, and invalid fallback configuration fails instead of silently
installing only the root package.
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 source closures#
Use --prod with a deployment workspace to follow only workspace edges from
dependencies, eligible optionalDependencies, and required peer providers:
flog install --workspace apps/website --prod
flog build --workspace apps/website --prod
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
version-zero target matrix is never rewritten or collapsed.
--prod is a source-build selection policy, not npm-style --omit=dev.
Every selected importer still receives its registry development dependencies,
including compilers, type declarations, and code generators; the root importer
is also materialized so shared registry build tooling remains available.
Development-only workspace edges remain excluded even when they name tools;
declare a production edge when a selected source build truly requires that
workspace. Final runtime image pruning is a separate deployment step. Use the
same --workspace and --prod arguments with flog build so
installation and build cannot disagree.
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.
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.