cigate

package
v0.1.45 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package cigate is the local CI gate: the one definition of what "CI passes" means for a forge project, runnable on a laptop and from a workflow.

A scaffolded workflow is a thin wrapper that prepares the environment (checkout, toolchains, caches) and calls `task ci:checks`, `task test` and `task test:integration`. `task ci` runs all three locally, so "CI did not run" never has to mean "unverified". This package is what makes the first and last of those honest, with four rules:

  1. Every check runs, even after one fails, so one pass shows every failure. The verdict is a summary table, and the gate exits non-zero if any row failed.
  2. A check that could not run (a missing tool or prerequisite) is SKIPPED, never PASS, and fails the gate unless CI_SKIP names it. A silent skip is false confidence. Rows a project's own configuration or kind makes moot are N/A, which is not a skip and does not fail the gate.
  3. Tools run at the versions CI pins (tools.go), not whatever is on PATH.
  4. Under GitHub Actions the table is appended to $GITHUB_STEP_SUMMARY.

Index

Constants

View Source
const SkipEnv = "CI_SKIP"

SkipEnv names the checks a caller has decided not to run, comma-separated.

Variables

This section is empty.

Functions

func HasIntegrationTests

func HasIntegrationTests(root string) bool

HasIntegrationTests reports whether any Go test file under root carries the `integration` build tag. The scaffolded workflow asks the same question in its `changes` job (git grep for the same constraint) to decide whether the integration-test job runs at all.

func ParseSkips

func ParseSkips(v string) []string

ParseSkips splits a CI_SKIP value into check names.

func UnknownSkips

func UnknownSkips(skips []string, all []Check) []string

UnknownSkips returns the names in skips that no check carries. A typo in CI_SKIP would otherwise quietly skip nothing, or — worse — be read as the caller having accounted for a check they did not.

Types

type Check

type Check struct {
	Name string
	Lane Lane

	// NotApplicable, when set, reports the row as N/A with this reason and
	// runs nothing.
	NotApplicable string
	// Done returns a non-empty note when the check's work is already done
	// (dependencies installed); the row is PASS and nothing runs.
	Done func() string
	// Prereq returns a non-empty reason when the check cannot run here. The
	// row is then SKIPPED, which fails the gate.
	Prereq func() string
	// Tools are pinned tools the command needs. Each is resolved to its
	// pinned version before the command runs, and its directory is put first
	// on the command's PATH.
	Tools []Tool

	Argv []string
	// Dir is project-relative; empty is the project root.
	Dir string
	Env []string

	// OnFail runs after the command failed, to undo what it left behind.
	OnFail func(out io.Writer)
}

Check is one row of the gate.

func Plan

func Plan(in PlanInput) ([]Check, error)

Plan returns every check of every lane, in the order they run.

func Select

func Select(all []Check, lanes []Lane) []Check

Select keeps the checks of the named lanes, in plan order, dropping a check whose name an earlier selected lane already ran (the frontend-deps row sits in both the checks and test lanes, so `task ci` installs once).

type ExecFunc

type ExecFunc func(ctx context.Context, dir string, env []string, argv []string, out io.Writer) error

ExecFunc runs argv in dir with env, writing combined output to out.

type Lane

type Lane string

Lane is a group of checks a CI job runs together.

const (
	// LaneChecks is the static and build lane: the `checks` CI job.
	LaneChecks Lane = "checks"
	// LaneTest is the unit-test lane: `task test`, the `test` CI job.
	LaneTest Lane = "test"
	// LaneIntegration is `task test:integration`, the `integration-test` job.
	LaneIntegration Lane = "integration"
)

type Options

type Options struct {
	// Out receives the checks' own output and the summary. Defaults to
	// os.Stdout.
	Out io.Writer
	// Skip names the checks the caller chose not to run.
	Skip []string
	// Resolver pins tools. Nil means checks that declare Tools cannot run.
	Resolver *Resolver
	// Exec runs one command. Nil uses os/exec; tests substitute.
	Exec ExecFunc
	// Root is the project root.
	Root string
	// Groups wraps each check's output in GitHub Actions ::group:: lines.
	Groups bool
	// Env is the base environment; nil is os.Environ().
	Env []string
}

Options configure a Run.

type PlanInput

type PlanInput struct {
	// Root is the project root.
	Root string
	// CI is the data the scaffolded ci.yml renders with: which lints, scanners
	// and jobs the project has. The gate reads the same struct so a toggle
	// means the same thing in both.
	CI templates.CIWorkflowData
	// Cfg supplies the project's extra checks and its derived features.
	Cfg *config.ProjectConfig
	// Forge is the argv that re-enters this forge binary.
	Forge []string
}

