Documentation menu

Script execution

flog run <script> executes the matching package.json script in the current package. Flog locates the nearest workspace root when invoked from a package or one of its subdirectories.

flog run build
flog run build -- --watch

Matching pre<script> and post<script> hooks run before and after the main script. Arguments after the script name are passed only to the main script and are shell-quoted individually.

Package binaries#

flog x <binary> [arguments] executes a dependency-provided binary directly, without a shell. It searches node_modules/.bin from the selected package up through the workspace root, nearest first:

flog x primate build --target=node
flog x --cwd apps/website primate build --target=node

Only project-local binaries are eligible. Flog does not download a missing package and does not fall back to an executable on the global PATH; run flog install first. Arguments and child exit status are preserved. Use -- before a binary name beginning with a hyphen.

When no installed shim exists, a built local package is also eligible when the current package directly declares it with a workspace: dependency. Flog reads that package's bin field and executes the target without creating links or installing anything. Ambiguous providers, missing build output, and targets outside the package are errors.

Workspace selection#

Workspace execution is deliberately sequential for now. Selected packages are ordered deterministically with dependencies before dependents, output is streamed directly, and the first failure stops further scheduling.

# Every workspace package
flog run --recursive --if-present test

# Exact package name
flog run --workspace @primate/core build

# A source file selects its owning declared workspace
flog run --workspace ./packages/core/src/index.ts test

# Workspace path glob
flog run --workspace './packages/**' --if-present build

# Brace-expanded path alternatives
flog run --workspace './{packages,apps}/**' --if-present build

# A low-level script selection including local dependencies
flog run --workspace @app/frontend --include-dependencies --if-present build

# A package and all transitive dependents
flog run --workspace @primate/core --include-dependents test

--dependencies-only and --dependents-only exclude the packages matched by the original workspace selectors. Exact selectors accept a package name, workspace directory, or file owned by a workspace. Path selectors resolve from the effective --cwd (or process directory). All repeated selectors must be exact or all must be globs; mixing the two is an error. Every selector must match, and selecting one workspace twice through repeated names, aliases, files, or overlapping globs is an error rather than a silent deduplication. Missing exact paths, unmatched or malformed globs, files outside declared workspaces, and paths whose symlinks escape the workspace root are errors before scripts run. --recursive remains the separate request for every workspace.

For normal source builds, prefer the dedicated graph-aware flog build command. It includes the selected app, permits and reports build-less intermediates without --if-present, supports the shared production-workspace policy, and avoids package-local nested orchestration.

During command migration, script selection and flog x working-directory resolution also accept the top-level packages block from an existing pnpm-workspace.yaml when the root package.json has no workspaces field. This fallback does not change flog's installation model: add the same patterns to package.json before expecting workspace dependency installation and links.

In default fail-fast mode, missing requested scripts are errors and are validated across the entire selection before any package runs. --if-present is the explicit opt-in to skip selected packages that do not define the script.

Retaining all workspace results#

Default execution remains fail-fast. Validation and release evidence can opt into complete sequential traversal:

flog run --recursive --if-present --no-bail test
flog run --recursive --if-present --no-bail --json test > test-results.json

With --no-bail, a failed or signaled package does not prevent later independent packages from running. A selected dependent is blocked when one of its selected workspace dependencies failed, was signaled, was missing its required script, was itself blocked, or was interrupted. A dependency-healthy package skipped by --if-present does not block its dependents. If that package has an unsuccessful selected dependency, it is blocked rather than skipped, so the failure continues through the selected dependency chain. Scripts remain sequential and in the same deterministic dependency-first order as default execution.

Human output streams each script normally and ends with one ordered result per package plus stable totals. The aggregate command exits 1 after a completed traversal containing a failure, signal, required missing script, or blocked package. Interruption stops the active process group, marks the active and all unstarted selections interrupted, emits the report, and retains the normal interrupted exit status.

--json requires --no-bail. It reserves stdout for one report and captures each executed package's stdout and stderr in its result. The report has schemaVersion: 2, the requested script, ordered results, summary totals, and optional coordinatedWork evidence. Every result includes package name, workspace path, script, and one of success, reused, failure, signaled, missing, skipped, blocked, or interrupted. Applicable results also include the failed lifecycle event, an exitCode or terminating signal, a stable reason, blockedBy package names, an optional diagnostic error, and captured stdout / stderr.

Parallel workspace execution#

