Composing sbt commands

Skip until a job needs more than one sbt task, or you are wiring a pack. Most builds never write this.

A CI job step is one sbt '<text>'. zipx validates text that cannot corrupt the generated file; it does not parse sbt syntax. Typing comes back through composition and provenance: real keys in the plugin, a step list on the wire.

Green is the paved path. Red is the escape hatch: raw text is warned at generate time and never preferred.

Never a string where sbt has a value

If zipx can derive it from the graph, do not write it. If sbt has a value for it, pass the value.

zipxCapabilities ++= Seq(
  zipxTasks.once(CapabilityName("lint"), lintAll),  // extra Once; builtin fmt is already parallel Verify
  Capability.testGraph,
  ZipxCentral.release.withCondition(upstream),
)

zipxTestTask := zipxTasks.of(testFull)   // plugin default; override per module if needed
zipxPublish := zipxOff                   // not Some(false)

zipxTasks.of / session / rows / each / only / except live in the plugin (sbt on the classpath). Scripted suites prove the key path; core unit tests prove the wire form.

Chaining and thenOnce

SbtCommand.session / andThen join steps. Capability setters: running / runningEach / runningEachCross / thenOnce / runningNothing. A session tail is Aggregate or Once only: Layer and Graph would release a partial bundle per wave (generate fails naming Aggregate / releaseOnce).

Verify twin: Capability.test.thenOnce(zipxTasks.of(docs / specularSite)) after the root test session.

Which Central release?

Pick the shape that matches your aggregate vs publish set. Rendered YAML for each lives on Packs.

releasePicker
Choose a Central release shape:
One job: every publishing module's publishSigned in dependency order, then sonaRelease. Prefer this when the publish set is not exactly the root .aggregate (projectMatrix rows, skipped docs).

What is checked, and when

zipxCheckCommandNames (default true) fails generate when a declared command name is unknown. Escape with zipxCheckCommandNames := false only for a false positive. See Validation.