Skip to main content
Version: 1.1

Run-level hooks

Seven hooks observe the run as a whole rather than any one package. They live in the top-level run object and execute in the monorepo root. Each is a no-op when unconfigured:

run:
beforeAll: check-preconditions
postAll: notify
HookRuns
run.beforeAllOnce, after planning and verification, before the task graph starts.
run.postAllOnce, after the whole task graph finishes, even when nothing released.
run.beforeCommitBefore the release commit. commit mode only, and only when something published.
run.afterCommitAfter the release commit succeeded.
run.postCommitAfter the release commit and the tags.
run.beforePushBefore the push. commit.push mode only.
run.afterPushAfter the push succeeded.

run.beforeAll is the one gating run hook. It fires before any release work begins. A failure aborts the run with exit 1 before dispat builds, publishes, or tags anything. Every other run hook only warns on failure. These run after the work they observe, so dispat executes every command in the sequence even if an earlier one fails. The "after" hooks only run when their bracketed operation succeeds.

All seven hooks receive DISPAT_STAGE naming the hook and the workspace listing. run.postAll and later hooks also receive the run outcome listing. This data reports which packages published, failed, were skipped, or were never planned to release. run.beforeAll fires before any outcome exists.

They belong to dispat release

These seven fire for dispat release and nothing else. The step commands do not fire them. For example, dispat commit --tag --push makes the release commit, writes tags, and pushes without ever triggering run.beforeCommit or run.afterPush.

This design is deliberate. A run hook gives you a seam into a moment dispat chooses. Inside a release, dispat decides when the commit happens, so it offers beforeCommit. A script that calls dispat commit already owns that moment. You bracket the commit by writing the line before and the line after:

scripts:
notify-before: ./notify.sh starting
commit: dispat commit --tag --push
notify-after: ./notify.sh done
spaces:
libs:
flow:
beforePublish: [notify-before, commit, notify-after]

The run object also means once per run. The flow.beforePublish hook executes per package, and nesting step commands there is the recommended pattern. A release of ten packages would fire each commit hook ten times from inside itself and once more from its own finalize. Nothing in the environment would distinguish one firing from another.

The branch guard

Configure run.allowBranch to guard your releases. A release run refuses to start unless your checked-out branch matches one of your listed patterns:

run:
allowBranch: [main, release/*]

A run from main proceeds normally. A run from feature/tryout stops with exit 1 and prints a message naming both the branch and the patterns. This happens before any verification, hooks, or release work, leaving the repository exactly as dispat found it.

A * matches any run of characters, including separators. This means release/* matches release/v2/hotfix. A detached HEAD has no branch name, so it matches nothing, even a pattern as broad as *.

The guard restricts releasing, not looking. Read-only commands like status, preview, and compute work on any branch. The step commands are also unguarded. They are built to run inside a release stage that this guard has already cleared.

Leave the list unset to release from any branch. This is the default behavior. It suits single-branch repositories and disposable clones.

Releasing from a stale checkout

A second guard needs no configuration. When you set the finalize phase to push (commit.push), dispat checks your checkout against the remote branch. It refuses to run if your local branch is behind:

$ dispat
ERR refusing to release error="the checkout is behind origin/main; pull before releasing"

dispat computes the plan from the tags your local clone can see. If someone else released in the meantime, your checkout plans versions that may already exist. The push at the end would fail after everything was built, published, and tagged. Failing at the start costs nothing. Failing at the end costs a release.

Run git pull and try again. dispat ignores this check for a detached HEAD or a new branch the remote does not have yet. The check uses ls-remote. Set commit.verify: false to turn off this check if your remote rejects ls-remote but accepts pushes.