All-results runs can schedule independent workspaces concurrently:

flog run -r --if-present --no-bail --jobs auto test
flog run --workspace='./apps/**' --if-present --no-bail --jobs 4 build
flog run -r --if-present --no-bail --jobs 1 test   # explicit serial debugging

--jobs accepts a positive maximum or auto, which uses the available Go process concurrency. It requires --no-bail; omitting --jobs keeps the existing sequential behavior. Ready workspaces are started in deterministic selection order. A selected dependent is not ready until every selected dependency succeeded or was safely skipped. Failures promptly block forbidden dependent work while other independent work continues. Result and JSON order always follows deterministic dependency-first selection, never completion timing.

Parallel package output is captured separately to prevent byte-level interleaving. On redirected stderr or an unsuitable terminal, completed package output is appended as labeled blocks. --stream explicitly requests those blocks on an interactive terminal as well. JSON mode remains silent until its single final stdout report and retains each package's stdout and stderr.

On a capable interactive stderr terminal, parallel human mode instead repaints one bounded status table showing queued/running/final state, current lifecycle event, and elapsed time. Successful output stays collapsed; complete output for failed, signaled, blocked, or interrupted packages is printed after the table. TERM=dumb disables repainting. The display emits no color, so redirected logs and NO_COLOR environments do not receive color escapes.

SIGINT or SIGTERM cancels every active package process group, waits for cleanup, marks queued work interrupted, and then emits the deterministic final report.

Nested dependency builds#

Every top-level flog run starts a private authenticated loopback coordinator and passes its endpoint only to child scripts. A nested Flog process joins that single command session. Equivalent work that already succeeded is reported as reused instead of executing again; all coordinator state disappears when the top-level command exits, so a later command always performs fresh work.

Work identity includes the canonical workspace root and importer path, script, pre/main/post command text, forwarded arguments, top-level invocation context, caller PATH, npm configuration, and the remaining caller environment. Flog also includes the package's effective selected workspace dependency subgraph, so production and full builds reuse an unchanged dependency but never reuse an app build whose newly selected development workspace changes its inputs. Flog records the exact complete PATH value supplied to each lifecycle and its normalized caller value. A descendant replaces only that exact generated value; caller prepends, appends, replacements, and other changes remain significant. Generated package/lifecycle, working-directory, and coordinator variables are excluded. Changing a compiler path, npm configuration, argument, command, invocation, or ordinary environment value therefore prevents reuse.

Only successful compatible work is reusable. A failure, signal, missing script, blocked result, or interruption is retained as unsuccessful and causes an equivalent waiter to fail rather than retrying or treating it as satisfied. Wait relationships are tracked centrally; circular nested invocations fail with an actionable nested run cycle diagnostic instead of deadlocking. Cancellation closes active process groups and the top-level coordinator, so no persistent cache, daemon, socket file, or cross-terminal state remains.

Nested coordination remains available for general scripts and legacy wrappers such as:

flog run --cwd ../.. --workspace @app/name --dependencies-only --if-present build

Migrate dependency-only prebuild orchestration to top-level flog build so the app and its dependency closure are selected together. During one multi-app top-level run, shared equivalent dependencies execute once and later nested requests report reused. Coordination spans root scripts that invoke another flog run, so ordinary commands such as flog run build:apps retain one session through the workspace traversal and nested child scripts. Joined all-results runs read an authenticated evidence snapshot from the owner; human and JSON summaries therefore retain shared-work reuse counts even through wrappers. Human output distinguishes selected-package reused results from the aggregate count of nested requests satisfied by coordinated work.

Script environment#

Flog runs scripts from their package directory using /bin/sh on Linux and macOS. It prepends node_modules/.bin directories from the package through the workspace root to PATH, nearest first.

The environment includes:

  • INIT_CWD
  • PWD
  • FLOG_WORKSPACE_ROOT
  • npm_command
  • npm_config_local_prefix
  • npm_config_user_agent
  • npm_execpath
  • npm_lifecycle_event
  • npm_lifecycle_script
  • npm_package_json
  • npm_package_name
  • npm_package_version

flog x receives the common workspace, invocation, path, package-manager, and working-directory values above, but not lifecycle- or package-specific values.

SIGINT and SIGTERM cancellation reaches the active process group. Flog waits briefly for graceful termination and then kills a process that does not exit. Child exit statuses are preserved by the flog CLI.