Quick start
Describe CI in the build, then generate it. Add the plugin, write ci.yml with zipxWorkflowGenerate, and let
zipxWorkflowCheck fail the PR when the committed workflow no longer matches the graph. Works for a single Aggregate
library as well as a monorepo; no hand-rolled module matrices required.
Install
// project/plugins.sbt
addSbtPlugin("rocks.earlyeffect" % "sbt-zipx" % "<version>")
Generate and commit:
sbt zipxWorkflowGenerate
git add .github/workflows/ci.yml && git commit -m "ci: generate with zipx"
If you enable zipxDependabotSync, also commit .github/workflows/zipx-action-pins-sync.yml when it appears.
Inspect what zipx sees: sbt zipxGraph and sbt zipxPublishOrder.
Defaults
Defaults are Aggregate: one root test job (sbt 'test') and one publish job (plus docker when any module enables
DockerPlugin). For a typical library you write zero module lists, needs edges, or project-id strings.
// project/plugins.sbt
addSbtPlugin("rocks.earlyeffect" % "sbt-zipx" % "<version>")
// build.sbt
lazy val lib = project.settings(/* publish settings */)
lazy val root = (project in file("."))
.aggregate(lib)
.settings(
// nothing required for Aggregate test + publish
// optional paved Central path:
zipxCapabilities += ZipxCentral.release,
zipxJavaVersion := "25",
)
{
val g = ModuleGraph(List(ModuleNode("lib", publishes = true, crossScalaVersions = List("3.8.4"))))
DocsRender.jobs("test", "publish")(Capability.test, Capability.publish)(using g)
}test:
name: test
runs-on: ubuntu-latest
if: "!startsWith(github.ref, 'refs/tags/') && github.event_name != 'workflow_dispatch'"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: "0"
fetch-tags: "true"
- name: Setup JDK 21
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95
with:
distribution: temurin
java-version: "21"
- uses: sbt/setup-sbt@d059c39de700f4cc5cb64f9f56577315e44a984e
with:
disk-cache: "false"
- name: Cache sbt
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: |
~/.sbt
~/.cache/sbt
~/.cache/coursier
target
key: ubuntu-latest-jdk21-sbt-0.1.0-ci-${{ github.run_id }}-test
restore-keys: |
ubuntu-latest-jdk21-sbt-0.1.0-ci-${{ github.run_id }}-
ubuntu-latest-jdk21-sbt-0.1.0-ci-
ubuntu-latest-jdk21-sbt-0.1.0-
ubuntu-latest-jdk21-sbt-
- name: test
run: sbt 'test'
publish:
name: publish
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: "0"
fetch-tags: "true"
- name: Setup JDK 21
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95
with:
distribution: temurin
java-version: "21"
- uses: sbt/setup-sbt@d059c39de700f4cc5cb64f9f56577315e44a984e
with:
disk-cache: "false"
- name: Cache sbt
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: |
~/.sbt
~/.cache/sbt
~/.cache/coursier
target
key: ubuntu-latest-jdk21-sbt-0.1.0-ci-${{ github.run_id }}-publish
restore-keys: |
ubuntu-latest-jdk21-sbt-0.1.0-ci-${{ github.run_id }}-
ubuntu-latest-jdk21-sbt-0.1.0-ci-
ubuntu-latest-jdk21-sbt-0.1.0-
ubuntu-latest-jdk21-sbt-
- name: publish
run: sbt 'lib/publish'Bare settings (sbt 2.0)
zipx reads build-level settings from the root project's scope, so write plain bare settings — no ThisBuild / prefix.
A bare zipxTestTask := "testFull" applies to every module; any module can override it in its own .settings(...).
zipxJavaVersion := "25"
zipxTestTask := "testFull"
zipxWorkflowDispatch := true
Self-checking
zipxWorkflowGenerate writes .github/workflows/ci.yml. zipxWorkflowCheck regenerates and diffs against the
committed file. Run the check in CI so drift fails the PR. Generation is deterministic (stable ordering, no timestamps).
Action pins (optional)
Workflows pin GitHub Actions to commit SHAs. To track upstream action releases without waiting on a zipx upgrade:
Commit
.github/zipx/action-pins.yml(see Action pins for the format)Add Dependabot for
package-ecosystem: github-actionsOn Dependabot PRs run
sbt zipxActionsPull, or setzipxDependabotSync := truefor hands-off sync
Staying on jar defaults needs no pin file — just upgrade sbt-zipx when pins move.