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
4 changes: 4 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
self-hosted-runner:
# No actionlint release knows the ubuntu-26.04 GitHub-hosted runner label yet.
labels:
- ubuntu-26.04
15 changes: 15 additions & 0 deletions .github/workflows/CHANGELOGS/run_infra_ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Run Infra CI Workflow Changelog

All notable changes to the **run_infra_ci** reusable workflow are documented in this file.

## 1.0.0

### Added

- First release of the `run_infra_ci` reusable workflow, with an optional `commit-identifier` input.
- Jobs `lint-gha` ("Lint GHA", actionlint with shellcheck) and `scan-gha` ("Scan GHA", zizmor), each
running the consuming repository's `lint:gha` or `scan:gha` mise task with GitHub annotation output.
- A contract check that fails with a README link when `mise.toml` or the job's task is missing.
- mise 2026.9.17 installed through `jdx/mise-action` 5.0.0, with the binary checksum pinned.
- zizmor runs offline (`scan:gha` uses `--offline`), so no token is needed.
- Job summaries with the result, the suppression count, and the local command.
77 changes: 77 additions & 0 deletions .github/workflows/READMES/run_infra_ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Run Infra CI

Lints GitHub Actions workflows with [actionlint](https://github.com/rhysd/actionlint) (and shellcheck)
and audits them with [zizmor](https://docs.zizmor.sh/), through tasks in the repo's `mise.toml`, so
CI and engineers run the same pinned tools.

## Usage

```yaml
jobs:
infra-ci:
name: Infra CI
needs: ci-context
if: ${{ needs.ci-context.outputs.should-run-ci == 'true' }}
uses: OpenSesame/core-github-actions/.github/workflows/run_infra_ci.yml@<released-sha> # workflows/run_infra_ci/1.0.0
permissions:
contents: read # Required to check out the workflows to lint and audit.
with:
commit-identifier: ${{ needs.ci-context.outputs.commit-identifier }}
```

Checks are reported as `Infra CI / Lint GHA` and `Infra CI / Scan GHA`. Add `infra-ci` to `ci-result`'s
needs.

## Setup

1. Commit this `mise.toml` at the repo root (infra tools only; Node stays on `.nvmrc`):

```toml
[tools]
"aqua:rhysd/actionlint" = "1.7.12"
"aqua:koalaman/shellcheck" = "0.11.0"
"aqua:zizmorcore/zizmor" = "1.29.0"

[tasks."lint:gha"]
run = "actionlint"

[tasks."scan:gha"]
run = "zizmor --offline .github"

[tasks.infra-ci]
depends = ["lint:gha", "scan:gha"]
```

2. Commit a lockfile for Macs and CI runners: `mise lock --platform linux-x64,macos-arm64`.
3. Declare the runner label actionlint doesn't know yet in `.github/actionlint.yaml`:

```yaml
self-hosted-runner:
labels:
- ubuntu-26.04
```

4. npm repos: add `"lint:gha": "mise run lint:gha"` and `"scan:gha": "mise run scan:gha"`. CI calls
specific scripts, never the `lint`/`scan` aggregates, so CI jobs don't need mise.

Run locally with `mise run infra-ci`. zizmor runs `--offline` locally and in CI: its online audits need a
token that can read the org's private action repos.

## Findings

Fix findings. Suppress only false positives or intentional patterns, one specific finding at a time,
each with a comment explaining why:

- actionlint: `.github/actionlint.yaml` `paths.<glob>.ignore`
- zizmor: inline `# zizmor: ignore[<rule>] <reason>`, or `.github/zizmor.yml` `rules.<rule>.ignore`

## Inputs

| Input | Default | Description |
| ------------------- | --------------------- | ----------------------------------- |
| `commit-identifier` | Triggering ref or SHA | Commit SHA, tag, or branch to check |

## Contribution

Update the workflow, README, and changelog together, and set a version label per
[VERSIONING.md](../../../VERSIONING.md).
15 changes: 9 additions & 6 deletions .github/workflows/internal_on_merge_tag_versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,30 @@ on:
pull_request:
types: [closed]

permissions:
contents: write
pull-requests: read
permissions: {}

jobs:
no-merge:
if: github.event.pull_request.merged == false
runs-on: ubuntu-26.04
permissions: {}
steps:
- name: No Merge Detected
run: |
const message = "Pull request was closed without merge. No version tags will be created."
message="Pull request was closed without merge. No version tags will be created."
echo "ℹ️ $message" >> "$GITHUB_STEP_SUMMARY"
echo "::notice title=No Merge::$message"

set-version-tags:
if: github.event.pull_request.merged == true
runs-on: ubuntu-26.04
permissions:
contents: write # Required to push the version tags.
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: true # Required for git push of the version tags.

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
Expand Down Expand Up @@ -60,7 +63,7 @@ jobs:
- name: Untracked Version Check
if: ${{ steps.get_tags.outputs.hasUntracked == 'true' }}
run: |
const message = "Untracked version label detected. Skipping tag creation."
message="Untracked version label detected. Skipping tag creation."
echo "ℹ️ $message" >> "$GITHUB_STEP_SUMMARY"
echo "::notice title=Untracked Version::$message"

Expand All @@ -69,7 +72,7 @@ jobs:
env:
COMPONENT_TAGS: ${{ steps.get_tags.outputs.componentTags }}
run: |
IFS=',' read -r -a tagsArray <<< "${{ env.COMPONENT_TAGS }}"
IFS=',' read -r -a tagsArray <<< "$COMPONENT_TAGS"
for tag in "${tagsArray[@]}"; do
git tag "$tag"
git push origin "$tag"
Expand Down
69 changes: 38 additions & 31 deletions .github/workflows/internal_on_pr_validate_component_version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@ on:
- unlabeled
- reopened

permissions:
contents: read
pull-requests: write
permissions: {}

jobs:
validate-version-labels:
name: Validate PR Version Labels
runs-on: ubuntu-26.04
permissions:
contents: read # Required to check out the changelogs and validation scripts.
pull-requests: write # Required to upsert the version summary comment.

steps:
- name: Checkout repo
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Get all labels from PR
id: get_labels
Expand Down Expand Up @@ -66,34 +69,36 @@ jobs:
INVALID_COMPONENTS: ${{ steps.validate.outputs.invalidComponents }}
MISSING_CHANGELOGS: ${{ steps.validate.outputs.missingChangelogs }}
run: |
echo "## Validation Outcome" >> "$GITHUB_STEP_SUMMARY"

if [ "${{ env.NO_LABELS }}" == "true" ]; then
echo "❌ No labels found on the PR. Add at least one version label." >> "$GITHUB_STEP_SUMMARY"
fi
{
echo "## Validation Outcome"

if [ "${{ env.IS_VALID }}" == "false" ]; then
echo "❌ Version label validation failed." >> "$GITHUB_STEP_SUMMARY"
if [ -n "${{ env.VALIDATION_MESSAGE }}" ]; then
echo "${{ env.VALIDATION_MESSAGE }}" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
fi
if [ -n "${{ env.INVALID_VERSION_LABELS }}" ]; then
echo "**Invalid version labels**: ${{ env.INVALID_VERSION_LABELS }}" >> "$GITHUB_STEP_SUMMARY"
if [ "$NO_LABELS" == "true" ]; then
echo "❌ No labels found on the PR. Add at least one version label."
fi
if [ -n "${{ env.INVALID_COMPONENTS }}" ]; then
echo "**Nonexistent/Invalid components**: ${{ env.INVALID_COMPONENTS }}" >> "$GITHUB_STEP_SUMMARY"

if [ "$IS_VALID" == "false" ]; then
echo "❌ Version label validation failed."
if [ -n "$VALIDATION_MESSAGE" ]; then
echo "$VALIDATION_MESSAGE"
echo ""
fi
if [ -n "$INVALID_VERSION_LABELS" ]; then
echo "**Invalid version labels**: $INVALID_VERSION_LABELS"
fi
if [ -n "$INVALID_COMPONENTS" ]; then
echo "**Nonexistent/Invalid components**: $INVALID_COMPONENTS"
fi
if [ -n "$MISSING_CHANGELOGS" ]; then
echo "**Components missing changelogs/entries**: $MISSING_CHANGELOGS"
fi
else
echo "✅ Version label validation passed"
fi
if [ -n "${{ env.MISSING_CHANGELOGS }}" ]; then
echo "**Components missing changelogs/entries**: ${{ env.MISSING_CHANGELOGS }}" >> "$GITHUB_STEP_SUMMARY"
echo -e "\n**Untracked Version**: $HAS_UNTRACKED_VERSION"
if [ -n "$COMPONENT_VERSION_LABELS" ]; then
echo "**Component Versions**: $COMPONENT_VERSION_LABELS"
fi
else
echo "✅ Version label validation passed" >> "$GITHUB_STEP_SUMMARY"
fi
echo -e "\n**Untracked Version**: ${{ env.HAS_UNTRACKED_VERSION }}" >> "$GITHUB_STEP_SUMMARY"
if [ -n "${{ env.COMPONENT_VERSION_LABELS }}" ]; then
echo "**Component Versions**: ${{ env.COMPONENT_VERSION_LABELS }}" >> "$GITHUB_STEP_SUMMARY"
fi
} >> "$GITHUB_STEP_SUMMARY"

- name: Write Tags Job Summary
id: tags_summary
Expand All @@ -107,7 +112,7 @@ jobs:
summary+="- No Tags will be created on main"
else
summary+="The following tags will be created on main after merge\n\n"
echo $VALID_COMPONENTS
echo "$VALID_COMPONENTS"
IFS=',' read -ra components <<< "$VALID_COMPONENTS"
for component_version in "${components[@]}"; do
summary+="🏷️ \`$component_version\`\n"
Expand All @@ -120,9 +125,11 @@ jobs:

# Replace any literal \n with real newlines for correct PR comment formatting
summary_with_eol="${summary//\\n/$'\n'}"
echo "summary<<EOF" >> "$GITHUB_OUTPUT"
echo "$summary_with_eol" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
{
echo "summary<<EOF"
echo "$summary_with_eol"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Write Tags to PR Comment
uses: ./.github/actions/upsert-pr-comment
Expand Down
16 changes: 13 additions & 3 deletions .github/workflows/internal_on_push_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,21 @@ jobs:

- name: Lint
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: npm run lint
run: npm run lint:js

- name: Check formatting
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: npm run format
run: npm run format:prettier

infra-ci:
name: Infra CI
needs: ci-context
if: ${{ needs.ci-context.outputs.should-run-ci == 'true' }}
uses: ./.github/workflows/run_infra_ci.yml
permissions:
contents: read # Required to check out the workflows to lint and audit.
with:
commit-identifier: ${{ needs.ci-context.outputs.commit-identifier }}

semgrep:
name: Semgrep
Expand All @@ -87,7 +97,7 @@ jobs:

ci-result:
name: CI Result
needs: [ci-context, app-ci, semgrep]
needs: [ci-context, app-ci, infra-ci, semgrep]
if: ${{ !cancelled() }}
runs-on: ubuntu-26.04
permissions: {}
Expand Down
Loading
Loading