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:

  1. Commit .github/zipx/action-pins.yml (see Action pins for the format)

  2. Add Dependabot for package-ecosystem: github-actions

  3. On Dependabot PRs run sbt zipxActionsPull, or set zipxDependabotSync := true for hands-off sync

Staying on jar defaults needs no pin file — just upgrade sbt-zipx when pins move.