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. 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.