Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Every lane here (composite-run-shellcheck.sh, ci.yml's schema step) globs
# Every lane here (composite-run-shellcheck.sh, pr-require-checks.yml's schema step) globs
# action.yml only, so a GitHub-legal action.yaml would be skipped silently.
set -uo pipefail

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ Both steps go in the `ci-status` job, in this order:
statuses: write
runs-on: ubuntu-24.04
steps:
- uses: melodic-software/ci-workflows/.github/actions/pr-contract@<sha> # vX.Y.Z
- uses: melodic-software/ci-workflows/.github/actions/pr-require-checks/check-contract@<sha> # vX.Y.Z
- name: Aggregate lane results
# `!cancelled()` so a failing pr-contract step does not skip the
# aggregation: the job still fails on the contract step's exit code, and
# the ci-lanes status lands on the SHA for the next contract-only run.
if: ${{ !cancelled() }}
uses: melodic-software/ci-workflows/.github/actions/ci-status@<sha> # vX.Y.Z
uses: melodic-software/ci-workflows/.github/actions/pr-require-checks/aggregate-results@<sha> # vX.Y.Z
with:
results: ${{ needs.lane-a.result }} ${{ needs.lane-b.result }}
```
Expand All @@ -132,12 +132,12 @@ branches on it before it looks at `same-repo`, so `contract-only: true` with
`same-repo: false` is treated as a carry-forward even though the shipped
defaults never produce that combination.

This repository's own `.github/workflows/ci.yml` is the reference wiring.
This repository's own `.github/workflows/pr-require-checks.yml` is the reference wiring.

## Tests

`run.test.sh` drives `run.sh` against fixture JSON served by a `gh` shim placed
first on `PATH`, which also logs every API call so the harness can assert on the
writes that did and did not happen. Run it with
`bash .github/actions/pr-contract/run.test.sh`; the `selector-contract` lane runs
`bash .github/actions/pr-require-checks/check-contract/run.test.sh`; the `selector-contract` lane runs
it in CI.
6 changes: 3 additions & 3 deletions .github/scripts/Invoke-CompositeRunPssa.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
to bash/sh: expression substitution, line-number mapping, and guards that
stop a selector matching nothing from passing as a clean scan.

Analysis itself is delegated to .github/actions/powershell/Invoke-Pssa.ps1
Analysis itself is delegated to .github/actions/psscriptanalyzer/Invoke-Pssa.ps1
so the repo keeps one analyzer invocation and one pinned version, read here
from the `powershell` action's own `analyzer-version` default.

Expand Down Expand Up @@ -48,8 +48,8 @@ param(
Set-StrictMode -Version 3.0
$ErrorActionPreference = 'Stop'

$actionMetadata = '.github/actions/powershell/action.yml'
$runner = '.github/actions/powershell/Invoke-Pssa.ps1'
$actionMetadata = '.github/actions/psscriptanalyzer/action.yml'
$runner = '.github/actions/psscriptanalyzer/Invoke-Pssa.ps1'

# The repo's own ruleset, applied unchanged: an embedded block is this repo's
# PowerShell and is held to the same policy as a .ps1. The bash/sh sibling
Expand Down
4 changes: 2 additions & 2 deletions .github/scripts/Invoke-CompositeRunPssa.test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ $customFixture = 'fixtures/composite-action/pwsh-custom-shell/action.yml'
$usesOnlyFixture = 'fixtures/composite-action/uses-only/action.yml'
$mappingFixture = 'fixtures/composite-action/mapping-steps/action.yml'
$noShellFixture = 'fixtures/composite-action/no-shell/action.yml'
$coveredAction = '.github/actions/powershell/action.yml'
$coveredAction = '.github/actions/psscriptanalyzer/action.yml'

function Assert-Condition {
[CmdletBinding()]
Expand Down Expand Up @@ -228,7 +228,7 @@ Assert-Condition -Condition ($noShell.Output.Contains('refusing to guess the dia
-Message 'The missing shell: was not reported.' `
-Context $noShell.Output

$empty = Invoke-Check -Argument @('.github/workflows/ci.yml')
$empty = Invoke-Check -Argument @('.github/workflows/pr-require-checks.yml')
Assert-Condition -Condition ($empty.ExitCode -ne 0) `
-Message 'A file set containing no composite pwsh run: block unexpectedly passed.' `
-Context $empty.Output
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/actionlint-shellcheck-pin.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ test("actionlint installs the canonical checksum-pinned ShellCheck release", ()
);

const ciWorkflow = fs.readFileSync(
path.join(workflowsRoot, "ci.yml"),
path.join(workflowsRoot, "pr-require-checks.yml"),
"utf8",
);
assert.match(ciWorkflow, /actionlint-shellcheck\.txt/u);
Expand Down
12 changes: 6 additions & 6 deletions .github/scripts/checks-markdown-globs.test.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

// `checks.yml` forwards markdownlint globs. Passing `globs` replaces the
// `pr-run-checks.yml` forwards markdownlint globs. Passing `globs` replaces the
// composite default, so the workflow repeats that default and appends
// `markdown-extra-globs`. The expression is the GitHub `case`/`format` pair
// pinned below; the arguments markdownlint-cli2 actually receives are that
Expand All @@ -20,11 +20,11 @@ function readRepo(relativePath) {
return fs.readFileSync(path.join(repoRoot, relativePath), "utf8");
}

const checks = parseWorkflow(readRepo(".github/workflows/checks.yml"));
const checks = parseWorkflow(readRepo(".github/workflows/pr-run-checks.yml"));
const markdownAction = parseWorkflow(
readRepo(".github/actions/markdown/action.yml"),
readRepo(".github/actions/markdownlint/action.yml"),
);
const markdownStep = checks.jobs.checks.steps.find(
const markdownStep = checks.jobs.check.steps.find(
(step) => step?.id === "markdown",
);
const extraGlobs = checks.on.workflow_call.inputs["markdown-extra-globs"];
Expand All @@ -41,7 +41,7 @@ const DOTFILES_EXTRA = [
".chezmoitemplates/agent-instructions-shared",
].join(" ");

assert.ok(markdownStep !== undefined, "checks.yml has no markdown step");
assert.ok(markdownStep !== undefined, "pr-run-checks.yml has no markdown step");
assert.ok(lintStep !== undefined, "markdown composite has no glob split");

function forwardedGlobs(extra) {
Expand Down Expand Up @@ -71,7 +71,7 @@ function wordSplit(value) {
}

test("markdown-extra-globs is an optional string and defaults empty", () => {
assert.ok(extraGlobs, "checks.yml has no markdown-extra-globs input");
assert.ok(extraGlobs, "pr-run-checks.yml has no markdown-extra-globs input");
assert.equal(extraGlobs.type, "string");
assert.equal(extraGlobs.default, "");
// Omitted `required` is optional. workflow_call inputs are required only
Expand Down
44 changes: 30 additions & 14 deletions .github/scripts/checks-outcome.test.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

// The `checks.yml` outcome join decides the consolidated lane's verdict. It is
// The `pr-run-checks.yml` outcome join decides the consolidated lane's verdict. It is
// the one place where a bug is silently catastrophic rather than noisy: every
// composite runs under `continue-on-error: true`, so a join that forgot a
// composite reports success for a run in which that composite failed, and
Expand All @@ -20,11 +20,19 @@ const { test } = require("node:test");

const { parseWorkflow } = require("./workflow-yaml.cjs");

const workflowPath = path.join(__dirname, "..", "workflows", "checks.yml");
const workflowPath = path.join(
__dirname,
"..",
"workflows",
"pr-run-checks.yml",
);
const workflow = parseWorkflow(fs.readFileSync(workflowPath, "utf8"));
const steps = workflow.jobs.checks.steps;
const steps = workflow.jobs.check.steps;
const joinStep = steps.find((step) => step?.id === "outcome");
assert.ok(joinStep !== undefined, "checks.yml has no `outcome` join step");
assert.ok(
joinStep !== undefined,
"pr-run-checks.yml has no `outcome` join step",
);

// Every step the join owns: the composites, plus the input-combination guard
// that must go red rather than let an enabled toggle skip silently.
Expand Down Expand Up @@ -89,17 +97,22 @@ test("the join reads every continue-on-error step", () => {
`step ${step.id} is never reported by the join`,
);
}
const renamed = {
"change-detection": "detect-changes",
editorconfig: "editorconfig-checker",
markdown: "markdownlint",
"exec-bit": "check-exec-bit",
"eol-renormalize": "check-line-endings",
};
for (const step of composites) {
const name = step.id.replaceAll("_", "-");
assert.match(
String(step.uses ?? ""),
new RegExp(
`^melodic-software/ci-workflows/\\.github/actions/${name}@[0-9a-f]{40}$`,
"u",
),
// A relative `./` reference inside a CALLED workflow resolves against the
// CALLER's checkout, so it would fail in every consumer.
`step ${step.id} does not reference its composite by pinned full path`,
const uses = String(step.uses ?? "");
// `$/` resolves against the repository and commit that host the called
// workflow, so it works in every consumer.
assert.equal(
uses,
`$/.github/actions/${renamed[name] ?? name}`,
`step ${step.id} does not reference its composite through the called workflow's own commit`,
);
}
// The join reports nothing the steps do not produce: an env name left behind
Expand Down Expand Up @@ -184,7 +197,10 @@ test("an enabled toggle with no configuration fails, it does not skip quietly",
// `check-jsonschema-files`; the join is what turns it into the job's verdict,
// so a silently skipped schema gate cannot report success.
const guard = steps.find((step) => step?.id === "configuration");
assert.ok(guard !== undefined, "checks.yml has no input-combination guard");
assert.ok(
guard !== undefined,
"pr-run-checks.yml has no input-combination guard",
);
assert.equal(
guard.if,
`\${{ inputs.check-jsonschema && inputs.check-jsonschema-files == '' }}`,
Expand Down
Loading
Loading