Skip to content
Closed
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
16 changes: 16 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,23 @@ updates:
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: 7
commit-message:
prefix: "chore(deps)"
groups:
bundler:
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: 7
commit-message:
prefix: "chore(deps)"
groups:
github-actions:
patterns:
- "*"
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: CI Test
on: [push, pull_request]
# push_gem.yml dispatches this on release PRs, since pushes made with GITHUB_TOKEN don't trigger it.
on: [push, pull_request, workflow_dispatch]
permissions:
contents: read
jobs:
Expand Down
114 changes: 100 additions & 14 deletions .github/workflows/push_gem.yml
Original file line number Diff line number Diff line change
@@ -1,37 +1,123 @@
# release-please keeps a release PR open that bumps lib/singed/version.rb,
# singed's line in Gemfile.lock, and CHANGELOG.md from the Conventional Commits
# merged to main. Merging it creates the tag and GitHub release, and the release
# job then publishes the gem to RubyGems.org with trusted publishing (OIDC). To
# retry a failed publish, re-run the failed jobs of that workflow run.
#
# Publishing happens in this run rather than on `release: published`, because a
# release created with GITHUB_TOKEN doesn't trigger other workflows. The filename
# and the rubygems.org environment are what the trusted publisher on
# RubyGems.org is registered against, so don't rename either.
name: Push Gem

on:
workflow_dispatch:
push:
branches: [main]

permissions: {}

permissions:
contents: read
concurrency:
group: push-gem
cancel-in-progress: false

jobs:
push:
release-please:
name: Update the release PR or create a release
if: github.repository == 'rubyatscale/singed'
runs-on: ubuntu-latest
permissions:
contents: write # create the release tag and GitHub release
issues: write # create the autorelease labels, which pull-requests: write doesn't allow
pull-requests: write # open and update the release PR
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
prs_created: ${{ steps.release.outputs.prs_created }}
pr: ${{ steps.release.outputs.pr }}
steps:
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
id: release
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json

# release-please pushes the release PR with GITHUB_TOKEN, which doesn't trigger
# workflows, so its required checks would never report. GITHUB_TOKEN can
# trigger workflow_dispatch, and the dispatched runs report on the PR's head
# commit. Each workflow listed here needs a workflow_dispatch trigger.
release-pr-checks:
name: Run CI on the release PR
needs: release-please
if: needs.release-please.outputs.prs_created == 'true'
runs-on: ubuntu-latest
permissions:
actions: write # dispatch the CI workflows
steps:
- name: Dispatch CI on the release branch
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
PR: ${{ needs.release-please.outputs.pr }}
run: |
# release-please reports PR number 0 when it found nothing to change.
if [ "$(jq -r .number <<<"$PR")" = "0" ]; then
echo "No release PR changes to check."
exit 0
fi
branch=$(jq -r .headBranchName <<<"$PR")
for workflow in build.yml rubocop.yml sorbet.yml; do
gh workflow run "$workflow" --ref "$branch"
done

release:
name: Publish to RubyGems
needs: release-please
if: needs.release-please.outputs.release_created == 'true'
runs-on: ubuntu-latest

environment:
name: rubygems.org
url: https://rubygems.org/gems/singed

permissions:
contents: write
id-token: write
contents: read
id-token: write # trusted publishing

steps:
# Set up
- name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit

- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Build from the release tag, so a re-run publishes the tagged tree even after main has
# moved. `rake release` only pushes the branch and tag when the tag is missing locally,
# and release-gem fetches tags first, so it never pushes and needs no write access.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ needs.release-please.outputs.tag_name }}
persist-credentials: false
# No bundler cache here: a job that publishes the gem shouldn't restore one.
- name: Set up Ruby
uses: ruby/setup-ruby@2e007403fc1ec238429ecaa57af6f22f019cc135 # v1.234.0
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
with:
bundler-cache: true
ruby-version: ruby
ruby-version: "3.4"
# Frozen, so a Gemfile.lock that disagrees with the gemspec fails here with a clear error,
# rather than being rewritten and failing `rake release`'s clean-tree check.
- run: bundle install
env:
BUNDLE_FROZEN: "true"

# Release
- uses: rubygems/release-gem@9e85cb11501bebc2ae661c1500176316d3987059 # v1
- uses: rubygems/release-gem@7f9650160c1a4e7989fdc9855807bdbd421d8b6b # v1.4.1

notify_on_failure:
name: Notify on failure
needs: [release-please, release-pr-checks, release]
if: failure()
runs-on: ubuntu-latest
steps:
- uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
text: "${{ github.repository }} Push Gem workflow FAILED\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
3 changes: 2 additions & 1 deletion .github/workflows/rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: RuboCop

on: [push, pull_request]
# push_gem.yml dispatches this on release PRs, since pushes made with GITHUB_TOKEN don't trigger it.
on: [push, pull_request, workflow_dispatch]

permissions:
contents: read
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/sorbet.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Sorbet

on: [push, pull_request]
# push_gem.yml dispatches this on release PRs, since pushes made with GITHUB_TOKEN don't trigger it.
on: [push, pull_request, workflow_dispatch]

permissions:
contents: read
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/validate-pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# release-please derives the next version and the changelog from squash-merged
# commit titles, which GitHub takes from the PR title.
name: Validate PR Title

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions: {}

concurrency:
group: validate-pr-title-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
validate:
name: Validate PR Title
runs-on: ubuntu-latest
steps:
- name: Check Conventional Commits format
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
pattern="^(feat|fix|chore|docs|refactor|perf|test|ci|build|revert)(\([^()]+\))?(!)?: .+"
if ! grep -qE "$pattern" <<<"$PR_TITLE"; then
echo "::error::PR title does not follow Conventional Commits format."
echo ""
echo "Expected: <type>(<optional scope>): <description>"
echo ""
echo "Examples:"
echo " feat: add a Sidekiq middleware option for sampling"
echo " fix: stop flamegraph from swallowing exceptions"
echo " chore(deps): bump stackprof"
echo " revert: feat: add a Sidekiq middleware option for sampling"
echo ""
echo "Allowed types: feat, fix, chore, docs, refactor, perf, test, ci, build, revert"
echo "feat bumps the minor version; fix, perf and revert bump the patch version; the other types don't release."
echo "A ! after the type marks a breaking change, which bumps the minor version until 1.0."
exit 1
fi
echo "PR title is valid: $PR_TITLE"
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.3.0"
}
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ bin/tapioca gems
- `sorbet-runtime` isn't a dependency of the gem, so nothing under `lib/` may reference `T` at runtime. Use the RBS assertion comments (`#: Type`, `#: as !nil`, `#: as Type`, `#: as untyped`, `#: self as Type`) instead of `T.let`, `T.must`, `T.cast`, `T.unsafe` and `T.bind`.
- Apps that use Tapioca still run these signatures: Tapioca rewrites RBS comments into runtime-checked `sig`s while it loads the app. A signature must therefore only name constants that are loaded whenever its file is (not `ActiveSupport` in `lib/singed.rb`), and must accept every value an app can pass while booting, such as `flamegraph [:show, :index]` in a controller. Otherwise the app's `tapioca gem` or `tapioca dsl` run errors. The specs here don't load `sorbet-runtime`, so they can't catch this.
- Types the generated gem RBIs are missing go in `sorbet/rbi/shims/`.

## Pull requests

PR titles must follow Conventional Commits (`feat: ...`, `fix: ...`, `chore: ...`, with `!` for a breaking change). release-please uses the squash-merged title to pick the next version and write the changelog entry. Don't edit `lib/singed/version.rb` or `CHANGELOG.md` by hand; the release PR does that.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,9 @@ The `open` command is expected to be available.

- using [rbspy](https://rbspy.github.io/) directly
- using [stackprof](https://github.com/tmm1/stackprof) (a dependency of singed) directly

## Releasing

Releases are automated with [release-please](https://github.com/googleapis/release-please). PR titles must follow [Conventional Commits](https://www.conventionalcommits.org/), because the squash-merged title decides the next version and becomes the changelog entry. For example, `feat: ...` bumps the minor version, `fix: ...` bumps the patch version (as do `perf: ...` and `revert: ...`), and `chore: ...` doesn't release anything. A `!` after the type, or a `BREAKING CHANGE:` footer in the squash message, marks a breaking change, which bumps the minor version until 1.0. A check on each PR flags titles that don't follow the format.

release-please keeps a release PR open that bumps `lib/singed/version.rb` and `CHANGELOG.md`. Merging it tags the release, creates the GitHub release, and publishes the gem to RubyGems.org with trusted publishing.
1 change: 1 addition & 0 deletions lib/singed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

require "json"
require "stackprof"
require "singed/version"

module Singed
# Methods defined with plain `def` below are both module methods (Singed.start) and public
Expand Down
6 changes: 6 additions & 0 deletions lib/singed/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# typed: strict
# frozen_string_literal: true

module Singed
VERSION = "0.3.0"
end
13 changes: 13 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"packages": {
".": {
"release-type": "ruby",
"package-name": "singed",
"include-component-in-tag": false,
"changelog-path": "CHANGELOG.md",
"version-file": "lib/singed/version.rb",
"bump-minor-pre-major": true
}
}
}
4 changes: 3 additions & 1 deletion singed.gemspec
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# frozen_string_literal: true

require_relative "lib/singed/version"

Gem::Specification.new do |spec|
spec.name = "singed"

spec.version = "0.3.0"
spec.version = Singed::VERSION
spec.license = "MIT"
spec.authors = ["Josh Nichols"]
spec.email = ["josh.nichols@gusto.com"]
Expand Down
Loading