Skip to content

Public hook for appending commands to the "Check headers and formatting" lint step #888

Description

@kubukoz

Problem

sbt-typelevel's generated Test job emits a step named "Check headers and formatting" that runs sbt commands like:

sbt '++ ${{ matrix.scala }}' headerCheckAll scalafmtCheckAll 'project /' scalafmtSbtCheck

Plugins and projects that contribute additional lint-style checks (custom formatters, schema validators, license linters, etc.) want to fold their check into this same step so it runs alongside the existing ones — same matrix cell, same conditions, same "lint failures show up here" semantics. There's no first-class way to do that today; the only option is a fragile rewrite of githubWorkflowBuild:

ThisBuild / githubWorkflowBuild ~= {
  _.map {
    case step: WorkflowStep.Sbt if step.name == Some("Check headers and formatting") =>
      step.withCommands(step.commands :+ "myLintCheckAll")
    case other => other
  }
}

This is nine lines per plugin, repeats the magic step-name string at every call site, and breaks if upstream renames the step or splits it. It also doesn't compose: two plugins both rewriting githubWorkflowBuild need to be careful about order and not stomping each other.

Proposed shape

A public setting that's just a list of extra commands appended into the existing lint step:

val tlCiLintCommands = settingKey[Seq[String]](
  "Additional sbt commands appended to the 'Check headers and formatting' step"
)

// existing default rendering becomes something like:
WorkflowStep.Sbt(
  List(
    s"$$ $${{ matrix.scala }}",
    "headerCheckAll",
    "scalafmtCheckAll",
    "project /",
    "scalafmtSbtCheck",
  ) ++ tlCiLintCommands.value,
  name = Some("Check headers and formatting"),
  cond = ...,
)

User code becomes one line:

ThisBuild / tlCiLintCommands += "smithyFmtCheckAll"

…and plugins can opt projects in by default:

override def projectSettings = Seq(
  tlCiLintCommands += "smithyFmtCheckAll",
)

+='d sequences compose naturally across plugins and project settings, so there's no ordering footgun.

Why it's worth doing

  • Removes a copy-paste recipe from documentation. We currently tell SmithyFormatPlugin users (in polyvariant/smithy-trait-codegen-scala) to write the nine-line githubWorkflowBuild ~= { … } snippet by hand — every reader has to understand step matching, WorkflowStep.Sbt, and withCommands just to bolt one task onto CI.
  • Plays nicely with autoplugins. A plugin that wants to opt projects into a check on CI can do so transparently via projectSettings, with users' explicit additions composing on top.
  • The current pattern is brittle to upstream changes (step name, step splitting) — a named setting decouples user code from the rendering layout.

Happy to send a PR if the shape lands.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions