Documentation menu

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.