Skip to content
RoleModelPublic

About

Shared Github Actions

Resources

Stars

0 stars

Watchers

3 watching

Forks

Repository files navigation

RoleModel/actions

Shared GitHub Actions

test-cleanup

Cleans up after test runs by uploading screenshots and dumping system information to stdout.

Inputs:

  • artifact-prefix*: A string prefix to name the uploaded log file. Artifact name will be named ${artifact-prefix}-logs

Example:

- name: Cleanup
  uses: RoleModel/actions/test-cleanup@v3
  with:
    artifact-prefix: rspec-system

test-runtime-analyzer

Posts the runtime of the longest test files. This is important because the most granular parallel_tests goes is at the file level, so when running in parallel, the entire suite can never be quicker than the slowest file.

Inputs:

  • test-output-path*: Path to the test output file that holds runtimes
  • top-count: Number of slowest tests to show. Default: 10

Example:

- name: Analyze test runtimes
  uses: RoleModel/actions/test-runtime-analyzer@v3
  with:
    test-output-path: tmp/turbo_rspec_runtime.log

staging-auto-merge

Use this action to maintain a staging branch with the changes from all open PRs labeled Staging. This is recommended to be used with a workflow that auto-deploys the staging branch.

If a PR cannot be merged cleanly, the action comments on the PR with conflict details and removes the Staging label.

This action assumes the repository has already been cloned with ref: staging and fetch-depth: 0 set. Commits/pushes made using the default GitHub Actions access key do not trigger other actions, so make sure to generate a personal access token or SSH key so that staging auto-deploys can work.

Inputs:

  • github-token: GitHub token used to list/update pull requests. Default: ${{ github.token }}
  • primary-branch: Branch used as the reset base before staged merges. Default: main

Example:

name: Maintain staging branch

on:
  pull_request:
    types:
      - labeled
      - unlabeled
      - closed
      - synchronize

jobs:
  maintain_staging:
    if: (contains(github.event.pull_request.labels[*].name, 'Staging')) || (github.event.action == 'closed' && github.event.pull_request.merged) || (github.event.label.name == 'Staging')
    runs-on: ubuntu-latest
    timeout-minutes: 2

    steps:
      - name: Check out repository
        uses: actions/checkout@v7
        with:
          ref: staging
          fetch-depth: 0
          ssh-key: ${{ secrets.AUTOMATED_COMMITS_KEY }} # IMPORTANT: Use a custom key to allow triggering other workflows

      - name: Rebuild staging branch
        uses: RoleModel/actions/staging-auto-merge@v3

Release (reusable workflow)

.github/workflows/release.yml is a workflow_call reusable workflow that cuts a release end to end: version bump, changelog entry, version commit and tag, GitHub release, and publishing to RubyGems and/or npm with trusted publishing. Call it from a workflow with two triggers:

  • workflow_dispatch (the Actions tab) runs the bump job. It bumps the version, updates Gemfile.lock and package-lock.json if present, adds a changelog entry if there's a changelog, then commits, tags and pushes with a GitHub App token.
  • A v* tag push runs the release and publish jobs. That tag push comes from the bump job, or from someone pushing a tag by hand. A push made with the default GITHUB_TOKEN wouldn't trigger the workflow again, which is why the bump job uses an App token.
# .github/workflows/release.yml
name: Release

on:
  workflow_dispatch:
    inputs:
      bump-type:
        type: choice
        options: [patch, minor, major]
        default: minor
      changelog:
        description: Changelog bullets (separate with $>). Leave blank to use the Unreleased notes or draft them from the commits.
        type: string
  push:
    tags: ['v*']

permissions:
  contents: write
  id-token: write

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && 'bump' || github.ref }}
jobs:
  release:
    uses: RoleModel/actions/.github/workflows/release.yml@v3
    with:
      bump-type: ${{ inputs.bump-type }}
      changelog: ${{ inputs.changelog }}
      publish-gem: true
    secrets: inherit

Inputs

  • bump-type: major, minor, or patch. Default: minor
  • changelog: Bullets for the changelog entry, separated by $> or newlines, placed above any Unreleased notes. Blank uses just those notes, or with none, drafts the entry from the commits since the last release (see changelog-entry)
  • changelog-file: The changelog to update. Blank, or a file that doesn't exist, skips it. Default: CHANGELOG.md
  • version-file: File holding the version. Default: the single lib/*/version.rb, else package.json
  • app-client-id: Client ID of the GitHub App that pushes the version commit and tag. Default: vars.RMS_VERSION_MANAGER_CLIENT_ID
  • publish-gem: Publish to RubyGems. Default: false
  • publish-npm: Publish to npm. Default: false

Secrets

  • app-private-key: Private key of the GitHub App. Default: secrets.RMS_VERSION_MANAGER_CERT

The defaults are the org's Version Manager App, which must be installed on the repository, with the org secret visible to it. A reusable workflow only sees the secrets it's handed, so the default key needs secrets: inherit, which passes the caller's secrets along. A repository with its own App passes both app-client-id and app-private-key instead.

Trusted publishers

Neither registry checks its trusted publisher configuration when you save it, so a mistake shows up as a failed publish job. Fix the configuration and re-run that job; the tag and GitHub release are already in place.

  • RubyGems: repository = the gem's repository, workflow filename = release.yml, Workflow Repository Owner = RoleModel, Workflow Repository Name = actions. RubyGems checks the reusable workflow that actually publishes.
  • npm: repository = the package's repository, workflow filename = the calling workflow's file in that repository. npm checks the caller. The job installs Node 24, since trusted publishing needs npm 11.5.1 or later.

bump-version, changelog-entry & push-version-tag

The composite actions behind the release workflow, for a repository that needs its own steps in the version commit (the release workflow covers lockfiles and package.json). bump-version rewrites the version in a file and outputs it. changelog-entry records the release in the changelog. push-version-tag commits every tracked change as that version, tags it, and pushes the commit and tag atomically, so a rejected branch push never leaves a published tag behind. Anything else that belongs in the version commit (another app's lockfile, generated docs) goes in steps between them.

None of them checks out the code or picks a token: the push uses whatever credentials actions/checkout persisted. Pushes made with the default GITHUB_TOKEN do not trigger other workflows (e.g. one listening for v* tags), so use a GitHub App token or deploy key when something downstream should run.

bump-version

Bumps a Ruby VERSION = "x.y.z" constant, keeping its quote style, or a package.json "version" via npm version, which also updates package-lock.json. A gem with a package.json beside it gets both bumped, so the gem and its npm package release under one version. A major, minor or patch bump only applies to a plain MAJOR.MINOR.PATCH version. From a prerelease such as 2.0.0.beta2, pass the version you want instead.

Inputs:

  • bump*: major, minor, patch, or an explicit version (e.g. 2.0.0.rc1)
  • version-file: File holding the version. Default: the single lib/*/version.rb, else package.json

Outputs: version, previous-version

changelog-entry

Adds ## [v1.5.0] Oct 9, 2026 and its bullets, plus a [v1.5.0]: <release URL> link if the changelog keeps reference links. An ## Unreleased section becomes the entry, keeping its notes beneath any description bullets; otherwise the entry goes above the newest one. With neither a description nor Unreleased notes, it drafts the bullets from the subjects of the commits since the previous release, folding bot commits (Dependabot) into one "Update dependencies" line. With no history to draft from, it writes a placeholder bullet to fill in later, so a release never goes unrecorded.

Inputs:

  • version*: The version being released
  • description: Bullets, separated by $> or newlines, placed above any Unreleased notes. Default: just those notes, else drafted from commits
  • previous-version: The version released before this one, whose tag starts the drafted commits. Check out with fetch-depth: 0 so the history is there
  • changelog-file: Skipped if there's no such file. Default: CHANGELOG.md
  • tag-prefix: Default: v

push-version-tag

Inputs:

  • version*: The version being released
  • tag-prefix: Default: v
  • branch: Branch to push the version commit to. Default: ${{ github.ref_name }}
  • git-user-name / git-user-email: Commit and tag author. Default: the github-actions[bot] user

Example (an extra lockfile in the version commit):

steps:
  # ... app token, checkout with fetch-depth: 0, setup-ruby

  - uses: RoleModel/actions/bump-version@v3
    id: bump
    with:
      bump: ${{ inputs.bump }}

  - run: bundle install && (cd example_app && bundle lock)

  - uses: RoleModel/actions/changelog-entry@v3
    with:
      version: ${{ steps.bump.outputs.version }}
      previous-version: ${{ steps.bump.outputs.previous-version }}
      description: ${{ inputs.changelog }}

  - uses: RoleModel/actions/push-version-tag@v3
    with:
      version: ${{ steps.bump.outputs.version }}
      git-user-name: ${{ steps.app-token.outputs.app-slug }}[bot]
      git-user-email: ${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com

Rails CI (reusable workflow)

.github/workflows/rails-ci.yml is a workflow_call reusable workflow that runs an entire Rails app's CI — linting, Brakeman, dependency audit, asset compilation/caching, and parallel_rspec — as a single job. It supersedes the composite actions below: use it for new projects, and prefer migrating an existing ci.yml to it over adding to the composite-action setup, since it schedules the independent setup steps (Ruby/gems, apt packages, Node/Yarn, asset cache, Brakeman, Rubocop, ESLint, dependency audit, Project Stats) to overlap with background:/wait:/parallel: step syntax instead of running them sequentially across multiple jobs.

Inputs

  • apt_packages: Space-separated apt packages to install alongside setup (e.g. "libvips")
  • brakeman_version: Brakeman gem version to run via reviewdog (omit to skip, e.g. "8.0.5")
  • dependency_audit_command: Dependency audit command to run (omit to skip)
  • extra_env: Additional non-secret environment variables, one KEY=VALUE per line (same format as a .env file). Use this for anything the app requires that isn't already covered by an existing input. For real secret values, use the extra_secrets secret instead — secrets.* can't be referenced here since this is a with: input, evaluated before the caller's secrets are in scope.
  • failure-screenshot-dir: the directory where your test runner saves screenshots on failure. Default: tmp/capybara
  • js_lint_command: JS linting command to run (omit to skip)
  • playwright: Install the Playwright Chromium browser (for apps using capybara-playwright-driver). Default: false
  • postgres_version: Postgres Docker image version tag. Default: "17"
  • project-stats: Run bin/rails stats and post the results to the job summary. Default: true
  • rubocop_command: Rubocop command to run (omit to skip)
  • runner: Runner to use. Default: blacksmith-8vcpu-ubuntu-2404
  • timeout: Job timeout in minutes. Default: 20

Secrets

  • RAILS_MASTER_KEY: Rails master key for decrypting credentials
  • BUNDLE_RUBYGEMS__PKG__GITHUB__COM: Credential for the RoleModel RubyGems package registry
  • extra_secrets: Additional secret environment variables, one KEY=VALUE per line (same shape as extra_env). Pass real secret values through here, referencing the caller's own secrets context on the right-hand side of each line.

extra_env and extra_secrets exist for the same reason (app-specific env vars the workflow doesn't already name), but land differently: extra_env is a with: input, evaluated before the caller's secrets: context is in scope, so it can only carry non-secret KEY=VALUE lines. extra_secrets is a real secrets: value, so it's the one to use for anything sensitive — reference the caller's own secrets on the right-hand side, e.g. STRIPE_PRIVATE_KEY=${{ secrets.STRIPE_PRIVATE_KEY }}.

project-stats is a built-in input (default true) that publishes its own "Project Stats" GitHub check as part of the job. Callers of rails-ci.yml get this for free and don't need the separate standalone project-stats job shown in the composite-action example below.

Example ci.yml (basic)

jobs:
  ci:
    uses: RoleModel/actions/.github/workflows/rails-ci.yml@v3
    with:
      rubocop_command: bundle exec rubocop --format github
      dependency_audit_command: bin/bundler-audit && bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error
      failure-screenshot-dir: tmp/screenshots
    secrets: inherit

Example ci.yml (with apt packages, Playwright, and app-specific env)

jobs:
  ci:
    uses: RoleModel/actions/.github/workflows/rails-ci.yml@v3
    with:
      apt_packages: libvips
      playwright: true
      rubocop_command: bundle exec rubocop --format github
      js_lint_command: yarn lint
      dependency_audit_command: bin/bundler-audit
      brakeman_version: "8.0.5"
      failure-screenshot-dir: tmp/screenshots
      extra_env: |
        STRIPE_PUBLISHABLE_KEY=pk_test_placeholder
    secrets:
      RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
      BUNDLE_RUBYGEMS__PKG__GITHUB__COM: ${{ secrets.BUNDLE_RUBYGEMS__PKG__GITHUB__COM }}
      extra_secrets: |
        STRIPE_PRIVATE_KEY=${{ secrets.STRIPE_PRIVATE_KEY }}

Shared workflow actions

This is a combination of multiple composite actions that can be used to run your entire CI flow for a rails app using parallel_tests. Each action allow you to customize the machine, environment variables, and any custom install steps that are needed. It does require you to check out the code yourself, since some install steps might happen after that.

Inputs, by composite action name

  • linting-and-non-system-tests:
    • linting_step_required
      • Whether linting is required
      • Optional, default is false
    • linting_step_command
      • Command for linting (e.g., "bundle exec rubocop")
      • Optional, default is ''
    • needs-compiled-assets
      • Whether to retrieve the Rails asset cache (set to false if your workflow does not depend on the compile-assets workflow)
      • Optional, default is true
  • system-tests:
    • web-driver
      • Supported values are 'selenium' and 'playwright'
      • Optional, default is selenium
    • failure-screenshot-dir
      • the directory where your test runner saves screenshots on failure
      • Optional, default is tmp/capybara

Example ci.yml workflow

name: CI
on:
  push:
    branches: [ main, master ]
  pull_request:

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number }}-${{ github.event.ref }}
  cancel-in-progress: true

