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
24 changes: 24 additions & 0 deletions .claude/hooks/rubocop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# PostToolUse hook: autocorrects the Ruby file Claude just wrote, then hands back what RuboCop cannot fix
# (exit 2 shows stderr to Claude). Files git ignores are left alone, such as vendored or cloned code.
set -u

file=$(jq -r '.tool_input.file_path // empty')
case "$file" in
"$CLAUDE_PROJECT_DIR"/*) ;;
*) exit 0 ;;
esac
case "$file" in
*.rb | *.rake | */Gemfile | */Rakefile | *.gemspec) ;;
*) exit 0 ;;
esac
[ -f "$file" ] || exit 0
cd "$CLAUDE_PROJECT_DIR" || exit 0
git check-ignore --quiet "$file" && exit 0

# Autocorrect, except for removing assignments: the next edit may well be the one that reads them.
bundle exec rubocop --autocorrect --server --force-exclusion --format quiet --except Lint/UselessAssignment "$file" >/dev/null 2>&1
if ! output=$(bundle exec rubocop --server --force-exclusion --no-color --format simple "$file" 2>&1); then
echo "$output" >&2
exit 2
fi
15 changes: 15 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/rubocop.sh"
}
]
}
]
}
}
26 changes: 26 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Bug report
description: A wrong sitemap, a file that should be rewritten and is not, a crash
labels: [bug]
body:
- type: textarea
id: code
attributes:
label: Source class and configuration
description: The smallest source class, configuration and records that reproduce it.
render: ruby
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected and actual output
description: The XML or the files you expected, what `Mapitaine.build` wrote instead, or the error and its backtrace.
validations:
required: true
- type: input
id: versions
attributes:
label: Versions
placeholder: Mapitaine 0.1.0 (or commit), Rails 8.1, Ruby 4.0.1, PostgreSQL 18
validations:
required: true
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Report a security vulnerability
url: https://github.com/levups/mapitaine/security/advisories/new
about: Privately, never in a public issue.
16 changes: 16 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Feature request
description: A sitemap Mapitaine cannot write yet, a storage it cannot reach
body:
- type: textarea
id: need
attributes:
label: What you need
description: The pages and sitemap you are trying to produce, and what stops you today.
validations:
required: true
- type: textarea
id: api
attributes:
label: How you would write it
description: The source class or configuration you would like to write.
render: ruby
5 changes: 5 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- Two short paragraphs: the problem, then the change; then 3 to 5 bullets, each with a bold lead-in.
Proof (tests, benchmarks) in a <details>. No headings. Link the issue it closes. -->

- [ ] Tests cover the change (a bug fix comes with a test that fails without it)
- [ ] Output unchanged, or every difference is intended and listed above
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# The actions are pinned by commit, not by tag, which their owner (or an attacker) can move: Dependabot proposes
# each new version, a week after its release, in one pull request. The same file in every Level UP Solutions gem.
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
actions:
patterns: ["*"]
112 changes: 112 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
name: CI

# The blocks shared by the Level UP Solutions gems are kept identical across the family.

on:
push:
branches: [main]
# A draft runs nothing: marking it ready for review starts the CI.
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
workflow_dispatch:

permissions:
contents: read

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

jobs:
rubocop:
name: RuboCop
if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@de492cf36cbab201753d720ce4f4e336cd688c94 # v1.336.0
with:
ruby-version: "4.0"
bundler-cache: true
- name: Lint with RuboCop
run: bundle exec rubocop --format github

test:
name: Ruby ${{ matrix.ruby }}${{ matrix.rails && format(' · Rails {0}', matrix.rails.version) || '' }}
if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
# Every Ruby the core team still maintains (https://endoflife.date/ruby); the -head ones are in head.
ruby: ["3.3", "3.4", "4.0"]
rails: [{version: "8.1", gemfile: Gemfile}, {version: "8.0", gemfile: gemfiles/rails_8_0.gemfile}]
env:
BUNDLE_GEMFILE: ${{ matrix.rails.gemfile != 'Gemfile' && matrix.rails.gemfile || '' }}
steps: &test-steps
- name: Check out
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@de492cf36cbab201753d720ce4f4e336cd688c94 # v1.336.0
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Run the tests
run: bundle exec rake test

# Allowed to fail, and truffleruby-head takes twice as long as the others: CI passed doesn't wait for them.
head:
name: Ruby ${{ matrix.ruby }}${{ matrix.rails && format(' · Rails {0}', matrix.rails.version) || '' }}
if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-latest
timeout-minutes: 10
continue-on-error: true
strategy:
fail-fast: false
matrix:
# ruby-head, then this gem's extra ones.
ruby: [ruby-head]
rails: [{version: "8.1", gemfile: Gemfile}, {version: "8.0", gemfile: gemfiles/rails_8_0.gemfile}]
env:
BUNDLE_GEMFILE: ${{ matrix.rails.gemfile != 'Gemfile' && matrix.rails.gemfile || '' }}
steps: *test-steps

docs:
name: Highlighted docs
if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@de492cf36cbab201753d720ce4f4e336cd688c94 # v1.336.0
with:
ruby-version: "4.0"
bundler-cache: true
- name: Check the highlighted docs
run: bundle exec rake docs:highlight:check

# The one check main's ruleset requires, so the Rubies, Rails and jobs above can change without touching it.
# Every other job belongs in needs: bin/x-gems-ci-check in levups/oss checks it.
passed:
name: CI passed
if: ${{ always() && !github.event.pull_request.draft }}
needs: [rubocop, test, docs]
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Check every job succeeded
run: echo "$NEEDS" | jq --exit-status 'all(.[]; .result == "success")'
env:
NEEDS: ${{ toJSON(needs) }}
64 changes: 64 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Release

# bin/release pushes a vX.Y.Z tag; this publishes that version to RubyGems, then the GitHub release with its
# section of CHANGELOG.md. RubyGems trusts this workflow (trusted publishing): no API key is stored anywhere.
on:
push:
tags: ["v*"]

permissions:
contents: read

# One release at a time, never cancelled halfway through a publication.
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

jobs:
release:
name: RubyGems and GitHub release
runs-on: ubuntu-latest
# Settings > Environments > release: who must approve a publication.
environment: release
timeout-minutes: 10
permissions:
contents: write # the GitHub release
id-token: write # trusted publishing to RubyGems
steps:
- name: Check out
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@de492cf36cbab201753d720ce4f4e336cd688c94 # v1.336.0
with:
ruby-version: "4.0"
- name: Check the tag against the gemspec and the changelog
run: bin/release --check "$GITHUB_REF_NAME"
# A tag is all it takes to publish: only one signed with a key GitHub verifies, on a commit of main, does.
- name: Check the tag is signed and on main
env:
GH_TOKEN: ${{ github.token }}
run: |
tag=$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$GITHUB_REF_NAME" --jq 'select(.object.type == "tag") | .object.sha')
test -n "$tag" || { echo "::error::$GITHUB_REF_NAME is not an annotated tag"; exit 1; }
verified=$(gh api "repos/$GITHUB_REPOSITORY/git/tags/$tag" --jq "select(.object.sha == \"$GITHUB_SHA\") | .verification.verified")
test "$verified" = true || { echo "::error::$GITHUB_REF_NAME is not signed with a key GitHub verifies"; exit 1; }
status=$(gh api "repos/$GITHUB_REPOSITORY/compare/$GITHUB_SHA...main" --jq .status)
case "$status" in
identical | ahead) ;;
*) echo "::error::$GITHUB_SHA is not on main ($status)"; exit 1 ;;
esac
- name: Build the gem
run: gem build *.gemspec
- name: Get RubyGems credentials (trusted publishing)
uses: rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a # v2.1.0
- name: Publish to RubyGems
run: gem push *.gem
- name: Create the GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: |
bin/release --notes "$GITHUB_REF_NAME" > "$RUNNER_TEMP/notes.md"
gh release create "$GITHUB_REF_NAME" *.gem --verify-tag \
--title "$(bin/release --title "$GITHUB_REF_NAME")" --notes-file "$RUNNER_TEMP/notes.md"
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/Gemfile.lock
/gemfiles/*.lock
/pkg/
mapitaine-*.gem
/tmp/
Loading