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
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: "bundler"
directory: "/"
schedule:
interval: "weekly"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
124 changes: 108 additions & 16 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,121 @@
name: Ruby
name: CI

on:
push:
branches:
- master

branches: [main, master]
pull_request:

jobs:
build:
test:
runs-on: ubuntu-latest
name: Ruby ${{ matrix.ruby }}
name: Ruby ${{ matrix.ruby }} / ${{ matrix.gemfile }}
strategy:
fail-fast: false
matrix:
ruby:
- '3.3.5'

ruby: ['3.2', '3.3', '3.4', '3.5', '4.0']
gemfile:
- gemfiles/rails_7_2.gemfile
- gemfiles/rails_8_0.gemfile
- gemfiles/rails_8_1.gemfile
- gemfiles/rails_8_1_sprockets.gemfile
exclude:
# Rails 7.2 predates Ruby 4.0 — skip that pairing.
- ruby: '4.0'
gemfile: gemfiles/rails_7_2.gemfile
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
# Nokogiri ships precompiled native gems, but not for every Ruby.
# Force a source build when no matching binary exists (e.g. Ruby 3.5).
BUNDLE_FORCE_RUBY_PLATFORM: ${{ matrix.ruby == '3.5' && 'true' || '' }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@v1
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Run the default task
run: bundle exec rake
# `chromium-browser` on Ubuntu pulls the Snap transitional package and
# blocks CI when the snap store is unreachable. Google Chrome stable is
# a plain .deb from Google's APT repo — same engine, no snap.
- name: Install Google Chrome for Cuprite
run: |
set -eux
sudo apt-get update
sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg
# Non-interactive CI has no /dev/tty — gpg must run in batch mode.
curl -fsSL https://dl.google.com/linux/linux_signing_key.pub \
| sudo gpg --batch --yes --dearmor -o /usr/share/keyrings/google-chrome.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] https://dl.google.com/linux/chrome/deb/ stable main" \
| sudo tee /etc/apt/sources.list.d/google-chrome.list
sudo apt-get update
sudo apt-get install -y --no-install-recommends google-chrome-stable
- name: Run RSpec
run: bundle exec rspec

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: RuboCop
run: bundle exec rubocop --no-server

js-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Run bun tests
run: bun test

js-build-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Smoke-test the JS bundle
run: bun run build:check

# The committed app/assets/javascripts/modal_stack.js is the importmap
# entry point. Catch PRs that touched the JS source without rebuilding it.
bundle-freshness:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Rebuild bundle and check for diff
run: |
bin/build
git diff --exit-code app/assets/javascripts/modal_stack.js

# Single aggregator for branch protection — require this one check
# rather than every individual matrix job.
ci-success:
if: always()
needs: [test, lint, js-test, js-build-check, bundle-freshness]
runs-on: ubuntu-latest
env:
TEST_RESULT: ${{ needs.test.result }}
LINT_RESULT: ${{ needs.lint.result }}
JS_TEST_RESULT: ${{ needs.js-test.result }}
JS_BUILD_CHECK_RESULT: ${{ needs.js-build-check.result }}
BUNDLE_FRESHNESS_RESULT: ${{ needs.bundle-freshness.result }}
steps:
- name: Verify all required jobs passed
run: |
if [ "$TEST_RESULT" != "success" ] \
|| [ "$LINT_RESULT" != "success" ] \
|| [ "$JS_TEST_RESULT" != "success" ] \
|| [ "$JS_BUILD_CHECK_RESULT" != "success" ] \
|| [ "$BUNDLE_FRESHNESS_RESULT" != "success" ]; then
echo "One or more required jobs failed"
exit 1
fi
82 changes: 82 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Release

on:
push:
branches:
- main
tags:
- "v*"

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true

- name: Resolve release tag
id: tag
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
tag="${GITHUB_REF_NAME}"
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
echo "should_release=true" >> "$GITHUB_OUTPUT"
echo "create_tag=false" >> "$GITHUB_OUTPUT"
exit 0
fi

version="$(ruby -e 'load "./lib/modal_stack/version.rb"; puts ModalStack::VERSION')"
tag="v${version}"
echo "tag=${tag}" >> "$GITHUB_OUTPUT"

if git rev-parse --verify --quiet "refs/tags/${tag}" >/dev/null; then
echo "Tag ${tag} already exists — nothing to release."
echo "should_release=false" >> "$GITHUB_OUTPUT"
echo "create_tag=false" >> "$GITHUB_OUTPUT"
else
echo "should_release=true" >> "$GITHUB_OUTPUT"
echo "create_tag=true" >> "$GITHUB_OUTPUT"
fi

- name: Create and push tag
if: steps.tag.outputs.create_tag == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.tag.outputs.tag }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "Release $TAG"
git push origin "$TAG"

- name: Build gem
if: steps.tag.outputs.should_release == 'true'
run: gem build modal_stack.gemspec

- name: Create GitHub Release
if: steps.tag.outputs.should_release == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.tag.outputs.tag }}
run: |
gh release create "$TAG" \
--title "modal_stack $TAG" \
--generate-notes \
modal_stack-*.gem

- name: Configure RubyGems credentials (OIDC)
if: steps.tag.outputs.should_release == 'true'
uses: rubygems/configure-rubygems-credentials@main

- name: Publish to RubyGems
if: steps.tag.outputs.should_release == 'true'
run: gem push modal_stack-*.gem
15 changes: 12 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,25 @@
/doc/
/pkg/
/spec/reports/
/spec/tmp/
/spec/dummy/tmp/
/tmp/
/Gemfile.lock
gemfiles/*.gemfile.lock

# rspec failure tracking
.rspec_status

# Lockfile (gem libraries should resolve against host app deps)
Gemfile.lock

# JS toolchain
/node_modules/
bun.lock
bun.lockb
.DS_Store

# secrets (preventive — nothing to clean from history, keep it that way)
.env
.env.*
/config/master.key
/config/credentials/*.key
*.pem
*.key
45 changes: 42 additions & 3 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,51 @@
inherit_from: .rubocop_todo.yml

AllCops:
TargetRubyVersion: 3.2
NewCops: enable
SuggestExtensions: false
Exclude:
- "lib/generators/modal_stack/install/templates/initializer.rb"
- 'vendor/**/*'
- 'tmp/**/*'
- 'spec/dummy/**/*'
- 'lib/generators/**/templates/**/*'
- 'gemfiles/**/*'
- 'spec/tmp/**/*'

Style/StringLiterals:
EnforcedStyle: double_quotes

Style/StringLiteralsInInterpolation:
EnforcedStyle: double_quotes

Style/Documentation:
Enabled: false

Metrics/MethodLength:
Max: 25

Metrics/ClassLength:
Max: 200

Metrics/BlockLength:
Exclude:
- 'spec/**/*'
- '*.gemspec'

Layout/LineLength:
Max: 140

Style/HashSyntax:
EnforcedShorthandSyntax: either

Naming/AccessorMethodName:
Enabled: false

# Capybara matcher convention is have_X — the cop's preference for predicate
# methods (X?) is wrong for matcher DSLs.
Naming/PredicatePrefix:
Exclude:
- 'lib/modal_stack/capybara.rb'

# DSL helpers (modal_stack_container, modal_push, modal_replace) intentionally
# expose every layer option as a keyword arg. Don't count keyword args.
Metrics/ParameterLists:
CountKeywordArgs: false
Loading