env:
  CI: true
  RAILS_ENV: test
  POSTGRES_USER: postgres
  POSTGRES_PASSWORD: password
  SECRET_KEY_BASE: 123 # If you are using encrypted credentials, you'll need to extract this into a GitHub secret.
                       # Otherwise, the only thing that matters is that the value is not nil.

jobs:
  compile-assets:
    name: Compile Assets
    runs-on: blacksmith-4vcpu-ubuntu-2204
    timeout-minutes: 5
    steps:
      - name: Checkout Code
        uses: actions/checkout@v7

      - uses: RoleModel/actions/compile-assets@v3
        id: check-asset-cache

  non-system-test:
    name: Linting & Ruby Non-System Tests
    runs-on: blacksmith-8vcpu-ubuntu-2204
    timeout-minutes: 5
    # If you have non-system tests that touch the browser, you may need to uncomment the following line.
    # needs: compile-assets
    services:
      postgres:
        image: postgres:17
        ports:
          - "5432:5432"
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: password

    steps:
      - name: Checkout Code
        uses: actions/checkout@v7

      - uses: RoleModel/actions/linting-and-non-system-tests@v3
        with:
          linting_step_required: true
          linting_step_command: bundle exec rubocop --fail-level warning --display-only-fail-level-offenses --format github
          needs-compiled-assets: false # Remove this input if you uncommented `needs: compile-assets` above.

  system-test:
    name: Ruby System Tests
    runs-on: blacksmith-16vcpu-ubuntu-2204
    timeout-minutes: 10
    needs: compile-assets
    services:
      postgres:
        image: postgres:17
        ports:
          - "5432:5432"
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: password

    steps:
      - name: Checkout Code
        uses: actions/checkout@v7

      # Custom install steps may be added between checkout & test run if needed. e.g.
      # If your application processes ActiveStorage::Variant records you probably need the following step:
      - run: |
          sudo apt-get update
          sudo apt-get install -y libvips

      - uses: RoleModel/actions/system-tests@v3
        with:
          failure-screenshot-dir: tmp/screenshots # if you've configured capybara to be compatible with the tmp:clear task
          web-driver: playwright # remove this input to use the default value (selenium)
  # completely optional job (for Rails projects only) - outputs a stats table in your workflow's summary page
  project-stats:
    name: Project Stats
    runs-on: blacksmith-4vcpu-ubuntu-2204
    timeout-minutes: 5
    steps:
      - name: Checkout Code
        uses: actions/checkout@v7

      - name: Capture Project Stats
        uses: RoleModel/actions/project-stats@v3

Versioning

This is using anothrNick/github-tag-action to automatically post version tags when a commit is merged to main. Unless you include #patch or #major in your commit message, it will automatically create a new tag a minor version higher. #none will skip tagging altogether.

About

Shared Github Actions

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages