Shared composite GitHub Actions to keep CI bootstrap consistent across projects rather than copy-pasted into each one.
Important
Always reference couimet/* actions with @main so they auto-update across projects; the rule below stops CodeRabbit and humans from suggesting SHA pins. Copy it into your project's CLAUDE.md:
<rule id="couimet-actions-main" priority="critical">
<title>couimet/* GitHub Actions always use @main</title>
<never>Pin a `couimet/*` GitHub Action to a commit SHA in workflows or composite action definitions</never>
<do>Always reference `couimet/*` actions with `@main` to get the latest version</do>
<rationale>The author wants these actions to auto-update across all repos</rationale>
</rule>See Versioning for the full rationale.
Listed alphabetically.
Runs a fix command (e.g., pnpm format:fix && pnpm lint:fix) and auto-commits any resulting changes. Thin wrapper around stefanzweifel/git-auto-commit-action pinned to a commit SHA, so consuming repos avoid duplicating the pin. The default commit message (chore: auto-fix [skip ci]) includes the [skip ci] token, which prevents the auto-commit from triggering another CI run — without it, the fix commit would re-trigger the pipeline and risk an infinite CI loop. Keep the [skip ci] token in the message when overriding commit-message; dropping it only re-triggers CI when the workflow is called with the auto-fix-token secret. The workflow's recursion guard matches the head commit subject against the configured message, so keep the message a single line, and keep the fix command idempotent as a backstop.
The consuming workflow's job needs contents: write in its permissions: block. On pull_request events, the caller must check out the PR head ref with persist-credentials: true so the auto-fix commit can be pushed instead of failing on a detached HEAD.
| Input | Required | Default | Description |
|---|---|---|---|
commit-message |
no | chore: auto-fix [skip ci] |
Commit message for the auto-fix commit. Keep the [skip ci] token when overriding; dropping it only re-triggers CI when the workflow is called with the auto-fix-token secret, and the recursion guard matches the head commit subject against this exact message. |
commit-user-email |
no | github-actions[bot]@users.noreply.github.com |
Email for the auto-fix commit author. |
commit-user-name |
no | github-actions[bot] |
Name for the auto-fix commit author. |
fix-command |
yes | (none) | Shell command to run for auto-fixing (e.g., pnpm format:fix && pnpm lint:fix). |
working-directory |
no | . |
Directory to run the fix command in. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: true
ref: ${{ github.event.pull_request.head.ref }}
- uses: couimet/github-actions/setup-node-pnpm@main
- uses: couimet/github-actions/install-deps@main
- uses: couimet/github-actions/auto-fix@main
with:
fix-command: pnpm format:fix && pnpm lint:fixPrefer pre-commit hooks (e.g., Husky + lint-staged) over CI auto-fix: hooks fix issues before they leave the developer's machine, with no CI permission elevation and no extra CI runs. Auto-fix is best used as a safety net for contributors who bypass hooks — keep the CI format/lint check as a read-only gate and let the auto-fix commit be the fix path. See the CodeRabbit analysis in issue #28 for the full trade-offs.
Runs BATS shell tests against a directory of .bats files. The step fails when any test fails. By default, posts a sticky PR comment with test result counts via publish-pr-comment (set publish-comment: 'false' to opt out). The consuming workflow's job needs pull-requests: write in its permissions: block when comment publishing is active.
| Input | Required | Default | Description |
|---|---|---|---|
assert-install |
no | true |
Install the bats-assert helper library. |
bats-version |
no | 1.14.0 |
BATS version installed; pinned so CI matches the local brew stable. |
comment-header |
no | BATS Test Results |
Unique header that identifies the BATS comment across re-runs (sticky update). |
detik-install |
no | false |
Install the detik helper library. |
file-install |
no | false |
Install the bats-file helper library. |
formatter |
no | (empty) | Passed as --formatter (e.g. tap, junit); empty uses the default pretty output. |
github-token |
no | (empty) | GitHub token for posting the comment. Required only when publish-comment is true. |
publish-comment |
no | true |
Post a sticky PR comment with test result counts. Set to false to opt out. |
recursive |
no | true |
Recurse into subdirectories of test-directory. |
support-install |
no | true |
Install the bats-support helper library. |
test-directory |
no | bats-tests/ |
Directory containing .bats test files. |
When publish-comment is true (the default), the action exposes these outputs:
| Output | Description |
|---|---|
total |
Total test cases parsed from TAP output. |
passed |
Number of passing tests. |
failed |
Number of failing tests (0 when all pass). |
exit_code |
BATS exit code: 0 = all passed, 1 = one or more failures, >1 = BATS error. |
When publish-comment is false, no outputs are set; success or failure is reported through the step exit code alone.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/bats-test@main
with:
test-directory: bats-tests/shell
github-token: ${{ secrets.GITHUB_TOKEN }}Runs a build command. Defaults to pnpm build; override command for non-pnpm projects (e.g., command: make build). The step fails if the build fails.
| Input | Required | Default | Description |
|---|---|---|---|
command |
no | pnpm build |
Command to run for building. |
working-directory |
no | . |
Directory to run the command in. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/setup-node-pnpm@main
- uses: couimet/github-actions/install-deps@main
- uses: couimet/github-actions/build@mainRuns a configurable command (e.g., pnpm api:types, make generate) and fails if git diff detects any uncommitted changes. On drift, posts a sticky PR comment listing the files that are out of sync. Use this to guard against PRs that edit a source spec without re-running codegen.
| Input | Required | Default | Description |
|---|---|---|---|
command |
yes | (none) | Shell command that regenerates generated files. |
post-generate |
no | (empty) | Optional hook that runs after generation but before drift detection. |
github-token |
yes | (none) | GitHub token for posting PR comments. Pass secrets.GITHUB_TOKEN. |
header |
no | Generated drift check |
Unique header that identifies the PR comment across re-runs (sticky update). |
working-directory |
no | . |
Directory to run the command in. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/setup-node-pnpm@main
- uses: couimet/github-actions/install-deps@main
- uses: couimet/github-actions/check-generated-drift@main
with:
command: pnpm api:types
github-token: ${{ secrets.GITHUB_TOKEN }}Scans all package.json files under the working directory for prerelease dependency patterns (-alpha, -beta, -rc, -pre) in dependencies, devDependencies, peerDependencies, and optionalDependencies. The step fails if any prerelease dependency is found. Used in CI to prevent accidentally depending on prerelease packages in main-branch PRs.
| Input | Required | Default | Description |
|---|---|---|---|
working-directory |
no | . |
Directory to scan for package.json. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/check-no-prerelease-deps@mainCounts TODO and FIXME comments across a configurable set of file extensions. On PRs, fetches the base ref and computes the delta to surface whether technical debt is being addressed or accumulated. The step never fails; it reports the count and delta as outputs and writes a markdown summary.
| Input | Required | Default | Description |
|---|---|---|---|
base-ref |
no | (empty) | Base ref for PR delta comparison. When empty, reports the current count only. |
file-extensions |
no | ts,tsx,js,jsx,mjs,cjs,py,rb,go,rs,java,cs,sh,bash,yaml,yml,toml,md,html,css,scss,sql,tf,graphql,vue,svelte |
Comma-separated file extensions to scan (without leading dot). |
path |
no | . |
Directory to scan for TODOs and FIXMEs. |
| Output | Description |
|---|---|
todo-count |
Current TODO/FIXME count. |
todo-delta |
Change vs base ref (PRs only, empty when base-ref is not set). |
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
- uses: couimet/github-actions/check-todos@mainsteps:
- uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
- uses: couimet/github-actions/check-todos@main
with:
base-ref: ${{ github.event.pull_request.base.sha }}Uploads a Jest coverage report to Codecov. Thin wrapper around codecov/codecov-action with TypeScript/Jest defaults. For non-JS projects, use the generic codecov-upload action instead.
| Input | Required | Default | Description |
|---|---|---|---|
files |
no | coverage/lcov.info |
Coverage report file(s) to upload (glob). |
working-directory |
no | . |
Directory to run in. Coverage file paths are relative to this directory. |
flags |
no | (empty) | Codecov flags to apply to the upload (e.g. unit, integration). |
token |
no | (empty) | Codecov upload token. Not needed for public repos. |
fail-ci-if-error |
no | false |
When true, fails the CI step if the upload fails. |
This action has no outputs; success or failure is reported through the step exit code, although Codecov upload failures do not fail the step by default (set fail-ci-if-error to true to change that).
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/setup-node-pnpm@main
- uses: couimet/github-actions/install-deps@main
- uses: couimet/github-actions/test@main
- uses: couimet/github-actions/codecov-typescript-upload@mainUploads a coverage report to Codecov. Language-agnostic wrapper around codecov/codecov-action. For TypeScript/Jest projects, use codecov-typescript-upload for sensible defaults.
| Input | Required | Default | Description |
|---|---|---|---|
files |
yes | (none) | Coverage report file(s) to upload (glob). |
working-directory |
no | . |
Directory to run in. Coverage file paths are relative to this directory. |
flags |
no | (empty) | Codecov flags to apply to the upload (e.g. unit, integration). |
token |
no | (empty) | Codecov upload token. Not needed for public repos. |
fail-ci-if-error |
no | false |
When true, fails the CI step if the upload fails. |
This action has no outputs; success or failure is reported through the step exit code, although Codecov upload failures do not fail the step by default (set fail-ci-if-error to true to change that).
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/test@main
with:
command: make test-coverage
- uses: couimet/github-actions/codecov-upload@main
with:
files: build/coverage/lcov.infoPosts a PR comment with Jest coverage summaries and optional JUnit test stats. Wraps MishaKav/jest-coverage-comment with monorepo auto-discovery: when coverage-summary-path is not set, the action discovers all coverage-summary.json files under working-directory (excluding node_modules) and maps each to a per-package section in the comment. On subsequent pushes, the same comment is updated rather than creating duplicates.
The consuming workflow's job needs pull-requests: write in its permissions: block.
| Input | Required | Default | Description |
|---|---|---|---|
coverage-summary-path |
no | (empty) | Path to a single coverage-summary.json file. When set, skips auto-discovery and uses this file directly. |
create-new-comment |
no | false |
When true, creates a new comment on every push. When false, updates the existing comment. |
github-token |
yes | (none) | GitHub token for posting PR comments. Pass secrets.GITHUB_TOKEN from the consuming workflow. |
junitxml-path |
no | (empty) | Path to a JUnit XML file for test stats in the comment. Requires jest-junit in the consuming project's Jest config. |
title |
no | Coverage Report |
Title for the PR comment. In monorepos, per-package section titles are auto-derived from file paths. |
working-directory |
no | . |
Directory to search for coverage-summary.json files. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/setup-node-pnpm@main
- uses: couimet/github-actions/install-deps@main
- uses: couimet/github-actions/test@main
- uses: couimet/github-actions/coverage-comment@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}Outputs whether the head commit subject matches a configured auto-fix commit message. This is the recursion guard the typescript-ci-checks workflow uses to skip its own fix commits: read outputs.is-auto-fix and skip the auto-fix step when it is 'true'.
| Input | Required | Default | Description |
|---|---|---|---|
commit-message |
yes | (none) | Commit subject that marks a commit as an auto-fix commit. |
outputs.is-auto-fix is 'true' when the latest commit subject matches commit-message.
steps:
- uses: actions/checkout@v4
- uses: couimet/github-actions/detect-auto-fix-commit@main
id: guard
with:
commit-message: 'chore: auto-fix [skip ci]'Runs a format command. Defaults to pnpm format; override command for non-pnpm projects (e.g., command: make fmt). The step fails if any file needs formatting.
| Input | Required | Default | Description |
|---|---|---|---|
command |
no | pnpm format |
Command to run for formatting. |
working-directory |
no | . |
Directory to run the command in. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/setup-node-pnpm@main
- uses: couimet/github-actions/install-deps@main
- uses: couimet/github-actions/format@mainBlocks PRs from merging pre-release versions to main. Compares base and head SHAs for pre-release semver patterns (e.g., 0.1.0-alpha.1) in changed package.json files. The step fails if any pre-release version is found.
| Input | Required | Default | Description |
|---|---|---|---|
base-ref |
no | github.event.pull_request.base.sha |
Base ref for diff comparison. |
head-ref |
no | github.event.pull_request.head.sha |
Head ref for diff comparison. |
working-directory |
no | . |
Directory to run in. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
- uses: couimet/github-actions/guard-versions@mainRestores the pnpm store from cache and runs pnpm install --frozen-lockfile.
| Input | Required | Default | Description |
|---|---|---|---|
working-directory |
no | . |
Directory containing package.json and pnpm-lock.yaml. |
| Output | Description |
|---|---|
cache-hit |
true when the pnpm store cache was restored from an exact key match. |
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/setup-node-pnpm@main
- uses: couimet/github-actions/install-deps@mainRuns a lint command. Defaults to pnpm lint; override command for non-pnpm projects (e.g., command: eslint . --max-warnings 0). The step fails on any lint error.
| Input | Required | Default | Description |
|---|---|---|---|
command |
no | pnpm lint |
Command to run for linting. |
working-directory |
no | . |
Directory to run the command in. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/setup-node-pnpm@main
- uses: couimet/github-actions/install-deps@main
- uses: couimet/github-actions/lint@mainLints Markdown files with markdownlint-cli2 at a pinned npm version. The action honors the consuming repo's own .markdownlint-cli2.* / .markdownlint.* config via markdownlint-cli2 auto-discovery (searched in or under the working directory). When the repo has no such config, the action falls back to the canonical @couimet/markdownlint-config (an npm dependency of the action, Dependabot-tracked), which enforces MD060 table-column style aligned instead of cli2's built-in defaults. To opt out of the fallback or extend the shared defaults, add your own .markdownlint-cli2.* / .markdownlint.* config extending @couimet/markdownlint-config; your config stays the authority. MD060 aligned violations are not auto-fixable by markdownlint-cli2 --fix, so in a configless repo mode: fix reports them but cannot repair the tables. In the default check mode the step fails on any lint error; set mode: fix to run markdownlint-cli2 --fix and fix issues in place instead.
In fix mode the action auto-registers the custom rule markdownlint-rule-force-align-table-columns (MD060A) when the effective config is an eligible JSON or JSONC file. JS-module configs, unparseable configs, and unresolved-rule fallbacks do not register the rule. A consumer config that extends @couimet/markdownlint-config can be just { "config": { "extends": "@couimet/markdownlint-config" } } and --fix will still re-align tables. The rule is injected into a temporary effective config written next to the consumer's config; the source config is never modified. Check mode is unaffected, because MD060A reports on every misaligned table even when MD060 is not configured.
| Input | Required | Default | Description |
|---|---|---|---|
config |
no | (empty) | Path to a config file passed as --config. When empty, the repo's own .markdownlint-cli2.* / .markdownlint.* config is the authority via auto-discovery (in or under the working directory, supports split configs); with no repo config, falls back to the bundled @couimet/markdownlint-config. |
markdownlint-version |
no | (empty) | Version of markdownlint-cli2. When empty, uses the version from package.json (Dependabot-tracked). Set to override. |
mode |
no | check |
check performs a read-only lint (the default); fix runs markdownlint-cli2 --fix to fix issues in place. |
paths |
no | **/*.md |
Space-separated glob(s) of Markdown files to lint. |
working-directory |
no | . |
Directory to run markdownlint in. Set when the target lives in a subdirectory. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
- uses: couimet/github-actions/markdownlint@mainChecks formatting with Prettier at a pinned npm version. The action honors the consuming repo's .prettierrc*, prettier.config.*, "prettier" key in package.json, and .prettierignore — defaulting to . paths lets the ignore file scope the check. When the repo has no .prettierrc*, prettier.config.*, or "prettier" key in package.json, the action falls back to the canonical @couimet/eslint-config/prettier config (an npm dependency of the action, Dependabot-tracked), which also enforces package.json key sorting via prettier-plugin-packagejson. In the default check mode the step fails when any file needs formatting; set mode: fix to run prettier --write and fix files in place instead.
| Input | Required | Default | Description |
|---|---|---|---|
config |
no | (empty) | Path passed as --config. When empty, Prettier auto-discovers .prettierrc* / prettier.config.* / "prettier" key in package.json in the consuming repo; with no repo config, falls back to the canonical @couimet/eslint-config/prettier config. |
mode |
no | check |
check runs prettier --check (read-only, the default); fix runs prettier --write to fix files in place. |
paths |
no | . |
Space-separated path(s) passed to prettier; the consuming repo's .prettierignore governs exclusions. |
prettier-version |
no | (empty) | Version of prettier. When empty, uses the version from package.json (Dependabot-tracked). Set to override. |
working-directory |
no | . |
Directory to run Prettier in. Set when the target lives in a subdirectory. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/prettier@mainPosts a sticky PR comment. Thin wrapper around marocchino/sticky-pull-request-comment so repos avoid duplicating the version pin and wiring across CI pipelines. The PR number defaults to the current pull request event; override pr-number for non-PR workflows or manual discovery.
The consuming workflow's job needs pull-requests: write in its permissions: block.
| Input | Required | Default | Description |
|---|---|---|---|
comment-file |
yes | (none) | Path to a markdown file containing the comment body. |
github-token |
yes | (none) | GitHub token for posting the comment. Pass secrets.GITHUB_TOKEN from the consuming workflow. |
header |
yes | (none) | Unique header that identifies the comment across re-runs (enables sticky update behavior). |
pr-number |
no | (empty) | PR number. When empty, defaults to github.event.pull_request.number. |
create-new |
no | 'false' |
When true, append a unique suffix to the header so each run creates a new comment instead of updating a previous one. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Build comment body
run: scripts/build-comment.sh > /tmp/comment-body.md
- uses: couimet/github-actions/publish-pr-comment@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
header: my-comment
comment-file: /tmp/comment-body.mdPosts a @coderabbitai full review comment on a pull request to trigger a fresh CodeRabbit full review. Companion action for the rabbit-maximizer-restacker workflow, also usable standalone. The action posts with the run's github.token by default; pass a PAT via the github-token input to post on another repository.
The consuming workflow's job needs pull-requests: write in its permissions: block.
| Input | Required | Default | Description |
|---|---|---|---|
github-token |
no | run's github.token |
GitHub token for posting the review request comment. Pass a PAT when targeting another repository. |
repo |
no | current repo | Repository to post the comment on (owner/repo). Defaults to the current repository. |
pr-number |
yes | (none) | Pull request number to post the review request on. |
trigger |
no | workflow |
What triggered this review request (e.g., rabbit-maximizer-restacker, workflow-dispatch). Included in comment metadata for debugging. |
metadata |
no | {} |
Additional JSON object metadata to include in the comment. Merged into the hidden metadata block. trigger/timestamp keys in metadata are overridden by the action's own values. |
| Output | Description |
|---|---|
comment-id |
GitHub comment ID of the posted review request |
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/request-coderabbit-full-review@main
with:
pr-number: ${{ github.event.pull_request.number }}
trigger: base-changed-to-main
metadata: |
{
"previous_base": ${{ toJSON(github.event.changes.base.ref.from) }}
}Installs BATS and its helper libraries. Thin wrapper around bats-core/bats-action pinned to a commit SHA, so bats-test and shell-coverage share one pin instead of duplicating the install step. Adopt it directly only when you need BATS without either action.
The bats-version default tracks the local brew stable, so a CI run and a local run use the same BATS release.
| Input | Required | Default | Description |
|---|---|---|---|
assert-install |
no | true |
Install the bats-assert helper library. |
bats-version |
no | 1.14.0 |
BATS version installed by bats-core/bats-action. Pinned so CI matches the local stable. |
detik-install |
no | false |
Install the bats-detik helper library. |
file-install |
no | false |
Install the bats-file helper library. |
support-install |
no | true |
Install the bats-support helper library. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/setup-bats@mainInstalls mise and the tools pinned in the consuming repo's mise.toml. Thin wrapper around jdx/mise-action so repos avoid duplicating the version pin and wiring across CI pipelines.
| Input | Required | Default | Description |
|---|---|---|---|
install |
no | true |
Run mise install for the tools declared in mise.toml. |
cache |
no | true |
Read and write the tool cache between runs. |
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/setup-mise@mainInstalls Node.js (reading the version from the consuming repo's .nvmrc unless overridden) and activates pnpm via Corepack from the consuming repo's package.json packageManager field.
| Input | Required | Default | Description |
|---|---|---|---|
node-version |
no | (reads .nvmrc) |
Overrides .nvmrc when set. |
working-directory |
no | . |
Directory containing the package.json whose packageManager field Corepack should resolve. Set this when package.json lives in a subdirectory (e.g. a test fixture). |
| Output | Description |
|---|---|
node-version |
Resolved Node.js version. |
pnpm-version |
Resolved pnpm version (from Corepack). |
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/setup-node-pnpm@mainLints shell scripts with shellcheck (preinstalled on GitHub-hosted Ubuntu runners). Scripts are discovered via find with configurable extensions and exclusions. The step fails on any lint error.
| Input | Required | Default | Description |
|---|---|---|---|
exclude |
no | .claude-work .history node_modules .git |
Space-separated path fragments excluded from the find. |
extensions |
no | sh bash |
Space-separated file extensions to lint. An empty value lints all regular files under paths. |
paths |
no | . |
Root to search for shell scripts. |
severity |
no | (empty) | Passed as --severity when set (e.g. warning, error). |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/shellcheck@mainRuns a test command. Defaults to pnpm test; override command for non-pnpm projects. The step fails if any test fails.
| Input | Required | Default | Description |
|---|---|---|---|
command |
no | pnpm test |
Command to run for testing. |
working-directory |
no | . |
Directory to run the command in. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/setup-node-pnpm@main
- uses: couimet/github-actions/install-deps@main
- uses: couimet/github-actions/test@mainOne-step CI for TypeScript projects. Bundles frequently-used CI steps into a single composite action. The input table below lists every bundled step and its toggle input. Use typescript-ci for the common case; use the individual actions when you need fine-grained control over step ordering, caching, or per-step timing.
The coverage-comment step posts a PR comment with Jest coverage summaries and optional test stats. It only runs on pull_request events. The consuming workflow's job needs pull-requests: write in its permissions: block.
When auto-fix-command is set, an auto-fix step runs after format and lint when either fails and commits the fixes back to the branch; the step only runs when the format or lint step itself failed, so unrelated build, test, or setup failures never trigger it. The consuming job must check out the PR head ref with persist-credentials: true and grant contents: write in its permissions: block. The step only runs on same-repository pull_request events, so fork PRs are skipped.
| Input | Required | Default | Description |
|---|---|---|---|
auto-fix-command |
no | (empty) | Command to run for auto-fixing after format and lint (e.g., pnpm format:fix && pnpm lint:fix). When empty (the default), auto-fix is disabled; when set, runs an auto-fix step on same-repository pull_request events. Requires the PR head ref checked out with persist-credentials: true and contents: write; fork PRs are skipped. |
build-command |
no | pnpm build |
Command to run for building. |
check-no-prerelease-deps |
no | true |
Whether to check for prerelease dependency patterns in package.json. |
check-todos |
no | true |
Whether to count TODOs and FIXMEs. On PRs, reports the delta vs the base branch. |
coverage-comment |
no | true |
Whether to post a coverage report as a PR comment after tests. Requires pull-requests: write on the job. |
format-command |
no | pnpm format |
Command to run for formatting. |
guard-versions |
no | true |
Whether to run guard-versions (block pre-release versions on main). |
lint-command |
no | pnpm lint |
Command to run for linting. |
node-version |
no | (reads .nvmrc) |
Node.js version override. When empty, reads .nvmrc from the consuming repo. |
test-command |
no | pnpm test |
Command to run for testing. |
working-directory |
no | . |
Directory containing package.json. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/typescript-ci@mainTo enable auto-fix, check out the PR head ref with persisted credentials (the job needs permissions: contents: write):
steps:
- uses: actions/checkout@v4
with:
persist-credentials: true
ref: ${{ github.event.pull_request.head.ref }}
- uses: couimet/github-actions/typescript-ci@main
with:
auto-fix-command: pnpm format:fix && pnpm lint:fixFor Turborepo monorepos, define root-level pnpm scripts that match the defaults so no overrides are needed (as done in ts-npm-packages):
When a command doesn't match the pnpm <name> convention, use the override inputs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/typescript-ci@main
with:
build-command: pnpm compileValidates that links in Markdown (or any text) files resolve; the step fails when a link is broken. The checker never contacts a private, link-local, or loopback target: hosts in the RFC1918 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), loopback (127.0.0.0/8 and ::1), link-local (169.254.0.0/16 and fe80::/10), unique-local (fc00::/7), and the localhost hostname are excluded from checking, so docs that reference internal hosts for local development still pass. When a github-token is supplied and the action runs on a pull request, it posts an informational comment listing the excluded private, link-local, or loopback URLs it encountered but did not test; on other events it logs them to the step log instead. Hidden directories are included in ** globs (so .github/ and .claude/ markdown is covered), and vendored node_modules markdown is excluded so the check stays on the project's own docs. Link resolution is backed by lychee at the pinned version in versions.mk. A lychee.toml in the working directory is honored, so a repository can exclude a host that answers CI with a status that does not reflect a broken link; this repository excludes npmjs.com that way, because it returns 403 to the runner for pages that exist.
| Input | Required | Default | Description |
|---|---|---|---|
lychee-version |
no | 0.24.2 |
Version of the underlying lychee link checker to install, in vX.Y.Z or X.Y.Z form. The default is the pinned LYCHEE_VERSION in versions.mk; set to override. |
paths |
no | **/*.md |
Space-separated paths or globs to check. Globs are resolved by the checker, not the shell. |
working-directory |
no | . |
Directory to run the check in. Set when the target lives in a subdirectory. |
github-token |
no | (empty) | GitHub token for posting the PR comment listing excluded private, link-local, or loopback targets. Pass secrets.GITHUB_TOKEN. |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
- uses: couimet/github-actions/validate-links@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}Validates a YAML file against a JSON Schema file. Uses Python (jsonschema + pyyaml) managed by uv for reproducible dependency resolution. The step fails if the YAML file does not conform to the schema. When github-token is provided, a validation failure also posts a sticky PR comment with the error output; the comment persists until a later failure run updates it.
| Input | Required | Default | Description |
|---|---|---|---|
schema |
yes | (none) | Path to the JSON Schema file. |
file |
yes | (none) | Path to the YAML file to validate. |
github-token |
no | (empty) | GitHub token for posting PR comments. Pass secrets.GITHUB_TOKEN. |
header |
no | YAML validation check |
Unique header that identifies the PR comment across re-runs (sticky update). |
This action has no outputs; success or failure is reported through the step exit code.
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: couimet/github-actions/validate-yaml@main
with:
schema: path/to/schema.json
file: path/to/data.yml
github-token: ${{ secrets.GITHUB_TOKEN }}A reusable workflow (on: workflow_call) never runs on its own, so it never produces a bare status check. When a job in your workflow calls one, GitHub reports each job inside the reusable workflow as a status check named {caller job} / {inner job}: the caller job's name: if it sets one, otherwise its job id, joined to the inner job's name-or-id with / (spaces around the slash included). None of this repo's reusable workflows names its inner jobs, so the checks they emit use the job ids as written in the workflow files, for example ci / format for a caller job ci.
Adopting one of the workflows below is therefore a branch-protection change, not just a CI change. If your default branch requires checks by their old names (for example bare shellcheck and bats-test from previously inline jobs), those contexts stop being reported the moment the reusable workflow runs, and GitHub holds every PR at "Expected — Waiting for status to be reported" even while all reported checks are green. Update the default branch's required checks to the new {caller job} / {inner job} names in the same change that adopts the workflow; do not merge the adoption first.
Each workflow section below lists the exact checks it produces for its example caller job. Two things to keep straight when you configure branch protection:
- GitHub matches a required check against the literal context string, spaces around the
/included. Confirm the exact names from a run on your own PR rather than deriving them from the UI, which may display the caller segment differently from the context string. - Jobs you disable with a workflow's boolean inputs (for example
markdownlint: false) stop reporting, so drop their required checks too or merges block again.
Run scripts/required-checks.sh inside a consumer repo to diff its current required checks against the checks these workflows produce before merging.
Listed alphabetically.
Reusable workflow for projects that only need four per-command jobs (format, lint, build, test) as separate checks, without the Node/pnpm conveniences of typescript-ci-checks. The caller supplies the commands through the required format, lint, build, and test inputs; each command runs in its own job.
| Input | Required | Default | Description |
|---|---|---|---|
runs-on |
no | ubuntu-latest |
Runner image for the four jobs. |
working-directory |
no | . |
Directory the check commands run in. |
setup |
no | (empty) | Command run before the check command in each job (for example corepack enable). |
format |
yes | - | Format check command. |
lint |
yes | - | Lint command. |
build |
yes | - | Build command. |
test |
yes | - | Test command. |
build-artifact-paths |
no | (empty) | Glob patterns of build output to pass from build to test; empty disables the upload/download. |
| Secret | Required | Description |
|---|---|---|
github-token |
no | Optional token exported as GITHUB_TOKEN for the four check commands. |
jobs:
ci:
uses: couimet/github-actions/.github/workflows/ci-checks.yml@main
with:
format: pnpm format
lint: pnpm lint
build: pnpm build
test: pnpm testThe test job depends on build (needs: [build]), so a failing build surfaces the failure fast while test still reports (skipped). Each job appears as a separate check named {caller job} / {job}. Required status checks (caller job ci):
ci / format
ci / lint
ci / build
ci / test
Adopting this workflow is a branch-protection change, not just a CI change: if your default branch required these checks under different names, update them in the same change that adopts the workflow. See Branch protection and reusable workflows.
Reusable workflow that requests a fresh CodeRabbit full review when a PR's base branch changes to main. Incubated in couimet/rabbit-maximizer; the caller keeps the pull_request_target trigger.
The job runs only when the event payload has changes.base (base was edited) and the PR's new base is main; otherwise it skips.
The calling job must grant pull-requests: write (the action posts with the run's github.token).
on:
pull_request_target:
types: [edited]
jobs:
restack:
permissions:
pull-requests: write
uses: couimet/github-actions/.github/workflows/rabbit-maximizer-restacker.yml@mainThe workflow takes no inputs or secrets; it reads github.event.pull_request.number and github.event.changes.base.ref.from from the caller's event context. This workflow only posts a comment and is not a merge gate: its restack job reports a check when it runs, but a consumer should not add it to required checks.
Reusable workflow for shell script projects. Runs shellcheck and bats-test as separate jobs so each produces its own PR status check. Use this when you want per-step visibility in the PR status section; call the leaf actions directly in a single job when you prefer fewer runner minutes and a single check entry. A composite action variant can be added later if single-step embedding is wanted.
| Input | Required | Default | Description |
|---|---|---|---|
paths |
no | . |
Root to search for shell scripts. |
extensions |
no | sh bash |
Space-separated file extensions to lint. An empty value lints all regular files under paths. |
exclude |
no | .claude-work .history node_modules .git |
Space-separated path fragments excluded from the find. |
severity |
no | (empty) | Passed as --severity when set (e.g. warning, error). |
shellcheck-timeout-minutes |
no | 360 |
Maximum minutes the shellcheck job may run before GitHub cancels it. |
test-directory |
no | bats-tests/ |
Directory containing .bats test files. |
bats-version |
no | 1.14.0 |
BATS version installed; pinned so CI matches the local brew stable. |
recursive |
no | true |
Recurse into subdirectories of test-directory. |
support-install |
no | true |
Install the bats-support helper library. |
assert-install |
no | true |
Install the bats-assert helper library. |
detik-install |
no | false |
Install the detik helper library. |
file-install |
no | false |
Install the bats-file helper library. |
formatter |
no | (empty) | Passed as --formatter (e.g. tap, junit); empty uses the default pretty output. |
publish-comment |
no | true |
Post a sticky PR comment with test result counts. Set to false to opt out. |
comment-header |
no | BATS Test Results |
Unique header that identifies the BATS comment across re-runs (sticky update). |
bats-test-timeout-minutes |
no | 360 |
Maximum minutes the bats-test job may run before GitHub cancels it. |
The bats-test job posts a sticky PR comment when publish-comment is true; the caller's job needs pull-requests: write in its permissions: block.
jobs:
shell-checks:
uses: couimet/github-actions/.github/workflows/shell-ci-checks.yml@main
permissions:
contents: read
pull-requests: writeEach job runs in parallel and appears as a separate status check named {caller job} / {job} (job ids joined by /). Required status checks (caller job shell-checks):
shell-checks / shellcheck
shell-checks / bats-test
Adopting this workflow is a branch-protection change: if your default branch requires the bare shellcheck and bats-test contexts from previously inline jobs, update them to the shell-checks / ... names above in the same change or merges stay blocked on the old names. See Branch protection and reusable workflows.
Reusable workflow alternative to typescript-ci. Runs the same sub-actions as separate jobs so each produces its own PR status check. Use this when you want per-step visibility in the PR status section; use the composite typescript-ci action when you prefer fewer runner minutes and a single check entry.
The test job depends on build (needs: [build]). When the build fails, tests are skipped to surface the right failure fast. Pass compiled output (or any generated files) from build to test via build-artifact-paths to avoid recompiling in the test runner.
When auto-fix-command is set, an auto-fix job runs when format or lint fails, so it can fix the files that caused the failure, and commits the fixes back to the branch. By default the fix commit uses chore: auto-fix [skip ci] and is pushed with GITHUB_TOKEN, so no checks report on the fixed SHA and repos with required status checks stay merge-blocked until another commit lands. For the opt-in mode, pass the auto-fix-token secret (a PAT or GitHub App token) plus an auto-fix-commit-message without [skip ci]: the fix commit then re-triggers the pipeline so checks report green on the fixed SHA, while the auto-fix job skips its own commits by matching the head commit subject against the configured auto-fix-commit-message, and git-auto-commit-action no-ops when there is nothing to fix. The auto-fix job declares no static permissions: it inherits the calling job's grant, so read-only callers are unaffected and auto-fix only pushes when the caller opts in. To enable auto-fix, grant contents: write on the calling job's permissions and pass auto-fix-command; callers that stay on contents: read need no per-repo change.
| Input | Required | Default | Description |
|---|---|---|---|
auto-fix-command |
no | (empty) | Command to run for auto-fixing after format and lint (e.g., pnpm format:fix && pnpm lint:fix). When empty (the default), auto-fix is disabled and the job is skipped; when set, the calling job must grant contents: write. |
auto-fix-commit-message |
no | chore: auto-fix [skip ci] |
Commit message for the auto-fix commit. Keep the [skip ci] token unless paired with the auto-fix-token secret; the recursion guard matches the head commit subject against this exact message (single line). |
build-artifact-paths |
no | packages/*/out/ |
Multi-line globs of build output to pass from the build job to the test job. Set to an empty string to disable the artifact upload/download. |
build-command |
no | pnpm build |
Command to run for building. |
check-no-prerelease-deps |
no | true |
Whether to check for prerelease dependency patterns in package.json. |
check-todos |
no | true |
Whether to count TODOs and FIXMEs. On PRs, reports the delta vs the base branch. |
codecov-files |
no | coverage/lcov.info |
Coverage report file(s) to upload (glob). Passed through to codecov-typescript-upload. Useful for monorepos (e.g., packages/*/coverage/lcov.info). |
codecov-token |
no | (empty) | Codecov upload token for private repos and fork PRs. Prefer the identically named codecov-token secret instead; this input is kept as a fallback. |
codecov-upload |
no | true |
Whether to upload coverage to Codecov from the test job. |
coverage-comment |
no | true |
Whether to post a coverage report as a PR comment after tests. |
format-command |
no | pnpm format |
Command to run for formatting. |
guard-versions |
no | true |
Whether to run guard-versions (block pre-release versions on main). |
lint-command |
no | pnpm lint |
Command to run for linting. |
node-version |
no | (reads .nvmrc) |
Node.js version override. When empty, reads .nvmrc from the consuming repo. |
test-command |
no | pnpm test |
Command to run for testing. |
working-directory |
no | . |
Directory containing package.json. |
| Secret | Required | Description |
|---|---|---|
auto-fix-token |
no | Optional PAT or GitHub App token for the auto-fix push. When set, the fix commit can trigger a fresh CI run on the fixed SHA, paired with an auto-fix-commit-message that omits [skip ci]. |
codecov-token |
no | Codecov upload token. Use this instead of the input when passing secrets.CODECOV_TOKEN from the caller. Falls back to the input. |
jobs:
ci:
uses: couimet/github-actions/.github/workflows/typescript-ci-checks.yml@main
with:
working-directory: .
secrets:
codecov-token: ${{ secrets.CODECOV_TOKEN }}To enable auto-fix, grant contents: write on the calling job and pass auto-fix-command:
jobs:
ci:
permissions:
contents: write # auto-fix opt-in; otherwise keep contents: read
pull-requests: write
uses: couimet/github-actions/.github/workflows/typescript-ci-checks.yml@main
with:
working-directory: .
auto-fix-command: pnpm format:fix && pnpm lint:fixEach job runs in parallel and appears as a separate status check named {caller job} / {job}. Required status checks (caller job ci):
ci / format
ci / lint
ci / markdownlint
ci / build
ci / test
ci / guard-versions
ci / check-no-prerelease-deps
ci / check-todos
The auto-fix job also reports a check, but only on the fix run after a format or lint failure when auto-fix-command is set, so it is not a merge-gate context and is not listed above. Toggle off a job with its boolean input (e.g., guard-versions: false) and drop its required check too, or merges block again. Adopting this workflow is a branch-protection change: see Branch protection and reusable workflows. The coverage-comment step inside the test job only runs on pull_request events. Coverage is uploaded to Codecov by default; set codecov-upload: false to disable.
Development tools are provisioned with mise. Install mise, run mise install once to fetch the pinned tool set, and keep mise active in your shell so its shims resolve the tools we need. The tool set is declared in mise.toml, which is generated from .nvmrc and versions.mk by scripts/generate-mise-toml.sh; run that script after changing either source instead of editing mise.toml by hand.
| Target | What |
|---|---|
make check |
Run lint, test, and check-actions — the same gate CI runs on push. |
make fmt-check |
Check formatting with Prettier; exits non-zero if any file needs formatting. |
make format |
Apply Prettier formatting to all supported files. |
make install-prereqs |
Install the mise-managed dev tools via mise install and verify they resolve on PATH. |
make lint |
Run lint-md, fmt-check, and lint-sh. |
make lint-fix |
Run lint-md-fix and format. |
make test |
Run BATS shell tests. |
Fine-grained targets (check-actions, lint-md, lint-md-fix, lint-sh) are available for individual tool runs. Run make install-prereqs to fetch and verify your dev environment before make check; make check-actions also fails when mise.toml has drifted from its sources.
Consumers reference actions with @main for now, which keeps friction low while the action set is small and every consumer is under the same maintainer. No version tags exist yet. When the first stable release cycle warrants it, this repository will adopt per-action compound tags (setup-node-pnpm/v1.2.3, install-deps/v1.0.0, and so on). See docs/ADR/ for the rationale and the migration plan.
docs/ADR/— architectural decision records (versioning policy, scope decisions, design choices).