PlanInput is everything the plan is derived from.

type Report

type Report struct {
	Lane    string
	Results []Result
}

Report is every row of one gate run.

func Run

func Run(ctx context.Context, lane string, checks []Check, opt Options) Report

Run executes checks in order and returns the report. It never stops early: a failing check is a row, not an abort.

func (Report) Counts

func (r Report) Counts() Tally

Counts tallies the rows by outcome.

func (Report) Failed

func (r Report) Failed() bool

Failed reports whether the gate is red: any FAIL, or any SKIPPED the caller did not allow.

func (Report) RenderMarkdown

func (r Report) RenderMarkdown(w io.Writer)

RenderMarkdown is the same table for $GITHUB_STEP_SUMMARY.

func (Report) RenderText

func (r Report) RenderText(w io.Writer)

RenderText writes the summary table.

type Resolver

type Resolver struct {
	// CacheRoot is where pinned tools are installed.
	CacheRoot string
	// ProjectDir selects the Go toolchain tools are built with: the one `go`
	// picks for the project, so a linter is never built with a Go older than
	// the module it must lint.
	ProjectDir string
	// Note receives one line per install, so a first-run build is explained.
	Note io.Writer

	LookPath func(string) (string, error)
	// Probe reports the version of the binary at path, or "".
	Probe func(path string, t Tool) string
	// Install builds t into dir.
	Install func(ctx context.Context, t Tool, dir, toolchain string) error
	// Toolchain reports the Go release `go` selects for ProjectDir.
	Toolchain func(ctx context.Context) string
	// ModuleGo is the project's go directive ("1.27.0"), "" when unknown.
	ModuleGo string
	// BuiltWith reports the Go release a binary was built with ("go1.27.2").
	BuiltWith func(path string) string
	// contains filtered or unexported fields
}

Resolver turns a Tool into a directory to put first on PATH.

func NewResolver

func NewResolver(projectDir string, note io.Writer) *Resolver

NewResolver is the production resolver.

func (*Resolver) Ensure

func (r *Resolver) Ensure(ctx context.Context, t Tool) (string, error)

Ensure returns the directory to prepend to PATH so t resolves at its pin, or "" when the binary on PATH already is the pin.

type Result

type Result struct {
	Name     string
	Lane     Lane
	Status   Status
	Duration time.Duration
	Note     string
	// Allowed marks a SKIPPED row the caller asked for (CI_SKIP), which does
	// not fail the gate.
	Allowed bool
	// Tail is the end of a failed command's output, for the job summary.
	Tail string
}

Result is the outcome of one Check.

type Status

type Status string

Status is one row's verdict.

const (
	// Pass is a check that ran and succeeded.
	Pass Status = "PASS"
	// Fail is a check that ran and failed.
	Fail Status = "FAIL"
	// Skip is a check that did not run. It fails the gate unless Allowed.
	Skip Status = "SKIPPED"
	// NA is a check this project has nothing for. It is not a skip.
	NA Status = "N/A"
)

type Tally

type Tally struct{ Passed, Failed, Skipped, Allowed, NA int }

Tally is the rows of a report counted by outcome. Skipped counts only the SKIPPED rows that fail the gate; Allowed counts the ones CI_SKIP named.

type Tool

type Tool struct {
	Binary string
	// Module is the `go install` package path.
	Module string
	// BuildModule is the module path the binary's build info records, when
	// that differs from Module's directory (golangci-lint's /cmd/ suffix).
	BuildModule string
	// Version is the pin, with the leading "v".
	Version string
	// MatchGo requires the binary to have been built with a Go at least as
	// new as the project's go directive: golangci-lint cannot analyze Go newer
	// than the Go it was built with, so a PATH copy at the right VERSION but
	// built with an older Go is the wrong binary.
	MatchGo bool
	// VersionArgs and VersionRE read the version from the binary's own output
	// when its build info does not carry one (release builds record
	// "(devel)").
	VersionArgs []string
	VersionRE   *regexp.Regexp
}

Tool is a binary the gate runs at a pinned version. CI installs these at the same versions (the scaffolded ci.yml renders the same templates constants), so a developer's `task ci` and the workflow judge the same code with the same linters. A tool on PATH at another version is not used: the pin is installed into forge's tool cache and put ahead of it for that command only. The developer's GOBIN and shell PATH are never touched.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL