Skip to content

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

@kubukoz

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions