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
34 changes: 34 additions & 0 deletions .github/actions/markdown/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: markdown
description: Lint Markdown with markdownlint-cli2 against a caller-supplied config.

inputs:
globs:
description: >-
markdownlint-cli2 glob arguments, space-separated (word-split on the
runner). Prefix a glob with '!' to exclude. Default lints all markdown.
default: '**/*.md'
config:
description: Path to the markdownlint-cli2 config file in the caller repo.
default: modules/markdown/.markdownlint-cli2.jsonc
version:
description: Exact markdownlint-cli2 version to run.
default: 0.22.1
node-version:
description: Node.js version to set up.
default: 24.17.0

runs:
using: composite
steps:
- name: Set up Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ inputs.node-version }}
- name: Lint markdown
shell: bash
env:
CONFIG: ${{ inputs.config }}
VERSION: ${{ inputs.version }}
GLOBS: ${{ inputs.globs }}
# $GLOBS is intentionally unquoted so multiple glob arguments word-split.
run: npx --yes "markdownlint-cli2@$VERSION" --config "$CONFIG" $GLOBS
File renamed without changes.
57 changes: 57 additions & 0 deletions .github/actions/powershell/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: powershell
description: Analyze PowerShell with PSScriptAnalyzer via the bundled per-file runner.

inputs:
paths:
description: >-
Files/dirs to analyze (relative to the caller repo), space-separated.
Directories recurse. Default analyzes the whole repo.
default: '.'
settings:
description: Path to the PSScriptAnalyzerSettings.psd1 ruleset in the caller repo.
default: modules/powershell/PSScriptAnalyzerSettings.psd1
exclude:
description: >-
Path substrings to skip, space-separated (e.g. intentionally-bad
fixtures). The .git directory is always skipped.
default: ''
analyzer-version:
description: Exact PSScriptAnalyzer version to install and require.
default: 1.25.0

runs:
using: composite
steps:
- name: Ensure PSScriptAnalyzer
shell: pwsh
env:
REQUIRED: ${{ inputs.analyzer-version }}
run: |
$required = $env:REQUIRED
$have = Get-Module -ListAvailable PSScriptAnalyzer |
Where-Object { $_.Version -eq [version]$required }
if (-not $have) {
try {
Install-Module PSScriptAnalyzer -RequiredVersion $required -Scope CurrentUser -Force -SkipPublisherCheck -ErrorAction Stop
} catch {
Install-PSResource PSScriptAnalyzer -Version $required -Scope CurrentUser -TrustRepository -Reinstall -ErrorAction Stop
}
}
Import-Module PSScriptAnalyzer -RequiredVersion $required -ErrorAction Stop
Write-Output "PSScriptAnalyzer $((Get-Module PSScriptAnalyzer).Version) on pwsh $($PSVersionTable.PSVersion)"

- name: Analyze
shell: pwsh
env:
PATHS: ${{ inputs.paths }}
SETTINGS: ${{ inputs.settings }}
EXCLUDE: ${{ inputs.exclude }}
REQUIRED: ${{ inputs.analyzer-version }}
ACTION_PATH: ${{ github.action_path }}
run: |
# Wrap in @() so an empty input stays an empty array, never $null
# (a bare null would violate the runner's non-null parameter contract).
$paths = @($env:PATHS -split '\s+' | Where-Object { $_ })
$exclude = @($env:EXCLUDE -split '\s+' | Where-Object { $_ })
& "$env:ACTION_PATH/Invoke-Pssa.ps1" -Path $paths -Settings $env:SETTINGS -AnalyzerVersion $env:REQUIRED -ExcludePath $exclude
exit $LASTEXITCODE
11 changes: 8 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
version: 2
updates:
# Keep the actions and reusable workflows referenced by SHA current. Review
# every PR before merge; never auto-merge (a SHA bump is executable CI logic).
# Keep SHA-pinned actions current. Review every PR before merge; never
# auto-merge (a SHA bump is executable CI logic). The "/" target scans
# .github/workflows; the glob target is required to also scan the composite
# actions' own action.yml files (Dependabot does not descend into
# .github/actions/<name>/ from "/" alone).
- package-ecosystem: github-actions
directory: /
directories:
- /
- /.github/actions/*
schedule:
interval: weekly
21 changes: 16 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,26 @@ on:
permissions:
contents: read

# Dogfood: this repo is the first consumer of its own reusable workflows. Each
# lane runs against this repo's vendored config copy; the local ci-status
# gateway aggregates them into the single required check.
# Dogfood: this repo is the first consumer of its own composite actions. Each
# lane checks out this repo and runs a lane action against the vendored config
# copy; the local ci-status gateway aggregates them into the single required
# check. Local (./) action refs work here because the checkout IS this repo.
jobs:
markdown:
uses: ./.github/workflows/markdown.yml
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Lint markdown
uses: ./.github/actions/markdown

powershell:
uses: ./.github/workflows/powershell.yml
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Analyze PowerShell
uses: ./.github/actions/powershell

ci-status:
if: always()
Expand Down
44 changes: 0 additions & 44 deletions .github/workflows/markdown.yml

This file was deleted.

80 changes: 0 additions & 80 deletions .github/workflows/powershell.yml

This file was deleted.

60 changes: 34 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,52 @@
# ci-workflows

Reusable, configurable CI **execution** for the melodic-software org: the
workflows that install and run each code-quality tool, plus the runner scripts
they call.
Reusable, configurable CI **execution** for the melodic-software org: composite
actions that install and run each code-quality tool, plus the runner scripts
they bundle.

Consumed by reference, never copied:
Consumed by reference from a consumer job, never copied:

```yaml
jobs:
markdown:
uses: melodic-software/ci-workflows/.github/workflows/markdown.yml@<sha>
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<sha>
- uses: melodic-software/ci-workflows/.github/actions/markdown@<sha>
```

A composite action is pulled cross-repo via GitHub's scoped, read-only
installation token (distinct from the caller's repo-scoped `GITHUB_TOKEN`), so
this repo stays private with no PAT, and an action's bundled script is reached
via `$GITHUB_ACTION_PATH` without any checkout of this repo.

## Contract

- **Configurable, not forkable.** Each reusable workflow exposes typed `inputs`
with global-standard defaults. Consumers override repo-specific scope (globs,
- **Configurable, not forkable.** Each action exposes typed `inputs` with
global-standard defaults. Consumers override repo-specific scope (globs,
paths, tool versions, config location) through inputs — never by editing the
workflow.
- **Pin by SHA.** Reference every reusable workflow at a full commit SHA;
Dependabot (`github-actions`, weekly) opens bump PRs that are reviewed and
merged manually.
- **Each consumer aggregates locally.** A reusable workflow runs one tool. The
required-check contract is a single check named `ci-status`, produced by a
thin gateway job the consumer keeps local so the required-check name stays
un-nested. The gateway `needs:` the called jobs and fails if any failed or was
cancelled; a skipped job is allowed.
action.
- **Pin by SHA.** Reference every action at a full commit SHA; Dependabot
(`github-actions`, weekly) opens bump PRs that are reviewed and merged
manually.
- **Each consumer aggregates locally.** One action runs one tool inside a
consumer job. The required-check contract is a single check named `ci-status`,
produced by a thin gateway job the consumer keeps local so the required-check
name stays un-nested. The gateway `needs:` the lane jobs and fails if any
failed or was cancelled; a skipped job is allowed.

## Workflows
## Actions

- `markdown.yml` — markdownlint-cli2 over the repo's markdown.
- `powershell.yml` — PSScriptAnalyzer over the repo's PowerShell, via
`scripts/Invoke-Pssa.ps1` (per-file subprocess isolation).
- `.github/actions/markdown` — markdownlint-cli2 over the repo's markdown.
- `.github/actions/powershell` — PSScriptAnalyzer over the repo's PowerShell,
via the bundled `Invoke-Pssa.ps1` (per-file subprocess isolation).

Each input's meaning and default is documented inline in the workflow's
`inputs:` block.
Each input's meaning and default is documented inline in the action's `inputs:`
block.

## Tool configuration lives elsewhere

These workflows execute tools; they do not carry the tools' rulesets. A consumer
supplies its own config file and points the workflow at it through an input, so
adopting a workflow never couples the consumer to this repo at runtime beyond
the referenced workflow itself.
These actions execute tools; they do not carry the tools' rulesets. A consumer
supplies its own config file and points the action at it through an input, so
adopting an action never couples the consumer to this repo at runtime beyond the
referenced action itself.